범위: @hm-design/sw 레이어 필드 Dropdown과 전구·눈꽃·자물쇠 On/Off. 기준: Figma rj6wpsfk24lOWvWiVRAANP 5개 Node. 규격은 디자인 명세 §5.2, 사용법은 사용 가이드를 따릅니다.
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건.
| 항목 |
기대(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, 스크린리더 실사용 검증은 수행하지 않았습니다.