style(sw): viewer popup typography, 전체 레이어 title and options layout
Deploy Storybook / deploy (push) Successful in 2m10s

- Property popup: row labels 14px 500, read-only values 14px in the panel text
  color, monospace removed; 일반 정보 / 기하 속성 groups 20px apart
  (--hm-sw-viewer-panel-spacing on the second section).
- Layer popup: 전체 레이어 is a plain SWViewerSection title (existing
  .hm-sw-viewer-section__title) with the all-layers switch on the same row;
  layer names 14px 500, no monospace.
- Options popup follows the user image: 표시 (LWDISPLAY), 표시 배율 with the
  px/mm unit on the input row, updated lineweight hints.
- Remove stray monospace font-family from SW Story CSS.
- VIEWER.md §4.1 / new §4.2, DESIGN_RULES §11, Example H aligned.
This commit is contained in:
shkim4 committed 2026-10-22 16:56:22 +09:00
1 parent da0e647b49
commit 84d70f5cdf
9 files changed
+41 -22

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@
.sw-footer-story-guide-frame { display: block; width: 194px; height: 111px; background-repeat: no-repeat; background-size: 250px 1179px; }
.sw-footer-story-guide-text { display: grid; place-items: center; height: 100%; padding: 8px; font-size: 12px; color: #d4cbbd; text-align: center; }
.sw-footer-story-lucide { display: grid; place-items: center; }
.sw-footer-story-status { padding: 0 12px; font-size: 12px; color: #d4cbbd; font-family: ui-monospace, monospace; }
.sw-footer-story-status { padding: 0 12px; font-size: 12px; color: #d4cbbd; }
.sw-footer-story-row { display: flex; align-items: flex-start; gap: 16px; }
.sw-footer-story-row--tight { gap: 0; }
.sw-footer-story-inline { display: inline-flex; flex-direction: column; padding: 0; }
+1 -1
View File
@@ -7,7 +7,7 @@
.sw-home-menu-story__single { display: flex; height: 46px; }
.sw-home-menu-story__toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.sw-home-menu-story__toolbar > button { padding: 4px 8px; border: 1px solid #999; border-radius: 4px; background: #fff; font: inherit; font-size: 13px; cursor: pointer; }
.sw-home-menu-story__log { margin: 12px 0 0; padding-left: 20px; font-family: monospace; font-size: 12px; }
.sw-home-menu-story__log { margin: 12px 0 0; padding-left: 20px; font-size: 12px; }
.sw-home-menu-story__tall .sw-home-menu-story__frame { height: 860px; }
/* 2026-10-21 Storybook simplification (SW pages): no event log or long descriptions on screen; the log stays in the DOM
+1 -1
View File
@@ -26,7 +26,7 @@
.sw-story-stages { display: grid; grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); gap: 16px; }
.sw-story-stage { margin: 0; }
.sw-story-stage figcaption { margin-top: 6px; font-size: 13px; color: #3d4a45; }
.sw-story-layer-state { margin: 0 0 8px; font-size: 12px; color: #3d4a45; font-family: ui-monospace, monospace; white-space: pre-wrap; }
.sw-story-layer-state { margin: 0 0 8px; font-size: 12px; color: #3d4a45; white-space: pre-wrap; }
.sw-story-dynamic { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.sw-story-property-room { min-height: 820px; }
/* Property panel states (2026-10-16): standalone panels at their natural size; the comparison wraps. */
+13 -9
View File
@@ -116,8 +116,11 @@ export function LayerPanel({ demo, ...placement }: { demo: ViewerDemo } & PanelP
<SWViewerSection surface="plain" aria-label="레이어 목록">
<div className="sw-viewer-story-toolbar">
<SWViewerInput type="search" placeholder="레이어 검색" aria-label="레이어 검색" value={query} onChange={event => setQuery(event.currentTarget.value)} />
<SWViewerField label="전체 레이어">{id => <SWViewerSwitch id={id} checked={allVisible}
onCheckedChange={next => { set(next ? [] : LAYERS.map(layer => layer.name)); demo.record(`layer:all=${next}`) }} />}</SWViewerField>
{/* 2026-10-22: the section title (.hm-sw-viewer-section__title) names the switch row; the switch stays on that line. */}
<SWViewerSection surface="plain" title="전체 레이어" className="sw-viewer-story-all">
<SWViewerSwitch aria-label="전체 레이어" checked={allVisible}
onCheckedChange={next => { set(next ? [] : LAYERS.map(layer => layer.name)); demo.record(`layer:all=${next}`) }} />
</SWViewerSection>
</div>
{filtered.length === 0 ? <SWViewerHint>검색 결과가 없습니다 ("{query}")</SWViewerHint> : (
<ul className="sw-viewer-story-list">
@@ -145,9 +148,9 @@ export function LayerPanel({ demo, ...placement }: { demo: ViewerDemo } & PanelP
)
}
function SliderRow({ label, value, min, max, step, disabled, onChange }: { label: string; value: number; min: number; max: number; step: number; disabled?: boolean; onChange: (value: number) => void }) {
function SliderRow({ label, unit, value, min, max, step, disabled, onChange }: { label: string; unit?: string; value: number; min: number; max: number; step: number; disabled?: boolean; onChange: (value: number) => void }) {
return (
<SWViewerField label={label} layout="stacked" disabled={disabled}
<SWViewerField label={label} unit={unit} layout="stacked" disabled={disabled}
below={<SWViewerSlider aria-label={label} value={value} min={min} max={max} step={step} onValueChange={onChange} />}>
{id => <SWViewerInput id={id} type="number" min={min} max={max} step={step} value={value} onChange={event => { const next = Number(event.currentTarget.value); if (Number.isFinite(next)) onChange(next) }} />}
</SWViewerField>
@@ -162,10 +165,11 @@ export function OptionsPanel({ demo, footer, ...placement }: { demo: ViewerDemo;
headerActions={<SWViewerButton variant="ghost" aria-label="기본값으로" title="기본값으로" onClick={() => { setOptions(DEFAULT_OPTIONS); demo.record("options:reset") }}><RotateCcw size={16} /></SWViewerButton>}
footer={footer ? <><SWViewerButton onClick={() => demo.toggle("options", false)}>취소</SWViewerButton><SWViewerButton variant="primary" onClick={() => { demo.record("options:apply"); demo.toggle("options", false) }}>적용</SWViewerButton></> : undefined}>
<SWViewerSection title="선가중치">
<SWViewerField label="표시 사용">{id => <SWViewerSwitch id={id} checked={options.lineweightOn} onCheckedChange={lineweightOn => set({ lineweightOn })} />}</SWViewerField>
<SliderRow label="표시 배율 (px/mm)" value={options.lineweightScale} min={2} max={60} step={0.5} onChange={lineweightScale => set({ lineweightScale })} />
<SWViewerHint>이 도면에는 굵은 선이 없습니다 (0.50mm 였다면 {(0.5 * options.lineweightScale).toFixed(1)}px)</SWViewerHint>
<SWViewerHint>AutoCAD의 "선가중치 설정 → 표시 배율"과 동일합니다.</SWViewerHint>
{/* 2026-10-22 options image: LWDISPLAY toggle; scale input + px/mm unit on the label row, slider below. */}
<SWViewerField label="표시 (LWDISPLAY)">{id => <SWViewerSwitch id={id} checked={options.lineweightOn} onCheckedChange={lineweightOn => set({ lineweightOn })} />}</SWViewerField>
<SliderRow label="표시 배율" unit="px/mm" value={options.lineweightScale} min={2} max={60} step={0.5} onChange={lineweightScale => set({ lineweightScale })} />
<SWViewerHint>이 도면에는 굵은 선이 없습니다 (0.50mm였다면 {(0.5 * options.lineweightScale).toFixed(1)}px)</SWViewerHint>
<SWViewerHint>AutoCAD "선가중치 설정 → 표시 배율"과 같습니다. 모델 공간의 선가중치는 줌과 무관한 화면 픽셀로 그려지므로 화면 DPI에 맞춰 조정합니다.</SWViewerHint>
</SWViewerSection>
<SWViewerSection title="표시">
<SWViewerField label="그리드">{id => <SWViewerSwitch id={id} checked={options.grid} onCheckedChange={grid => set({ grid })} />}</SWViewerField>
@@ -192,7 +196,7 @@ export function PropertiesPanel({ demo, selected = true, ...placement }: { demo:
headerActions={selected ? <SWViewerButton variant="ghost" aria-label="선택 확대" title="선택 확대" onClick={() => demo.record("zoomToSelection (미연결)")}><ZoomIn size={16} /></SWViewerButton> : undefined}>
{selected ? <>
<SWViewerSection surface="plain" title="일반 정보 (General)"><SWViewerBadge>LINE · h=2F1A</SWViewerBadge>{rows(ENTITY_ROWS)}</SWViewerSection>
<SWViewerSection surface="plain" title="기하 속성 (Geometry)">{rows(GEOMETRY_ROWS)}</SWViewerSection>
<SWViewerSection surface="plain" title="기하 속성 (Geometry)" className="sw-viewer-story-props-next">{rows(GEOMETRY_ROWS)}</SWViewerSection>
</> : (
<div className="sw-viewer-story-empty"><MousePointerClick size={24} aria-hidden="true" />도면에서 객체(엔티티)를 클릭하면<br />상세 속성이 표시됩니다</div>
)}
+8 -4
View File
@@ -11,19 +11,23 @@
/* Search, then the all-layers switch row (SWViewerField + SWViewerSwitch, 2026-10-22). */
.sw-viewer-story-toolbar { display: flex; flex-wrap: wrap; gap: 12px 4px; }
.sw-viewer-story-toolbar > * { flex: 1 0 100%; }
/* 전체 레이어: the plain SWViewerSection laid out as one row — its title on the left, the switch on the right. */
.sw-viewer-story-toolbar > .sw-viewer-story-all { flex-direction: row; align-items: center; justify-content: space-between; min-height: 20px; }
.sw-viewer-story-list { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
.sw-viewer-story-list li { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 3px; }
.sw-viewer-story-list li:hover { background: rgba(154, 146, 132, 0.25); }
.sw-viewer-story-list li[data-visible="false"] { opacity: 0.45; }
.sw-viewer-story-swatch { flex: 0 0 10px; width: 10px; height: 10px; border: 1px solid var(--hm-sw-viewer-panel-inner-border); border-radius: 9999px; }
.sw-viewer-story-name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-family: ui-monospace, monospace; font-size: 12px; }
.sw-viewer-story-name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 14px; font-weight: 500; }
/* Visibility button around the public SWLayerVisibilityIcon (header bulb art, 20 x 20); 24 x 24 hit area kept. */
.sw-viewer-story-bulb { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 4px; cursor: pointer; }
.sw-viewer-story-bulb:hover { background: rgba(154, 146, 132, 0.3); }
.sw-viewer-story-props { margin: 0; }
.sw-viewer-story-props > div { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--hm-sw-viewer-panel-inner-border); font-size: 12px; }
.sw-viewer-story-props dt { color: var(--hm-sw-viewer-panel-label); }
.sw-viewer-story-props dd { margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-family: ui-monospace, monospace; user-select: all; }
/* Property groups read as separate blocks: the panel spacing token on top of the shared 8px body gap (20px in all). */
.sw-viewer-story-props-next { margin-top: var(--hm-sw-viewer-panel-spacing); }
.sw-viewer-story-props > div { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--hm-sw-viewer-panel-inner-border); font-size: 14px; }
.sw-viewer-story-props dt { color: var(--hm-sw-viewer-panel-label); font-weight: 500; }
.sw-viewer-story-props dd { margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--hm-sw-viewer-panel-text); user-select: all; }
.sw-viewer-story-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 40px 0; text-align: center; font-size: 12px; color: var(--hm-sw-viewer-panel-label); }
.sw-viewer-story-steps { margin: 0; padding-left: 20px; font-size: 14px; }
.sw-viewer-story-reopen { position: absolute; left: 712px; top: 16px; padding: 6px 10px; border-radius: 4px; background: #f5f0e5; font-size: 12px; }