Documentation and comment-only changes (no component logic, Props or design change): - VIEWER.md / AI_GUIDE / AI_USAGE / catalog / patterns: the two SWViewerHeader versions (default, and minimize/close with SWHeaderWindowControls at the end of `trailing`); the earlier "never mix SWHeader parts" rule now names this approved exception. - collapseBelow / iconOnlyBelow (types.ts JSDoc, VIEWER.md, AI_GUIDE): measured steps, the numbers only set the order, not pixel breakpoints. Viewer file open is the onDocumentOpen button (logoMenu is compatibility only). - COMPONENT_CATALOG / DESIGN_RULES: recent files show at most 3 rows; preview caption is previewTitle or path + file name; body is the red no-preview sign on the brown panel; preview / renderPreview are accepted but not rendered (JSDoc updated to match). - packages/sw/README.md and sw-header-usage.md: current Story list and counts (40, 14 shown + 26 verification); 760px+ no-More condition; read-only Property Bar values as <output>. - SWHeader.tsx comment: titles are kept (not hidden) from 760px up. - AI_GUIDE synced into the package ai/ copies (npm run sync:ai-docs).
663 lines
69 KiB
Markdown
663 lines
69 KiB
Markdown
# SW Header 사용 가이드
|
||
|
||
> **0.2.0 통합 개선(2026-10-19):** 로고 오른쪽 파일 열기(`documentOpenPlacement="header"`, `documentOpenLabel` 재사용, 실측 기반 More 이동), `leading` 슬롯, 하위 항목 숨김(`hiddenControls`의 `documentNew`·`documentOpen`·`layerFreeze`·`layerLock`), 도구 막대 `SWToolButton` / `SWToolGroup`, 속성창 프레임 `SWPopup`(자유 `children`)과 `SWHeaderPropertyPanel`의 `children`·`title`을 추가했습니다. API·예제는 패키지 [AI 카탈로그](../../packages/sw/ai/COMPONENT_CATALOG.md)와 [조합 패턴](../../packages/sw/ai/COMPOSITION_PATTERNS.md), Story는 `SW / Header`의 Document Open·Hidden Sub Controls, `SW / Tool`, `SW / Popup`, 검증은 [통합 개선 기록](./sw-integrated-improvement-2026-10-19.md)을 따릅니다.
|
||
|
||
> **현재 기준 색인(2026-10-19 1차 정리):** 아래 날짜별 안내 중 최신 항목이 같은 주제의 이전 설명보다 우선합니다. 속성 필드 13개·필드 Dropdown(`picker`·`menus`·`decimals`)은 [속성창·K 가이드 추가 연결](#속성창k-가이드-추가-연결-2026-10-16)의 2026-10-19 항목, 표시·숨김(`hiddenControls`)·메뉴 순서(`menuOrder`)·속성창 4상태·형상 Slot은 [3차 절](#구성요소-표시메뉴-순서속성창-4상태형상-slot-2026-10-16-3차), 속성Bar·`controls`·Colorize는 [후속 절](#후속-5개-요구사항-연결-2026-10-16)을 봅니다. 현재 Storybook 33개 목록은 [§12](#12-storybook)와 [1차 정리 보고](./sw-first-pass-cleanup-review-2026-10-19.md)에 있습니다.
|
||
|
||
> **상세 버튼 최신 기준(2026-10-16):** 레이어·색상·선·문자·치수 상세는 문자스타일 디자인의 `SWHeaderMenuDetail`을 공통 사용합니다. `on*Detail` 미전달 시 Hover 유지·클릭 무동작이며, Callback 전달 시 기존 1회 호출·닫힘·Focus 복귀를 유지합니다. 내부 공통 컴포넌트는 `label`, `icon`, `onClick`, `disabled`, `className`, `aria-label` 등 native 버튼 Props를 지원합니다. 새 공개 export는 추가하지 않았습니다. 비교 Story는 `SW/Header → Detail Buttons (Shared States)`(verification 태그, 직접 ID `sw-header--detail-buttons`)입니다. 과거의 Callback 미전달=Disabled 및 치수만 밝은 Hover 설명보다 이 기준이 우선합니다. [이번 검증 기록](./sw-header-detail-buttons-verification-2026-10-16.md).
|
||
|
||
> **3차 기준(2026-10-16):** 로고 외 개별 제거 `hiddenControls`, 유사 메뉴 순서 `menuOrder`, 속성창 4상태 Story·비교, 형상 Slot `shapeContent`를 추가했습니다. [3차 절](#구성요소-표시메뉴-순서속성창-4상태형상-slot-2026-10-16-3차)과 [3차 검증 기록](./sw-header-composition-shape-verification-2026-10-16.md)이 같은 주제의 이전 설명보다 우선합니다.
|
||
|
||
> **후속 5개 요구사항 기준(2026-10-16):** `SWHeaderPropertyBar`, 메뉴 단위 `controls`, `btn_colorize` Default/Hover를 추가했습니다. 상세 pill의 `solid-stroke-width`도 제거했습니다. 아래 후속 연결 절과 [새 검증 기록](./sw-header-followup-five-verification-2026-10-16.md)이 현재 기준입니다. 과거 실행 기록은 보존합니다.
|
||
|
||
> **최신 API/동작:** 아래 [속성창·K 가이드 추가 연결](#속성창k-가이드-추가-연결-2026-10-16)을 먼저 확인하세요. 색상 기준은 정확히 하나 표시되며, 상세 버튼 Hover와 목록 Selected 정책이 변경됐습니다. 과거 예시의 `colorMode=null`은 직접 색상 의미를 유지하지만 모드 표시까지 해제하지 않습니다.
|
||
|
||
`@hm-design/sw`의 SW Header를 화면에 연결하는 방법입니다. 디자인 값과 판정은 [디자인 명세](./sw-header-design-spec.md)를 따릅니다.
|
||
|
||
> 패키지는 아직 npm Registry에 게시하지 않았습니다. 아래 설치 방법은 HMDesign Workspace 또는 로컬 tarball 기준입니다.
|
||
|
||
## 1. 패키지 구조
|
||
|
||
```text
|
||
packages/sw/
|
||
├── src/
|
||
│ ├── assets/ Figma 원본 SVG와 Hover Variant (build 시 data URI로 포함, 로고 제외)
|
||
│ ├── components/
|
||
│ │ ├── sw-header/
|
||
│ │ │ ├── SWHeader.tsx 전체 헤더
|
||
│ │ │ ├── components/ 하위 컨트롤 (공개 + 내부)
|
||
│ │ │ ├── types.ts Props / 값 타입
|
||
│ │ │ └── index.ts
|
||
│ │ └── sw-theme/SWTheme.tsx 하위 컨트롤 단독 사용 시 토큰 범위
|
||
│ ├── theme/sw.css SW 전용 토큰·스타일
|
||
│ └── index.ts
|
||
├── build.mjs 독립 library build (Vite lib + tsc 선언)
|
||
├── package.json exports: "." , "./styles.css"
|
||
└── README.md
|
||
```
|
||
|
||
`@hm-design/ui`·`@hm-design/center-graphic`·OrgChart 패키지를 import하지 않습니다. Peer는 `react`·`react-dom` `^19.0.0`뿐입니다.
|
||
|
||
## 2. 설치
|
||
|
||
### HMDesign Workspace 안
|
||
|
||
루트 `package.json`의 Workspace(`packages/*`)에 포함되어 있습니다.
|
||
|
||
```bash
|
||
npm install
|
||
npm run build:sw
|
||
```
|
||
|
||
`build:packages`(typecheck·build·Storybook 전처리)도 마지막에 `build:sw`를 실행합니다.
|
||
|
||
### 다른 React 프로젝트(로컬 tarball)
|
||
|
||
```bash
|
||
npm pack -w @hm-design/sw --pack-destination ../packs
|
||
```
|
||
|
||
```bash
|
||
npm install ../packs/hm-design-sw-0.1.0.tgz
|
||
```
|
||
|
||
소비 프로젝트는 `src` 내부 파일을 참조하지 않고 `dist` 공개 API만 사용합니다.
|
||
|
||
## 3. Import
|
||
|
||
```tsx
|
||
import { SWHeader } from "@hm-design/sw"
|
||
import "@hm-design/sw/styles.css"
|
||
```
|
||
|
||
- CSS는 JS에서 자동 import되지 않으므로 앱 진입점 등에서 한 번 import합니다.
|
||
- TypeScript 7에서 CSS side-effect import 타입 오류가 나면 Vite 프로젝트의 `/// <reference types="vite/client" />`(또는 `declare module "*.css"`)를 둡니다.
|
||
- 글꼴: 패키지는 폰트 파일을 포함하지 않습니다. 앱에서 Noto Sans KR(400/700)을 로드합니다.
|
||
|
||
## 4. 공개 API
|
||
|
||
| Export | 용도 |
|
||
|---|---|
|
||
| `SWHeader` | 전체 헤더. `ref`는 `<header>` |
|
||
| `SWHeaderHome` | 홈 버튼(로고 영역). `logoSrc` / `logo` / `logoAlt` / `onClick` |
|
||
| `SWHeaderDocumentTitle` | 파일명 버튼. `documents`를 주면 문서 Dropdown(`title`, `onClick`, `documents`, `menuOpen`, `onMenuOpenChange`, `onDocumentSelect`, `onNew`, `onOpen`, `documentIcon`, `newLabel`, `openLabel`) |
|
||
| `SWHeaderLayerControl`, `SWHeaderColorControl`, `SWHeaderLineStyle`, `SWHeaderTextStyle`, `SWHeaderDimensionStyle` | 속성 필드 5종. `SWHeaderLayerControl`은 선택 `state`(현재 레이어의 표시·동결·잠금)로 필드 아이콘을 회색으로 표시 |
|
||
| `SWHeaderLayerMenu` | 레이어 필드 + 레이어 Dropdown(`value`, `layers`, `activeLayerId`, `open`, `onOpenChange`, `onClick`, `onLayerSelect`, `onVisibilityChange`, `onFreezeChange`, `onLockChange`, `onDetail`). `SWHeader`는 `layers`가 있을 때 이것을 사용 |
|
||
| `SWHeaderColorMenu` | 색상 필드 + 색상 Dropdown(2026-10-16, `value`, `colors`, `userColors`, `mode`, `open`, `onOpenChange`, `onClick`, `onColorSelect`, `onModeChange`, `onColorInput`, `onPick`, `onDetail`). `SWHeader`는 `colors`가 있을 때 사용 |
|
||
| `SWHeaderLineStyleMenu` | 선스타일 필드 + 3열 Dropdown(`value`, `lineTypes`, `lineWeights`, `activeLineTypeId`, `activeLineWeightId`, `onLineTypeSelect`, `onLineWeightSelect`, `onLineWeightValueChange`, `onScaleChange`, `onDetail`, 열림 Props). `SWHeader`는 `lineTypes` 또는 `lineWeights`가 있을 때 사용 |
|
||
| `SWHeaderTextStyleMenu`, `SWHeaderDimensionStyleMenu` | 문자·치수스타일 필드 + 목록 Dropdown(`value`, `textStyles` / `dimensionStyles`, `active*Id`, `on*Select`, `onDetail`, 열림 Props) |
|
||
| `SWHeaderLinePreview`, `swHeaderLinePatterns` | 70 × 2 선 미리보기와 Figma CENTER(22·7·22·7) / HIDDEN(33·33) 패턴 |
|
||
| `SWHeaderInput` | 수정 가능한 SW Input(Figma input 26:11656 · cursor 26:11664). 레이어 상세 등 상세 영역의 값 편집용 |
|
||
| `parseSWHeaderColor`, `parseSWHeaderNumber` | Input 유효성 검사 도우미(`r,g,b` / `#rrggbb`, 소수) |
|
||
| `SWHeaderProperties` | 상세보기(속성Bar / 속성창) |
|
||
| `SWHeaderActions` | 인쇄 · 팝업 가이드 |
|
||
| `SWHeaderWindowControls` | 창 이동 · 최소화 · 종료 |
|
||
| `SWTheme` | 하위 컨트롤을 헤더 밖에서 쓸 때 토큰·글꼴·버튼 초기화 범위 |
|
||
| 타입 | `SWHeaderProps`, `SWHeaderMenuId`, `SWHeaderLayerValue`, `SWHeaderLayer`, `SWHeaderColorValue`, `SWHeaderLineStyleValue`, `SWHeaderTextStyleValue`, `SWHeaderDimensionStyleValue`, `SWHeaderDocument`, `SWHeaderColorMode`, `SWHeaderColorOption`, `SWHeaderColorInputValue`, `SWHeaderLineType`, `SWHeaderLineWeight`, `SWHeaderTextStyleOption`, `SWHeaderDimensionStyleOption`, `SWHeaderInputValueType`, 각 하위 컴포넌트 `*Props`, `SWThemeProps` |
|
||
|
||
내부 구현(`SWHeaderField`, `SWHeaderMore`, `useSWHeaderDropdown`, `SWHeaderMenuDetail`)은 공개하지 않습니다.
|
||
|
||
## 5. SWHeader Props
|
||
|
||
`HTMLAttributes<HTMLElement>`(단 `title`, `children`, `color` 제외)를 함께 받습니다. `className`·`style`·`aria-*`는 `<header>`에 전달됩니다.
|
||
|
||
| Prop | 타입 | 기본값 | 설명 |
|
||
|---|---|---|---|
|
||
| `documentTitle` | `string` | 필수 | 파일명. 좁으면 말줄임, 전체 이름은 `title` 툴팁 |
|
||
| `drawingModeLabel` | `string` | `"그리기모드"` | 그리기 모드 라벨 |
|
||
| `layer` | `{ name }` | `{ name: "0" }` | 현재 레이어(`layers`에 `activeLayerId`와 같은 항목이 없을 때 필드에 표시) |
|
||
| `layers` | `SWHeaderLayer[]` | — | 레이어 필드 아래 Dropdown 목록(배열 순서 그대로). 없으면 Dropdown 없이 기존 필드 버튼, `[]`이면 하단(레이어 상세)만 |
|
||
| `activeLayerId` | `string` | — | 현재 레이어. 필드에 이름·상태 아이콘 표시, 목록에서 이름만 `#FFC600` |
|
||
| `layerMenuOpen` | `boolean` | — | 레이어 Dropdown 열림 상태(제어형). 없으면 내부에서 관리 |
|
||
| `color` | `{ value, swatch? }` | `{ value: "ByLayer", swatch: "#ffffff" }` | 색상 값 표시와 칩 색 |
|
||
| `lineStyle` | `{ name, weight, scale }` | `ByLayer / ByLayer / 1.00` | 선스타일 |
|
||
| `textStyle` | `{ font, name }` | `Arial / Standard` | 문자스타일 |
|
||
| `dimensionStyle` | `{ name }` | `Standard` | 치수스타일 |
|
||
| `activeMenu` | `"layer" \| "color" \| "lineStyle" \| "textStyle" \| "dimensionStyle" \| null` | `null` | 선택(열림) 표시 |
|
||
| `propertyBarOpen` | `boolean` | `false` | 속성Bar 눌림 |
|
||
| `propertyPanelOpen` | `boolean` | `false` | 속성창 눌림 |
|
||
| `disabled` | `boolean` | `false` | 메뉴·속성·인쇄/가이드 비활성(창 제어 제외) |
|
||
| `tools` | `ReactNode` | — | 객체 수정 영역의 필드 뒤 추가 도구 |
|
||
| `logoSrc` | `string` | — | 로고 이미지 URL. 32 × 17 로고 영역에 `contain`으로 맞춤 |
|
||
| `logo` | `ReactNode` | — | 로고 요소(인라인 SVG 등). `logoSrc`보다 우선 |
|
||
| `logoAlt` | `string` | — | 제품명. 홈 버튼 이름이 "<logoAlt> 홈"(없으면 "홈") |
|
||
| `documents` | `SWHeaderDocument[]` | — | 파일명 아래 문서 Dropdown 목록. 없으면 Dropdown 없이 기존 버튼, `[]`이면 New / Open만 |
|
||
| `documentMenuOpen` | `boolean` | — | 문서 Dropdown 열림 상태(제어형). 없으면 내부에서 관리 |
|
||
| `activeDocumentId` | `string` | — | 현재 문서. 목록 순서는 그대로 두고 이 문서의 이름만 `#FFC600` |
|
||
| `documentIcon` | `ReactNode` | Figma 문서 아이콘 | `icon`이 없는 문서의 아이콘(29 × 12 영역) |
|
||
| `documentNewLabel` / `documentOpenLabel` | `string` | `+ New` / `Open` | 하단 버튼 글자 |
|
||
| `colors` | `SWHeaderColorOption[]` | — | 색상 Dropdown 기본 팔레트(2026-10-16). 없으면 Dropdown 없이 기존 필드 버튼 |
|
||
| `userColors` | `SWHeaderColorOption[]` | — | 사용자 색상(개수 가변, 2px 간격 줄바꿈, 4행 초과 시 팔레트 영역 스크롤) |
|
||
| `colorMode` | `"byLayer" \| "byBlock" \| null` | — | 눌린 ByLayer / ByBlock. `null`·미지정 = 명시 색 |
|
||
| `colorMenuOpen` | `boolean` | — | 색상 Dropdown 열림(제어형). 없으면 내부 관리 |
|
||
| `lineTypes` / `lineWeights` | `SWHeaderLineType[]` / `SWHeaderLineWeight[]` | — | 선스타일 Dropdown의 선 종류 / 가중치 열. 둘 다 없으면 기존 필드 버튼. 스케일 열은 `lineStyle.scale` |
|
||
| `activeLineTypeId` / `activeLineWeightId` | `string` | `lineStyle.name` / `.weight`와 같은 이름의 행 | 현재 행(이름 `#FFC600`) |
|
||
| `lineStyleMenuOpen` | `boolean` | — | 선스타일 Dropdown 열림(제어형) |
|
||
| `textStyles` / `dimensionStyles` | `SWHeaderTextStyleOption[]` / `SWHeaderDimensionStyleOption[]` | — | 문자 / 치수스타일 Dropdown 행. 없으면 기존 필드 버튼 |
|
||
| `activeTextStyleId` / `activeDimensionStyleId` | `string` | `textStyle.name` / `dimensionStyle.name`과 같은 이름의 행 | 현재 행 |
|
||
| `textStyleMenuOpen` / `dimensionStyleMenuOpen` | `boolean` | — | 각 Dropdown 열림(제어형) |
|
||
|
||
Dropdown 데이터 타입(모두 `id`로 식별, 배열 순서 = 표시 순서, 패키지에 고정 데이터 없음):
|
||
|
||
| 타입 | 필드 |
|
||
|---|---|
|
||
| `SWHeaderColorOption` | `{ id; color; value?; label?; disabled? }` — `color` CSS 색, `value` 보고·표시 값(기본 `color`의 `r,g,b`), `label` 접근성 이름 |
|
||
| `SWHeaderColorInputValue` | `{ value: "r,g,b"; rgb: [r, g, b]; hex: "#rrggbb" }` |
|
||
| `SWHeaderLineType` | `{ id; name; pattern?: number[]; disabled? }` — `pattern` 70px 미리보기의 대시 길이(4px 간격, 비율 맞춤), 없으면 실선 |
|
||
| `SWHeaderLineWeight` | `{ id; name; value?; editable?; disabled? }` — `value` 오른쪽 값(ByDefault 0.25), `editable` 이면 값 Input(User) |
|
||
| `SWHeaderTextStyleOption` | `{ id; name; font; disabled? }` |
|
||
| `SWHeaderDimensionStyleOption` | `{ id; name; disabled? }` |
|
||
|
||
`SWHeaderDocument`: `{ id: string; name: string; title?: string; icon?: ReactNode; disabled?: boolean }` — `title`은 툴팁(기본 `name`).
|
||
|
||
`SWHeaderLayer`: `{ id: string; name: string; color?: string; visible?: boolean; frozen?: boolean; locked?: boolean; disabled?: boolean }` — `color`는 16px 견본 색(기본 `#ffffff`), 기본값은 `visible: true`, `frozen: false`, `locked: false`. 확정 매핑([명세 §5.2.1](./sw-header-design-spec.md#521-아이콘-onoff와-데이터-의미)):
|
||
|
||
| 아이콘 | 컬러 | 회색 | 클릭 시 Callback 값 |
|
||
|---|---|---|---|
|
||
| 전구 | `visible=true` | `visible=false` | `onLayerVisibilityChange(layer, !visible)` |
|
||
| 눈꽃 | `frozen=false`(동결 해제) | `frozen=true`(동결) | `onLayerFreezeChange(layer, !frozen)` |
|
||
| 자물쇠 | `locked=false`(잠금 해제) | `locked=true`(잠금) | `onLayerLockChange(layer, !locked)` |
|
||
|
||
헤더는 값을 직접 바꾸지 않으므로 호스트가 받은 값으로 `layers`를 갱신해야 아이콘 색이 바뀝니다.
|
||
|
||
> **로고는 확정 디자인이 아닙니다.** 로고 영역의 위치·크기는 공통 디자인이고, 로고 이미지는 사용하는 SW 프로그램이 정합니다. 패키지에는 로고가 들어 있지 않으며 `logo`·`logoSrc`를 전달하지 않으면 로고 없이 표시됩니다(배치는 그대로). Storybook의 기본 로고는 임시 자산(`sw-header-logo-placeholder.svg`)입니다.
|
||
|
||
기본값은 Figma 표시값이 아니라 중립값입니다. Figma 샘플 값은 Storybook `figmaArgs`를 참고합니다.
|
||
|
||
## 6. Callback
|
||
|
||
| Callback | 호출 시점 |
|
||
|---|---|
|
||
| `onHomeClick` | 홈(로고) 클릭 |
|
||
| `onDocumentClick` | 파일명 클릭(Dropdown 열고 닫을 때도 호출) |
|
||
| `onDocumentMenuOpenChange(open)` | 문서 Dropdown 열림·닫힘 |
|
||
| `onDocumentSelect(document)` | 문서 항목 선택. 문서를 여는 동작은 호스트가 구현 |
|
||
| `onDocumentClose(document)` | 행의 X(문서 닫기). 주면 모든 행에 X가 생기고 Hover·키보드 Focus한 행에만 보입니다. 선택 Callback과 중복되지 않습니다. 실제 닫기·목록 갱신·다음 선택은 호스트가 처리 |
|
||
| `onDocumentNew`, `onDocumentOpen` | Dropdown 하단 `+ New` / `Open` |
|
||
| `onLayerOpen`, `onColorOpen`, `onLineStyleOpen`, `onTextStyleOpen`, `onDimensionStyleOpen` | 각 필드 클릭(Dropdown이 있으면 열고 닫을 때도 호출) 또는 더보기 항목 선택 1회(대표 필드 전환, §7 "반응형 More") |
|
||
| `onLayerMenuOpenChange(open)` | 레이어 Dropdown 열림·닫힘 |
|
||
| `onLayerSelect(layer)` | 레이어 행(견본·이름) 선택. 목록이 닫힙니다. 현재 레이어 변경은 호스트가 `activeLayerId`로 반영 |
|
||
| `onLayerVisibilityChange(layer, nextVisible)` | 전구 클릭. 목록 유지, 선택 Callback 없음 |
|
||
| `onLayerFreezeChange(layer, nextFrozen)` | 눈꽃 클릭. 목록 유지, 선택 Callback 없음 |
|
||
| `onLayerLockChange(layer, nextLocked)` | 자물쇠 클릭. 목록 유지, 선택 Callback 없음 |
|
||
| `onLayerDetail()` | 하단 `레이어 상세`. 호출 후 목록이 닫힙니다. 없으면 버튼은 Figma 외형 그대로 비활성 |
|
||
| `onColorMenuOpenChange(open)` 등 `on*MenuOpenChange` | 각 Dropdown 열림·닫힘(색상·선/문자/치수스타일) |
|
||
| `onColorSelect(option)` | 팔레트·사용자 색상 칩. 목록 유지 |
|
||
| `onColorModeChange(mode)` | ByLayer / ByBlock. 목록 유지. 상호 배타 표시는 호스트가 `colorMode`로 반영 |
|
||
| `onColorInput(value)` | RGB 입력 확정(Enter·Focus 이동·목록 닫힘). 유효한 `r,g,b`(0–255) 또는 `#rrggbb`만, 값이 바뀐 경우만 |
|
||
| `onColorPick()` | 색상 추출 버튼. 목록이 닫히고 실제 추출(도면 픽셀 등)은 호스트가 구현 |
|
||
| `onLineTypeSelect(type)` / `onLineWeightSelect(weight)` | 선 종류 / 가중치 행. 다른 열 값은 건드리지 않고 목록 유지 |
|
||
| `onLineWeightValueChange(weight, value)` | `editable` 가중치 Input 확정(0 이상 소수) |
|
||
| `onLineScaleChange(value)` | 스케일 Input 확정(0 초과 소수) |
|
||
| `onTextStyleSelect(style)` / `onDimensionStyleSelect(style)` | 행 선택 후 목록 닫힘 |
|
||
| `onColorDetail`, `onLineStyleDetail`, `onTextStyleDetail`, `onDimensionStyleDetail` | 하단 `… 상세`. 전달 시 호출 후 목록 닫힘. 없으면 공통 Default/Hover를 유지하고 클릭은 무동작. 상세 팝업은 패키지 범위 밖(추후 Figma 전달 후) |
|
||
| `onPropertyBarToggle`, `onPropertyPanelToggle` | 속성 토글 클릭 또는 더보기 항목 선택 |
|
||
| `onPrint`, `onPopupGuide` | 인쇄·팝업 가이드 |
|
||
| `onWindowMoveStart(event)` | 창 이동 영역 pointer down |
|
||
| `onMinimize`, `onClose` | 최소화·종료 |
|
||
|
||
헤더는 선택·열림 상태를 스스로 저장하지 않습니다. 호스트가 Callback에서 상태를 바꾸고 Props로 다시 내려줍니다. 실제 드롭다운 목록, 파일 열기, 창 이동·최소화·종료(브라우저 / Electron / 설치형 SW마다 다름)는 호스트가 구현합니다.
|
||
|
||
## 7. 전체 헤더 예제
|
||
|
||
```tsx
|
||
import { useState } from "react"
|
||
import { SWHeader, type SWHeaderMenuId } from "@hm-design/sw"
|
||
import "@hm-design/sw/styles.css"
|
||
|
||
export function DrawingHeader({ fileName }: { fileName: string }) {
|
||
const [activeMenu, setActiveMenu] = useState<SWHeaderMenuId | null>(null)
|
||
const [panelOpen, setPanelOpen] = useState(false)
|
||
const toggle = (id: SWHeaderMenuId) => () => setActiveMenu(current => (current === id ? null : id))
|
||
|
||
return (
|
||
<SWHeader
|
||
documentTitle={fileName}
|
||
layer={{ name: "0_Layer_Cludmark" }}
|
||
color={{ value: "255,229,0", swatch: "#ffe500" }}
|
||
lineStyle={{ name: "Continuous", weight: "0.25", scale: "1.00" }}
|
||
textStyle={{ font: "Arial", name: "Standard" }}
|
||
dimensionStyle={{ name: "Standard" }}
|
||
activeMenu={activeMenu}
|
||
propertyPanelOpen={panelOpen}
|
||
onLayerOpen={toggle("layer")}
|
||
onColorOpen={toggle("color")}
|
||
onLineStyleOpen={toggle("lineStyle")}
|
||
onTextStyleOpen={toggle("textStyle")}
|
||
onDimensionStyleOpen={toggle("dimensionStyle")}
|
||
onPropertyPanelToggle={() => setPanelOpen(open => !open)}
|
||
onClose={() => window.close()}
|
||
/>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 로고 지정 예
|
||
|
||
```tsx
|
||
import productLogo from "./assets/product-logo.svg" // 각 프로그램의 로고 파일
|
||
|
||
<SWHeader documentTitle={fileName} logoSrc={productLogo} logoAlt="제품명" />
|
||
|
||
// React 요소로 전달(인라인 SVG 등). logoSrc보다 우선합니다.
|
||
<SWHeader documentTitle={fileName} logo={<ProductMark />} logoAlt="제품명" />
|
||
```
|
||
|
||
- 로고 영역은 32 × 17px입니다. 가로·세로 비율이 달라도 영역 안에 맞춰지고 잘리지 않습니다. 선명하게 보이도록 SVG 또는 64 × 34px 이상 이미지를 권장합니다.
|
||
- 로고 영역 크기는 디자인 기준이므로 바꾸지 않는 것을 권장합니다(토큰 `--hm-sw-header-logo-width` / `-logo-height`).
|
||
|
||
### 문서 Dropdown 예
|
||
|
||
```tsx
|
||
const [openDocuments, setOpenDocuments] = useState<SWHeaderDocument[]>([])
|
||
|
||
<SWHeader
|
||
documentTitle={current.name}
|
||
documents={openDocuments} // 각 프로그램의 문서 목록(표시 순서 그대로)
|
||
activeDocumentId={current.id} // 이름만 #FFC600, 순서는 그대로
|
||
onDocumentSelect={document => activate(document.id)}
|
||
onDocumentClose={document => closeDocument(document.id)} // 목록·현재 문서는 호스트가 갱신
|
||
onDocumentNew={createDocument}
|
||
onDocumentOpen={showOpenDialog} // 파일 열기는 호스트가 구현
|
||
/>
|
||
```
|
||
|
||
- 클릭으로 열고 닫습니다(Hover로 열리지 않음). 바깥 클릭·Escape·항목 선택·New/Open 후 닫힙니다.
|
||
- Dropdown은 헤더 아래로 겹쳐 열립니다. SWHeader의 조상 요소에 `overflow: hidden`이 있으면 잘리므로, 헤더는 페이지 최상단처럼 잘리지 않는 위치에 둡니다.
|
||
|
||
### 레이어 Dropdown 예
|
||
|
||
```tsx
|
||
const [layers, setLayers] = useState<SWHeaderLayer[]>(() => drawing.layers.map(toHeaderLayer))
|
||
const patch = (id: string, change: Partial<SWHeaderLayer>) =>
|
||
setLayers(list => list.map(layer => (layer.id === id ? { ...layer, ...change } : layer)))
|
||
|
||
<SWHeader
|
||
documentTitle={fileName}
|
||
layers={layers} // 각 프로그램의 레이어 목록(표시 순서 그대로)
|
||
activeLayerId={currentLayerId} // 필드 표시 + 목록에서 이름만 #FFC600
|
||
onLayerSelect={layer => setCurrentLayer(layer.id)}
|
||
onLayerVisibilityChange={(layer, visible) => { viewer.setLayerVisible(layer.id, visible); patch(layer.id, { visible }) }}
|
||
onLayerFreezeChange={(layer, frozen) => { viewer.setLayerFrozen(layer.id, frozen); patch(layer.id, { frozen }) }}
|
||
onLayerLockChange={(layer, locked) => { viewer.setLayerLocked(layer.id, locked); patch(layer.id, { locked }) }}
|
||
onLayerDetail={openLayerManager} // 없으면 버튼 비활성(임의 팝업 없음)
|
||
/>
|
||
```
|
||
|
||
- 레이어 필드를 클릭해야 열립니다(Hover는 테두리·글자색만). 행 선택·Escape·바깥 클릭·Tab 이탈·레이어 상세 후 닫힙니다. 전구·눈꽃·자물쇠 클릭은 목록을 유지합니다.
|
||
- 헤더는 레이어 상태를 저장하지 않습니다. Callback에서 도면(`viewer.*`은 예시)과 `layers`를 함께 갱신해야 아이콘이 바뀝니다. 실제 표시·동결·잠금 처리와 편집 가능 여부 판단은 호스트 책임입니다.
|
||
- 키보드: 필드에서 ↓로 열면 현재 레이어에 Focus, ↓↑ 행 이동(같은 열), ←→ 행 안 이동, Enter/Space 실행. 상세 규격은 [디자인 명세 §5.2](./sw-header-design-spec.md#52-레이어-dropdown-figma-headermenu레이어-2610940-2026-10-16).
|
||
|
||
### 색상 · 선/문자/치수스타일 Dropdown 예 (2026-10-16)
|
||
|
||
```tsx
|
||
const [color, setColor] = useState<SWHeaderColorValue>({ value: "ByLayer", swatch: "#ffffff" })
|
||
const [colorMode, setColorMode] = useState<SWHeaderColorMode | null>("byLayer")
|
||
const [lineStyle, setLineStyle] = useState({ name: "Continuous", weight: "0.25", scale: "1.00" })
|
||
|
||
<SWHeader
|
||
documentTitle={fileName}
|
||
color={color}
|
||
colors={program.palette} // [{ id: "red", color: "#ff0000", label: "빨강" }, ...]
|
||
userColors={program.userColors} // 개수 가변
|
||
colorMode={colorMode}
|
||
onColorSelect={option => { viewer.setColor(option.color); setColor({ value: option.value ?? option.color, swatch: option.color }); setColorMode(null) }}
|
||
onColorModeChange={mode => { viewer.setColorMode(mode); setColorMode(mode) }}
|
||
onColorInput={({ value, hex }) => { viewer.setColor(hex); setColor({ value, swatch: hex }); setColorMode(null) }}
|
||
onColorPick={startPickFromDrawing} // 실제 픽셀 추출은 호스트
|
||
onColorDetail={openColorDetail} // 없으면 Hover 유지·클릭 무동작(상세 팝업 미제공)
|
||
lineStyle={lineStyle}
|
||
lineTypes={program.lineTypes} // [{ id: "center", name: "CENTER", pattern: swHeaderLinePatterns.center }, ...]
|
||
lineWeights={program.lineWeights} // [{ id: "user", name: "User", value: "0.25", editable: true }, ...]
|
||
onLineTypeSelect={type => setLineStyle(s => ({ ...s, name: type.name }))} // 가중치·스케일 유지
|
||
onLineWeightSelect={weight => setLineStyle(s => ({ ...s, weight: weight.value ?? weight.name }))}
|
||
onLineWeightValueChange={(weight, value) => updateWeight(weight.id, value)}
|
||
onLineScaleChange={scale => setLineStyle(s => ({ ...s, scale }))}
|
||
textStyle={textStyle}
|
||
textStyles={program.textStyles} // [{ id, font: "Arial", name: "Standard" }, ...]
|
||
onTextStyleSelect={style => setTextStyle({ font: style.font, name: style.name })}
|
||
dimensionStyle={dimensionStyle}
|
||
dimensionStyles={program.dimensionStyles} // [{ id, name: "ISO - 25" }, ...]
|
||
onDimensionStyleSelect={style => setDimensionStyle({ name: style.name })}
|
||
/>
|
||
```
|
||
|
||
- 클릭으로 열고 닫습니다(Hover는 노란 테두리·글자만). 다른 Dropdown을 열면 앞의 것이 닫히고, 바깥 클릭·Escape·Tab 이탈로 닫힙니다. 헤더 높이(49px)와 다른 필드 위치는 변하지 않습니다.
|
||
- 색상·선스타일은 여러 속성을 이어서 바꿀 수 있도록 선택 후에도 열려 있고, 문자·치수스타일은 행 선택 후 닫힙니다. 색상 추출과 `… 상세`는 호출 후 닫힙니다.
|
||
- 헤더는 값을 저장하지 않습니다. Callback 값으로 `color` / `colorMode` / `lineStyle` 등을 갱신해야 필드가 바뀝니다. 명시 색을 고를 때 ByLayer/ByBlock을 해제할지는 호스트 정책입니다(Storybook 예제는 해제).
|
||
- 키보드: 필드에서 ↓로 열면 현재(눌린) 항목 또는 첫 항목에 Focus, ↓↑로 Dropdown 안 버튼·Input 순서대로 이동, Enter/Space 실행, Escape 닫기(필드로 Focus 복귀).
|
||
- Hover: 상세 버튼 5개는 문자스타일 상세 기준 공통 Hover(배경 .25·radius 25·아이콘 35%·글자 35%)를 적용합니다. ByLayer/ByBlock은 기존 후속 Hover 기준을 따릅니다. 색상 칩·색상 추출·목록 행(선 종류·가중치·스케일·문자·치수)·Input의 현재 동작은 유지합니다.
|
||
- 좁은 헤더: 목록은 헤더 안으로 옮겨지며, 468px 선스타일 목록은 헤더 폭 480 미만에서 세로로 쌓입니다.
|
||
|
||
### 동적 데이터 구조
|
||
|
||
모든 Dropdown(문서·레이어·색상·선 종류·가중치·문자·치수스타일)은 Props 배열로만 그립니다.
|
||
|
||
- 항목 추가·삭제·이름/값 변경·순서 변경은 배열을 바꾸면 되고 컴포넌트 소스 수정이 필요 없습니다. 각 항목은 `id`로 식별되어 바뀐 항목만 다시 그리고 다른 행의 상태(입력 중 값 등)를 보존합니다.
|
||
- 현재 항목(`active*Id`)이 배열에서 사라지면 선택 표시가 없어지고 필드는 값 Props(`layer`, `lineStyle` 등)를 그대로 표시합니다. 다음 현재 항목은 호스트가 정합니다.
|
||
- `disabled`(선택 불가), `editable`(가중치 값 Input), Input의 `readOnly` / `disabled`를 항목별로 줄 수 있습니다.
|
||
- 목록이 길면 Dropdown 안에서만 스크롤합니다(행 목록 최대 240px = 8행, 사용자 색상은 팔레트 118px). 헤더 레이아웃은 유지됩니다.
|
||
- Storybook `SW / Header / Dynamic Data`에서 추가·삭제·이름·값·순서 변경을 직접 확인할 수 있습니다.
|
||
|
||
### 수정 가능한 Input — `SWHeaderInput`
|
||
|
||
레이어 상세 등 상세 영역에서 속성값을 직접 고칠 때 씁니다(Figma input 26:11656, cursor 26:11664). 상세 팝업 레이아웃은 아직 없으므로 Input과 계약만 제공합니다.
|
||
|
||
```tsx
|
||
import { SWHeaderInput, parseSWHeaderNumber, SWTheme } from "@hm-design/sw"
|
||
|
||
<SWTheme>
|
||
<SWHeaderInput
|
||
aria-label="X 좌표"
|
||
valueType="number" // "text"(기본) | "number"
|
||
value={entity.x} // 확정값(호스트). 바뀌면 최신 값 표시
|
||
onValueChange={draft => preview(draft)} // 편집 중 임시값(선택)
|
||
onCommit={next => updateEntity({ x: next })} // Enter · Focus 이동 · 감싼 Dropdown 닫힘, 유효 + 변경 시만
|
||
onCancel={() => clearPreview()} // Escape: 확정값으로 복원
|
||
parse={draft => parseSWHeaderNumber(draft, { min: 0 })} // 선택: 형식·범위 직접 지정
|
||
/>
|
||
</SWTheme>
|
||
```
|
||
|
||
- 상태: Default(배경·테두리 없음, 58 × 20 기준) / Hover(1px `rgba(212,203,189,.25)`, 24px) / 편집 중(`#0F0D0A`, 1px `.3`, 24px). 글자 `#E60403` 14px + 검정 Text Stroke, 글자 위치는 상태와 무관하게 고정. 폭은 값 길이에 맞춥니다.
|
||
- 커서: 네이티브 caret을 `#E60403`(`caret-color`)으로 표시하고 깜박임도 네이티브입니다(가짜 커서 없음). 선택·IME(한글 조합 중 Enter는 확정하지 않음)는 브라우저 기본 동작입니다.
|
||
- `onCommit`이 없거나 `readOnly`이면 읽기 전용(Hover·편집 표시 없음), `disabled`는 40%.
|
||
- 선택형(목록) 입력은 Figma 디자인 전달 후 같은 데이터 정의(`id`·이름·값·타입)로 확장합니다. 현재는 텍스트·숫자와 사용자 `parse`(임의 형식)를 지원합니다.
|
||
|
||
### 반응형 More — 대표 필드 전환
|
||
|
||
> **2026-10-21 (사용자 결정):** 헤더 폭 760px 이상에서는 More를 쓰지 않습니다. 5개 메뉴는 로고 옆 Open 유무와 관계없이 항상 Header에 남고, 좁아질수록 간격 축소 → 파일명 `…` → 정보값 `…` 순으로 줄어듭니다. 메뉴 타이틀은 모든 폭에서 표시하며 공간이 부족하면 `…`로 줄어듭니다. 아래 내용(대표 필드 전환·실측 More)은 760px 미만에만 적용됩니다. 상세는 [DESIGN_RULES](../../packages/sw/ai/DESIGN_RULES.md)의 반응형 항목.
|
||
|
||
헤더가 좁아 필드가 더보기(…)로 옮겨졌을 때, 더보기에서 필드 항목을 고르면 그 필드가 객체 수정 영역 첫 칸(레이어 필드 자리)에 표시되고 Dropdown이 바로 아래에 열립니다. Focus는 그 필드로 옮겨갑니다.
|
||
|
||
- 레이어는 그동안 더보기로 이동하며, 더보기 → 레이어로 원래 배치로 돌아갑니다. 이미 화면에 있는 필드는 더보기에 나오지 않습니다.
|
||
- 전환은 UI 상태입니다. 데이터 Callback은 호출되지 않고, 필드 클릭과 같은 `on*Open` 1회와 `on*MenuOpenChange(true)`만 호출됩니다. 값은 그대로 유지됩니다.
|
||
- 폭이 넓어져 그 필드가 다시 보이는 단계에서는 1920 기준 배치로 돌아갑니다(Breakpoint 1800 / 1180 / 1080 / 900 / 640 / 520은 그대로).
|
||
- 실측 More(2026-10-19): Breakpoint 위에서 객체 수정 영역의 실제 남은 공간도 측정합니다. `leading`·`tools`·긴 라벨·사이드 패널 등으로 공간이 모자라면 표시할 수 없는 메뉴만 더보기로 옮기고, 공간이 다시 생기면 원래 자리·순서로 돌려놓습니다. 메뉴를 열고 닫는 것만으로는 Header 요소의 위치·폭이 바뀌지 않습니다([명세 §12.1](./sw-header-design-spec.md#121-실측-more--실제-사용-가능한-공간-기준-2026-10-19)).
|
||
- Dropdown 데이터(`colors` 등)가 없는 필드도 같은 방식으로 대표 칸에 표시됩니다(목록 없이 `on*Open`만).
|
||
|
||
## 8. 하위 컴포넌트 재사용
|
||
|
||
헤더 밖에서는 `SWTheme`로 감쌉니다. 소비자가 `hm-sw-*` class를 붙이지 않습니다.
|
||
|
||
```tsx
|
||
import { SWTheme, SWHeaderLayerControl, SWHeaderColorControl, SWHeaderProperties } from "@hm-design/sw"
|
||
|
||
<SWTheme>
|
||
<SWHeaderLayerControl value={{ name: "0" }} onClick={openLayers} />
|
||
<SWHeaderColorControl value={{ value: "255,229,0", swatch: "#ffe500" }} selected />
|
||
<SWHeaderProperties panelOpen={panelOpen} onPanelToggle={togglePanel} />
|
||
</SWTheme>
|
||
```
|
||
|
||
`SWTheme`는 배치(flex 등)를 정하지 않습니다. 배치는 `className`·`style`로 소비 화면에서 정합니다.
|
||
|
||
## 9. 테마 커스터마이징
|
||
|
||
`--hm-sw-header-*` 토큰만 바꿉니다. 사용자 확정 3개 면(파일명·객체 수정·속성 입력)은 기본값을 유지하는 것을 권장합니다.
|
||
|
||
```tsx
|
||
import type { CSSProperties } from "react"
|
||
|
||
<SWHeader documentTitle="a.dwg" style={{ "--hm-sw-header-selected-color": "#ffd84d" } as CSSProperties} />
|
||
```
|
||
|
||
토큰 목록과 사용처는 [디자인 명세 §14](./sw-header-design-spec.md#14-sw-전용-토큰-목록)를 따릅니다. `@hm-design/ui` Theme Runtime(JSON)과는 연결되어 있지 않습니다.
|
||
|
||
## 10. DWG/DXF 뷰어 연동 시 고려사항
|
||
|
||
- 기존 뷰어 `Toolbar.tsx`의 파일명(`fileName`)은 `documentTitle`로, 패널 토글(`propsOpen` 등)은 `propertyPanelOpen` + `onPropertyPanelToggle`로 연결할 수 있습니다.
|
||
- 뷰어의 파일 열기·샘플·Model/Layout 탭·2.5D·어두운 배경·로드 상태는 Figma SW Header에 없는 뷰어 전용 도구입니다. 필요하면 `tools` 슬롯에 호스트가 렌더링합니다(스타일은 호스트 책임).
|
||
- 헤더 높이는 49px입니다. 고정 배치(`position: fixed`)는 `className`·`style`로 호스트에서 지정합니다.
|
||
- 반응형은 모두 헤더 자신의 폭(container query)을 기준으로 하므로 사이드 패널 때문에 헤더가 좁아져도 동작합니다. 단계(1800 / 1180 / 1080 / 900 / 640 / 520)는 [디자인 명세 §12](./sw-header-design-spec.md#12-반응형)를 따르고, 그 위에서 실제 남은 공간을 측정해 부족한 메뉴만 더보기로 옮깁니다([§12.1](./sw-header-design-spec.md#121-실측-more--실제-사용-가능한-공간-기준-2026-10-19)).
|
||
- 뷰어는 현재 `@hm-design/ui`의 SWHeader 패턴을 사용합니다. 이 패키지로 바꾸는 작업은 이번 범위가 아닙니다.
|
||
|
||
## 10.1 상호작용 상태
|
||
|
||
- 그룹 필드 Hover(현재 추가 기준): 레이어·색상·선/문자/치수스타일은 버튼 전체 Hover 시 라벨·값과 지정된 선/폰트/치수 미리보기가 `#FFC600`이 됩니다. 기존 Gradient Hover Border와 검정 외곽선은 유지하며 색상 견본·레이어 상태 아이콘은 바꾸지 않습니다. Disabled는 새 Hover 효과를 적용하지 않고 Selected 색은 기존대로입니다. 상세보기의 텍스트 전용 Hover는 변경하지 않습니다. [디자인 명세 §11.5](./sw-header-design-spec.md#115-현재-추가-기준--1400ms-및-그룹-필드-hover-2026-10-16).
|
||
|
||
- Hover Border: 테두리가 있는 버튼(홈, 5개 속성 필드, 더보기, 인쇄, 팝업 가이드)은 마우스를 올리면 테두리가 Figma 홈 버튼의 노란 gradient로 바뀝니다. 5개 필드 내부 텍스트·미리보기는 위 추가 기준을 함께 적용합니다. 파일명과 속성Bar·속성창은 예외로, 테두리 없이 글자만 `#FFC600`으로 바뀝니다(파일명 Dropdown은 클릭해야 열림). 문서 Dropdown 안은 Figma hover를 따릅니다: 목록 항목은 검정 40% 배경, `+ New` / `Open`은 배경이 진해지고(20% → 30%) 글자가 검정 100%가 됩니다(노란 테두리 없음). 배경·크기·위치는 변하지 않습니다. 비활성 버튼은 바뀌지 않습니다.
|
||
- Hover(창 제어 · 바로가기 아이콘): 인쇄·팝업 가이드는 노란 테두리와 함께 아이콘의 `#B6D0C9` 부분만 `#FFC600`으로 바뀝니다(외곽선·그림자·타일 색은 그대로). 최소화는 검정 20% 배경 + 흰 막대, 종료는 `#E40000` 배경 + 흰 X로 바뀝니다(Figma 26:12156 / 26:12161, 32 × 48 유지, 노란 테두리 없음). 소비자는 class나 Prop을 추가하지 않습니다. 상세는 [디자인 명세 §11.2](./sw-header-design-spec.md#112-창-제어--바로가기-아이콘-hover-2026-10-16).
|
||
- Selected(`activeMenu`, `propertyBarOpen`, `propertyPanelOpen`): 글자만 `#FFC600`. Hover와 함께 표시됩니다.
|
||
- Focus: 키보드 Focus 윤곽선은 Hover 테두리 위에 표시됩니다.
|
||
- Hover 테두리 색은 `--hm-sw-header-hover-border-gradient` 토큰, 최소화 Hover 배경·막대 색은 `--hm-sw-header-window-button-hover-background` / `--hm-sw-header-window-icon-hover-color` 토큰으로 바꿀 수 있습니다. 종료·인쇄·팝업 가이드 Hover 아이콘은 패키지 SVG Asset입니다. 대상·제외 목록은 [디자인 명세 §11.1](./sw-header-design-spec.md#111-hover-테두리-공통-상호작용-규칙)을 따릅니다.
|
||
|
||
- Text Stroke(2026-10-16): Figma에 Stroke가 있는 헤더·Dropdown 텍스트는 검정 외곽선(14·16px 1.3px, 12px 라벨 1px)을 표시합니다(자기 색 0.3px Stroke는 2026-10-16 추가 수정으로 제거). Hover·Selected는 채움색만 바뀌고 외곽선은 유지됩니다. 더보기 메뉴·창 이동 Tooltip은 Figma에 Stroke가 없어 적용하지 않고, `+ New` / `Open`은 요청에 따라 기존 그대로입니다. 외곽선 색은 `--hm-sw-header-text-outline-color`, 외곽선 전체는 `--hm-sw-header-text-outline` / `--hm-sw-header-label-outline`으로 바꿀 수 있습니다(`--hm-sw-header-text-stroke-width`는 제거됨)([명세 §6.1](./sw-header-design-spec.md#61-text-stroke-2026-10-16)).
|
||
|
||
## 10.2 창 이동 Hover Motion
|
||
|
||
별도 Prop이나 의존성 없이 Hover하면 GIF의 경로·구간 비율을 유지하면서 후속 사용자 요청에 따라 **1.4초마다 반복**합니다(원본 GIF 1.64초와 다름). 커서가 아래에서 위로 먼저 올라가고 약 444ms에 문구가 나타난 뒤 창·커서·문구가 오른쪽 위로 이동합니다. 약 888ms에 이동을 마치고 약 512ms 유지한 뒤 반복합니다. 28px / −4.5px 이동과 가속·감속 비율은 그대로이며 이탈 시 즉시 기본 SVG로 돌아갑니다. 크기·줄무늬·SVG·Tooltip 규격은 SW/Figma를 유지합니다.
|
||
|
||
`onWindowMoveStart`는 기존 `PointerEventHandler<HTMLDivElement>`입니다. 예를 들어 호스트는 `event.currentTarget.setPointerCapture(event.pointerId)`로 이후 포인터를 추적할 수 있습니다. 패키지는 포인터를 캡처하거나 실제 창을 이동시키지 않습니다. 아이콘과 `창 이동` Tooltip은 `pointer-events: none`이며 Callback 이벤트와 최소화·종료 클릭을 가로채지 않습니다. 누르는 동안 CSS `:active`로 모션을 멈추고 Tooltip을 숨깁니다. Tooltip은 창 이동 영역 밖의 최소화 및 종료 왼쪽 일부에 겹칠 수 있습니다.
|
||
|
||
- 헤더 폭 ≥1080px: 88px 영역, GIF 기준 반복 모션과 Tooltip.
|
||
- 640–1079px: 24px 드래그 영역, 그림은 숨기고 정적 Tooltip만 표시.
|
||
- <640px: 기존대로 창 이동 영역 숨김.
|
||
- Reduced Motion: 기본 아이콘과 정적 Hover Tooltip을 유지하고 애니메이션을 끕니다. 24px 단계에서는 기존대로 그림이 숨겨집니다. OS 설정을 재생 중 바꾸어도 적용됩니다.
|
||
- Focus: 창 이동 div는 기존대로 Tab 대상이 아니며 키보드 창 이동·Focus Tooltip을 제공하지 않습니다. 실제 호스트의 키보드 창 이동 대안과 보조기술 검증은 별도로 필요합니다. 최소화·종료는 이름이 있는 기본 버튼이며 Focus 표시가 유지됩니다.
|
||
|
||
Tooltip은 45 × 17px, 검정 80% 배경과 흰색 12px/400 글자입니다. Tooltip만 Figma의 Inter를 우선하고 한글·미설치 환경은 기존 SW 글꼴로 fallback합니다. 별도 폰트 설치는 하지 않습니다. 경로·자산은 [명세 §11.4](./sw-header-design-spec.md#114-현재-창-이동-motion--ani_hover_movegif-우선-2026-10-16), 현재 시간과 필드 Hover는 [명세 §11.5](./sw-header-design-spec.md#115-현재-추가-기준--1400ms-및-그룹-필드-hover-2026-10-16), 결과는 [현재 통합 검증](./sw-header-speed-composed-hover-verification-2026-10-16.md)을 참고합니다. 원본 GIF 속도 검증과 GIF 전달 전 결과는 각각 [GIF 검증 기록](./sw-header-window-motion-gif-verification-2026-10-16.md), [과거 검증 기록](./sw-header-window-motion-verification-2026-10-16.md)에 보존합니다.
|
||
|
||
## 11. 지원 · 미지원
|
||
|
||
| 지원 | 미지원(호스트 또는 후속 작업) |
|
||
|---|---|
|
||
| Figma 헤더 1개(26:8944) 표시·선택·Focus·Disabled | 색상·선스타일·문자스타일·치수·레이어 **상세 팝업**(Figma 전달 후), 색 선택 팝업(Figma "추후 디자인 진행예정") |
|
||
| 색상(26:10986 / 26:11036)·선스타일(26:11104)·문자스타일(26:11181)·치수스타일(26:11215) Dropdown, ByLayer/ByBlock, RGB·가중치·스케일 Input(2026-10-16) | 실제 색 적용·픽셀 색상 추출·선/문자/치수 스타일 적용(호스트 구현) |
|
||
| 반응형 More 대표 필드 전환, `SWHeaderInput`(Default / Hover / 편집 / 커서), Text Stroke(2026-10-16) | 선택형 상세 Input(Figma 미전달) |
|
||
| 문서 Dropdown(26:10662) 목록·선택·New/Open Callback·키보드 | 실제 파일 열기·새 문서 만들기(호스트 구현) |
|
||
| 레이어 Dropdown(26:10940) 목록·선택·전구/눈꽃/자물쇠 Callback·레이어 상세 Callback·키보드 | 실제 레이어 표시·동결·잠금과 레이어 상세 화면(호스트 구현) |
|
||
| 1800 / 1180 / 1080 / 900 / 640 / 520 반응형 + 더보기(옮긴 기능도 같은 Props·Callback) | SW 팝업(DraggablePanel) |
|
||
| SSR 문자열 렌더링(window 접근 없음) | 창 이동·최소화·종료의 실제 동작 |
|
||
| React 19, ESM, 타입 선언 | CommonJS 빌드, Light/Dark 테마, Runtime Theme JSON 연결 |
|
||
|
||
## 12. Storybook
|
||
|
||
```bash
|
||
npm run storybook
|
||
```
|
||
|
||
> **현재 기준(2026-10-21):** SW Story는 40개입니다. `SW / Header` 20개(사이드바 기본 표시 4개: `SW Header`(`sw-header--overview`)·`Home Menu`·`Logo Slot`·`Responsive More` + `verification` 태그 16개), `SW / Footer` 6개(기본 2 + verification 4), `SW / Popup` 3개(기본 3), `SW / Viewer` 10개(기본 5 + verification 5), `SW / Theme` 1개(verification). 최신 목록은 [패키지 README](../../packages/sw/README.md#storybook-hmdesign-저장소)를 따르고, 아래 2026-10-20 이전 문단은 당시 기록입니다.
|
||
|
||
> **(2026-10-20 기록) SW Storybook 간소화:** SW 메뉴 순서는 `Header → Footer → Popup → Viewer → Theme`입니다. `SW / Header`에는 20개 Story가 있습니다(사이드바 기본 표시 10개 + `verification` 태그 10개). `Property Panel · 4개 비교`는 사이드바에서 `SW / Popup` 아래로 옮겼고 ID(`sw-header--property-panel-compare`)는 그대로입니다. `Window Move Motion`·`K Guide`는 `verification` 태그로 숨겼고, `Home Menu · 단독 사용 · 조합형`은 개별 부품 예제로 사이드바에 표시합니다. 테스트·문서에서 쓰지 않던 Home Menu Figma 비교 Story 6개(`sw-header--home-menu-figma`, `-capture-active`, `-capture-outline`, `-capture-recent`, `-variants`, `-recent-files`)와 `Home Menu`의 Figma 스크린샷 비교 섹션은 제거했습니다. Figma 원본은 `docs/sw/figma-additional/screenshots/`에 있습니다. 상세: [SW Storybook 간소화 보고](./sw-storybook-simplification-2026-10-20.md). 아래는 작성 당시 기록입니다.
|
||
|
||
(2026-10-19 기록) 홈 메뉴 통합 후 `SW / Header`에는 27개 Story가 있습니다(사이드바 기본 표시 12개 + `verification` 태그 15개). 이전 시점의 Story 표는 [1차 정리 보고 §2](./sw-first-pass-cleanup-review-2026-10-19.md#2-storybook-전체-목록-및-분류), 홈 메뉴 통합 후 목록은 [홈 메뉴 통합·실측 More 보고](./sw-header-home-menu-responsive-2026-10-19.md)를 봅니다. 아래 문단은 Story를 추가한 시점별 설명입니다.
|
||
|
||
2026-10-19 홈 메뉴 통합: 기존 `SW / Home Menu` 메뉴는 사이드바에서 제거하고 `SW / Header`로 옮겼습니다. 사이드바에는 `Home Menu`(`sw-header--home-menu`: 로고 클릭 열기/닫기·이벤트 기록, 헤더 폭 조절, Figma 26:10591 비교, Default/Hover/Active, 최근파일 하위 패널, 속성창·SWPopup·Escape 통합) 하나만 표시합니다. Figma 비교 캔버스·Variant·데이터 변경·단독 사용·속성Bar 비교 등은 `sw-header--home-menu-*` ID의 `verification` 태그 Story로 남아 직접 URL과 사이드바 태그 필터로 엽니다. `Overview`의 로고도 같은 홈 메뉴를 엽니다. `Responsive More` 첫 섹션 "실제 사용 가능한 공간 기준"은 헤더 폭 Range와 로고 오른쪽 버튼·추가 도구·홈 메뉴 On/Off로 실측 More의 이동·복귀를 확인합니다. 홈 메뉴는 SW 일반 모드 전용이며 `SW / Viewer`에는 없습니다.
|
||
|
||
(2026-10-16 기록 — 아래 이름 중 Default·Controls·Responsive·Composed Controls·Layer Dropdown 등 다수는 이후 제거·통합되어 현재 없습니다) `SW / Header` 아래 Story: Overview, Default, Controls, Interactive States(인쇄·팝업 가이드·최소화·종료 Default/Hover 비교와 Callback 기록 포함), Responsive, Composed Controls, Logo Slot(기본 임시 로고 / 사용자 지정 로고 / 로고 미전달), Document Dropdown(Default / Hover / Open / 선택 / 여러 문서 / 문서 없음 / 긴 파일명 / 좁은 화면), Layer Dropdown(Layer Default / Hover / Open(Figma 8행) / Row Hover / 전구·눈꽃·자물쇠 4상태 3배 확대 / 선택·Toggle Callback 기록과 상태표 / 여러 상태·현재 레이어 / 긴 레이어명 / 20개 스크롤 / 레이어 없음 / 레이어 상세 / 상태 표시 전용 / 좁은 화면 1440–390). Responsive Story는 헤더 폭 18개(1920–390, 각 단계 경계 전후 포함) 프레임을 나열합니다. 모든 단계가 헤더 폭 기준이므로 Viewport를 바꾸지 않아도 확인할 수 있습니다.
|
||
|
||
`Window Move Motion`은 GIF 주요 3상태와 속도 변경 후 약 0 / 205 / 410 / 444 / 683 / 888 / 1366 / 1400ms 프레임, 실제 Hover·Drag·최소화·종료 Callback 및 반복(`motion cycle`) 기록, 축소 폭 예제를 제공합니다. 내부 고정 시간은 원본 GIF 시간 × 1400/1640의 정확한 값입니다. 고정 프레임은 Storybook 전용 Hover attribute와 Web Animations API로 시간을 멈춥니다. Reduced Motion에서는 기본 아이콘·정적 Tooltip 정책을 따르므로 정상 타임라인 비교는 해당 설정을 끄고 Story를 다시 로드합니다.
|
||
|
||
2026-10-16 추가 Story: `Color Dropdown`, `Line Style Dropdown`, `Text · Dimension Style Dropdown`(Default / Hover / Expanded / 행 Hover / 독립 버튼 Hover 무변화 / Callback 기록 / 좁은 화면), `Responsive More`(대표 필드 전환 Callback 기록 1440·768·390 + 16개 폭), `Text Stroke`(Figma 원본 이미지 비교 · 3배 확대 · 말줄임), `Layer Detail · Input`(Figma 참조 이미지 · 레이어 상세 Default/Hover 무변화/Disabled/Callback · Input Default/Hover/ReadOnly/Disabled/편집/확정·취소·외부 값·IME), `Dynamic Data`(항목 추가·삭제·이름·값·순서 변경). `Document Dropdown`의 첫 예제는 현재 열린 문서 1개이며, 기본 `figmaArgs`도 그 문서 1개를 전달합니다(`documents=[]`는 예외 상태 예제로만 남김). 검증 결과는 [Dropdown·More·Text Stroke·Input 검증 기록](./sw-header-dropdowns-stroke-input-verification-2026-10-16.md)에 있습니다.
|
||
|
||
`Composed Controls`에는 기존 레이어·색상·상세보기와 공개 선/문자/치수 컨트롤, 5개 필드의 고정 Hover 비교가 있습니다. 실제 공용 컴포넌트를 렌더링하며 60 × 15px 치수 Hover SVG는 흰 foreground만 노란색으로 바꾸고 검정 mask 외곽선을 유지합니다. 기존 Story만 보완했으며 새 Story는 추가하지 않았습니다. [현재 통합 검증](./sw-header-speed-composed-hover-verification-2026-10-16.md)을 참고합니다.
|
||
|
||
## 속성창·K 가이드 추가 연결 (2026-10-16)
|
||
|
||
기존 공개 Props를 유지한 추가 API입니다. `propertyPanel`을 전달하면 기존 속성창 버튼에 패널이 연결됩니다. 미전달 시 기존 토글/Callback 계약만 유지하며 임의의 CAD 데이터/편집 창을 만들지 않습니다.
|
||
|
||
```tsx
|
||
import { useState } from "react"
|
||
import { SWHeader, type SWHeaderProperty } from "@hm-design/sw"
|
||
import "@hm-design/sw/styles.css"
|
||
|
||
function DrawingHeader() {
|
||
const [properties, setProperties] = useState<SWHeaderProperty[]>([
|
||
{ id: "layer", groupId: "common", name: "레이어", type: "text", value: "층_01" },
|
||
{ id: "weight", groupId: "common", name: "선가중치", type: "number", value: "0.25", min: 0, unit: "mm" },
|
||
])
|
||
return <SWHeader
|
||
documentTitle="도면"
|
||
propertyPanel={{
|
||
selection: { id: "selection-42", kind: "single", title: "선택된 객체 - 호스트 객체" },
|
||
groups: [{ id: "common", collapsedCount: 6 }],
|
||
properties,
|
||
onPropertyChange: (property, value) => {
|
||
// 실제 CAD 반영 및 계산은 호스트에서 수행한 뒤 결과를 Props로 돌려줍니다.
|
||
setProperties(items => items.map(item => item.id === property.id ? { ...item, value } : item))
|
||
},
|
||
onToolActiveChange: active => console.info("호스트 선택 도구", active),
|
||
}}
|
||
onPropertyPanelOpenChange={open => console.info("속성창 열림", open)}
|
||
/>
|
||
}
|
||
```
|
||
|
||
- Uncontrolled: `propertyPanelOpen`을 생략합니다. Controlled: `propertyPanelOpen={open}`, `onPropertyPanelOpenChange={setOpen}`을 함께 전달합니다. 기존 `onPropertyPanelToggle`은 버튼/More 클릭 시 그대로 호출됩니다. 이전 Controlled 호스트가 새 Open Callback 없이 기존 Toggle Callback만 사용한다면 X/Escape 닫기에서도 기존 Toggle로 알립니다.
|
||
- 선택 대상이 바뀔 때 `selection.id`도 반드시 변경합니다. 같은 property id라도 이전 객체의 임시 편집값은 재사용하지 않습니다.
|
||
- `selection.kind`: none/all/single. `selection.title`·`counts`·`overviewTitle`은 호스트 데이터입니다. counts의 선택적 row/column은 Figma 집계 배치를 표현할 수 있습니다. `onOverviewClick`도 선택 변경을 요청할 뿐 내부 CAD 동작은 없습니다. 2026-10-21: 전체 선택 하단의 객체 유형·개수 목록은 표시 전용이며, 각 항목에 마우스를 올리면 그 항목 글자만 `#FF5C00`으로 바뀝니다(클릭·선택·필터 동작 없음).
|
||
- 항목 타입: text/number/select/color/slider/computed. `id/name/value/type/groupId`가 필수이고, `order`로 표시 순서를 지정합니다. 중복 id를 사용하지 마세요.
|
||
- `options`는 select의 id/name/disabled 목록입니다. 선택은 Enter 또는 Focus 이동으로 확정하고 Escape로 취소합니다. 삭제되거나 disabled가 된 option은 새 값으로 확정하지 않습니다. 옵션 데이터가 바뀌면 임시 선택을 현재 호스트 값으로 돌립니다.
|
||
- `color`는 기존 RGB/HEX parser를 재사용합니다. `swatch`로 별도 견본을 제공할 수 있습니다. `linePreview/linePattern`은 기존 선 미리보기를 재사용합니다. `annotation`은 (ByL) 같은 보조 정보, `unit`은 mm/% 같은 단위입니다.
|
||
- number/slider는 `min/max`, slider는 `step`을 지원합니다. slider의 기본 범위는 0~100이며 Story의 0~1 등은 호스트가 지정한 범위입니다. 수치를 계산하거나 Figma의 시각적 thumb 위치에 맞춰 임의 변환하지 않습니다.
|
||
- `validate(draft)`가 false면 거부합니다. 항목의 `onCommit(value, property)`와 패널의 `onPropertyChange(property, value)`는 서로 다른 통지 지점입니다. 둘 다 연결하면 각각 통지됩니다.
|
||
- 그룹은 `layout=rows/circle/position`, `order/name/collapsedCount/positionLabel`을 받습니다. circle은 원본 SVG 위 `graphicSlot=circumference/diameter/radius/area`의 항목을 배치합니다. 다른 항목은 그래픽 아래에 배치합니다. 계산값은 computed/readOnly로 전달합니다.
|
||
- `expandedGroupIds/onExpandedGroupsChange`로 펼침을 제어하거나, Uncontrolled일 때 `defaultExpandedGroupIds`로 시작합니다. 펼침은 property Callback을 실행하지 않습니다.
|
||
- `toolActive/onToolActiveChange/toolLabel`로 내부 아이콘을 연결합니다. 현재 의미는 호스트가 정하는 선택 도구 상태 통지이며 CAD 기능을 내장하지 않습니다.
|
||
- 단독 사용: `<SWTheme><SWHeaderPropertyPanel ... onClose={close} /></SWTheme>`. 단독 배치는 호스트의 `style`로 지정할 수 있습니다.
|
||
- 동적 패널 입력은 `SWHeaderInput commitOnUnmount={false}`를 사용합니다. 기존 Input의 기본 `true` 동작은 유지합니다.
|
||
- 속성 필드 상태(2026-10-19, Figma 26:12388 ~ 26:12649): 공통 행은 200 × 28 필드(선가중치처럼 단위 없는 숫자와 투명도 값은 41px 내외 Hug)입니다. Default는 값만, Hover는 `rgba(33,29,19,.4)` 테두리, 숫자 편집 On은 `#0F0D0A` + 흰 단위, Dropdown On은 `#211D13`입니다. 하이퍼링크(text)·재료(computed)는 Figma에 On이 없어 Default/Hover만 적용합니다. [검증 기록](./sw-property-fields-verification-2026-10-19.md).
|
||
- 필드 Dropdown: `property.picker`(`"layer" | "lineStyle" | "color" | false`)와 패널 `menus`(`SWHeaderPropertyMenus`: layers·toggle Callback, lineTypes·lineWeights·lineScale, colors·colorMode·onColorPick 등)를 전달합니다. `type: "color"`는 색상, `type: "select"` + `linePreview`는 선스타일 Dropdown이 기본이며 레이어는 `picker: "layer"`를 지정합니다. 목록 선택은 해당 속성의 `onPropertyChange`(값 = 행 id)로 확정되고, 레이어 Toggle·가중치·스케일·ByLayer/ByBlock·색상 추출·상세는 `menus` Callback만 호출합니다. `menus`가 없으면 `options`로 목록을 만듭니다. `mixed`는 `다양함`으로 표시하며 현재 행을 표시하지 않습니다.
|
||
- 2026-10-21 디자인 검토: 속성창 선꼴 Dropdown은 선 종류 목록 1열만 표시합니다(`menus.lineWeights`·`lineScale`·가중치/스케일 Callback은 속성창 Dropdown에서 쓰이지 않으며 SWHeader 선스타일 필드는 3열 유지). 숫자 편집 Input은 값 영역 전체 너비를 채우고, 투명도는 값 + `annotation`이 하나의 편집 상자입니다. 수정 불가능한 값(readOnly·computed·Disabled)은 속성창에서 검정, 속성Bar에서 Bar 편집이 없는 값은 포커스 없는 텍스트(`<output>`)입니다. 상세는 `packages/sw/ai/DESIGN_RULES.md` 속성값 색상 항목.
|
||
- `decimals`를 지정한 숫자는 확정값을 고정 소수 자릿수로 정규화합니다(예: 선축적 `2` → `2.0000`). 색상 입력은 기존 0~255 검증을 유지합니다.
|
||
- K 가이드는 별도 데이터를 요구하지 않습니다. 기존 K 버튼 Hover/Focus로 원본 이미지가 Pan/Zoom 반복되고, 작은 화면의 More에서도 같은 미리보기가 보입니다. Reduced Motion·Escape·재진입 초기화가 적용됩니다. 클릭은 기존 `onPopupGuide`만 호출합니다.
|
||
- 색상 모드의 null은 직접 색상 의미를 보존하고, 필수 모드 표시는 마지막 유효 모드(처음 ByLayer)를 유지합니다. 선택된 모드를 재클릭해도 onColorModeChange가 중복 호출되지 않습니다.
|
||
- 신규 Story: Property Panel / Property Panel Variants / K Guide / Additional Hover. 기존 17개 Story를 유지합니다.
|
||
|
||
실행 결과와 제한은 [추가 검증 기록](./sw-header-additional-verification-2026-10-16.md)을 따릅니다.
|
||
|
||
## 후속 5개 요구사항 연결 (2026-10-16)
|
||
|
||
### 속성Bar와 공유 데이터
|
||
|
||
```tsx
|
||
import { SWHeader, SWHeaderPropertyBar, type SWHeaderProperty } from "@hm-design/sw"
|
||
import "@hm-design/sw/styles.css"
|
||
|
||
const fields: SWHeaderProperty[] = [
|
||
{ id: "length", groupId: "shape", name: "길이", type: "number", value: length, min: 0 },
|
||
]
|
||
const data = {
|
||
selection: { id: objectId, title: objectName, kind: "single" as const },
|
||
properties: fields,
|
||
onPropertyChange: (property: SWHeaderProperty, next: string) => updateObject(property.id, next),
|
||
}
|
||
<SWHeader documentTitle={fileName}
|
||
propertyBar={data} propertyBarOpen={barOpen} onPropertyBarOpenChange={setBarOpen}
|
||
propertyPanel={{ ...data, groups: [{ id: "shape", name: "형상" }] }}
|
||
propertyPanelOpen={panelOpen} onPropertyPanelOpenChange={setPanelOpen} />
|
||
```
|
||
|
||
- `propertyBar`는 `SWHeaderPropertyBarProps`: selection/properties/onPropertyChange, 전체 readOnly/disabled 및 div HTML 속성입니다. 개별 property의 readOnly/disabled/validate/min/max도 적용합니다. 컴포넌트 밖에서 단독 사용하면 `SWTheme`로 감쌉니다.
|
||
- `propertyBar` 생략 시 propertyPanel의 selection/properties/onPropertyChange만 공유합니다. 두 데이터를 모두 생략하면 토글 상태만 제공하며 임의 속성이나 CAD 수치를 만들지 않습니다.
|
||
- `propertyBarOpen` 생략 시 내부 상태, `defaultPropertyBarOpen`으로 초기값을 정합니다. Controlled는 `propertyBarOpen/onPropertyBarOpenChange`를 함께 사용합니다. 기존 `onPropertyBarToggle`도 클릭마다 보존됩니다. 속성창 열림과 독립적입니다.
|
||
- properties는 id로 식별하고 `order` 오름차순(같은 값은 배열 순서)으로 표시합니다. 객체 교체 시 selection.id를 바꿉니다. name/value/항목 추가·제거·외부 변경을 반영합니다.
|
||
- 값 편집은 `SWHeaderInput`을 사용합니다. Enter/Blur 확정, Escape 취소, 유효하지 않은 입력 복원, 외부 value 반영, 편집 중 삭제 draft 폐기를 지원합니다. 바의 select 타입은 option id를 입력하고 해당 options로 검사합니다. slider 타입은 숫자로 편집합니다. computed는 읽기 전용입니다. 속성창의 select/range UI를 바에 복제하지 않습니다.
|
||
- 헤더는 49px, 바는 별도 28px입니다. 폭은 최대 1222px이며 객체 수정 영역의 실제 위치부터 부모의 남은 폭을 사용합니다. x=377 고정값은 없습니다. 좁으면 focus 가능한 region 안에서 가로 스크롤하며, 편집 가능한 값은 input focus로 접근할 수 있습니다. 2026-10-21부터 바에서 편집하지 않는 값(읽기 전용·`computed`·Disabled)은 input 대신 포커스 없는 `<output>` 텍스트(빨강)로 표시하고 호스트 값을 그대로 따릅니다. 호스트 콘텐츠는 열림 상태에 따라 필요한 28px 공간을 예약하세요. 기존 Dropdown은 바 위에 표시됩니다.
|
||
|
||
### 메뉴 컴포넌트 자체 구성
|
||
|
||
```tsx
|
||
<SWHeader documentTitle={fileName}
|
||
controls={[{ id: "lineStyle", label: "선 종류" }, { id: "layer" }]}
|
||
layers={layers} activeLayerId={layerId} onLayerSelect={selectLayer}
|
||
lineTypes={lineTypes} lineStyle={lineStyle} onLineTypeSelect={selectLineType} />
|
||
```
|
||
|
||
- `controls?: readonly SWHeaderControlConfig[]`는 전체 allowlist입니다. 미전달하면 승인된 기존 구성·계층·1920px 디자인을 유지합니다. `[]`는 선택적 컨트롤 전부 제거입니다. 홈/로고·창 제어는 유지합니다.
|
||
- id: document / drawingMode / layer / color / lineStyle / textStyle / dimensionStyle / details / propertyBar / propertyPanel / print / popupGuide. `details`는 상세보기 안내 caption이며 필수 액션이 아닙니다.
|
||
- 배열에서 빼거나 `visible:false`로 숨기며, 배열 순서가 실제 DOM·키보드·배치 순서입니다. 중복 id는 첫 번째 표시 항목만 사용합니다. 숨김 해제/재추가할 수 있습니다. `label`은 필드 라벨·버튼 이름·More 이름을 바꿉니다. 문서는 실제 documentTitle을 유지하고 접근성 이름 접두사를 바꿉니다. K는 아이콘을 유지하고 접근성 이름을 바꿉니다.
|
||
- 구성 모드는 각 컨트롤을 하나의 객체 수정 영역 안에서 재배치합니다. 기본 모드의 그룹 외곽 배치를 바꾸지 않습니다. 파일·색상 등을 제거한 공간은 고정 슬롯으로 남지 않습니다.
|
||
- 기존 1800/1180/1080/900/640/520px 기준은 유지합니다(2026-10-21부터 **헤더 폭 760px 미만에만** 적용, 760px 이상은 More 없이 5개 메뉴 유지 — §7 반응형 More). 남은 필드 중 레이어(없으면 배열의 첫 필드)를 대표 슬롯으로 사용하고 More 선택으로 대체합니다. 대표 요소 삭제 시 남은 필드로 대체합니다. 표시 중인 대표는 More에서 제외되고, 구성에서 빠진 요소는 More에서도 사라집니다.
|
||
- 값은 기존 Props/Callback으로 계속 호스트가 소유합니다. 재정렬은 keyed 컴포넌트를 유지하고, 제거된 Dropdown과 draft는 정리하며 데이터 Callback을 생성하지 않습니다. 내부 열림 상태는 제거 후 재추가 시 닫힘입니다. Controlled 열림은 호스트가 전달한 값이 우선합니다. 일반적인 Dropdown 닫힘 시 기존 입력 확정 계약은 유지합니다.
|
||
- 구성요소 배열과 Dropdown 내부 옵션 배열은 별개입니다. layers/colors/userColors/lineTypes/lineWeights/textStyles/dimensionStyles의 동적 기능은 그대로 사용합니다.
|
||
|
||
### Colorize와 CSS 경로
|
||
|
||
- 색상 메뉴의 기존 `색상 추출` 위치에 Figma `26:11302`/`26:11305` 원본 SVG를 연결했습니다. 버튼 24×24, 아이콘 18×18, Hover는 주황 경계와 검정 25% overlay입니다. Hover는 Callback을 실행하지 않으며 클릭/Enter는 기존 `onColorPick`을 호출하고 메뉴를 닫습니다.
|
||
- `26:11338 color_돋보기` Overlay는 패키지에 구현되어 있지 않습니다. 호스트가 onColorPick에서 연결할 대상이며 임의 대체 색상창은 만들지 않았습니다.
|
||
- CSS 편집 원본은 `packages/sw/src/theme/sw.css`입니다. SW build가 dist/styles.css를 만들고 Story가 공개 `@hm-design/sw/styles.css`를 읽습니다. dist만 편집하면 다음 빌드에서 원본이 다시 포함됩니다. 원본 상세 span의 Stroke 선언과 미사용 solid-stroke-width 변수를 제거했으므로 산출물 override는 필요하지 않습니다. 외부 소비 앱에는 SW를 다시 빌드한 뒤 산출물을 갱신하세요.
|
||
- Text Stroke Story의 지정 원본 스크린샷·제목·전용 import를 제거했습니다. 이미지 파일과 나머지 비교 Story·text-shadow는 보존했습니다.
|
||
- 기존 21개 Story에 Property Bar / Dynamic Controls / Colorize를 추가했습니다. 현재 실행 결과는 [후속 검증 기록](./sw-header-followup-five-verification-2026-10-16.md)을 따릅니다.
|
||
|
||
|
||
|
||
## 구성요소 표시·메뉴 순서·속성창 4상태·형상 Slot (2026-10-16 3차)
|
||
|
||
이 절이 같은 주제의 이전 설명보다 우선합니다. 검증 결과는 [3차 검증 기록](./sw-header-composition-shape-verification-2026-10-16.md)에 별도로 기록했습니다.
|
||
|
||
### 로고 필수, 나머지 개별 제거 — `hiddenControls`
|
||
|
||
```tsx
|
||
<SWHeader documentTitle={fileName} logoSrc={logoUrl} logoAlt="EasyBeam"
|
||
hiddenControls={["document", "color", "textStyle", "print", "minimize"]}
|
||
/* 값·목록·Callback Props는 기존 그대로 */ />
|
||
```
|
||
|
||
- `hiddenControls?: readonly SWHeaderHideableControlId[]`. 대상 id: `home`, `document`, `drawingMode`, `layer`, `color`, `lineStyle`, `textStyle`, `dimensionStyle`, `details`, `propertyBar`, `propertyPanel`, `print`, `popupGuide`, `windowMove`, `minimize`, `close`. 로고는 대상이 아니며 항상 표시합니다(`logo` / `logoSrc` / `logoAlt` Props 유지).
|
||
- `home`을 제거하면 홈 동작만 사라지고 로고는 같은 32×17 슬롯에 정적 이미지로 남습니다(Hover·`onHomeClick` 없음).
|
||
- 전체 `controls` 목록 없이 쓸 수 있으며, 남은 요소는 기본 디자인의 크기·계층·Breakpoint를 유지합니다. `controls`와 함께 쓰면 두 조건을 모두 적용합니다(`controls`의 `visible:false`와 같은 의미).
|
||
- 제거한 요소는 렌더링하지 않습니다: 공간 없음, Dropdown·속성창·속성Bar·K 가이드 표시 안 함, Callback 없음. 열린 상태에서 제거하면 내부 열림 상태만 조용히 닫힙니다(데이터 Callback 없음).
|
||
- 배열에서 다시 빼면 재표시됩니다. 선택값은 호스트 Props이므로 그대로이고, 내부 Dropdown은 닫힌 상태로 돌아옵니다.
|
||
- 미전달 또는 `[]`이면 승인된 기본 Header와 동일합니다(1920~390px DOM 위치·크기·색 비교 차이 0).
|
||
- 객체 영역 요소를 제거하면 More 이동은 CSS 대신 스크립트가 같은 Breakpoint(1800/1180/1080/900/640)로 결정합니다(2026-10-21부터 헤더 폭 760px 미만에서만; 760px 이상은 More로 옮기지 않음). 표시할 항목이 없는 폭에서는 More 버튼을 만들지 않습니다. 창 영역은 남은 요소 폭만 차지합니다.
|
||
- 로고만 남겨도 Header 높이 49px와 녹색 객체 영역 배경은 유지합니다.
|
||
|
||
### 유사 메뉴 순서 — `menuOrder`
|
||
|
||
```tsx
|
||
<SWHeader documentTitle={fileName}
|
||
menuOrder={["color", "layer", "dimensionStyle", "lineStyle", "textStyle"]} />
|
||
```
|
||
|
||
- 대상: `layer`, `color`, `lineStyle`, `textStyle`, `dimensionStyle`(`SWHeaderMenuId`). 메뉴는 메뉴가 이미 차지한 자리 안에서만 서로 바뀌며 홈·파일·로고·그리기모드·상세보기·창 제어는 움직이지 않습니다.
|
||
- 미전달: 승인된 기본 순서 `레이어 → 색상 → 선스타일 → 문자스타일 → 치수스타일`(`controls`를 쓰면 그 배열 순서).
|
||
- 누락 규칙: 지정한 id가 먼저 지정 순서로 오고, 빠진 id는 그 뒤에 기본 순서로 붙습니다. 중복 id는 첫 번째만, 알 수 없는 id는 무시합니다. 숨긴 메뉴가 들어 있어도 오류 없이 건너뜁니다.
|
||
- 순서는 DOM·Tab 순서·More 목록 순서에 함께 반영됩니다. React key는 메뉴 id라 재정렬해도 값·Callback·Dropdown·Focus가 유지되며, 순서 변경만으로는 어떤 Callback도 호출하지 않습니다.
|
||
- 대표 슬롯: 레이어가 표시 중이면 레이어, 아니면 순서상 첫 메뉴가 기본 대표입니다. More에서 고른 메뉴가 대표 자리를 대신하며, 그 메뉴가 제거되면 기본 대표로 돌아갑니다.
|
||
|
||
### 속성창 4가지 상태
|
||
|
||
새 팝업 없이 기존 `SWHeaderPropertyPanel` 하나를 호스트 데이터로 바꿉니다.
|
||
|
||
| 상태 | Figma | selection.kind | 데이터 |
|
||
|---|---|---|---|
|
||
| 선택된 객체 없음 | 26:11886 · 314×256 | `none` | common 그룹만 |
|
||
| 전체 객체 선택 | 26:11823 · 314×361 | `all` + `counts` | common 그룹, 혼합값 `mixed` |
|
||
| 원 1개 선택 | 26:11932 · 314×658 | `single` + `overviewTitle` | common(collapsedCount) + shape(circle) + position |
|
||
| 모든 항목 펼침 | 26:12035 · 314×754 | `single` | 위와 같고 `expandedGroupIds={["common"]}` |
|
||
|
||
Storybook: `Property Panel · 선택 없음 / 전체 선택 / 원 1개 / 모두 펼침` 개별 Story와 `Property Panel · 4개 비교`(디자인 검증용, 실측 크기 표시, 좁으면 줄바꿈). 실제 Header는 현재 선택에 맞는 패널 하나만 표시합니다.
|
||
|
||
### 형상 영역 Slot — `shapeContent`
|
||
|
||
```tsx
|
||
<SWHeader documentTitle={fileName} propertyPanel={{
|
||
selection, properties, groups,
|
||
shapeContent: ({ selection, group, properties }) => <MyShapeViewer objectId={selection.id} />,
|
||
}} />
|
||
```
|
||
|
||
- `layout: "circle"` 그룹의 208×208 영역(그룹 260px 안 39, 26)에 렌더링합니다. `SWHeaderPropertyPanel`과 `SWHeader.propertyPanel` 모두 같은 Prop입니다.
|
||
- `undefined`(미지정): Figma 원형 그림(기존 기본). `null` / `false`: 빈 영역 — 크기·치수 라벨 위치·패널 높이 유지. ReactNode: 외부 컴포넌트. 함수: `{ selection, group, properties }`(해당 그룹 항목)를 받아 렌더링 — 객체 유형별 교체에 사용합니다.
|
||
- 내용은 영역 안에 맞춰지고(overflow hidden) 원주·지름·반지름·면적 입력은 그 위에 그대로 표시됩니다. 값은 `properties`로만 전달되며 형상 Slot과 독립입니다. 입력은 기존 SWHeaderInput 검증·Enter/Blur 확정·Escape 취소를 사용합니다.
|
||
- 패키지는 CAD 계산·형상 렌더링 엔진을 포함하지 않습니다. Storybook의 사각형/점선 원 SVG는 Story 전용 샘플입니다.
|
||
|
||
## 로고 클릭 메뉴 — `homeMenu` (2026-10-19)
|
||
|
||
Figma 26:10591의 2단 Header 메뉴입니다. 기본 숨김, 로고 클릭으로 열고 다시 클릭하면 닫힙니다.
|
||
|
||
```tsx
|
||
<SWHeader documentTitle={name} documents={documents} onDocumentNew={newFile} onDocumentOpen={openFile}
|
||
homeMenu={{ sections, onItemSelect }} // SWHomeMenu Props (열림 상태 제외)
|
||
onHomeMenuOpenChange={(open, reason) => {}} /> // reason: home | escape | background | outside | select | file | document
|
||
```
|
||
|
||
- `sections`: `{ id, title, items: [{ id, label, icon, hoverIcon?, toggle?, panel?, disabled?, hidden?, onSelect? }] }[]`. 최근파일은 타일의 `panel: <SWRecentFiles files={…} onFileOpen={…} />`(선택). `panel`이 없는 항목은 실행 버튼입니다(클릭 시 `onSelect`/`onItemSelect`, 클릭 후 ON 유지 없음). 최근파일만 목록이 열린 동안 Active이며, 유지형이 필요하면 `toggle: true`.
|
||
- 이벤트 우선순위: `homeMenu`가 있으면 로고 클릭은 메뉴 토글만 하고 `onHomeClick`은 호출되지 않습니다. `homeMenu`가 없으면 기존과 같이 `onHomeClick`만 호출됩니다.
|
||
- New / Open은 문서 Dropdown 안 그대로입니다(`documentOpenPlacement` 기본 `"menu"`). 메뉴가 열리면 필드·문서 Dropdown이 닫히고, 문서 Dropdown을 열면 메뉴가 닫힙니다.
|
||
- 위치: 객체 수정 영역 −2px ~ 창 영역(DOM 측정, 1920에서 x 375 · 폭 1305). 좁은 폭에서는 가로 스크롤 없이 들어가는 섹션만 표시하고 나머지는 끝의 더보기(⋯) 섹션으로 옮깁니다(Dropdown은 섹션 제목 + 기능 텍스트 목록, 타일과 같은 Hover·Active·클릭, 폭이 넓어지면 자동 복귀, 배지 문구 `homeMenu.moreLabel`).
|
||
- Escape: 하위 패널 → 메뉴 순서, 닫히면 로고로 Focus. 속성창·`SWPopup`과 같은 Escape 순서(Focus가 모두 밖이면 최근 사용 하나).
|
||
- 상세: 패키지 [카탈로그 5절](../../packages/sw/ai/COMPONENT_CATALOG.md), [통합 검증 기록](./sw-home-menu-integration-2026-10-19.md).
|