diff --git a/docs/ai/AI_GUIDE.md b/docs/ai/AI_GUIDE.md index 839262a..36e9642 100644 --- a/docs/ai/AI_GUIDE.md +++ b/docs/ai/AI_GUIDE.md @@ -225,7 +225,7 @@ import "@hm-design/sw/styles.css" - 읽기: `node_modules/@hm-design/sw/ai/` → AI_USAGE §0(뷰어 모드) → `VIEWER.md`, 예제 `ai/examples/ExampleH-ViewerMode.tsx` - 재사용: `SWViewerHeader`(`groups` 데이터: `button`·`toggle`·`menu`·`tabs`·`switch`·`custom` 항목, `onCommand`, `trailing`에 `SWViewerStatus`·`SWViewerHeaderButton`. 창 최소화·닫기가 필요한 버전은 `trailing` 맨 끝에 `SWHeaderWindowControls showWindowMove={false}` — VIEWER.md §2.1), Dropdown은 `type: "menu"` 항목의 `entries`(단독·우클릭은 `SWViewerMenu`), 팝업은 `SWViewerPanel` + `SWViewerSection`·`SWViewerField`·`SWViewerSwitch`·`SWViewerSlider`·`SWViewerInput`·`SWViewerButton` - 신규: 뷰어에 없는 패널 콘텐츠 → 소비 프로젝트 컴포넌트를 `SWViewerPanel`의 `children`에, 안쪽은 `SWViewerField` + `SWViewerInput` 등, 외형은 `--hm-sw-viewer-*` 토큰 -- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어는 상단 `SWViewerSwitch` 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) +- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어도 상단의 같은 전구 버튼 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) ```tsx import { SWViewerHeader, SWViewerPanel, SWViewerSection, SWViewerField, SWViewerSwitch, type SWViewerHeaderGroup } from "@hm-design/sw" diff --git a/packages/org-chart-react/ai/AI_GUIDE.md b/packages/org-chart-react/ai/AI_GUIDE.md index 839262a..36e9642 100644 --- a/packages/org-chart-react/ai/AI_GUIDE.md +++ b/packages/org-chart-react/ai/AI_GUIDE.md @@ -225,7 +225,7 @@ import "@hm-design/sw/styles.css" - 읽기: `node_modules/@hm-design/sw/ai/` → AI_USAGE §0(뷰어 모드) → `VIEWER.md`, 예제 `ai/examples/ExampleH-ViewerMode.tsx` - 재사용: `SWViewerHeader`(`groups` 데이터: `button`·`toggle`·`menu`·`tabs`·`switch`·`custom` 항목, `onCommand`, `trailing`에 `SWViewerStatus`·`SWViewerHeaderButton`. 창 최소화·닫기가 필요한 버전은 `trailing` 맨 끝에 `SWHeaderWindowControls showWindowMove={false}` — VIEWER.md §2.1), Dropdown은 `type: "menu"` 항목의 `entries`(단독·우클릭은 `SWViewerMenu`), 팝업은 `SWViewerPanel` + `SWViewerSection`·`SWViewerField`·`SWViewerSwitch`·`SWViewerSlider`·`SWViewerInput`·`SWViewerButton` - 신규: 뷰어에 없는 패널 콘텐츠 → 소비 프로젝트 컴포넌트를 `SWViewerPanel`의 `children`에, 안쪽은 `SWViewerField` + `SWViewerInput` 등, 외형은 `--hm-sw-viewer-*` 토큰 -- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어는 상단 `SWViewerSwitch` 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) +- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어도 상단의 같은 전구 버튼 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) ```tsx import { SWViewerHeader, SWViewerPanel, SWViewerSection, SWViewerField, SWViewerSwitch, type SWViewerHeaderGroup } from "@hm-design/sw" diff --git a/packages/org-chart/ai/AI_GUIDE.md b/packages/org-chart/ai/AI_GUIDE.md index 839262a..36e9642 100644 --- a/packages/org-chart/ai/AI_GUIDE.md +++ b/packages/org-chart/ai/AI_GUIDE.md @@ -225,7 +225,7 @@ import "@hm-design/sw/styles.css" - 읽기: `node_modules/@hm-design/sw/ai/` → AI_USAGE §0(뷰어 모드) → `VIEWER.md`, 예제 `ai/examples/ExampleH-ViewerMode.tsx` - 재사용: `SWViewerHeader`(`groups` 데이터: `button`·`toggle`·`menu`·`tabs`·`switch`·`custom` 항목, `onCommand`, `trailing`에 `SWViewerStatus`·`SWViewerHeaderButton`. 창 최소화·닫기가 필요한 버전은 `trailing` 맨 끝에 `SWHeaderWindowControls showWindowMove={false}` — VIEWER.md §2.1), Dropdown은 `type: "menu"` 항목의 `entries`(단독·우클릭은 `SWViewerMenu`), 팝업은 `SWViewerPanel` + `SWViewerSection`·`SWViewerField`·`SWViewerSwitch`·`SWViewerSlider`·`SWViewerInput`·`SWViewerButton` - 신규: 뷰어에 없는 패널 콘텐츠 → 소비 프로젝트 컴포넌트를 `SWViewerPanel`의 `children`에, 안쪽은 `SWViewerField` + `SWViewerInput` 등, 외형은 `--hm-sw-viewer-*` 토큰 -- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어는 상단 `SWViewerSwitch` 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) +- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어도 상단의 같은 전구 버튼 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) ```tsx import { SWViewerHeader, SWViewerPanel, SWViewerSection, SWViewerField, SWViewerSwitch, type SWViewerHeaderGroup } from "@hm-design/sw" diff --git a/packages/sw/ai/AI_GUIDE.md b/packages/sw/ai/AI_GUIDE.md index 839262a..36e9642 100644 --- a/packages/sw/ai/AI_GUIDE.md +++ b/packages/sw/ai/AI_GUIDE.md @@ -225,7 +225,7 @@ import "@hm-design/sw/styles.css" - 읽기: `node_modules/@hm-design/sw/ai/` → AI_USAGE §0(뷰어 모드) → `VIEWER.md`, 예제 `ai/examples/ExampleH-ViewerMode.tsx` - 재사용: `SWViewerHeader`(`groups` 데이터: `button`·`toggle`·`menu`·`tabs`·`switch`·`custom` 항목, `onCommand`, `trailing`에 `SWViewerStatus`·`SWViewerHeaderButton`. 창 최소화·닫기가 필요한 버전은 `trailing` 맨 끝에 `SWHeaderWindowControls showWindowMove={false}` — VIEWER.md §2.1), Dropdown은 `type: "menu"` 항목의 `entries`(단독·우클릭은 `SWViewerMenu`), 팝업은 `SWViewerPanel` + `SWViewerSection`·`SWViewerField`·`SWViewerSwitch`·`SWViewerSlider`·`SWViewerInput`·`SWViewerButton` - 신규: 뷰어에 없는 패널 콘텐츠 → 소비 프로젝트 컴포넌트를 `SWViewerPanel`의 `children`에, 안쪽은 `SWViewerField` + `SWViewerInput` 등, 외형은 `--hm-sw-viewer-*` 토큰 -- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어는 상단 `SWViewerSwitch` 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) +- 하지 않음: `SWHeader`·`SWPopup`을 뷰어 화면에 섞기(예외: 위 `trailing` 끝의 `SWHeaderWindowControls` 최소화·닫기), 로고(홈) 메뉴(`SWHomeMenu`·`SWRecentFiles`·`homeMenu`)를 뷰어 화면에 넣거나 비슷하게 만들기(홈 메뉴는 일반 모드 전용), 레이어 Popup의 레이어별 표시·숨김을 `SWViewerSwitch`·Checkbox·눈 아이콘으로 만들기(전구 `SWLayerVisibilityIcon` 아이콘 버튼 필수, 전체 레이어도 상단의 같은 전구 버튼 하나, VIEWER.md §4.1), 로고가 없다고 임의 문자·아이콘 넣기, 연결할 명령이 없는 메뉴를 가짜 동작으로 만들기(제외 또는 `disabled` + 미연결 보고) ```tsx import { SWViewerHeader, SWViewerPanel, SWViewerSection, SWViewerField, SWViewerSwitch, type SWViewerHeaderGroup } from "@hm-design/sw" diff --git a/packages/sw/ai/COMPONENT_CATALOG.md b/packages/sw/ai/COMPONENT_CATALOG.md index 12adc11..c894ee2 100644 --- a/packages/sw/ai/COMPONENT_CATALOG.md +++ b/packages/sw/ai/COMPONENT_CATALOG.md @@ -158,7 +158,7 @@ SW **뷰어 모드** 전용 Export입니다(2026-10-19 추가, 참고 DWG/DXF - **검증 표시:** 뷰어 Export는 이 카탈로그 기준의 독립 소비 프로젝트(Tarball) 브라우저 검증 기록이 없어 ◻입니다. 예제 H는 공개 Export만 사용합니다. - 상세 크기·상태는 [VIEWER.md](./VIEWER.md) §2~4가 기준입니다. 이 표에 없는 Props(`items`, `content`, `renderBody` 등)는 없습니다. -- **레이어 Popup 가시성(필수, 2026-10-22):** SW Viewer 레이어 Popup에서 레이어별 표시·숨김은 `SWLayerVisibilityIcon`(SW Header 전구)을 담은 아이콘 버튼을 사용한다. 전체 레이어 켜기·끄기는 Popup 상단의 `SWViewerSwitch` 하나(전체 표시일 때만 ON, 일부 표시는 OFF → 누르면 전체 표시)다. 레이어별 표시·숨김을 Switch·Checkbox로 대체하지 않는다. 행 구성·크기·접근성 이름은 [VIEWER.md](./VIEWER.md) §4.1, 예제 H. 그 밖의 `SWViewerSwitch`는 옵션 ON/OFF용입니다. +- **레이어 Popup 가시성(필수, 2026-10-22):** SW Viewer 레이어 Popup에서 레이어별 표시·숨김은 `SWLayerVisibilityIcon`(SW Header 전구)을 담은 아이콘 버튼을 사용한다. 전체 레이어 켜기·끄기도 Popup 상단의 같은 전구 버튼 하나(`role="switch"`, 전체 표시일 때만 켜짐, 일부 표시는 꺼짐 → 누르면 전체 표시)다. 검색·전체 레이어는 고정, 목록만 스크롤. 개별·전체 표시·숨김을 Switch·Checkbox로 대체하지 않는다. 행 구성·크기·접근성 이름은 [VIEWER.md](./VIEWER.md) §4.1, 예제 H. 그 밖의 `SWViewerSwitch`는 옵션 ON/OFF용입니다. ## 10. Footer (`SWFooter*`, SW 일반 모드) diff --git a/packages/sw/ai/COMPOSITION_PATTERNS.md b/packages/sw/ai/COMPOSITION_PATTERNS.md index 061fb2c..94f319d 100644 --- a/packages/sw/ai/COMPOSITION_PATTERNS.md +++ b/packages/sw/ai/COMPOSITION_PATTERNS.md @@ -174,7 +174,7 @@ const groups: SWViewerHeaderGroup[] = [ | 오른쪽 끝 | `trailing`: `SWViewerStatus` + `SWViewerHeaderButton iconOnly` | | 도구 창 | 비모달 `SWViewerPanel`(드래그·크기 조절·접기 기본). 여러 개면 `inset`·`defaultPosition`으로 겹치지 않게 | | 확인·정보 | `SWViewerPanel modal` + `footer`의 `SWViewerButton variant="primary"` | -| 패널 내용 | `SWViewerSection` > `SWViewerField` > `SWViewerSwitch` / `SWViewerSlider` / `SWViewerInput`, 목록은 `surface="plain"` + 프로그램 목록, 설명 `SWViewerHint`. **레이어별 표시·숨김은 전구(`SWLayerVisibilityIcon`) 아이콘 버튼(Switch 금지), 전체 레이어는 상단 `SWViewerSwitch` 하나**(VIEWER.md §4.1, 예제 H) | +| 패널 내용 | `SWViewerSection` > `SWViewerField` > `SWViewerSwitch` / `SWViewerSlider` / `SWViewerInput`, 목록은 `surface="plain"` + 프로그램 목록, 설명 `SWViewerHint`. **레이어별·전체 표시·숨김은 전구(`SWLayerVisibilityIcon`) 아이콘 버튼(Switch 금지), 검색·전체 레이어 고정 + 목록만 스크롤**(VIEWER.md §4.1, 예제 H) | | 반응형 | 고정 너비가 아니라 Header의 실제 가용 폭으로 3단계: 기본(아이콘+텍스트) → `iconOnlyBelow` 항목 모두 아이콘만(툴팁 유지) → `collapseBelow` 항목을 하나씩 More로(값이 큰 것, 같으면 오른쪽 먼저). 넓어지면 역순 복귀. 패널 토글은 `collapseBelow` 없이 아이콘까지만 | | 우클릭 메뉴 | `SWViewerMenu` + `anchorPoint` | diff --git a/packages/sw/ai/DESIGN_RULES.md b/packages/sw/ai/DESIGN_RULES.md index 8dc74a1..1c563fa 100644 --- a/packages/sw/ai/DESIGN_RULES.md +++ b/packages/sw/ai/DESIGN_RULES.md @@ -161,7 +161,7 @@ | 상태 표시 | `SWViewerStatus`: 글자 `--hm-sw-viewer-muted` 11px 400, `error`는 `--hm-sw-viewer-accent`, `loading` 아이콘 회전 | — | - **Typography(뷰어):** Header 항목 14px 500, 캡션·개수·상태 11px 400, 패널 필드 라벨 14px 500, Section 제목 12px 700, 패널 버튼·Badge 12px 500, Input 12px. 패널 콘텐츠(2026-10-22): 레이어 이름·속성 행 Label 14px 500, 읽기 전용 속성값 14px(패널 기본 Weight), 설명·Input·Select 값·Placeholder 12px, 별도 `font-family` 지정 없음([VIEWER.md](./VIEWER.md) §4.2). Font Weight는 400/500/700만 씁니다. -- **레이어 Popup 가시성(2026-10-22 필수, 10-21 눈 아이콘 규정 대체):** SW Viewer 레이어 Popup에서 레이어별 표시·숨김은 SW Header 레이어 메뉴와 같은 전구 아이콘(`SWLayerVisibilityIcon`, 20 × 20, 표시 = 색 있는 전구 / 숨김 = 회색) 버튼을 사용한다. 버튼 24 × 24 · radius 4 · Hover 면 `rgba(154, 146, 132, .3)` · `cursor: pointer`, 숨긴 행 불투명도 0.45. 전체 레이어 켜기·끄기는 Popup 상단 `SWViewerSection title="전체 레이어"`(기존 Section 제목) + 같은 줄의 `SWViewerSwitch` 하나(전체 표시일 때만 ON). 레이어별 표시·숨김은 Switch로 대체하지 않는다([VIEWER.md](./VIEWER.md) §4.1). +- **레이어 Popup 가시성(2026-10-22 필수, 10-21 눈 아이콘 규정 대체):** SW Viewer 레이어 Popup에서 레이어별 표시·숨김은 SW Header 레이어 메뉴와 같은 전구 아이콘(`SWLayerVisibilityIcon`, 20 × 20, 표시 = 색 있는 전구 / 숨김 = 회색) 버튼을 사용한다. 버튼 24 × 24 · radius 4 · Hover 면 `rgba(154, 146, 132, .3)` · `cursor: pointer`, 숨긴 행 불투명도 0.45. 전체 레이어 켜기·끄기는 Popup 상단 `SWViewerSection title="전체 레이어"`(기존 Section 제목) + 같은 줄의 같은 전구 버튼 하나(`role="switch"`, 전체 표시일 때만 켜짐, 행 전구와 세로 정렬). 검색·전체 레이어 행은 고정, 목록만 세로 스크롤; 검색과 전체 레이어 사이 구분선 없음, 전체 레이어 행 아래 1px `#d2c8b4`(`--hm-sw-property-table`) + 아래 여백 8px. 개별·전체 표시·숨김은 Switch로 대체하지 않는다([VIEWER.md](./VIEWER.md) §4.1). - **뷰어 신규 컴포넌트 체크리스트:** ① VIEWER.md·카탈로그 §9 컴포넌트로 해결되는지 먼저 본다 ② 위 토큰을 `var(--…)`로만 읽고 일반 모드 전용 모양(§8 `SWPopupRow`, §9 `SWToolButton`)을 섞지 않는다 ③ 상태는 위 표대로 ④ 토큰에 없는 값은 결과 보고에 "AI 구성 값 — 디자인 확인 필요"로 적는다 ⑤ `hm-sw-viewer-*` class를 쓰거나 덮어쓰지 않는다. ## 12. 로고 클릭 메뉴 (`SWHeader homeMenu` / `SWHomeMenu`, Figma 26:10591) — SW 일반 모드 전용 diff --git a/packages/sw/ai/VIEWER.md b/packages/sw/ai/VIEWER.md index c6b8619..213a532 100644 --- a/packages/sw/ai/VIEWER.md +++ b/packages/sw/ai/VIEWER.md @@ -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)). - 전구 아이콘 버튼: 프로그램의 ``. 버튼은 행의 전구 버튼과 같은 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 범위(`` 또는 단독 컴포넌트 루트)의 `style`로 조정(기본값 유지 권장, [AI_USAGE.md](./AI_USAGE.md) §3.1) | | Header·Dropdown·More·Panel의 글꼴을 각각 따로 지정, `.hm-sw-viewer-menu` 등 class로 덮기 | 화면을 `` 하나로 감싸면 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` + 미연결 보고 | diff --git a/packages/sw/ai/examples/ExampleH-ViewerMode.tsx b/packages/sw/ai/examples/ExampleH-ViewerMode.tsx index 975b653..8f92eae 100644 --- a/packages/sw/ai/examples/ExampleH-ViewerMode.tsx +++ b/packages/sw/ai/examples/ExampleH-ViewerMode.tsx @@ -36,8 +36,10 @@ export function ExampleHViewerMode({ onEvent }: { onEvent?: (name: string) => vo setHiddenLayers(next) onEvent?.(`layer:${name}=${!visible}`) } - // All-layers switch: ON only when every layer is shown. A partial state reads OFF, so turning it on shows all. + // All-layers bulb: ON only when every layer is shown. A partial state reads OFF, so turning it on shows all. const allVisible = hiddenLayers.size === 0 + const [query, setQuery] = useState("") + const shownLayers = layers.filter(layer => layer.name.toLowerCase().includes(query.trim().toLowerCase())) const setAllLayers = (visible: boolean) => { setHiddenLayers(new Set(visible ? [] : layers.map(layer => layer.name))) onEvent?.(`layer:all=${visible}`) @@ -74,14 +76,17 @@ export function ExampleHViewerMode({ onEvent }: { onEvent?: (name: string) => vo setAbout(true)} /> } /> - setPanels({ ...panels, layers: false })}> - - {/* Top: all-layers SWViewerSwitch. Rows: swatch · name · count · bulb button (SWLayerVisibilityIcon), not a switch. */} + setPanels({ ...panels, layers: false })}> + + {/* Fixed: search, then the all-layers bulb (same button + SWLayerVisibilityIcon as the rows, role="switch"). + Only the list below scrolls. Rows: swatch · name · count · bulb button, never a switch. */} + setQuery(event.currentTarget.value)} /> - + -
    - {layers.map(layer => { + {shownLayers.length === 0 ? 검색 결과가 없습니다 ("{query}") :
      + {shownLayers.map(layer => { const visible = !hiddenLayers.has(layer.name) return (
    • @@ -94,7 +99,7 @@ export function ExampleHViewerMode({ onEvent }: { onEvent?: (name: string) => vo
    • ) })} -
    +
}
diff --git a/packages/sw/ai/examples/example-layout.css b/packages/sw/ai/examples/example-layout.css index 95fe275..dca6591 100644 --- a/packages/sw/ai/examples/example-layout.css +++ b/packages/sw/ai/examples/example-layout.css @@ -36,13 +36,23 @@ } /* Viewer layer list (example H), the same look as Storybook SW/Viewer's layer popup. Panel tokens only. Layer visibility is a 24 x 24 button around SWLayerVisibilityIcon (header bulb), never a switch or checkbox; - the all-layers control above the list is SWViewerSection title="전체 레이어" + SWViewerSwitch on one row. */ + the all-layers control above the list is SWViewerSection title="전체 레이어" + the same bulb button on one row. + Search and that row stay fixed; only the list scrolls (the section fills the panel body). */ +.app-viewer-layer-popup { flex: 1 1 auto; min-height: 0; } +.app-viewer-layer-popup > :not(.app-viewer-layers) { flex-shrink: 0; } +/* Search spans the popup (the panel input is 80px by default). */ +.app-viewer-layer-popup > input.app-viewer-layer-search { width: 100%; } +.app-viewer-layer-popup > .app-viewer-layers { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; } .app-viewer-layers { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; } .app-viewer-layers > li { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 3px; } .app-viewer-layers > li:hover { background: rgba(154, 146, 132, 0.25); } .app-viewer-layers > li[data-visible="false"] { opacity: 0.45; } -/* 전체 레이어: the plain SWViewerSection laid out as one row — title (.hm-sw-viewer-section__title) left, switch right. */ -section.app-viewer-all-layers { flex-direction: row; align-items: center; justify-content: space-between; min-height: 20px; } +/* 전체 레이어: the plain SWViewerSection laid out as one row — title (.hm-sw-viewer-section__title) left, bulb right. + No line between search and this row; one light line (#d2c8b4, --hm-sw-property-table) under it. The bulb lines up + with the row bulbs: row padding (6px) + the list's scrollbar gutter (needs overflow, so the focus ring sits inside). */ +section.app-viewer-all-layers { flex-direction: row; align-items: center; justify-content: space-between; min-height: 20px; + padding: 0 6px 8px 0; border-bottom: 1px solid var(--hm-sw-property-table); overflow-y: hidden; scrollbar-gutter: stable; } +.app-viewer-all-layers .app-viewer-layer-bulb:focus-visible { outline-offset: -2px; } .app-viewer-layer-swatch { flex: 0 0 10px; width: 10px; height: 10px; border: 1px solid var(--hm-sw-viewer-panel-inner-border); border-radius: 9999px; } .app-viewer-layer-name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 14px; font-weight: 500; } .app-viewer-layer-bulb { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 4px; background: none; cursor: pointer; } diff --git a/packages/sw/src/components/sw-footer/SWFooterParts.tsx b/packages/sw/src/components/sw-footer/SWFooterParts.tsx index 9f430b6..53cbdd1 100644 --- a/packages/sw/src/components/sw-footer/SWFooterParts.tsx +++ b/packages/sw/src/components/sw-footer/SWFooterParts.tsx @@ -321,7 +321,8 @@ export const SWFooterMenu = forwardRef(functi const selected = item.id === current const previousSelected = index > 0 && items[index - 1].id === current return [ - index > 0 && !selected && !previousSelected &&
{filtered.length === 0 ? 검색 결과가 없습니다 ("{query}") : ( diff --git a/src/sw/sw-viewer-stories.css b/src/sw/sw-viewer-stories.css index afbcd4f..ae7e7c7 100644 --- a/src/sw/sw-viewer-stories.css +++ b/src/sw/sw-viewer-stories.css @@ -8,11 +8,23 @@ background: var(--sw-story-canvas); } .sw-viewer-story-room { padding-bottom: 320px; } -/* Search, then the all-layers switch row (SWViewerField + SWViewerSwitch, 2026-10-22). */ +/* Search, then the all-layers bulb row (section title + SWLayerVisibilityIcon button, 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. */ +/* 전체 레이어: the plain SWViewerSection laid out as one row — its title on the left, the bulb on the right. */ .sw-viewer-story-toolbar > .sw-viewer-story-all { flex-direction: row; align-items: center; justify-content: space-between; min-height: 20px; } +/* Its bulb lines up with the row bulbs: the row padding (6px) plus the same scrollbar gutter the list reserves. The + gutter needs overflow, so the focus ring is drawn inside the 24px button. */ +.sw-viewer-story-layers .sw-viewer-story-all { padding-right: 6px; overflow-y: hidden; scrollbar-gutter: stable; } +/* 2026-10-22 layer image: one light line under the 전체 레이어 row (none between search and that row), the SW + property table tone (#d2c8b4) rather than the darker inner border. */ +.sw-viewer-story-layers .sw-viewer-story-all { padding-bottom: 8px; border-bottom: 1px solid var(--hm-sw-property-table); } +.sw-viewer-story-all .sw-viewer-story-bulb:focus-visible { outline-offset: -2px; } +/* Layer panel (2026-10-22): the section fills the panel body, search + 전체 레이어 stay put and only the list scrolls. + No divider between the fixed rows and the list; the list keeps the panel's scrollbar style. */ +.sw-viewer-story-layers { flex: 1 1 auto; min-height: 0; } +.sw-viewer-story-layers > .sw-viewer-story-toolbar { flex-shrink: 0; } +.sw-viewer-story-layers > .sw-viewer-story-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; } .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); }