feat(sw): viewer layer eye-icon rule, header hover, trailing strip and file name width

User-requested SW Viewer changes (2026-10-21); no new Props, public .d.ts unchanged.
- Layer popup visibility: AI docs (VIEWER §4.1, catalog, design rules, patterns,
  AI_GUIDE) require the Eye / EyeOff icon button, never a switch; example H now
  renders a real layer list with eye buttons; Storybook eye button gets the pointer.
- Viewer header hover: text and icon #FFC600, the #266651 hover face removed; size and
  position unchanged. Status stays display only.
- Trailing strip: status, the trailing icon button (info / license) and SWHeader
  minimize / close are full-height (48px) segments on the window-area face, no gap, top
  and bottom on the object surface border; no line around the license button.
- File name: the measured steps use the file-name basis (header width), not the text
  width, so a short and a long name give the same Open / file-name widths.
- File open icon: the black icon outline of every viewer icon (SWViewerIcon), no frame.
This commit is contained in:
shkim4 committed 2026-10-21 18:52:38 +09:00
1 parent 32e46c32be
commit caf4c32592
14 files changed
+109 -26

No files matched your search

+15 -4
View File
@@ -37,16 +37,16 @@
| `onCommand(command)` | 모든 명령을 한 곳에서 받습니다: `{ id, itemId, value? }`. 항목 자체 Callback 다음에 호출 |
| `documentTitle`, `documentPlaceholder`, `documentTooltip`, `document?` | 파일명 면 |
| `documents?: SWHeaderDocument[]`, `activeDocumentId?`, `onDocumentSelect?(document)` | 파일명 클릭 시 아래 문서 목록(샘플 도면 등). `SWHeader` 파일명 목록(`SWHeaderDocumentTitle`)과 같은 모양·키보드(ArrowDown 열기, Escape, 바깥 클릭)이며 **Open·+ New는 표시하지 않음**. 목록 외곽 너비는 파일명 버튼 외곽 너비와 `SWHeader` 목록 너비(312px) 이상이며 긴 항목이면 넓어집니다. 열려도 로고가 위에 표시됨. 선택 시 프로그램이 파일을 불러오고 `documentTitle`·`activeDocumentId`를 갱신. `document`가 있으면 `document`가 우선 |
| `onDocumentOpen?()`, `documentOpenLabel?` | 로고 오른쪽 파일 열기 버튼(`SWHeader` `documentOpenPlacement="header"`와 같은 파일명 면 `SWToolButton appearance="document"` + `SWHeader` `...` 버튼 테두리의 폴더 아이콘 + `documentOpenLabel`, 기본 `Open`). 공간이 부족하면 아이콘만(라벨은 Tooltip·접근성 이름) 표시하고 More로 옮기지 않습니다. 실행 후 `onCommand({ id: "open", itemId: "document" })` |
| `onDocumentOpen?()`, `documentOpenLabel?` | 로고 오른쪽 파일 열기 버튼(`SWHeader` `documentOpenPlacement="header"`와 같은 파일명 면 `SWToolButton appearance="document"` + 폴더 아이콘(다른 Header 아이콘과 같은 검정 외곽선 `SWViewerIcon`, 테두리·면 없음) + `documentOpenLabel`, 기본 `Open`). 공간이 부족하면 아이콘만(라벨은 Tooltip·접근성 이름) 표시하고 More로 옮기지 않습니다. 실행 후 `onCommand({ id: "open", itemId: "document" })` |
| `logoMenu?: { title?, items }` | (2026-10-21 이후 뷰어 화면에서 사용하지 않음, 호환용) 로고 클릭 메뉴. 파일 열기는 `onDocumentOpen` 버튼을 씁니다. 항목 `{ id, label, icon?, value?, checked?, disabled?, hidden?, onSelect? }` |
| `logo?`, `logoSrc?`, `logoAlt?`, `onHomeClick?` | `SWHeader`와 같은 로고 Props. **주지 않으면 아무것도 그리지 않습니다**(“HM” 문자·기본 아이콘 없음, 로고 면은 사선 끝 24px만). 주면 72px 면(로고가 더 넓으면 그만큼), 높이 최대 24px |
| `trailing?: ReactNode` | 오른쪽 끝 자유 영역: `SWViewerStatus`, `SWViewerHeaderButton`, 창 최소화·닫기 버전이면 맨 끝에 `SWHeaderWindowControls`(§2.1) |
| `trailing?: ReactNode` | 오른쪽 끝 자유 영역: `SWViewerStatus`, `SWViewerHeaderButton`, 창 최소화·닫기 버전이면 맨 끝에 `SWHeaderWindowControls`(§2.1). 이 세 부품은 `SWHeader` 창 영역처럼 전체 높이(48px)의 칸이 이어진 띠로 그려집니다: 같은 면, 칸 앞 1px 구분선(라이선스 버튼 앞뒤에는 없음), 위·아래 경계는 객체 면 테두리 하나라 모든 칸의 위·아래가 같음, 칸 사이 여백 없음, 객체 면 오른쪽 끝까지(2026-10-21). 상태 칸은 표시 전용, 아이콘 버튼(정보·라이선스) 칸은 테두리·선 없음 |
| `moreLabel?`, `toolbarLabel?`, `<header>` 속성, `ref` | |
- **반응형(2026-10-21, 실제 가용 폭 기준 3단계):** 고정 Breakpoint를 쓰지 않고 Header 행의 실제 남는 폭(로고·파일명·메뉴·Gap·Padding·More 버튼 포함, 파일명은 글자 폭까지만)을 측정합니다. ① 모두 들어가면 기본 형태(아이콘+텍스트), ② 부족하면 `icon`과 `iconOnlyBelow`가 있는 항목을 모두 아이콘만(라벨은 Tooltip·접근성 이름), ③ 그래도 부족하면 `collapseBelow` 항목을 하나씩 More(⋯, 베이지 260px Dropdown)로 옮깁니다(값이 큰 것부터, 같으면 오른쪽 먼저). 넓어지면 More → 아이콘 → 기본 순서로 복귀하고, 상단과 More에 같은 항목이 동시에 나오지 않습니다. `collapseBelow`·`iconOnlyBelow`의 숫자는 px Breakpoint가 아니며 순서만 정합니다(`iconOnlyBelow`는 지정 여부만, `collapseBelow`는 값이 큰 것부터; Props 형식 동일). 로고 옆 파일 열기 버튼(`onDocumentOpen`)은 ② 단계에서 아이콘만으로 줄고 More로 옮기지 않으며, 샘플은 `documents`. 레이어·설정·속성은 `collapseBelow` 없이 아이콘까지만. 1180px 미만 간격 2px, 560px 미만 상태 글자 숨김(CSS container)은 그대로입니다. 가로 스크롤은 만들지 않습니다.
- **반응형(2026-10-21, 실제 가용 폭 기준 3단계):** 고정 Breakpoint를 쓰지 않고 Header 행의 실제 남는 폭(로고·파일명·메뉴·Gap·Padding·More 버튼 포함)을 측정합니다. 파일명 면은 글자 수가 아니라 Header 폭으로 정한 기본 폭으로 계산하므로, 같은 폭에서는 짧은 파일명과 긴 파일명의 단계·파일 열기 버튼·파일명 면 너비가 같고 긴 이름은 `…`입니다(2026-10-21). ① 모두 들어가면 기본 형태(아이콘+텍스트), ② 부족하면 `icon`과 `iconOnlyBelow`가 있는 항목을 모두 아이콘만(라벨은 Tooltip·접근성 이름), ③ 그래도 부족하면 `collapseBelow` 항목을 하나씩 More(⋯, 베이지 260px Dropdown)로 옮깁니다(값이 큰 것부터, 같으면 오른쪽 먼저). 넓어지면 More → 아이콘 → 기본 순서로 복귀하고, 상단과 More에 같은 항목이 동시에 나오지 않습니다. `collapseBelow`·`iconOnlyBelow`의 숫자는 px Breakpoint가 아니며 순서만 정합니다(`iconOnlyBelow`는 지정 여부만, `collapseBelow`는 값이 큰 것부터; Props 형식 동일). 로고 옆 파일 열기 버튼(`onDocumentOpen`)은 ② 단계에서 아이콘만으로 줄고 More로 옮기지 않으며, 샘플은 `documents`. 레이어·설정·속성은 `collapseBelow` 없이 아이콘까지만. 1180px 미만 간격 2px, 560px 미만 상태 글자 숨김(CSS container)은 그대로입니다. 가로 스크롤은 만들지 않습니다.
- More는 그룹/항목 단위 구획으로 자동 구성됩니다: menu·tabs는 자기 구획(`moreLabel`), button·toggle·switch는 그룹 구획(`group.moreLabel ?? group.label`). toggle·switch는 체크 항목, tabs는 라디오 항목이 됩니다.
- **Dropdown 색(2026-10-21):** Header 안의 Dropdown — 메뉴 버튼(`type: "menu"`), More(⋯), 로고 메뉴 — 은 크기·간격·글자는 `SWViewerMenu` 그대로, 색은 일반 `SWHeader`의 `...` 메뉴와 같습니다(면 #0e3d2f, Border `--hm-sw-header-object-border-color`, 그림자 0 4px 12px rgba(0,0,0,.5), 글자 `--hm-sw-header-text-color`, 보조 글자는 같은 색 80%, Hover `--hm-sw-header-hover`, 구분선 `--hm-sw-header-document-menu-divider-color`). 파일명 목록(`documents`)은 `SWHeader` 파일명 목록 모양을 유지하고, 단독 `SWViewerMenu`(우클릭 메뉴 등)는 기존 색 그대로입니다.
- **상태 표현:** Hover = #266651 면, Active·Pressed·열림·선택 탭 = 글자·아이콘 #FFC600(선택 박스 없음), Disabled = 60% 불투명, Focus = 2px #FFC600 외곽선.
- **상태 표현:** Hover·Active·Pressed·열림·선택 탭 = 글자·아이콘 #FFC600(면·선택 박스 없음, Hover 전후 크기·위치 동일 — 2026-10-21 Hover 면 #266651 제거), Disabled = 60% 불투명, Focus = 2px #FFC600 외곽선.
- **글자·아이콘 외곽선(SWHeader와 동일):** 14px 글자 `--hm-sw-header-text-outline`(검정 1.3px), 11px 캡션·개수·상태 `--hm-sw-header-label-outline`(검정 1px), 아이콘 `SWViewerIcon`(SWHeader 아이콘 자산의 1px 검정 바깥 Stroke를 **SVG stroke**로 재현: 아이콘 뒤 복사본의 모든 도형을 검정, 원래 선 두께 + 화면 기준 양쪽 1px(`--hm-sw-viewer-icon-outline-width`)로 그림. 위 아이콘은 그대로라 색·모양·선 두께 유지. CSS `filter` 미사용, glow는 생략). Header 항목·`SWViewerHeaderButton`의 `icon`은 자동 적용, `custom` / `trailing`의 직접 그린 아이콘은 `<SWViewerIcon>`으로 감쌉니다. 색·크기·굵기·간격은 그대로이고 Hover/Active는 채움 색만 바뀝니다. Dropdown·패널 안에는 외곽선이 없습니다(SWHeader More 메뉴와 같음). 프로그램 아이콘은 `currentColor`로 그리면 상태 색과 외곽선이 자동 적용됩니다.
### 2.1 두 가지 구성 (2026-10-21)
@@ -96,6 +96,16 @@
- **콘텐츠 우선순위:** `children`·`footer`는 타입상 `ReactNode`이고 소스 주석도 자유 콘텐츠(“any component”)라고 적습니다. 그래도 기본은 `SWViewerSection`·`SWViewerField`·`SWViewerSwitch`·`SWViewerSlider`·`SWViewerInput`·`SWViewerButton`·`SWViewerBadge`·`SWViewerHint`와 선택 목록 `SWSelect`(축척 지정 단위, 뷰어 패널 토큰으로 표시) 우선입니다. 이것으로 안 되는 컨트롤(예: Select·Checkbox·표)만 `@hm-design/ui` 컴포넌트나 소비 프로젝트 요소를 넣을 수 있습니다. `@hm-design/ui`는 SW 패키지 dependency가 아니어서 별도 설치(사용 유형 혼합)가 필요하고, `SWPopup`과 달리 뷰어 패널 안 `@hm-design/ui` 외형 정합성은 문서화·검증 기록이 없으므로 결과 보고에 “디자인 확인 필요”로 적습니다. Header 안에는 `@hm-design/ui` DropdownMenu 등을 직접 두지 않습니다(§1 표).
- **콘텐츠 교체:** 같은 프레임에 다른 `children`을 넣습니다(레이어 목록 / 옵션 / 속성 / 입력 폼 — Storybook `SW/Viewer › Settings Popup`). 프레임을 복사하거나 `hm-sw-viewer-*` class로 새 팝업을 만들지 않습니다.
### 4.1 레이어 Popup — 표시·숨김은 눈 아이콘 버튼 (필수, 2026-10-21)
> **규칙:** SW Viewer 레이어 Popup에서 레이어 표시·숨김 기능은 반드시 HMDesign의 눈(Eye/EyeOff) 아이콘 버튼을 사용한다. Toggle Switch로 대체하지 않는다.
- 패키지에는 레이어 목록 전용 컴포넌트가 없습니다. 목록은 `SWViewerPanel` > `SWViewerSection surface="plain"` 안에 프로그램이 두고, 행은 색 점 · 레이어 이름 · 개수(`SWViewerBadge`) · **눈 아이콘 버튼** 순서입니다(Storybook `SW/Viewer › Viewer Header`의 레이어 Popup, 예제 [ExampleH-ViewerMode.tsx](./examples/ExampleH-ViewerMode.tsx)).
- 눈 아이콘 버튼: `<button type="button">` 24 × 24, radius 4, 16px 아이콘(`currentColor`, 색 `--hm-sw-viewer-panel-heading`), Hover 면 `rgba(154, 146, 132, .3)`, `cursor: pointer`. 표시 중이면 `Eye`, 숨김이면 `EyeOff`(사선이 있는 눈). 숨긴 행은 불투명도 0.45.
- 접근성 이름에 다음 동작을 넣습니다: 표시 중 `"<레이어> 숨기기"`, 숨김 `"<레이어> 표시하기"`(`title`도 같은 동사).
- 클릭하면 그 레이어 하나의 가시성만 바꾸고 프로그램의 가시성 Callback·상태를 그대로 호출합니다. 아이콘만 그리고 기능을 빼지 않습니다. 전체 켜기·끄기는 `SWViewerButton tone="green"`.
- `SWViewerSwitch`는 옵션 패널의 ON/OFF 설정(그리드·2.5D 등)에만 씁니다. 레이어 가시성에는 `SWViewerSwitch`, Checkbox, ON/OFF 스위치, 임의 컨트롤을 쓰지 않습니다.
## 5. 올바른 사용
```tsx
@@ -126,6 +136,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: 눈(`Eye`/`EyeOff`) 아이콘 버튼. Switch는 옵션 설정 전용 |
| 존재하지 않는 Props 추측(`items`, `content`, `renderBody` …) | 위 표와 `dist/index.d.ts`가 전부 |
| 일반 모드 화면에서 `SWViewerHeader` 사용, 뷰어에서 `SWHeader` 재스타일 | 모드별 Header를 바꿔 끼웁니다(창 최소화·닫기 `SWHeaderWindowControls`를 `trailing` 끝에 두는 §2.1 구성은 허용) |
| 기능 없는 메뉴를 가짜 동작으로 연결 | 항목 제외 또는 `disabled` + 미연결 보고 |