148 lines
11 KiB
Markdown
148 lines
11 KiB
Markdown
# SW Header 레이어 Dropdown 검증 기록 (2026-10-16)
|
||
|
||
> 범위: `@hm-design/sw` 레이어 필드 Dropdown과 전구·눈꽃·자물쇠 On/Off. 기준: Figma `rj6wpsfk24lOWvWiVRAANP` 5개 Node. 규격은 [디자인 명세 §5.2](./sw-header-design-spec.md#52-레이어-dropdown-figma-headermenu레이어-2610940-2026-10-16), 사용법은 [사용 가이드](./sw-header-usage.md#레이어-dropdown-예)를 따릅니다.
|
||
|
||
## 1. 작업 환경
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| Worktree / Branch | `D:\디자인시스템\HMDesign-SW` / `feature/sw-package` |
|
||
| HEAD | `9407c85` (시작·종료 동일, Commit 없음) |
|
||
| 시작 상태 | `M package.json`, `M package-lock.json`, `?? docs/sw/`, `?? packages/sw/`, `?? src/sw/` — 기존 미커밋·Untracked 파일 보존(시작 시 스크래치 백업, Reset/Clean/Checkout 없음) |
|
||
| 검증 Storybook | `npx storybook dev -p 6047`(6024·6031·6032는 다른 세션 사용 중) |
|
||
|
||
## 2. Figma 5개 Node
|
||
|
||
| # | Node | 내용 | 확인 방법 |
|
||
|---|---|---|---|
|
||
| ① | `26:10940` `header/menu/레이어` (default 26:10941, hover 26:10952, Variant3 26:10963) | 필드·펼침 목록·레이어 상세 | `get_design_context` + screenshot |
|
||
| ② | `26:10810` `icon_전구` | 4 Variant: 컬러 / 컬러 Hover(검정 25%) / 회색(ellipse `#E6E6E6→#555`) / 회색 Hover | `get_design_context` + screenshot |
|
||
| ③ | `26:10855` `icon_눈꽃` | 4 Variant SVG(`#00CCFF` / `#949494`, Hover = 검정 25% 타일 추가) | 동일 |
|
||
| ④ | `26:10912` `icon_자물쇠` | 4 Variant SVG(`#E4BF4D·#D8A300` / `#BEBEBE·#A1A1A1`, Hover = 타일 추가) | 동일 |
|
||
| ⑤ | `26:10687` `header/dropdown/brown/row` (Default 26:10688, Hover 26:10695) | 행 234 × 30, Hover `#62533D` / `#FF5C00` | 동일 |
|
||
|
||
Prototype의 Smart Animate(Off → Off Hover, ease-out 300ms)는 사용자 요청서 기재값을 따랐습니다. Figma Prototype player 재생으로는 확인하지 않았습니다(NOT VERIFIED).
|
||
|
||
## 3. 자산
|
||
|
||
- 신규 8개(`layer-ellipse-off`, `layer-freeze-hover/off/off-hover`, `layer-lock-hover/off/off-hover`, `layer-detail`)를 Figma MCP에서 내려받아 `packages/sw/src/assets/`에 저장. 수정·재작성 없음.
|
||
- 기존 `layer-union/ellipse/state-a/state-b`는 Figma 원본과 mask id 외 동일 → 재사용.
|
||
- Hover SVG = Default SVG + `<rect 20×20 rx2 fill=black 25%>` 1줄 외 동일(정규화 diff).
|
||
- `packages/sw/src`, `src/sw`, `packages/sw/dist`, `storybook-static`에서 `figma.com/api` 검색 0건.
|
||
|
||
## 4. 디자인 측정 (Storybook `SW / Header / Layer Dropdown`, 1920 프레임, `getBoundingClientRect`·computed style)
|
||
|
||
| 항목 | 기대(Figma) | 측정 | 판정 |
|
||
|---|---|---|---|
|
||
| 필드 | 234 × 38, x 467 | 234 × 38, x 466.7(헤더 기준) | MATCH |
|
||
| 펼친 필드 | 테두리 `#081813`, radius 3 3 0 0, 라벨·값 `#FFC600` | `rgb(8,24,19)`, `3px 3px 0 0`, `rgb(255,198,0)` | MATCH |
|
||
| 펼침 외곽선 | 236 × 320 at −1/−1, radius 4 | `::before` inset −39 / −1 / −1 / −1, radius 4, 노란 gradient | MATCH |
|
||
| 목록 | 234 × 280, 필드 바로 아래 | 234 × 280, y = 필드 하단 | MATCH |
|
||
| 배경·그림자·radius | `#27241D`, `0 4px 8px rgba(16,48,38,.25)`, 0 0 3 3 | 동일 | MATCH |
|
||
| 행 | 234 × 30 × 8 | 234 × 30, 8개 | MATCH |
|
||
| 아이콘 | 20 × 20, x+12 / 32 / 52 | 동일 | MATCH |
|
||
| 견본 | 16 × 16 at x+80, 안쪽 14 × 14 | 동일 | MATCH |
|
||
| 이름 | x+104(+1), `#F6F4F2`, 700 | 동일 | MATCH |
|
||
| 하단 | 40, 버튼 오른쪽 12px | 40, 버튼 오른쪽 끝 = 목록 −12 | MATCH |
|
||
| 레이어 상세 | radius 24, 배경 `rgba(197,177,158,.15)`, 아이콘 14, 글자 opacity .35 | 동일, 버튼 107.3 × 24(글자 69.3) | MATCH(글자 폭은 글꼴 차이) |
|
||
| 행 Hover | `#62533D` / `#FF5C00` | `rgb(98,83,61)` / `rgb(255,92,0)`(고정·실제 Hover 모두) | MATCH |
|
||
| 아이콘 Hover | 전구 검정 25% 배경, 눈꽃·자물쇠 Hover SVG | 전구 `rgba(0,0,0,.25)`, Hover img opacity 1 | MATCH |
|
||
| Off Hover 전환 | 300ms ease-out | Off: `0.3s ease-out`, On: 0s | MATCH |
|
||
| 헤더 높이 | 49 | 49 | MATCH |
|
||
|
||
3배 확대 아이콘 표(12개 Variant)를 Figma screenshot과 육안 비교: 형태·색 일치. 픽셀 diff는 수행하지 않았습니다.
|
||
|
||
## 5. 동작 검증
|
||
|
||
JS 이벤트(`click`, `PointerEvent`, `KeyboardEvent`)와 실제 마우스·키보드 입력(브라우저 pane `left_click`, `hover`, `key`)으로 확인했습니다.
|
||
|
||
| 시나리오 | 결과 |
|
||
|---|---|
|
||
| 필드 클릭 → 열림, 다시 클릭 → 닫힘 | PASS (`aria-expanded` 동기화, `onLayerMenuOpenChange` true/false) |
|
||
| 전구 / 눈꽃 / 자물쇠 클릭(JS 4회, 실제 마우스 1회, 키보드 Space·Enter 2회) | PASS — 각 Callback 1회, `onLayerSelect` 0회, 목록 유지, 해당 레이어만 변경(상태표 확인) |
|
||
| 행 선택(JS, 실제 Enter) | PASS — `onLayerSelect` 1회, 목록 닫힘, Focus 필드 복귀, 필드에 레이어명·상태 아이콘 반영(회색 전구 확인) |
|
||
| 바깥 pointerdown, 다른 필드 pointerdown | PASS — 닫힘 |
|
||
| Escape(JS, 실제 키) | PASS — 닫힘, Focus 필드 |
|
||
| ↓(닫힘) | PASS — 열림, 현재 레이어 이름 버튼 Focus(2px `#F0F7F5` outline) |
|
||
| ↓↑ / ←→ | PASS — 같은 열 유지 행 이동, 행 안 이동 |
|
||
| Tab 진입 / Shift+Tab 두 번으로 이탈 | PASS — 목록 안에서는 유지, 밖으로 나가면 닫힘 |
|
||
| 레이어 상세(Callback 있음) | PASS — `onLayerDetail` 1회 후 닫힘 |
|
||
| 레이어 상세(Callback 없음) | PASS — `disabled`, opacity 1(Figma 외형), cursor default |
|
||
| Callback 없는 아이콘 | PASS — `role="img"` span 24개, 버튼 0개 |
|
||
| disabled 레이어 | PASS — 아이콘 3개·선택 버튼 모두 disabled |
|
||
| 20개 레이어 | PASS — 목록 240px 내부 스크롤(scrollHeight 600), 전체 280, 헤더 가로 스크롤 0 |
|
||
| 레이어 없음 | PASS — 하단만(40px), 필드는 `layer.name` |
|
||
| 긴 레이어명 | PASS — 행·필드 ellipsis, 오른쪽 padding 12 유지 |
|
||
| 콘솔 오류 | 0건 |
|
||
|
||
## 6. 반응형
|
||
|
||
`Layer Dropdown`의 Callback 예제 프레임 폭을 바꿔 측정하고, 같은 폭의 기존 `Responsive` Story(`layers` 없음) 필드 위치와 비교했습니다.
|
||
|
||
| 폭 | 필드 x / 폭 (기존과 동일) | 열린 뒤 필드 이동 | 헤더 가로 스크롤 | 목록 헤더 안 | 목록 이동 | 아이콘·텍스트 겹침 | 견본 |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 1920 | 466.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 1800 | 346.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 1799 | 466.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 1440 | 466.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 1180 | 306.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 1080 | 207.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 900 | 246.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 768 | 310.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 640 | 182.7 / 234 ✔ | 없음 | 0 | ✔ | 0 | 없음 | 16 × 16 |
|
||
| 390 | 167.7 / 113.7 ✔ | 없음 | 0 | ✔ | 왼쪽 이동, 오른쪽 여백 1.3 | 없음 | 16 × 16 |
|
||
|
||
- 1차 측정에서 390px 열림 상태에 헤더 `scrollWidth`가 1px 늘어나는 문제(외곽선이 목록 밖 1px)를 발견해, 이동 계산에 1px 여백을 두도록 수정 후 320 / 360 / 390 / 500 / 519 / 640에서 0을 재확인했습니다.
|
||
- 390에서 실제 조작(열기 + 자물쇠 Toggle → Callback 1회, 목록 유지) 확인. 브라우저 Viewport 390 에뮬레이션은 Storybook 페이지 내부 폭이 766px로 잡혀 헤더 프레임 390 기준으로만 확인했습니다(실기기 NOT VERIFIED).
|
||
- 더보기 메뉴 정책·기존 Breakpoint 변경 없음(필드 위치가 기존 Story와 동일).
|
||
|
||
## 7. 회귀 확인
|
||
|
||
| 항목 | 결과 |
|
||
|---|---|
|
||
| `layers` 미전달 필드 | 기존 DOM(아이콘 4개 img 14×18 / 14×14 / 20×20 / 20×20, `aria-expanded=false`)과 동일 |
|
||
| Composed Controls 고정 Hover | 5개 필드 값 `#FFC600`, Gradient ring opacity 1, 선 미리보기 `#FFC600` 유지 |
|
||
| 문서 Dropdown·창 이동·인쇄·최소화·종료·속성 토글 CSS | 변경 없음(diff는 레이어 관련 규칙 추가뿐) |
|
||
|
||
## 8. Build
|
||
|
||
| 명령 | 결과 |
|
||
|---|---|
|
||
| `npm run typecheck -w @hm-design/sw` | PASS |
|
||
| `npm run build:sw` | PASS (`dist/styles.css` 45.35 kB, `dist/index.js` 202.28 kB) |
|
||
| `npm run typecheck`(root, packages build 포함) | PASS |
|
||
| `npm run build`(root) | PASS (기존 500 kB chunk 경고만) |
|
||
| `npm run build-storybook` | PASS, index에 `sw-header--layer-dropdown` |
|
||
|
||
## 9. 아이콘 색상 ↔ 상태 매핑 확정 검증 (사용자 확정, 2026-10-16)
|
||
|
||
확정 기준: 전구 컬러 `visible=true` / 회색 `false`, 눈꽃 컬러 `frozen=false`(동결 해제) / 회색 `true`(동결), 자물쇠 컬러 `locked=false`(잠금 해제) / 회색 `true`(잠금). 클릭 시 해당 boolean 반전값을 Callback으로 전달합니다. 기존 구현(`layerIconOn()`, `onChange(layer, !pressed)`)이 이 기준과 같아 동작 코드는 바꾸지 않았고, 타입 주석·Story 설명·문서의 잠정 표기만 확정으로 바꿨습니다. SVG·Default/Hover CSS 변경 없음.
|
||
|
||
검증: Storybook `Layer Dropdown`의 Callback 예제에서 각 아이콘을 두 번씩 클릭했습니다. 색은 렌더링된 SVG의 실제 fill(전구 `#FAB846`/`#9C9C9C`, 눈꽃 `#00CCFF`/`#949494`, 자물쇠 `#E4BF4D`/`#BEBEBE`)로 판정했습니다.
|
||
|
||
| 레이어 · 아이콘 | 클릭 전 | Callback | 클릭 후 |
|
||
|---|---|---|---|
|
||
| layer-0 전구 | `visible=true` 컬러 | `onLayerVisibilityChange(layer-0, false)` | `false` 회색 |
|
||
| layer-0 전구 | `false` 회색 | `(layer-0, true)` | `true` 컬러 |
|
||
| layer-0 눈꽃 | `frozen=false` 컬러 | `onLayerFreezeChange(layer-0, true)` | `true` 회색 |
|
||
| layer-0 눈꽃 | `true` 회색 | `(layer-0, false)` | `false` 컬러 |
|
||
| layer-0 자물쇠 | `locked=false` 컬러 | `onLayerLockChange(layer-0, true)` | `true` 회색 |
|
||
| layer-0 자물쇠 | `true` 회색 | `(layer-0, false)` | `false` 컬러 |
|
||
| layer-5 전구 | `visible=false` 회색 | `onLayerVisibilityChange(layer-5, true)` | `true` 컬러 |
|
||
| layer-5 전구 | `true` 컬러 | `(layer-5, false)` | `false` 회색 |
|
||
| layer-5 눈꽃 | `frozen=true` 회색 | `onLayerFreezeChange(layer-5, false)` | `false` 컬러 |
|
||
| layer-5 눈꽃 | `false` 컬러 | `(layer-5, true)` | `true` 회색 |
|
||
| layer-5 자물쇠 | `locked=true` 회색 | `onLayerLockChange(layer-5, false)` | `false` 컬러 |
|
||
| layer-5 자물쇠 | `false` 컬러 | `(layer-5, true)` | `true` 회색 |
|
||
|
||
- 모든 클릭에서 Callback 1회, `onLayerSelect` 0회, 목록 유지. `aria-pressed`는 표시됨 / 동결됨 / 잠김과 일치.
|
||
- 클릭하지 않은 다른 레이어 7개의 상태는 변하지 않음.
|
||
|
||
## 10. 남은 차이 · NOT VERIFIED
|
||
|
||
- 아이콘 색상 ↔ 상태 매핑은 위 §9로 확정·검증했습니다. 실제 SW 호스트에서 이 값으로 도면이 표시·동결·잠금되는지는 외부 연동이 필요합니다(NOT VERIFIED).
|
||
- 실제 SW 호스트에서의 레이어 표시·동결·잠금 반영과 레이어 상세 화면: 패키지 단독 검증 불가(NOT VERIFIED).
|
||
- 현재 레이어 이름 `#FFC600`, 목록 최대 높이 240px, 좁은 폭 왼쪽 이동은 Figma에 없는 구현값입니다.
|
||
- 레이어 상세 버튼 Hover는 Figma Variant가 없어 추가하지 않았습니다.
|
||
- Figma Smart Animate 곡선의 Prototype player 실재생 비교, 픽셀 diff, 실기기 390px, 스크린리더 실사용 검증은 수행하지 않았습니다.
|