From 4551edfd8244a8d4c4bacdd4c9ef627342bf79c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Fri, 23 Oct 2026 13:12:36 +0900 Subject: [PATCH] fix(storybook): align viewer property popup card layout --- docs/sw/sw-viewer-panel-region-styles.md | 14 +++++++------- src/sw/sw-viewer-fixture.tsx | 7 +++++-- src/sw/sw-viewer-stories.css | 12 +++++++----- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/docs/sw/sw-viewer-panel-region-styles.md b/docs/sw/sw-viewer-panel-region-styles.md index bfb689d..f80bcce 100644 --- a/docs/sw/sw-viewer-panel-region-styles.md +++ b/docs/sw/sw-viewer-panel-region-styles.md @@ -25,13 +25,13 @@ SWViewerPanel (= SWPopup, .hm-sw-property.hm-sw-viewer-panel) ← 테두리 |---|---|---|---|---|---|---| | Footer 위 구분선 | 스크롤 본문과 하단 버튼 영역 분리 | `.hm-sw-viewer-panel__footer` | `border-top: 1px solid var(--hm-sw-viewer-panel-inner-border)`(#948871), `padding: 7px`, `gap: 8px`, 오른쪽 정렬 | `footer` Prop이 있고 접힘(`collapsed`)이 아닐 때만 렌더링 | 공통 | `SW/Viewer › Viewer Header`의 `모듈 구성 · 라이선스 고지` modal(`AboutPanel`, `확인` 버튼). Settings Popup 3개에는 footer 없음 | | 전체 레이어 행 아래 선 | 고정 행(검색·전체 레이어)과 레이어 목록 분리 | `.sw-viewer-story-layers .sw-viewer-story-all` | `border-bottom: 1px solid var(--hm-sw-property-table)`(#d2c8b4), `padding-bottom: 8px`(실측 `padding: 0 6px 8px 0`) | 레이어 Popup에만 해당. 검색과 전체 레이어 사이에는 선 없음 | **Storybook 전용** | Settings Popup › `레이어 (9개)` | -| 속성 행 사이 선 | 속성 키/값 행 구분 | `.sw-viewer-story-props > div` | `border-bottom: 1px solid var(--hm-sw-viewer-panel-inner-border)`(#948871), `padding: 6px 0`, `gap: 12px` | 각 그룹의 **마지막 행에도** 선이 있음(실측 9행 모두) | **Storybook 전용** | Settings Popup › `속성` | +| 속성 행 사이 선 | 속성 키/값 행 구분 | `.sw-viewer-story-props > div` | `border-bottom: 1px solid var(--hm-sw-property-table)`(#d2c8b4), `padding: 6px 0`, `gap: 12px`, 이름 왼쪽·값 오른쪽(말줄임) | 각 Card의 마지막 행에는 선 없음(2026-10-23, 뷰어 화면 기준) | **Storybook 전용** | Settings Popup › `속성` | ## 3. 선이 표시되지 않는 영역 | 영역 | 목적 | 선택자 | 실제 값 | 조건·예외 | 구분 | Storybook 예시 | |---|---|---|---|---|---|---| -| Plain Section | 패널 본문에 카드 없이 콘텐츠를 바로 배치 | `.hm-sw-viewer-section[data-surface="plain"]` | 전용 규칙 없음 → 기본 `.hm-sw-viewer-section`만 적용: `display: flex; flex-direction: column; gap: 12px`, 테두리·배경·padding 0 | `SWViewerSection surface="plain"`을 명시했을 때 | 공통 | 레이어 Popup의 목록 Section, 속성 Popup의 `일반 정보`·`기하 속성` | +| Plain Section | 패널 본문에 카드 없이 콘텐츠를 바로 배치 | `.hm-sw-viewer-section[data-surface="plain"]` | 전용 규칙 없음 → 기본 `.hm-sw-viewer-section`만 적용: `display: flex; flex-direction: column; gap: 12px`, 테두리·배경·padding 0 | `SWViewerSection surface="plain"`을 명시했을 때 | 공통 | 레이어 Popup의 목록 Section | | 제목줄 ↔ 본문 경계 | — | `.hm-sw-property__titlebar` / `.hm-sw-property__body` | 경계선 없음. 제목줄은 배경 그라데이션, 본문은 위쪽 inset 하이라이트(§5)로만 구분 | 모든 패널 | 공통(SWPopup) | Settings Popup 전체 | | 검색 ↔ 전체 레이어 | 고정 행 두 개 | `.sw-viewer-story-toolbar` | 선 없음, `gap: 12px 4px` | 레이어 Popup | Storybook 전용 | `레이어 (9개)` | | 레이어 목록 행 사이 | 레이어 항목 나열 | `.sw-viewer-story-list`, `li` | 선 없음, 목록 `gap: 4px`, 행 `padding: 4px 6px` | — | Storybook 전용 | `레이어 (9개)` | @@ -42,7 +42,7 @@ SWViewerPanel (= SWPopup, .hm-sw-property.hm-sw-viewer-panel) ← 테두리 | 영역 | 목적 | 선택자 | Border | Background | Radius | Padding·기타 | 조건·예외 | 구분 | 예시 | |---|---|---|---|---|---|---|---|---|---| | 패널 프레임 | 팝업 전체 외곽 | `.hm-sw-property`(SWPopup) + `.hm-sw-viewer-panel` | `1px solid var(--hm-sw-property-border)`(#a67d03) + `box-shadow: inset 0 0 0 1px #000`(안쪽 검정 1px) | `var(--hm-sw-property-surface)`(#e4dbc9) | 4px | padding 0, `overflow: hidden` | 모든 SWViewerPanel. modal이면 뒤에 `.hm-sw-viewer-panel-backdrop`(`rgba(0,0,0,.1)`) | 공통(SWPopup 그대로) | Settings Popup 3개 | -| Card Section | 설정 그룹 묶음 | `.hm-sw-viewer-section[data-surface="card"]` | `1px solid var(--hm-sw-viewer-panel-inner-border)`(#948871) | `var(--hm-sw-viewer-panel-field-background)`(#f1ece1) | `var(--hm-sw-viewer-radius)` 4px | `padding: var(--hm-sw-viewer-panel-spacing)` 12px, `gap: 12px`, `box-shadow: inset 0 1px 0 #fff`, `margin-top: 0`(2026-10-23: 그룹 박스 사이는 본문 gap 8px만, 소비 앱 단일 class의 상단 여백 무시) | `SWViewerSection`의 **기본값**(`surface` 생략 시 card). plain Section의 상단 여백은 유지 | 공통 | `옵션`의 `선가중치`·`표시`·`2.5D` | +| Card Section | 설정 그룹 묶음 | `.hm-sw-viewer-section[data-surface="card"]` | `1px solid var(--hm-sw-viewer-panel-inner-border)`(#948871) | `var(--hm-sw-viewer-panel-field-background)`(#f1ece1) | `var(--hm-sw-viewer-radius)` 4px | `padding: var(--hm-sw-viewer-panel-spacing)` 12px, `gap: 12px`, `box-shadow: inset 0 1px 0 #fff`, `margin-top: 0`(2026-10-23: 그룹 박스 사이는 본문 gap 8px만, 소비 앱 단일 class의 상단 여백 무시) | `SWViewerSection`의 **기본값**(`surface` 생략 시 card). plain Section의 상단 여백은 유지 | 공통 | `옵션`의 `선가중치`·`표시`·`2.5D`, `속성`의 `일반 정보 (GENERAL)`·`기하 속성 (GEOMETRY)`(2026-10-23) | | 검색 입력 | 레이어 검색 | `.hm-sw-theme .hm-sw-viewer-input[type="search"]` | `1px solid var(--hm-sw-viewer-panel-inner-border)` | field-background #f1ece1 | 4px | 80px 기본 폭, 높이 28px, `padding: 0 10px` | `type="search"`만. 값 입력(§5)과 다름 | 공통 | `레이어 (9개)` | | 패널 버튼 outline | 보조 버튼 | `.hm-sw-theme .hm-sw-viewer-button` | `1px solid var(--hm-sw-viewer-panel-primary)`(#123328) | #fff (`primary`는 #123328) | 4px | 높이 24px, `padding: 0 8px` | 제목줄의 `variant="ghost"`는 테두리·배경 없음(26 × 26) | 공통 | `모듈 구성` modal의 `확인`(primary). Settings Popup에는 ghost만 | @@ -54,9 +54,9 @@ SWViewerPanel (= SWPopup, .hm-sw-property.hm-sw-viewer-panel) ← 테두리 | 본문 | 스크롤 콘텐츠 영역 | `.hm-sw-property__body` + `.hm-sw-viewer-panel .hm-sw-property__body` | `padding: 7px 7px 0`, `border-bottom: 8px solid transparent`(스크롤 영역 밖 하단 여백), `gap: 8px`, `box-shadow: inset 0 2px 2px rgba(255,255,255,.6)`, 배경은 프레임 surface | 접힘이면 `display: none` | 공통(SWPopup 7px + 뷰어 하단 8px 덮어쓰기) | 전체 | | Field | 라벨 + 컨트롤 행 | `.hm-sw-viewer-field`, `__row` | 선·배경 없음, field `gap: 6px`, row `gap: 8px`, `min-height: 20px` | disabled면 `opacity: .5` | 공통 | `옵션` | | 값 입력·단위 칸 | 숫자 입력 | `.hm-sw-viewer-input:not([type="search"])`, `.hm-sw-viewer-field__control:has(> unit)` | 기본 **투명 테두리**(1px transparent), radius 2px, 높이 28px. Hover/편집 시에만 `--hm-sw-property-field-hover-border-color` 테두리, 편집 중 배경 `--hm-sw-property-field-editing-background` | 상태가 바뀔 때만 박스로 보임 | 공통 | `옵션`의 `표시 배율`(px/mm) | -| Badge | 개수·유형 표시 | `.hm-sw-viewer-badge` | 테두리 없음, `background: var(--hm-sw-viewer-panel-badge-background)`(#d2c8b4), radius 4px, `padding: 2px 8px` | — | 공통 | 레이어 개수, `LINE · h=2F1A` | +| Badge | 개수·유형 표시 | `.hm-sw-viewer-badge` | 테두리 없음, `background: var(--hm-sw-viewer-panel-badge-background)`(#d2c8b4), radius 4px, `padding: 2px 8px` | — | 공통 | 레이어 개수, `LINE · h=2F1A`(`속성` 맨 위, Card 밖, 자기 폭) | | 레이어 행 Hover | 행 강조 | `.sw-viewer-story-list li:hover` | `background: rgba(154,146,132,.25)`, radius 3px | Hover일 때만. 숨긴 레이어는 `opacity: .45` | Storybook 전용 | `레이어 (9개)` | -| 속성 그룹 간격 | 두 속성 그룹 분리 | `.sw-viewer-story-props-next` | `margin-top: var(--hm-sw-viewer-panel-spacing)` 12px(본문 gap 8px과 합쳐 20px) | 두 번째 그룹 | Storybook 전용 | `속성` | +| 속성 그룹 간격 | 두 속성 그룹 분리 | Card Section 사이 | 본문 `gap` 8px만(Card `margin-top: 0`, 2026-10-23 `.sw-viewer-story-props-next` 제거) | 두 Card 사이 | Storybook 전용 | `속성` | | Hint | 보조 설명 | `.hm-sw-viewer-hint` | 선·배경 없음, 12px | — | 공통 | `옵션` | | 빈 상태 | 선택 없음 안내 | `.sw-viewer-story-empty` | 선·배경 없음, `padding: 40px 0`, 가운데 정렬 | `PropertiesPanel selected={false}` | Storybook 전용 | `SW/Viewer › States` 등 | | Resize Grip | 크기 조절 | `.hm-sw-viewer-panel__resize` | 14 × 14, inner-border 색 빗금 그라데이션(선 아님) | `resizable` 이고 접힘이 아닐 때 | 공통 | 전체 | @@ -65,8 +65,8 @@ SWViewerPanel (= SWPopup, .hm-sw-property.hm-sw-viewer-panel) ← 테두리 1. **Card가 기본값인 이유:** `SWViewerSection`의 기본 `surface`는 `card`입니다. 그러나 레이어·속성 Popup은 모두 `plain`을 명시하고, card는 `옵션` Popup만 씁니다. 어느 콘텐츠에 card/plain을 쓰는지에 대한 디자인 기준 문서는 확인되지 않았습니다. 2. **레이어 구분선 위치:** "전체 레이어 행 아래 `#d2c8b4` 1px 선, 검색과 사이에는 없음" 규칙은 `packages/sw/ai/VIEWER.md`에 소비 앱 규칙으로 적혀 있습니다. 실제 구현은 Storybook/예제의 소비 앱 CSS뿐이고 패키지 공통 스타일은 아닙니다. -3. **구분선 색 두 가지:** 레이어 구분선은 `--hm-sw-property-table`(#d2c8b4)이고, 속성 행 선·Footer 선·Card 테두리는 `--hm-sw-viewer-panel-inner-border`(#948871)입니다. 용도별로 색을 나누는 기준은 문서화되어 있지 않습니다. -4. **속성 행 마지막 선:** 각 그룹의 마지막 행에도 아래 선이 그려집니다(Storybook 전용 CSS). 의도인지 확인되지 않았습니다. +3. **구분선 색 두 가지:** 레이어 구분선·속성 행 선(2026-10-23)은 `--hm-sw-property-table`(#d2c8b4)이고, Footer 선·Card 테두리는 `--hm-sw-viewer-panel-inner-border`(#948871)입니다. 용도별로 색을 나누는 기준은 문서화되어 있지 않습니다. +4. **속성 행 마지막 선:** 2026-10-23 뷰어 화면 기준으로 각 Card의 마지막 행에는 선을 그리지 않습니다(Storybook 전용 CSS). 5. **Footer 예시 부족:** Footer 구분선은 대표 Settings Popup에 없습니다. `Viewer Header` Story의 modal에서만 보이며, 비-modal 패널 Footer의 디자인 기준 화면은 확인되지 않았습니다. 6. **입력 박스 두 형태:** 검색 입력은 항상 테두리 박스이고, 값 입력은 Hover/편집 때만 테두리가 생깁니다(2026-10-22 사용자 요청 주석). 다른 텍스트 입력(예: 검색이 아닌 일반 text)에 어느 쪽을 쓸지는 `type="search"` 여부로만 갈립니다. 7. **Card 안쪽 하이라이트:** `box-shadow: inset 0 1px 0 #fff`는 하드코드 값이고 대응 토큰이 없습니다. diff --git a/src/sw/sw-viewer-fixture.tsx b/src/sw/sw-viewer-fixture.tsx index 4658313..6338f8a 100644 --- a/src/sw/sw-viewer-fixture.tsx +++ b/src/sw/sw-viewer-fixture.tsx @@ -194,6 +194,8 @@ export function OptionsPanel({ demo, ...placement }: { demo: ViewerDemo } & Pane ) } +// 2026-10-23 (user request, viewer screenshot layout; same data): the id badge above two card groups. +const ENTITY_ID = "LINE · h=2F1A" const ENTITY_ROWS: [string, string][] = [["유형", "LINE"], ["레이어", "CX-BORD-LIN1"], ["색상", "ByLayer"], ["선종류", "Continuous"]] const GEOMETRY_ROWS: [string, string][] = [["시작 X", "18432.512"], ["시작 Y", "2210.004"], ["끝 X", "18992.040"], ["끝 Y", "2210.004"], ["길이", "559.528"]] @@ -205,8 +207,9 @@ export function PropertiesPanel({ demo, selected = true, ...placement }: { demo: demo.toggle("properties", false)} {...placement} headerActions={selected ? demo.record("zoomToSelection (미연결)")}> : undefined}> {selected ? <> - LINE · h=2F1A{rows(ENTITY_ROWS)} - {rows(GEOMETRY_ROWS)} + {ENTITY_ID} + {rows(ENTITY_ROWS)} + {rows(GEOMETRY_ROWS)} : (
)} diff --git a/src/sw/sw-viewer-stories.css b/src/sw/sw-viewer-stories.css index 93efb1f..8a04922 100644 --- a/src/sw/sw-viewer-stories.css +++ b/src/sw/sw-viewer-stories.css @@ -34,12 +34,14 @@ /* 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); } +/* 속성 Popup (2026-10-23): the id badge above the card groups (own width); the groups are default card Sections, so the + 8px body gap alone separates them. Rows: name left, value right (ellipsis), thin property-table rule between rows. */ +.sw-viewer-story-props-id { align-self: flex-start; max-width: 100%; } .sw-viewer-story-props { margin: 0; } -/* 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-props > div { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--hm-sw-property-table); font-size: 14px; } +.sw-viewer-story-props > div:last-child { border-bottom: 0; } +.sw-viewer-story-props dt { flex-shrink: 0; color: var(--hm-sw-viewer-panel-label); font-weight: 500; } +.sw-viewer-story-props dd { min-width: 0; margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-align: right; 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-context { display: flex; align-items: center; justify-content: center; height: 160px; border: 1px dashed #9a9284; border-radius: 4px; background: #f5f0e5; color: #3e3523; font-size: 13px; }