feat(sw): add SW Footer component and Storybook stories
This commit is contained in:
1 parent
77a809baa3
commit
6cf885412e
105 files changed
+3288
-3
No files matched your search
@@ -11,6 +11,7 @@
|
||||
| [DESIGN_RULES.md](./DESIGN_RULES.md) | SW 전용 토큰·Typography·간격·Hover/Focus/Selected·Overlay·Motion·반응형·Popup 외형·도구 버튼·로고 클릭 메뉴(일반 모드 §1~10·§12), 뷰어 토큰·상태값 요약(§11) |
|
||||
| [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) | 완성형·간소화·기능 중심·확장 Header, Popup(자유 콘텐츠), 일반 모드 Header + 도구 막대(1~6), 뷰어 모드 기본 조합(7) |
|
||||
| [examples/](./examples) | 공개 API만 쓰는 예제. **일반 모드** A~F(E = 사용자 정의 Popup, F = 파일 열기 + 도구 막대. 독립 소비 프로젝트에서 Typecheck·Build·브라우저 검증. B·F 파일명의 "Viewer"는 과거 이름이며 일반 모드 `SWHeader` 구성), **뷰어 모드** H(`SWViewer*`) |
|
||||
| [FOOTER.md](./FOOTER.md) | **일반 모드 하단 막대**: `SWFooter`(보조기능·바 안 명령창·명령 칸·분류 탭·명령어 전체보기), `SWFooterLayoutTabs`(Model/Layout 탭), `SWFooterCommandLine`(명령창). AI 작업 순서 1~6 |
|
||||
| [VIEWER.md](./VIEWER.md) | **뷰어 모드 전용**: `SWViewerHeader`(데이터 메뉴·More·외곽선·로고 규칙), `SWViewerMenu`(Dropdown·서브메뉴·우클릭), `SWViewerPanel`(팝업 프레임 + 자유 콘텐츠). 일반 SW 모드는 `SWHeader`·`SWPopup`을 그대로 씁니다 |
|
||||
|
||||
## 0. SW 모드 선택 — SW 일반 모드 / SW 뷰어 모드 (코드 작성 전에 결정)
|
||||
@@ -42,12 +43,14 @@ SW 패키지에는 Header·Popup 계열이 두 벌 있습니다. 화면마다 **
|
||||
| Dropdown | 각 필드 Dropdown(`SWHeaderLayerMenu` …) | `type: "menu"` 항목, 단독·우클릭은 `SWViewerMenu` |
|
||||
| 팝업·설정창 | `SWPopup`(`titleActions`, `footer`) + `SWPopupSection` / `SWPopupRow` + `SWHeaderInput`, 속성창 `SWHeaderPropertyPanel`, 속성Bar `SWHeaderPropertyBar` | `SWViewerPanel`(이동·크기 조절·접기·`modal`, `headerActions`, `footer`) + `SWViewerSection` / `SWViewerField` / `SWViewerSwitch` / `SWViewerSlider` / `SWViewerInput` / `SWViewerButton` / `SWViewerBadge` / `SWViewerHint` |
|
||||
| 로고(홈) 버튼 메뉴 | `SWHeader`의 `homeMenu`(`SWHomeMenu` 데이터, 최근파일 `SWRecentFiles`, Figma 26:10591). 기본 숨김 → 로고 클릭 열기 → 다시 클릭 닫기 | **사용하지 않음.** `SWViewerHeader`에는 홈 메뉴 Props가 없고 로고 클릭은 `onHomeClick`만 호출합니다. 파일 열기·샘플은 Header `groups`의 `button`·`menu` 항목 |
|
||||
| 하단 Footer | `SWFooter`(+ 바 안 명령창 `command={<SWFooterCommandLine />}`, 좌표 `status`, `SWFooterLayoutTabs`, 부품 `SWFooter*`), [FOOTER.md](./FOOTER.md) | **사용하지 않음.** 뷰어 화면에는 하단 명령 막대가 없습니다. 도면 공간 탭은 `SWViewerHeader`의 `tabs` 항목 |
|
||||
| 버튼·탭·토글 | `SWToolButton` / `SWToolGroup` | Header 항목 `type: "button" \| "toggle" \| "tabs" \| "switch"`, 패널 안 `SWViewerButton` |
|
||||
| 아이콘 | 패키지 자산(필드 안 아이콘) | 프로그램 아이콘 16px, 직접 그린 아이콘은 `SWViewerIcon`으로 감쌈 |
|
||||
| 범위 | Header 밖 부품은 `<SWTheme>` 안 | `SWViewerHeader`·`SWViewerMenu`·`SWViewerPanel`은 루트에 `hm-sw-theme`이 있어 `<SWTheme>` 불필요. 패널 컨트롤은 `SWViewerPanel` 안 |
|
||||
| 토큰 | `--hm-sw-header-*`, Popup 콘텐츠 `--hm-sw-property-*` | `--hm-sw-viewer-*` + 공유 `--hm-sw-header-*`(아래 0.3) |
|
||||
| 읽을 문서 | [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) §1~8, [DESIGN_RULES.md](./DESIGN_RULES.md) §1~10·§12, [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 1~6·8, 예제 A~G | [VIEWER.md](./VIEWER.md), [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) §9, [DESIGN_RULES.md](./DESIGN_RULES.md) §11, [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 7, [예제 H](./examples/ExampleH-ViewerMode.tsx) |
|
||||
| 읽을 문서 | [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) §1~8·§10, [DESIGN_RULES.md](./DESIGN_RULES.md) §1~10·§12·§13, [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 1~6·8·10, [FOOTER.md](./FOOTER.md), 예제 A~G·I | [VIEWER.md](./VIEWER.md), [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) §9, [DESIGN_RULES.md](./DESIGN_RULES.md) §11, [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 7, [예제 H](./examples/ExampleH-ViewerMode.tsx) |
|
||||
|
||||
- **하단 명령 막대(Footer)** 요청은 일반 모드입니다: [FOOTER.md](./FOOTER.md) §1 순서로 `SWFooter`가 있는지 확인하고, 명령·분류·보조기능·문구·아이콘만 Props로 바꿉니다. Footer 외형을 새로 그리지 않습니다.
|
||||
- **일반 모드 Header에 도구 막대를 더한 구성**(로고 옆 파일 열기 + Model/Layout·Fit·2.5D 버튼을 `SWHeader`에 추가)은 **일반 모드**입니다: `SWHeader`의 `documentOpenPlacement`·`leading`·`tools` + `SWToolButton` / `SWToolGroup`(PATTERNS 6, 예제 F). "뷰어"·"도면 보기" 화면을 요청받았다면 이 구성이 아니라 뷰어 모드(`SWViewer*`)를 씁니다.
|
||||
- 모드 진입·종료, 파일 처리, 도면 렌더링, 메뉴 명령 실행은 어느 모드든 **프로그램 담당**입니다.
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- **검증 표시:** ✅ = 2026-10-19 독립 소비 프로젝트(Tarball 설치)에서 브라우저 검증, ◻ = 소비 검증 범위 밖(Storybook 검증 기록만 있음).
|
||||
- **먼저 모드:** SW 일반 모드(편집) / SW 뷰어 모드(도면 보기)를 [AI_USAGE.md](./AI_USAGE.md) §0으로 정합니다. **뷰어 모드는 §9(`SWViewer*`)만** 쓰고, 아래 §1~8은 일반 모드용입니다. 두 모드를 한 화면에 섞지 않습니다.
|
||||
- **무엇을 쓸까(일반 모드):** Header 전체 → `SWHeader`(1). Header 부품 조합 → 개별 컨트롤(2). 팝업·설정창·정보창 → `SWPopup`(3). 도구 막대·탭·토글 → `SWToolButton` / `SWToolGroup`(4). 로고 클릭 2단 메뉴·최근파일 → `SWHeader homeMenu` / `SWHomeMenu`(5). Slider·Switch·Badge·Progress·Dialog(모달)·Label·FilterBar는 SW에 없습니다 → `@hm-design/ui`를 그대로 씁니다(필요하면 `SWPopup` children 안에).
|
||||
- **무엇을 쓸까(일반 모드 하단):** 하단 명령 막대(보조기능·명령 칸·분류 탭·명령어 전체보기) → `SWFooter`, 도면 공간 탭(Model/Layout) → `SWFooterLayoutTabs`, 명령창 → `SWFooterCommandLine`(10, [FOOTER.md](./FOOTER.md)).
|
||||
- **무엇을 쓸까(뷰어 모드):** Header → `SWViewerHeader`(`groups` + `trailing`), Dropdown·우클릭 → `SWViewerMenu`, 팝업·설정창·모달 → `SWViewerPanel` + `SWViewerSection`/`SWViewerField`/`SWViewerSwitch`/`SWViewerSlider`/`SWViewerInput`/`SWViewerButton`, 상태 → `SWViewerStatus`(9).
|
||||
|
||||
## 1. 완성형 템플릿
|
||||
@@ -151,3 +152,24 @@ SW **뷰어 모드** 전용 Export입니다(2026-10-19 추가, 참고 DWG/DXF
|
||||
- **공개 타입:** `SWViewerHeaderProps`, `SWViewerHeaderButtonProps`, `SWViewerMenuProps`, `SWViewerIconProps`, `SWViewerPanelProps`, `SWViewerPanelPlacement`(`"top-end"` / `"top-start"` / `"center"`), `SWViewerSectionProps`, `SWViewerFieldProps`, `SWViewerSwitchProps`, `SWViewerSliderProps`, `SWViewerInputProps`, `SWViewerButtonProps`, `SWViewerBadgeProps`, `SWViewerStatusProps`, `SWViewerStatusState`, `SWViewerHeaderItem`(`button`·`toggle`·`menu`·`tabs`·`switch`·`custom`), `SWViewerHeaderGroup`, `SWViewerMenuEntry`(`item`·`checkbox`·`radio`·`submenu`·`label`·`separator`), `SWViewerCommand`(`id`, `itemId`, `value`).
|
||||
- **검증 표시:** 뷰어 Export는 이 카탈로그 기준의 독립 소비 프로젝트(Tarball) 브라우저 검증 기록이 없어 ◻입니다. 예제 H는 공개 Export만 사용합니다.
|
||||
- 상세 크기·상태는 [VIEWER.md](./VIEWER.md) §2~4가 기준입니다. 이 표에 없는 Props(`items`, `content`, `renderBody` 등)는 없습니다.
|
||||
|
||||
## 10. Footer (`SWFooter*`, SW 일반 모드)
|
||||
|
||||
하단 명령 막대(2026-10-20 추가, Figma 이지빔-SW 26:10097 하단 72px + 부품 34개, 바 안 명령창·좁은 폭은 사용자 제공 실제 화면). 상세 Props·상태·키보드·금지 사항은 [FOOTER.md](./FOOTER.md), 토큰은 [DESIGN_RULES.md](./DESIGN_RULES.md) §13, 조합은 [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 10과 [예제 I](./examples/ExampleI-Footer.tsx). 뷰어 모드 화면에서는 쓰지 않습니다. `SWFooter`·`SWFooterCommandLine`은 루트에 `hm-sw-theme`이 있어 `<SWTheme>`가 필요 없고, 나머지 부품은 `<SWTheme>` 안에 둡니다.
|
||||
|
||||
| 컴포넌트 | 용도 | 주요 Props | 검증 |
|
||||
|---|---|---|---|
|
||||
| `SWFooter` | 72px 하단 막대: 보조기능 버튼 → 3 × 2 보조기능 → (바 안 명령창) → 명령 칸(68 × 47) + 분류 행(24) → 명령어 전체보기, 위 오른쪽 끝 Layout 탭 | `categories: SWFooterCategory[]`(필수), `categoryId`/`defaultCategoryId`/`onCategoryChange`, `activeToolId`, `onToolSelect(toolId, categoryId, tool)`, `slotCount`(17), `onSlotAdd(slotIndex, categoryId)`, `slotGuide`(`SWFooterGuideContent \| false`), `onCategoryAdd`, `auxItems`, `onAuxPressedChange`, `onAuxItemClick`, `auxAll`/`commandAll`(`SWFooterSideAction \| false`), `command`(바 안 명령창), `status`(바 위 왼쪽 좌표), `layoutTabs`, `trailing`, `label`, `<footer>` 속성·`ref`. 좁은 폭: 빈 칸 축소 → ▲ 패널, 분류 ◀ ▶, 보조기능 아이콘만 | ◻ |
|
||||
| `SWFooterSideButton` | 양끝 74 × 71 버튼(보조기능 / 명령어 전체보기) | `title`(필수), `subtitle`, `side`(`start`/`end`), `tooltip`, `button` 속성 | ◻ |
|
||||
| `SWFooterAuxGroup` | 보조기능 토글 격자(Figma 3 × 2) | `items: SWFooterAuxItem[]`(필수), `onPressedChange`, `onItemClick`, `columns`(3), `label` | ◻ |
|
||||
| `SWFooterAuxButton` | 보조기능 104 × 33 버튼 | `label`(필수), `icon`, `pressed`, `button` 속성 | ◻ |
|
||||
| `SWFooterToolButton` | 명령 칸 68 × 47 | `label`(필수), `icon`, `activeIcon`, `pressed`, `button` 속성 | ◻ |
|
||||
| `SWFooterAddSlot` | 빈 칸(+), Hover 가이드 | `guide`(기본 Figma / `false`), `guideOpen`, `onGuideOpenChange`, `button` 속성 | ◻ |
|
||||
| `SWFooterGuide` | 빈 칸 안내 카드 208 × 149 | `title`(`명령어 등록`), `caption`(`빠른사용`), `media`, `div` 속성 | ◻ |
|
||||
| `SWFooterMenu` | 분류 행(현재 탭 Pocket, 넘치면 ◀ ▶) | `items: SWFooterMenuItem[]`·`value`(필수), `onValueChange`, `onAdd`, `addLabel`, `label`, `panelId`, `tabIdPrefix` | ◻ |
|
||||
| `SWFooterLayoutTabs` | 도면 공간 탭(+ · Layout · Model) | `layouts: SWFooterLayout[]`·`value`(필수), `onValueChange`, `onAdd`, `onRemove`, `onRename`, `editingId`/`onEditingChange`, `modelLabel`(`"Model"` / `false`), `label`, `addLabel`, `removeLabel` | ◻ |
|
||||
| `SWFooterCommandLine` | 명령창 566 × 71(접힘) / 270(펼침) | `history: ReactNode[]`(필수), `value`/`onValueChange`, `onSubmit`, `prompt`(`Command:`), `expanded`/`defaultExpanded`/`onExpandedChange`, `inputProps`, `historyLabel`, `expandLabel`, `collapseLabel` | ◻ |
|
||||
|
||||
- **상수·아이콘:** `SW_FOOTER_MODEL`(`"model"`, Model 탭의 `value`), `swFooterToolIcons`(`line`·`pline`·`arc`·`circle`·`ellipse`·`rect`·`poly`·`point`·`hatch`·`ncopy` → `{ icon, activeIcon }`), `swFooterAuxIcons`(`objectSnap`·`selectionEffect`·`orthogonal`·`gridSnap`·`gridBackground`·`lineWeight`).
|
||||
- **공개 타입:** `SWFooterProps`, `SWFooterSideAction`, `SWFooterSideButtonProps`, `SWFooterAuxGroupProps`, `SWFooterAuxButtonProps`, `SWFooterToolButtonProps`, `SWFooterAddSlotProps`, `SWFooterGuideProps`, `SWFooterGuideContent`, `SWFooterMenuProps`, `SWFooterMenuItem`, `SWFooterLayoutTabsProps`, `SWFooterCommandLineProps`, `SWFooterTool`(`id`, `label`, `icon`, `activeIcon`, `title`, `ariaLabel`, `disabled`), `SWFooterCategory`(`id`, `label`, `tools`, `disabled`), `SWFooterAuxItem`(`id`, `label`, `icon`, `pressed`, `title`, `disabled`), `SWFooterLayout`(`id`, `label`, `locked`, `shape`), `SWFooterToolIconName`, `SWFooterAuxIconName`.
|
||||
- **검증 표시:** Footer Export는 Storybook(`SW/Footer`)과 저장소 안 Import·Build 검증만 있고 독립 소비 프로젝트(Tarball) 브라우저 검증 기록이 없어 ◻입니다.
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 0. 어떤 패턴을 고를까
|
||||
|
||||
먼저 SW 모드를 정합니다([AI_USAGE.md](./AI_USAGE.md) §0). 1~6과 8은 **SW 일반 모드**, 7은 **SW 뷰어 모드**이며 한 화면에서 섞지 않습니다.
|
||||
먼저 SW 모드를 정합니다([AI_USAGE.md](./AI_USAGE.md) §0). 1~6과 8·10은 **SW 일반 모드**, 7은 **SW 뷰어 모드**이며 한 화면에서 섞지 않습니다.
|
||||
|
||||
| 요청 | 모드 | 패턴 |
|
||||
|---|---|---|
|
||||
@@ -16,6 +16,7 @@
|
||||
| 일반 모드 `SWHeader`에 로고 옆 파일 열기·도구 막대(Model/Layout, Fit, 2.5D) 추가 | 일반 | 6. 파일 열기 + 도구 막대 |
|
||||
| 뷰어·DWG/DXF 보기·도면 뷰어·읽기 전용 화면(Header 메뉴, 패널, 모달, 불러오기 상태) | 뷰어 | 7. 뷰어 모드 기본 조합 |
|
||||
| 일반 모드 `SWHeader`에서 로고를 누르면 열리는 2단 메뉴(저장·내보내기·찾아열기·최근파일 등) | 일반 | 8. 로고 클릭 메뉴 |
|
||||
| 화면 하단 명령 막대(Line·Arc 등 명령, 분류 탭, 보조기능 토글, 명령창), Model/Layout 탭 | 일반 | 10. Footer |
|
||||
|
||||
## 1. 완성형 SW Header — [ExampleA-DefaultHeader.tsx](./examples/ExampleA-DefaultHeader.tsx)
|
||||
|
||||
@@ -200,6 +201,29 @@ const sections: SWHomeMenuSectionData[] = [
|
||||
5. New / Open은 문서 Dropdown에 그대로 둔다(`documentOpenPlacement` 기본 `"menu"`). 로고 오른쪽에 별도 Open 버튼을 두지 않는다. 메뉴의 찾아열기·새파일은 같은 handler를 연결한다.
|
||||
6. 위치·폭은 `SWHeader`가 정한다. 소비 코드에서 메뉴 위치를 계산하거나 겹치는 wrapper를 만들지 않는다.
|
||||
|
||||
## 10. Footer (일반 모드) — [ExampleI-Footer.tsx](./examples/ExampleI-Footer.tsx)
|
||||
|
||||
상세는 [FOOTER.md](./FOOTER.md). 화면 = `SWHeader` → 도면 영역 → `SWFooter`(바 안 명령창 `command`, 위 왼쪽 좌표 `status`, 위 오른쪽 `SWFooterLayoutTabs`). 실제 SW 화면(2026-10-20) 구성입니다.
|
||||
|
||||
```tsx
|
||||
<div className="app-screen">{/* 소비 레이아웃: flex column, 높이 100% */}
|
||||
<SWHeader documentTitle={title} />
|
||||
<main className="app-canvas">{/* flex: 1 */}</main>
|
||||
<SWFooter status={coordinates}
|
||||
command={<SWFooterCommandLine history={history} onSubmit={run} expanded={listOpen} onExpandedChange={setListOpen} />}
|
||||
categories={categories} categoryId={categoryId} onCategoryChange={setCategoryId}
|
||||
activeToolId={running} onToolSelect={toolId => run(toolId)} onSlotAdd={openRegister}
|
||||
auxItems={aux} onAuxPressedChange={toggleAux} commandAll={{ onClick: () => setListOpen(open => !open), pressed: listOpen }}
|
||||
layoutTabs={<SWFooterLayoutTabs layouts={layouts} value={space} onValueChange={setSpace} onAdd={addLayout} onRemove={removeLayout} onRename={renameLayout} />} />
|
||||
</div>
|
||||
```
|
||||
|
||||
1. 명령·분류·보조기능은 데이터입니다. 프로젝트마다 바꾸고 Footer 외형은 그대로 둡니다.
|
||||
2. 명령 실행·등록·Layout 생성은 Callback에서 호스트가 처리하고 Props를 갱신합니다(`activeToolId`, `categories`, `layouts`).
|
||||
3. Figma 아이콘은 `swFooterToolIcons`·`swFooterAuxIcons`, 새 명령 아이콘은 프로그램 제공 18 × 18 요소.
|
||||
4. 좁은 화면 처리(빈 칸 축소, ▲ 패널, 분류 ◀ ▶, 보조기능 아이콘만)는 `SWFooter`가 합니다. 소비 코드에서 명령 수를 줄이거나 media query로 다시 배치하지 않습니다.
|
||||
5. 일부만 필요하면(보조기능만, 분류 탭만) `<SWTheme>` 안에서 `SWFooterAuxGroup`·`SWFooterMenu`·`SWFooterToolButton`을 씁니다. `SWFooterMenu` 단독은 부모 면을 `var(--hm-sw-footer-group-background)`로 둡니다.
|
||||
|
||||
## 9. 하지 말 것
|
||||
|
||||
- `hm-sw-*` class를 소비 코드에 쓰거나 CSS로 덮어쓰기
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의됩니다. 새 컴포넌트는 그 안에 렌더링합니다.
|
||||
- SW는 `@hm-design/ui` 토큰을 쓰지 않습니다. UI 패키지 토큰을 복제하거나 섞지 않습니다.
|
||||
- Light/Dark 분기 없음. 전역 선택자(`html`, `body`, `*`) 없음.
|
||||
- **모드:** §1~10과 §12는 SW **일반 모드**(`SWHeader`·`SWPopup`·`SWToolButton`·`SWHomeMenu`) 기준입니다. SW **뷰어 모드**(`SWViewer*`)의 토큰·상태는 §11과 [VIEWER.md](./VIEWER.md)를 봅니다. 모드 선택·혼합 금지는 [AI_USAGE.md](./AI_USAGE.md) §0.
|
||||
- **모드:** §1~10과 §12·§13은 SW **일반 모드**(`SWHeader`·`SWPopup`·`SWToolButton`·`SWHomeMenu`·`SWFooter`) 기준입니다. SW **뷰어 모드**(`SWViewer*`)의 토큰·상태는 §11과 [VIEWER.md](./VIEWER.md)를 봅니다. 모드 선택·혼합 금지는 [AI_USAGE.md](./AI_USAGE.md) §0.
|
||||
|
||||
## 1. 크기·간격
|
||||
|
||||
@@ -173,3 +173,30 @@ Container `hm-sw-header` 기준 Breakpoint: **< 1800** 선/문자/치수스타
|
||||
|
||||
- Home On(Figma 662:67267, 2026-10-19 사용자 확정): 메뉴가 열려 있는 동안 SWHeader가 자동으로 로고에 노란 Hover 테두리를 유지하고, Header 전폭 상·하단에 `#5F4B0F` 2px 선(`--hm-sw-header-home-on-line-color` / `-width`)을 그립니다. 닫으면 기본 상태로 돌아갑니다. 크기·위치 변화가 없고 SW 일반 모드 전용입니다(뷰어 모드에는 없음). 소비 코드에서 별도 class나 상태를 추가하지 않습니다.
|
||||
- 새 색·크기는 위 토큰만 `style`로 덮어씁니다. `hm-sw-home-menu*` class를 소비 코드에 쓰지 않습니다.
|
||||
|
||||
## 13. Footer (`SWFooter*`, Figma 26:10097) — SW 일반 모드 전용
|
||||
|
||||
원본 `src/theme/sw-footer.css`. 토큰은 `--hm-sw-footer-*`(`.hm-sw-header`, `.hm-sw-theme` 범위). 값은 Figma Plugin API로 읽은 값입니다(2026-10-20). 글꼴은 Header와 같이 Dotum 대신 Noto Sans KR, 글자 외곽선은 Header 토큰(`--hm-sw-header-text-outline` 1.3px / `--hm-sw-header-label-outline` 1px)을 공유하고, Figma의 자기 색 0.3 / 0.4px stroke는 Header 2026-10-16 수정과 같이 그리지 않습니다.
|
||||
|
||||
| 항목 | 값 | 토큰 |
|
||||
|---|---|---|
|
||||
| 바 | 높이 72, 배경 `#060606` | `--hm-sw-footer-height`, `-background` |
|
||||
| 배치(1920, Figma) | 보조기능 버튼 0~74 → 2 → 보조기능 격자 76~392(위 2) → 4 → 명령 그룹 396~(위 1) → 명령어 전체보기(명령 칸 바로 뒤, 1552~1626) → 나머지 bg_vacant | — |
|
||||
| 측면 버튼 | 74 × 71, 자산 면(그라데이션 + 그라데이션 테두리), 그림자 8px 60% 검정(왼쪽 버튼 → 오른쪽 5px, 오른쪽 버튼 → 왼쪽 5px). 제목 14px 1.3px 외곽선, 부제 14px 1px 외곽선, 위 18 · 줄 간격 2 | `--hm-sw-footer-side-*` |
|
||||
| 보조기능 | 104 × 33, gap 2, 테두리 1px `#886D35` radius 3, 면 `linear-gradient(0deg, #262218 8%, #4A3C2B 34%, #594C31 55%, #312A1B 90%)`, 아이콘 중심 (19.5, 16.5), 라벨 13px 왼쪽 36 폭 57 가운데 | `--hm-sw-footer-aux-*` |
|
||||
| 명령 그룹 면 | 테두리 위·왼쪽·아래 1px `#886D35`, 왼쪽 위 radius 4, 면 `linear-gradient(0deg, #2F281A 0%, #312A1B 5%, #332B1C 36%, #463C29 58%, #38301F 79%, #19160E 98%)`. 마지막 명령 뒤(빈 칸·나머지)는 bg_vacant `linear-gradient(0deg, #18150E 28%, #262015 41%, #332B1C 58%, #38301F 81%, #18150E 100%)` | `--hm-sw-footer-group-*`, `--hm-sw-footer-vacant-background` |
|
||||
| 명령 칸 | 68 × 47, 라벨 14px `#F6F4F2` 위 6, 아이콘 18 × 18 위 26 가운데. 칸 사이 구분선 1 × 37(위 5) `linear-gradient(0deg, #20190E, #8A7A5B 45%, #F3DBA8 70%, #423625)` + `-1px 0 1px #000` | `--hm-sw-footer-slot-*`, `--hm-sw-footer-divider*` |
|
||||
| 명령 상태 | Hover = 선택(Figma Variant2 = Variant3): 글자 `#FF5C00` + 주황 아이콘. Disabled 40%(Figma 없음) | `--hm-sw-footer-accent` |
|
||||
| 빈 칸 | 68 × 46(위 1), + `#BEB5A6`. Hover: 주황 + 와 주황 그라데이션 점선 링(자산) | — |
|
||||
| 가이드 | 208 × 149, 빈 칸 위 4px 가운데, 1px `#DA6813` radius 2, 면 `#0E3C2E`, 미리보기 194 × 111(5, 5, 검정 1px), 제목 14px 외곽선, 캡션 12px `#B6D0C9` 1px 외곽선 | `--hm-sw-footer-guide-*` |
|
||||
| 분류 행 | 높이 24, 면 `#282216` → 위 14% `#574B30`, 안쪽 1px 테두리 그라데이션, 현재 탭 Pocket(좌우 7px 곡선, 아래 radius 2, 가장자리 `#675A3B`). 탭 14px `#D4CBBD`, 자간 10%, 위 4, 좌우 padding 8, 최소 43. 구분선 1 × 10 검정(위 7). Hover·현재 `#FF5C00` | `--hm-sw-footer-menu-*` |
|
||||
| Layout 탭 | 높이 22, 12px 자간 2%. Default 면 `#C3B79E` 글자 `#161410`. On: `linear-gradient(0deg, #7C6C4C, #7C6C4C 85%, #A6946F)` 흰 글자. 이름 편집: `#0F0D0A` 빨강 `#E60403` 주황 테두리. Hover·On·편집 글자 아래 70% 검정 1.3px 외곽선. 둥근 탭: 1px `rgba(0,0,0,.05)`(Hover·편집 `#DA6813`), 위 radius 4, 안쪽 그림자 `inset 0 -3px 4px rgba(56,48,31,.25)`, ×(16) Hover 노랑. 사선 탭(마지막): 97 × 22 자산, Hover = 주황 면 + 검정 글자, On hover = 베이지 면 + 주황 글자, ×(28, 빨강 사선) | `--hm-sw-footer-tab-*` |
|
||||
| Model 탭 | 73 × 22 자산(왼쪽 사선), 글자 왼쪽 22, Hover 주황, On 갈색. 앞 사선 탭과 8px 겹침 | — |
|
||||
| 명령창 | 566 × 71 / 270, 위 radius 4, 위 71px `linear-gradient(180deg, #635537, #262218 5%, #281F16 10%, #282216)` + 아래 `#282216`. 12px 자간 10% 줄 14, 1px 외곽선. 입력 칸 23(좌우 4) `#16130D` + `0 -1px 2px rgba(255,255,255,.25)`, `inset 0 1px 4px #000`. 화살표 18 × 18 검정 radius 2(4, 4 오른쪽 위) | `--hm-sw-footer-command-*` |
|
||||
| 바 안 명령창(실제 화면) | 보조기능 뒤 4px, 남는 폭(최소 280), 바 아래에 붙어 펼치면 위로 270 | `--hm-sw-footer-command-min-width` |
|
||||
| 좌표(실제 화면) | 바 위 왼쪽, 12px `#F6F4F2` 1px 외곽선, 아래 3px | — |
|
||||
| 보조기능 pressed(실제 화면) | 테두리 `#DA6813` + 글자 `#FF5C00` | `--hm-sw-footer-aux-pressed-border-color` |
|
||||
| 좁은 폭(실제 화면) | 빈 칸이 먼저 줄고, 넘치는 명령은 마지막 칸 ▲(ico_arrow 18 × 18, 오른쪽 위 4) → 명령 칸 위 패널(같은 칸, 오른쪽 맞춤, 테두리 1px `#886D35`·아래 없음·위 radius 4, 명령 칸 면). 분류 탭 넘침 = 탭 영역 잘림 + ◀ ▶(13 × 16, 6 × 8 삼각형 `#F6F4F2`, Hover `#FF5C00`) + +. 바 1279px 이하 보조기능 아이콘만 45 × 33 | `--hm-sw-footer-aux-compact-width` |
|
||||
|
||||
- Figma에 없는 값: 실제 화면에서 가져온 값(바 안 명령창 배치, 좌표, 보조기능 pressed, ▲ 패널, 분류 ◀ ▶, 보조기능 아이콘만)과 AI 구성 값 — 디자인 확인 필요(보조기능·측면 버튼 Hover 글자 `#FF5C00`, Disabled 40%, 명령창 최소 280px, 보조기능 아이콘 전환 1279px, Layout 탭 이름 편집 입력의 동작).
|
||||
- 새 하단 UI는 위 토큰을 `var(--…)`로 읽고 `hm-sw-footer*` class를 쓰거나 덮어쓰지 않습니다.
|
||||
@@ -0,0 +1,113 @@
|
||||
# SW Footer — `SWFooter` 계열 (SW 일반 모드)
|
||||
|
||||
SW 프로그램 하단 명령 막대입니다. 기준은 Figma 이지빔-SW `레이아웃 분리`(26:10097) 하단 72px과 부품 노드 34개(Plugin API로 읽은 값, 2026-10-20)이고, 바 안 명령창·좌표·좁은 폭 동작은 사용자 제공 실제 화면(2026-10-20)을 따릅니다. 두 기준이 다르면 Figma가 우선입니다(예: 선택된 Model 탭은 Figma On 갈색). **SW 일반 모드(편집 화면) 전용**이며 뷰어 모드(`SWViewer*`) 화면에는 쓰지 않습니다([AI_USAGE.md](./AI_USAGE.md) §0).
|
||||
|
||||
컴포넌트는 **표시와 사용자 입력 전달**만 합니다. CAD 명령 실행, 명령 등록, 레이아웃 생성·삭제, 명령 해석은 소비 프로그램이 Callback을 받아 처리하고 Props를 갱신합니다.
|
||||
|
||||
## 1. AI 작업 순서 (Footer 요청을 받았을 때)
|
||||
|
||||
1. **존재 확인:** `node_modules/@hm-design/sw/ai/FOOTER.md`(이 문서)와 [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) §10이 있는지 확인합니다. 있으면 Footer를 새로 만들지 않습니다.
|
||||
2. **Export 확인:** `node_modules/@hm-design/sw/dist/index.d.ts`에서 `SWFooter`·`SWFooterLayoutTabs`·`SWFooterCommandLine`·`swFooterToolIcons`가 Export되는지 확인합니다. CSS는 `@hm-design/sw/styles.css` 한 개(이미 Import했다면 추가 없음).
|
||||
3. **기존 Footer 우선:** 하단 막대 전체 → `SWFooter`. 일부만(보조기능만, 분류 탭만 …) → 아래 §3 부품을 `<SWTheme>` 안에서 조합.
|
||||
4. **Props로 구성:** 분류·명령·보조기능·문구·아이콘은 데이터(`categories`, `auxItems`, `auxAll`, `commandAll`, `slotGuide`)로 넣습니다. 선택 상태(`categoryId`, `activeToolId`, `pressed`, `value`)는 호스트 상태로 제어합니다.
|
||||
5. **Variant:** Figma 상태는 Props로만 나옵니다(§4 표). Hover·Focus는 CSS가 처리합니다.
|
||||
6. **재구현 금지:** Footer의 배경·크기·Pocket 탭·구분선·글자 외곽선을 `div`와 CSS로 다시 그리지 않습니다. `hm-sw-footer*` class를 소비 CSS에서 재정의하거나 복사하지 않습니다. 내용(명령·메뉴·아이콘·문구)만 바꿉니다.
|
||||
|
||||
판단을 결과 보고에 남깁니다: "SWFooter 재사용 — categories 3개, 보조기능 6개, Layout 탭 연결, 명령 실행은 호스트 onToolSelect".
|
||||
|
||||
## 2. `SWFooter` (완성형)
|
||||
|
||||
```tsx
|
||||
import { SWFooter, SWFooterLayoutTabs, SW_FOOTER_MODEL, swFooterToolIcons, swFooterAuxIcons } from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
|
||||
<SWFooter
|
||||
categories={[{ id: "shape", label: "도형", tools: [{ id: "line", label: "Line", ...swFooterToolIcons.line }] }]}
|
||||
categoryId={categoryId} onCategoryChange={setCategoryId}
|
||||
activeToolId={running} onToolSelect={(toolId, categoryId, tool) => runCommand(toolId)}
|
||||
onSlotAdd={(slotIndex, categoryId) => openRegisterDialog(slotIndex)}
|
||||
auxItems={[{ id: "snap", label: "객체스냅", icon: swFooterAuxIcons.objectSnap, pressed: snap }]}
|
||||
onAuxPressedChange={(id, pressed) => setSnap(pressed)}
|
||||
commandAll={{ onClick: toggleCommandList, pressed: listOpen }}
|
||||
status={`${x}, ${y}, ${z}`}
|
||||
command={<SWFooterCommandLine history={history} onSubmit={runCommandText} expanded={listOpen} onExpandedChange={setListOpen} />}
|
||||
layoutTabs={<SWFooterLayoutTabs layouts={layouts} value={space} onValueChange={setSpace} onAdd={addLayout} onRemove={removeLayout} onRename={renameLayout} />}
|
||||
/>
|
||||
```
|
||||
|
||||
| Prop | 기본값 | 설명 |
|
||||
|---|---|---|
|
||||
| `categories` | (필수) | `SWFooterCategory[]` — `{ id, label, tools: SWFooterTool[], disabled? }`. 아래 분류 행과 위 명령 칸 |
|
||||
| `categoryId` / `defaultCategoryId` / `onCategoryChange` | 첫 분류 | 현재 분류(Controlled / Uncontrolled) |
|
||||
| `activeToolId` / `onToolSelect(toolId, categoryId, tool)` | `null` | 실행 중 명령 = 주황(Figma Variant3, `aria-pressed`) |
|
||||
| `slotCount` | `17` / `command`가 있으면 명령 수 + 2 | 명령 + 빈 칸(+) 수. 바 폭이 좁으면 빈 칸부터 줄고, 그래도 넘치는 명령은 마지막 칸의 ▲ 패널로 이동(§4.1) |
|
||||
| `onSlotAdd(slotIndex, categoryId)` | 없음 | 빈 칸 클릭(명령 등록). **없으면 빈 칸은 모양만 있고 반응하지 않음** |
|
||||
| `slotGuide` | Figma 가이드 | 빈 칸 Hover·키보드 Focus 시 위에 뜨는 208 × 149 안내 `{ title = "명령어 등록", caption = "빠른사용", media? }`. `media`는 194 × 111 영상·이미지(프로그램 제공). `false` = 없음 |
|
||||
| `onCategoryAdd` | 없음 | 분류 행 끝 + 버튼 표시 |
|
||||
| `auxItems` / `onAuxPressedChange(id, pressed)` / `onAuxItemClick(id)` | 없음 | 3 × 2 보조기능 토글(`SWFooterAuxItem`: `id, label, icon?, pressed?, title?, disabled?`). `pressed`를 주면 토글 |
|
||||
| `auxAll` / `commandAll` | 표시 | 양끝 74 × 71 버튼 `{ title, subtitle, tooltip, onClick, pressed, disabled }`. 기본 문구 `보조기능 / 전체보기`, `명령어 / 전체보기`. `false` = 숨김 |
|
||||
| `command` | 없음 | 바 안 명령창(보통 `<SWFooterCommandLine>`, 실제 화면 구성). 보조기능 격자와 명령 칸 사이의 남는 폭(최소 280px)을 쓰고, 명령 칸은 오른쪽 `명령어 전체보기` 바로 앞으로 붙습니다. 펼치면 위(도면 영역)로 커집니다 |
|
||||
| `status` | 없음 | 바 위 왼쪽 글자(커서 좌표 등, 12px) |
|
||||
| `layoutTabs` | 없음 | 바 위 오른쪽 끝에 붙는 탭(보통 `SWFooterLayoutTabs`) |
|
||||
| `trailing` | 없음 | 바 오른쪽 빈 영역 콘텐츠(좌표·상태) |
|
||||
| `label` | `명령 도구` | `<footer>` 접근성 이름 |
|
||||
|
||||
`SWFooterTool`: `{ id, label, icon?, activeIcon?, title?, ariaLabel?, disabled? }`. `icon` = 기본(18 × 18), `activeIcon` = Hover·선택 아이콘(없으면 `icon` 유지). 줄인 라벨(`Rect...`)은 `ariaLabel`로 전체 이름을 줍니다. Figma 명령 아이콘은 `swFooterToolIcons.line / pline / arc / circle / ellipse / rect / poly / point / hatch / ncopy`(각 `{ icon, activeIcon }`), 보조기능 아이콘은 `swFooterAuxIcons.objectSnap / selectionEffect / orthogonal / gridSnap / gridBackground / lineWeight`.
|
||||
|
||||
## 3. 부품 (조합용, `<SWTheme>` 안)
|
||||
|
||||
| 컴포넌트 | 크기 | 주요 Props |
|
||||
|---|---|---|
|
||||
| `SWFooterSideButton` | 74 × 71 | `title`(필수), `subtitle`, `side`(`start`/`end`, 그림자 방향), `tooltip`, `button` 속성 |
|
||||
| `SWFooterAuxGroup` | 104 × 33 칸, gap 2 | `items`(필수), `onPressedChange`, `onItemClick`, `columns`(3), `label` |
|
||||
| `SWFooterAuxButton` | 104 × 33 | `label`(필수), `icon`, `pressed`, `button` 속성 |
|
||||
| `SWFooterToolButton` | 68 × 47 칸 | `label`(필수), `icon`, `activeIcon`, `pressed`, `button` 속성. 연속 배치하면 칸 사이 1px 금색 구분선 |
|
||||
| `SWFooterAddSlot` | 68 × 47 칸(+ 68 × 46) | `guide`(기본 Figma 가이드 / `false`), `guideOpen`(Controlled), `onGuideOpenChange`, `button` 속성 |
|
||||
| `SWFooterGuide` | 208 × 149 | `title`, `caption`, `media`, `div` 속성. 단독 카드로 둘 때 `data-standalone=""` |
|
||||
| `SWFooterMenu` | 높이 24 | `items`(`{ id, label, disabled? }[]`, 필수), `value`(필수), `onValueChange`, `onAdd`, `addLabel`, `label`, `panelId`, `tabIdPrefix`. 현재 탭은 위 칸과 이어지는 Pocket 모양(SVG, 위치 자동 계산) |
|
||||
| `SWFooterLayoutTabs` | 높이 22 | `layouts`(`SWFooterLayout[]` = `{ id, label, locked?, shape? }`, 필수), `value`(layout id 또는 `SW_FOOTER_MODEL`), `onValueChange`, `onAdd`, `onRemove`, `onRename`, `editingId`/`onEditingChange`, `modelLabel`(`"Model"` / `false`), `label`, `addLabel`, `removeLabel` |
|
||||
| `SWFooterCommandLine` | 566 × 71 / 270 (`SWFooter command` 안에서는 남는 폭) | `history`(`ReactNode[]`, 필수), `value`/`onValueChange`, `onSubmit`, `prompt`(`Command:`), `expanded`/`defaultExpanded`/`onExpandedChange`, `inputProps`, `historyLabel`, `expandLabel`, `collapseLabel` |
|
||||
|
||||
- `SWFooter`·`SWFooterCommandLine`은 루트에 `hm-sw-theme`이 있어 `<SWTheme>`가 필요 없습니다. 나머지 부품은 `<SWTheme>` 안에 둡니다.
|
||||
- `SWFooterMenu`의 Pocket은 부모 배경(명령 칸 면)을 비춰 보입니다. 단독으로 둘 때 부모에 `background: var(--hm-sw-footer-group-background)`를 줍니다.
|
||||
- `SWFooterLayoutTabs`의 마지막 탭은 사선 모양(Figma layout01, × = btn_close2), 나머지는 둥근 모양(layout02/03, × = btn_delete)입니다. `shape`로 바꿀 수 있습니다. 탭 순서는 배열 순서 그대로(Figma 예시: `+`, Layout 03, 02, 01, Model).
|
||||
- `SWFooterCommandLine`은 `SWFooter`의 `command`에 넣는 것이 기본 구성입니다(실제 화면). 바 밖에 두면 위치(보통 도면 영역 왼쪽 아래)는 소비 레이아웃이 정합니다. 접힘 = 마지막 3줄, 펼침 = 전체 기록 스크롤.
|
||||
|
||||
## 4. 상태 (Figma Variant ↔ Props)
|
||||
|
||||
| 요소 | Figma | 구현 |
|
||||
|---|---|---|
|
||||
| 명령 | Default `#F6F4F2` / Variant2 Hover = Variant3 선택: `#FF5C00` + 주황 아이콘 | Hover는 CSS, 선택은 `activeToolId`·`pressed` |
|
||||
| 분류 탭 | Default `#D4CBBD` / Hover·현재 `#FF5C00`, 현재 탭은 Pocket | `categoryId`·`value` |
|
||||
| 빈 칸 | Default + / Hover 주황 점선 링 + 주황 + | Hover·Focus 시 `slotGuide` 표시 |
|
||||
| Layout 탭 | Default 베이지 `#C3B79E` / Hover(×) / On 갈색 그라데이션·흰 글자 / On hover / Variant5 이름 편집(`#0F0D0A`, 빨강 `#E60403`) / Variant6 편집 + Hover | `value`, 편집 = 더블클릭·F2 또는 `editingId` |
|
||||
| Model 탭 | Default / Hover 주황 / On 갈색 | `value === SW_FOOTER_MODEL` |
|
||||
| 명령창 화살표 | Default ↑ / Hover 주황 ↑ / On ↓ / On hover 주황 ↓ | `expanded` |
|
||||
| Disabled | Figma 없음 | SW 공통 40%(명령·빈 칸·탭·보조기능) |
|
||||
| 보조기능 pressed | Figma는 Default만 | 실제 화면: 주황 테두리 `#DA6813` + 글자 `#FF5C00`(아이콘 색은 자산 그대로) |
|
||||
| 보조기능·측면 버튼 Hover | Figma 없음 | Footer Hover 규칙 재사용: 글자 `#FF5C00` (디자인 확인 필요) |
|
||||
|
||||
### 4.1 좁은 폭 (Figma 없음, 실제 화면 2026-10-20)
|
||||
|
||||
1. 빈 칸(+)이 먼저 줄어듭니다.
|
||||
2. 명령이 남는 칸보다 많으면 들어가는 명령만 두고 **마지막 칸이 ▲**(Figma ico_arrow)가 됩니다. ▲를 누르면 나머지 명령이 명령 칸 바로 위(오른쪽 맞춤, 같은 칸 모양)에 펼쳐지고 ▼가 됩니다. 명령을 고르거나 Escape·바깥 클릭이면 닫힙니다. 숨은 명령이 선택 중이면 ▲가 주황입니다.
|
||||
3. 분류 탭이 넘치면 탭 영역 안에서 잘리고 ◀ ▶(스크롤)와 +가 오른쪽에 붙습니다. 현재 탭과 Focus 탭은 자동으로 보이게 스크롤됩니다.
|
||||
4. 바 폭 1279px 이하에서 보조기능은 아이콘만(45 × 33, 이름은 접근성 이름·tooltip)입니다.
|
||||
5. 바 안 명령창은 최소 280px을 유지합니다. 측면 버튼·Layout 탭은 크기가 바뀌지 않습니다(▲ 패널은 Layout 탭 위에 겹침).
|
||||
|
||||
## 5. 키보드·접근성
|
||||
|
||||
- 명령 칸: `role="toolbar"`, 칸 하나만 Tab 정지, ←/→/Home/End 이동. 명령 버튼 `aria-pressed`. ▲ = `aria-expanded`, 열면 첫 명령으로 Focus, Escape = 닫고 ▲로 Focus.
|
||||
- 분류 행: `role="tablist"`, ←/→/Home/End = 이동 + 선택, 명령 칸이 `tabpanel`. 분류 + 버튼은 tablist 밖의 일반 버튼. ◀ ▶는 마우스용(`tabIndex -1`, 키보드는 탭 이동이 자동 스크롤).
|
||||
- 빈 칸: 이름 `명령어 등록 (n번 칸)`, 가이드가 보이면 `aria-describedby`.
|
||||
- Layout 탭: `role="tablist"`, ←/→ 선택, **Delete** = 삭제(`onRemove`), **F2**·더블클릭 = 이름 편집(Enter 저장, Escape 취소, 바깥 클릭 저장). ×는 마우스 전용(`tabIndex -1`).
|
||||
- 명령창: 기록 `role="log"`(`aria-live="polite"`), 입력 `<label>` 연결, Enter = `onSubmit`(한글 조합 중 Enter 무시), 화살표 `aria-expanded`.
|
||||
- Focus: SW 공통 2px `#F0F7F5`.
|
||||
|
||||
## 6. 하지 말 것
|
||||
|
||||
- 뷰어 모드 화면에 `SWFooter`를 넣지 않습니다(뷰어는 `SWViewerHeader` 탭·버튼으로 Model/Layout을 다룸).
|
||||
- 명령 실행·명령 등록 로직을 컴포넌트 안에 넣으려고 래핑하지 않습니다. Callback에서 처리합니다.
|
||||
- 칸 크기(68 × 47)·바 높이(72)·탭 높이(22)를 바꾸려고 class를 덮어쓰지 않습니다. 필요한 경우 공개 토큰(`--hm-sw-footer-*`)을 `style`로 바꾸고 결과 보고에 "디자인 확인 필요"로 남깁니다.
|
||||
- 아이콘을 Figma 자산 대신 임의 SVG로 바꿔 "Figma 아이콘"이라고 보고하지 않습니다. 새 아이콘은 프로그램 제공 18 × 18(보조 14~18) 요소입니다.
|
||||
- 이 문서에 없는 Props(`items`, `tools` 최상위, `onCommand` 등)는 없습니다.
|
||||
@@ -0,0 +1,52 @@
|
||||
// Example I — SW Footer (general mode): the bottom command bar with its command line, layout tabs and data.
|
||||
// Commands are not run here: the host receives callbacks and updates the props.
|
||||
import { useState } from "react"
|
||||
import {
|
||||
SWFooter, SWFooterCommandLine, SWFooterLayoutTabs, SW_FOOTER_MODEL, swFooterAuxIcons, swFooterToolIcons,
|
||||
type SWFooterAuxItem, type SWFooterCategory, type SWFooterLayout,
|
||||
} from "@hm-design/sw"
|
||||
|
||||
const categories: SWFooterCategory[] = [
|
||||
{ id: "shape", label: "도형", tools: [
|
||||
{ id: "line", label: "LINE", ...swFooterToolIcons.line },
|
||||
{ id: "pline", label: "PLINE", ariaLabel: "Polyline", ...swFooterToolIcons.pline },
|
||||
{ id: "circle", label: "CIRCLE", ...swFooterToolIcons.circle },
|
||||
{ id: "rect", label: "RECTA...", ariaLabel: "Rectangle", ...swFooterToolIcons.rect },
|
||||
] },
|
||||
{ id: "modify", label: "수정", tools: [{ id: "ncopy", label: "NCOPY", ...swFooterToolIcons.ncopy }] },
|
||||
{ id: "layer", label: "레이어", tools: [] },
|
||||
]
|
||||
|
||||
export function ExampleIFooter({ onEvent }: { onEvent?: (name: string) => void }) {
|
||||
const [categoryId, setCategoryId] = useState("shape")
|
||||
const [running, setRunning] = useState<string | null>(null)
|
||||
const [aux, setAux] = useState<SWFooterAuxItem[]>([
|
||||
{ id: "objectSnap", label: "객체스냅", icon: swFooterAuxIcons.objectSnap, pressed: true },
|
||||
{ id: "orthogonal", label: "직각교차", icon: swFooterAuxIcons.orthogonal, pressed: false },
|
||||
{ id: "gridSnap", label: "격자스냅", icon: swFooterAuxIcons.gridSnap, pressed: false },
|
||||
])
|
||||
const [layouts, setLayouts] = useState<SWFooterLayout[]>([{ id: "l2", label: "Layout 02" }, { id: "l1", label: "Layout 01" }])
|
||||
const [space, setSpace] = useState(SW_FOOTER_MODEL)
|
||||
const [history, setHistory] = useState<string[]>(["Command: LINE", "Specify first point:"])
|
||||
const [listOpen, setListOpen] = useState(false)
|
||||
const run = (command: string) => { setHistory(lines => [...lines, `Command: ${command}`]); onEvent?.(`command:${command}`) }
|
||||
return (
|
||||
<div className="example-screen">
|
||||
<main className="example-canvas" />
|
||||
<SWFooter
|
||||
status="0.0000, 0.0000, 0.0000"
|
||||
command={<SWFooterCommandLine history={history} onSubmit={run} expanded={listOpen} onExpandedChange={setListOpen} />}
|
||||
categories={categories} categoryId={categoryId} onCategoryChange={setCategoryId}
|
||||
activeToolId={running} onToolSelect={toolId => { setRunning(toolId); run(toolId.toUpperCase()) }}
|
||||
onSlotAdd={(slotIndex, category) => onEvent?.(`register:${category}#${slotIndex}`)}
|
||||
auxItems={aux} onAuxPressedChange={(id, pressed) => setAux(items => items.map(item => (item.id === id ? { ...item, pressed } : item)))}
|
||||
auxAll={{ onClick: () => onEvent?.("auxAll") }}
|
||||
commandAll={{ onClick: () => setListOpen(open => !open), pressed: listOpen }}
|
||||
layoutTabs={<SWFooterLayoutTabs layouts={layouts} value={space} onValueChange={setSpace}
|
||||
onAdd={() => setLayouts(list => [{ id: `l${list.length + 1}`, label: `Layout 0${list.length + 1}` }, ...list])}
|
||||
onRemove={id => { setLayouts(list => list.filter(item => item.id !== id)); if (space === id) setSpace(SW_FOOTER_MODEL) }}
|
||||
onRename={(id, label) => setLayouts(list => list.map(item => (item.id === id ? { ...item, label } : item)))} />}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user