feat(sw): viewer layer bulb, popup toggle switch, footer layout and 1440px story inner
Deploy Storybook / deploy (push) Successful in 1m11s

- SW Viewer layer popup: search + 전체 레이어 fixed, only the list scrolls; the all-layers
  control is the SWLayerVisibilityIcon bulb (role="switch"), one light #d2c8b4 line under it.
- Example H and the layer popup AI docs follow the bulb layout (VIEWER, DESIGN_RULES,
  COMPONENT_CATALOG, COMPOSITION_PATTERNS, AI_GUIDE sync).
- Popup toggle switch (Figma toggle_btn_new 633:75188 / 633:75902): one sw.css face for the
  HM UI Switch in SWPopup and SWViewerSwitch in SWViewerPanel; SW Header switches unchanged.
- SW Footer stories: no inner scrollers, fluid screens; workspace fits the viewport.
- SW Footer category tabs keep their positions when selected (divider slots kept, pocket
  edges drawn over the neighbours' padding).
- Storybook .sb-page-inner: 1440px max from 1920px (was 80vw).
- SW Header equal-split change was cancelled; header CSS unchanged.

Verified: check:ai-docs PASS, viewer test 49 PASS (1 FAIL hover face + timeout pre-existing),
footer-design-review 21 PASS, browser checks of switch/footer/inner.
Not verified: footer-popup-viewer-refinement (needs full Storybook build), text-weight/hover
tests, Switch 37x16 AI docs (deferred).
This commit is contained in:
shkim4 committed 2026-10-22 20:20:00 +09:00
1 parent 258e0a4b68
commit c1549b2b4f
22 files changed
+121 -63

No files matched your search

+6 -6
View File
@@ -83,7 +83,7 @@
|---|---|
| `title`, `open`, `onClose`, `closeLabel?` | 필수는 `title`·`open`·`onClose`. 제목 아이콘은 없습니다(SW 팝업 디자인) |
| `children` | 스크롤 본문. `SWViewerSection`(카드/plain, 12px 제목), `SWViewerField`(라벨 + 컨트롤, `inline` / `stacked` + `below`), `SWViewerSwitch`, `SWViewerSlider`, `SWViewerInput`, `SWViewerButton`, `SWViewerBadge`, `SWViewerHint`, 프로그램 목록·표 등 무엇이든 |
| `footer?` | 하단 버튼 영역(본문 스크롤과 무관하게 고정). `SWViewerButton variant="outline" \| "primary"`. `tone="green"` = 기본은 그대로, Hover는 primary(확인) 버튼 Hover와 같은 토큰 `--hm-sw-viewer-hover`, 누르는 동안 primary 면 `--hm-sw-viewer-panel-primary` (레이어 전체 표시는 버튼이 아니라 §4.1의 전체 레이어 Switch) |
| `footer?` | 하단 버튼 영역(본문 스크롤과 무관하게 고정). `SWViewerButton variant="outline" \| "primary"`. `tone="green"` = 기본은 그대로, Hover는 primary(확인) 버튼 Hover와 같은 토큰 `--hm-sw-viewer-hover`, 누르는 동안 primary 면 `--hm-sw-viewer-panel-primary` (레이어 전체 표시는 footer 버튼이 아니라 §4.1의 전체 레이어 전구) |
| `headerActions?` | 제목 막대 추가 버튼: `SWViewerButton variant="ghost"` (26px, 글자색만 Hover #FFC600 — SW 제목 막대 방식) |
| `width?`(314 = SWPopup), `height?`(420, `"auto"` 가능), `minWidth?`(250), `minHeight?`(140) | 메뉴별 크기. 최대 90vw × 90vh |
| `defaultPosition?` | `{ left, top }` 또는 `"top-end"`(기본) / `"top-start"` / `"center"`(modal 기본), `inset?: { top?, side? }`(기본 56 / 24 = 52px Header 아래) |
@@ -97,15 +97,15 @@
- **콘텐츠 교체:** 같은 프레임에 다른 `children`을 넣습니다(레이어 목록 / 옵션 / 속성 / 입력 폼 — Storybook `SW/Viewer › Settings Popup`). 프레임을 복사하거나 `hm-sw-viewer-*` class로 새 팝업을 만들지 않습니다.
- **본문 하단 경계(2026-10-22):** 뷰어 패널 본문은 아래 7px 여백 대신 스크롤 영역 밖의 8px 투명 경계를 둡니다(패키지 CSS, `padding-bottom: 0` + `border-bottom: 8px solid transparent`). 스크롤된 목록 행은 테두리 8px 위에서 잘리고, 맨 아래로 스크롤하면 마지막 행이 다 보입니다. 소비 코드에서 하단 여백·`padding`을 덧대지 않습니다. 일반 `SWPopup`은 7px 그대로입니다.
### 4.1 레이어 Popup — 개별 표시·숨김은 전구 아이콘 버튼, 전체는 Switch (필수, 2026-10-22)
### 4.1 레이어 Popup — 개별·전체 표시·숨김 모두 전구 아이콘 버튼 (필수, 2026-10-22)
> **규칙:** SW Viewer 레이어 Popup에서 레이어별 표시·숨김은 SW Header 레이어 메뉴와 같은 전구 아이콘(`SWLayerVisibilityIcon`) 버튼을 사용한다(2026-10-21 눈 `Eye`/`EyeOff` 규정 대체). 전체 레이어 켜기·끄기는 Popup 상단의 `SWViewerSwitch` 하나로 제어한다. 레이어별 표시·숨김을 Switch·Checkbox로 대체하지 않는다.
> **규칙:** SW Viewer 레이어 Popup에서 레이어별 표시·숨김은 SW Header 레이어 메뉴와 같은 전구 아이콘(`SWLayerVisibilityIcon`) 버튼을 사용한다(2026-10-21 눈 `Eye`/`EyeOff` 규정 대체). 전체 레이어 켜기·끄기도 Popup 상단의 같은 전구 버튼(`SWLayerVisibilityIcon`) 하나로 제어한다(2026-10-22 `SWViewerSwitch` 규정 대체). 개별·전체 표시·숨김을 Switch·Checkbox로 대체하지 않는다.
- 패키지에는 레이어 목록 전용 컴포넌트가 없습니다. 목록은 `SWViewerPanel` > `SWViewerSection surface="plain"` 안에 프로그램이 두고, 위에서부터 검색(선택) · **전체 레이어 Switch** · 목록 순서입니다. 행은 색 점 · 레이어 이름 · 개수(`SWViewerBadge`) · **전구 아이콘 버튼** 순서입니다(Storybook `SW/Viewer › Viewer Header`의 레이어 Popup, 예제 [ExampleH-ViewerMode.tsx](./examples/ExampleH-ViewerMode.tsx)).
- 패키지에는 레이어 목록 전용 컴포넌트가 없습니다. 목록은 `SWViewerPanel` > `SWViewerSection surface="plain"` 안에 프로그램이 두고, 위에서부터 검색(선택) · **전체 레이어 전구** · 목록 순서입니다. 검색과 전체 레이어 행은 고정이고 **목록만 세로 스크롤**합니다: 바깥 Section(`app-…`)에 `flex: 1 1 auto; min-height: 0`, 목록에 `flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-gutter: stable`, 나머지 자식은 `flex-shrink: 0`(소비 앱 class, 예제 `example-layout.css`). 패널 본문 자체는 스크롤되지 않으며 팝업 크기(기본 314 × 420)는 그대로입니다. 검색과 전체 레이어 사이에는 구분선이 없고, 전체 레이어 행 아래에만 연한 1px 선 `#d2c8b4`(`--hm-sw-property-table`, `padding-bottom: 8px`)를 둡니다. 행은 색 점 · 레이어 이름 · 개수(`SWViewerBadge`) · **전구 아이콘 버튼** 순서입니다(Storybook `SW/Viewer › Viewer Header`의 레이어 Popup, 예제 [ExampleH-ViewerMode.tsx](./examples/ExampleH-ViewerMode.tsx)).
- 전구 아이콘 버튼: 프로그램의 `<button type="button">` 24 × 24, radius 4, Hover 면 `rgba(154, 146, 132, .3)`, `cursor: pointer` 안에 `<SWLayerVisibilityIcon on={visible} />`(20 × 20, 장식용). 표시 중이면 색 있는 전구, 숨김이면 회색 전구(Header 레이어 메뉴와 같은 그림). 숨긴 행은 불투명도 0.45. 아이콘을 직접 그리거나 `hm-sw-header__*` class를 붙이지 않습니다.
- 접근성 이름에 다음 동작을 넣습니다: 표시 중 `"<레이어> 숨기기"`, 숨김 `"<레이어> 표시하기"`(`title`도 같은 동사).
- 클릭하면 그 레이어 하나의 가시성만 바꾸고 프로그램의 가시성 Callback·상태를 그대로 호출합니다. 아이콘만 그리고 기능을 빼지 않습니다.
- 전체 레이어 Switch(2026-10-22 제목 형식): `<SWViewerSection surface="plain" title="전체 레이어" className="app-…"><SWViewerSwitch aria-label="전체 레이어" checked={allVisible} onCheckedChange={setAll} /></SWViewerSection>`. 제목은 기존 Section 제목(`.hm-sw-viewer-section__title`, 12px 700)을 그대로 쓰고 별도 제목 CSS·Prop을 만들지 않습니다. 제목과 Switch를 한 줄에 두는 것은 소비 앱 자기 class(예: `section.app-viewer-all-layers { flex-direction: row; align-items: center; justify-content: space-between; min-height: 20px }`, 예제 `example-layout.css`)로 하고 `hm-sw-viewer-*` class는 덮지 않습니다. `checked`는 모든 레이어가 표시 중일 때만 `true`이고, 일부만 표시 중이면 `false`로 보이며 누르면 전체 표시가 됩니다. ON → 전체 표시, OFF → 전체 숨김. 별도 상태를 두지 않고 레이어 가시성 상태에서 계산하므로 개별 전환 시 자동으로 맞춰집니다. 이전의 `전체 켜기`·`전체 끄기` 버튼 두 개는 쓰지 않습니다.
- 전체 레이어 전구(2026-10-22): `<SWViewerSection surface="plain" title="전체 레이어" className="app-…"><button type="button" role="switch" aria-checked={allVisible} aria-label="전체 레이어" className="app-…-bulb" onClick={() => setAll(!allVisible)}><SWLayerVisibilityIcon on={allVisible} /></button></SWViewerSection>`. 버튼은 행의 전구 버튼과 같은 class·크기(24 × 24)이고 전구를 따로 그리지 않습니다. `role="switch"`·`aria-checked`·이름 `전체 레이어`로 켜짐 상태를 알리며 Enter·Space는 기본 button 동작입니다. 행 전구와 세로로 맞추려면 이 행에 목록과 같은 `padding-right: 6px` + `overflow-y: hidden; scrollbar-gutter: stable`을 주고 포커스 링은 `outline-offset: -2px`로 버튼 안에 둡니다. 제목은 기존 Section 제목(`.hm-sw-viewer-section__title`, 12px 700)을 그대로 쓰고 별도 제목 CSS·Prop을 만들지 않습니다. 제목과 전구를 한 줄에 두는 것은 소비 앱 자기 class(예: `section.app-viewer-all-layers { flex-direction: row; align-items: center; justify-content: space-between; min-height: 20px }`, 예제 `example-layout.css`)로 하고 `hm-sw-viewer-*` class는 덮지 않습니다. 켜짐(`aria-checked`, 색 있는 전구)은 모든 레이어가 표시 중일 때만 `true`이고, 일부만 표시 중이면 `false`(회색 전구)로 보이며 누르면 전체 표시가 됩니다. 켜진 전구를 누르면 전체 숨김. 별도 상태를 두지 않고 레이어 가시성 상태에서 계산하므로 개별 전환 시 자동으로 맞춰집니다. 이전의 `전체 켜기`·`전체 끄기` 버튼 두 개는 쓰지 않습니다.
- 그 밖의 `SWViewerSwitch`는 옵션 패널의 ON/OFF 설정(그리드·2.5D 등)에 씁니다.
- **옵션(설정) 패널(2026-10-22):** 하단 `취소`·`적용` 버튼이 없습니다(`footer` 미사용 → Footer 영역·구분선도 없음). 토글·Input·Slider는 바뀌는 즉시 프로그램 상태에 반영하고, 기본값 복원은 제목 막대의 초기화(`headerActions`) 버튼입니다. 확인이 필요한 입력 팝업(축척 지정 등)의 `footer`는 그대로 씁니다.
@@ -146,7 +146,7 @@ const groups: SWViewerHeaderGroup[] = [
| Header 버튼 글자에 `-webkit-text-stroke`·`border`로 외곽선 만들기 | 외곽선은 패키지 토큰이 이미 적용합니다 |
| 메뉴 색·간격을 `#123456`, `margin` 등 임의 값으로 덮기 | 토큰 `--hm-sw-viewer-*` / `--hm-sw-header-*`만 가장 바깥 SW 범위(`<SWTheme>` 또는 단독 컴포넌트 루트)의 `style`로 조정(기본값 유지 권장, [AI_USAGE.md](./AI_USAGE.md) §3.1) |
| Header·Dropdown·More·Panel의 글꼴을 각각 따로 지정, `.hm-sw-viewer-menu` 등 class로 덮기 | 화면을 `<SWTheme style={theme}>` 하나로 감싸면 Header·Dropdown·More·Panel이 같은 값을 상속합니다. 글꼴 기본값은 Noto Sans KR이며 Web UI 글꼴 토큰을 복사하지 않습니다 |
| 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·ON/OFF 스위치·눈 아이콘으로 만들기 | §4.1: 전구(`SWLayerVisibilityIcon`) 아이콘 버튼. Switch는 상단 전체 레이어 제어와 옵션 설정에만 |
| 레이어 Popup의 개별·전체 표시·숨김을 `SWViewerSwitch`·Checkbox·ON/OFF 스위치·눈 아이콘으로 만들기 | §4.1: 전구(`SWLayerVisibilityIcon`) 아이콘 버튼(전체 레이어 포함). Switch는 옵션 설정에만 |
| 존재하지 않는 Props 추측(`items`, `content`, `renderBody` …) | 위 표와 `dist/index.d.ts`가 전부 |
| 일반 모드 화면에서 `SWViewerHeader` 사용, 뷰어에서 `SWHeader` 재스타일 | 모드별 Header를 바꿔 끼웁니다(창 최소화·닫기 `SWHeaderWindowControls`를 `trailing` 끝에 두는 §2.1 구성은 허용) |
| 기능 없는 메뉴를 가짜 동작으로 연결 | 항목 제외 또는 `disabled` + 미연결 보고 |