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.
260 lines
21 KiB
Markdown
260 lines
21 KiB
Markdown
# SW 조합 패턴
|
|
|
|
모든 패턴은 공개 Export만 사용합니다. 전체 코드는 [examples/](./examples)에 있으며, 설치된 패키지에서 꺼낸 그대로 독립 React 19 + TypeScript + Vite 프로젝트의 `tsc`·Production Build·브라우저 검증을 통과했습니다(2026-10-19, 예제 A~F. 뷰어 모드 예제 H는 공개 Export만 쓰지만 같은 독립 소비 검증 기록은 이 문서에 없습니다).
|
|
|
|
## 0. 어떤 패턴을 고를까
|
|
|
|
먼저 SW 모드를 정합니다([AI_USAGE.md](./AI_USAGE.md) §0). 1~6과 8·10은 **SW 일반 모드**, 7은 **SW 뷰어 모드**이며 한 화면에서 섞지 않습니다. 11(테마 설정)은 두 모드 공통입니다.
|
|
|
|
| 요청 | 모드 | 패턴 |
|
|
|---|---|---|
|
|
| 기존 SW Header 전체(일부 부품 제거·메뉴 순서 변경 포함) | 일반 | 1. 완성형 |
|
|
| 기존 컨트롤 일부만, 다른 배치·순서 | 일반 | 2. 조합 Header |
|
|
| 기존 Dropdown을 툴바·패널 등 다른 곳에 | 일반 | 3. 개별 컨트롤 |
|
|
| 패키지에 없는 기능 | 일반(뷰어는 7 + AI_USAGE §0.4) | 4. 확장(소비 프로젝트 신규 컴포넌트) |
|
|
| SW Popup 디자인의 설정창·정보창·사용자 정의 폼 | 일반 | 5. Popup(외형 고정, 콘텐츠 자유) |
|
|
| 일반 모드 `SWHeader`에 로고 옆 파일 열기·도구 막대(Model/Layout, Fit, 2.5D) 추가 | 일반 | 6. 파일 열기 + 도구 막대 |
|
|
| 뷰어·DWG/DXF 보기·도면 뷰어·읽기 전용 화면(Header 메뉴, 패널, 모달, 불러오기 상태) | 뷰어 | 7. 뷰어 모드 기본 조합 |
|
|
| 일반 모드 `SWHeader`에서 로고를 누르면 열리는 2단 메뉴(저장·내보내기·찾아열기·최근파일 등) | 일반 | 8. 로고 클릭 메뉴 |
|
|
| 화면 하단 명령 막대(Line·Arc 등 명령, 분류 탭, 보조기능 토글, 명령창), Model/Layout 탭 | 일반 | 10. Footer |
|
|
| 글꼴 등 SW 토큰을 화면 전체(Header·Dropdown·More·Panel·명령창)에 같은 값으로 바꾸기, Portal 요소 포함 | 일반·뷰어 공통 | 11. 테마(토큰) 설정 |
|
|
|
|
## 1. 완성형 SW Header — [ExampleA-DefaultHeader.tsx](./examples/ExampleA-DefaultHeader.tsx)
|
|
|
|
```tsx
|
|
<SWHeader documentTitle={name} documents={documents} layers={layers} colors={colors} onClose={close} />
|
|
```
|
|
|
|
- 부품 제거: `hiddenControls={["print", "popupGuide"]}`(로고는 항상 표시, `"home"`은 정적 로고).
|
|
- 메뉴 순서: `menuOrder={["color", "layer"]}`(5개 메뉴끼리, 누락분은 기본 순서로 뒤에).
|
|
- 객체 영역 전체를 직접 나열: `controls={[{ id: "document" }, { id: "layer" }, { id: "print" }]}`.
|
|
- 추가 도구: `tools={<MyTool />}`(객체 영역 메뉴 뒤). 새 도구는 아래 4의 규칙을 따른다.
|
|
- 이 경우에만 반응형 More가 동작합니다.
|
|
|
|
## 2. 조합 Header (간소화·기능 중심)
|
|
|
|
`<SWTheme>`을 Header 막대로 쓰고, 막대·면 배치 CSS는 소비 프로젝트가 작성합니다([example-layout.css](./examples/example-layout.css)). 이 CSS는 `--hm-sw-header-*` 토큰만 읽고 `hm-sw-*` class를 건드리지 않습니다.
|
|
|
|
```tsx
|
|
<SWTheme className="app-sw-bar"> {/* 49px, #060606, flex, gap 2px */}
|
|
<SWHeaderHome logoAlt="뷰어" onClick={goHome} /> {/* 80px, 다음 파일 면과 겹침 */}
|
|
<SWHeaderDocumentTitle title={name} documents={documents} onDocumentSelect={open} />
|
|
<div className="app-sw-surface"> {/* 객체 수정 면 토큰 */}
|
|
<SWHeaderLayerMenu value={layer} layers={layers} onLayerSelect={selectLayer} />
|
|
<SWHeaderColorMenu value={color} colors={colors} onColorSelect={selectColor} />
|
|
</div>
|
|
<div className="app-sw-window"> {/* 창 영역 면 토큰 */}
|
|
<SWHeaderActions showPrint={false} onPopupGuide={showGuide} />
|
|
</div>
|
|
</SWTheme>
|
|
```
|
|
|
|
- 간소화 Header(일반 모드): [ExampleB-ViewerHeader.tsx](./examples/ExampleB-ViewerHeader.tsx) — 로고 → 파일 → 레이어 → 색상 → K. 파일명·주석의 "Viewer"는 과거 이름이며 SW 뷰어 모드가 아닙니다(뷰어 모드는 7).
|
|
- 기능 중심: [ExampleC-FeatureHeader.tsx](./examples/ExampleC-FeatureHeader.tsx) — 파일 → 선 → 문자 → 치수스타일 → 인쇄.
|
|
- 순서는 JSX 순서입니다(DOM·Tab 순서 동일). CSS `order`로 바꾸지 않습니다.
|
|
- 로고 없이 파일 면이 처음이면 왼쪽 테두리가 없으므로 wrapper에 1px 테두리를 둡니다(예제 C `app-sw-file-edge`).
|
|
- K만/인쇄만: `SWHeaderActions`의 `showPrint={false}` / `showPopupGuide={false}`.
|
|
- 창 제어: `<SWHeaderWindowControls onWindowMoveStart={startDrag} onMinimize={min} onClose={close} />`(창 영역 면 안).
|
|
- 조합 Header에는 반응형 More가 없습니다. 좁은 화면이 필요하면 완성형을 쓰거나 표시할 컨트롤을 줄입니다.
|
|
|
|
## 3. 기존 Dropdown을 다른 레이아웃에서
|
|
|
|
```tsx
|
|
<SWTheme className="app-toolbar">
|
|
<SWHeaderLineStyleMenu value={lineStyle} lineTypes={lineTypes} lineWeights={lineWeights} onLineTypeSelect={setType} />
|
|
</SWTheme>
|
|
```
|
|
|
|
- Dropdown은 필드 바로 아래에 열리고 뷰포트 안으로 좌우 보정됩니다. 부모에 `overflow: hidden`을 두지 않습니다.
|
|
- Controlled가 필요하면 `open` / `onOpenChange`를 함께 줍니다.
|
|
- Dropdown이 필요 없고 호스트가 자체 팝업을 열면 `SWHeaderLayerControl` 등 필드 버튼만 씁니다.
|
|
|
|
## 4. 확장 — 없는 컨트롤 새로 만들기 — [SWSearchControl.tsx](./examples/SWSearchControl.tsx), [ExampleD-SearchExtension.tsx](./examples/ExampleD-SearchExtension.tsx)
|
|
|
|
요청 예: "레이어와 색상은 기존 SW 컴포넌트, 검색 버튼은 SW 디자인으로 새로."
|
|
|
|
1. 카탈로그 검색: 검색 컴포넌트 없음(MISSING).
|
|
2. 조합 검토: 텍스트 입력·확정·취소·IME는 `SWHeaderInput`으로 충족 → 재사용. 필드 면과 검색 아이콘 버튼은 공개 컴포넌트가 없음 → 신규.
|
|
3. 신규 부분은 **소비 프로젝트**에 작성: 38px 속성 필드 면, 12px 라벨, gradient Hover 링, 아이콘 버튼은 글자색 Hover `#FFC600`. 모든 값은 토큰([sw-search-control.css](./examples/sw-search-control.css)).
|
|
4. 기존 컨트롤과 같은 면(`app-sw-surface`)에 나란히 배치.
|
|
5. 검증: 레이어 필드와 높이·위치·테두리·radius·배경·그림자·라벨 크기 일치, Hover 링, Enter 확정 → `onSearch`, Escape 복원.
|
|
|
|
새 컨트롤은 패키지 공식 컴포넌트로 자동 등록하지 않습니다. 여러 프로젝트에서 반복되면 SW 패키지 승격을 별도로 검토합니다.
|
|
|
|
## 5. Popup — 외형 고정, 콘텐츠 자유 — [ExampleE-CustomPopup.tsx](./examples/ExampleE-CustomPopup.tsx)
|
|
|
|
요청 예: "HMDesign SW Popup 디자인을 사용해서 내부에 새로운 설정 폼을 만들어 줘."
|
|
|
|
1. 카탈로그 3절에서 `SWPopup`을 찾는다(프레임 = 속성창 외형·닫기·Escape·스크롤).
|
|
2. 내용은 `children`으로 구성한다. 행 모양은 `SWPopupSection` + `SWPopupRow`, 값 입력은 `SWHeaderInput`.
|
|
3. 레이어·색상·선스타일은 `SWPopupPropertyRow`(속성창 `picker`)로 둔다. SWHeader 필드 Dropdown(`SWHeaderLayerMenu`·`SWHeaderColorMenu`)은 Header 전용이라 Popup에 넣지 않는다. 게이지바는 `SWSlider`(`appearance` `"panel"` 기본 / `"property"` 속성창 모양), 선택 목록은 `SWSelect`를 `SWPopupRow field="none"`에 둔다. SW에 없는 컨트롤(Switch, Button 등)은 `@hm-design/ui`를 `children` 안에 그대로 둔다. 구조 제한은 없다.
|
|
4. 프레임(폭, 제목줄, 닫기, 표면색)을 다시 만들지 않고, `hm-sw-property*` class를 쓰지 않는다.
|
|
|
|
```tsx
|
|
<SWTheme>
|
|
<SWPopup title="도면 설정" onClose={close}>
|
|
<SWPopupSection title="도면">
|
|
<SWPopupRow label="이름" htmlFor={id}><SWHeaderInput id={id} value={name} onCommit={setName} /></SWPopupRow>
|
|
<SWPopupRow label="축척" unit="%" numeric><SWHeaderInput aria-label="축척" valueType="number" value={scale} onCommit={setScale} /></SWPopupRow>
|
|
</SWPopupSection>
|
|
<SWPopupSection title="표시">
|
|
<SWPopupRow label="단위" htmlFor={unitId} field="none"><SWSelect id={unitId} value={unit} options={[{ value: "mm" }, { value: "m" }]} onValueChange={setUnit} /></SWPopupRow>
|
|
<SWPopupPropertyRow property={{ id: "layer", name: "레이어", value: layerId, type: "select", groupId: "g", picker: "layer", options }} menus={{ layers }} onPropertyChange={(_, id) => setLayerId(id)} />
|
|
<SWPopupRow label="투명도" field="none" unit="%"><SWSlider aria-label="투명도" value={opacity} onValueChange={setOpacity} onValueCommit={applyOpacity} /></SWPopupRow>
|
|
</SWPopupSection>
|
|
<MyProjectFields />
|
|
</SWPopup>
|
|
</SWTheme>
|
|
```
|
|
|
|
- 데이터 기반 속성창이 필요하면 `SWHeaderPropertyPanel`(`properties` / `groups`)을 쓰고, 추가 내용은 같은 컴포넌트의 `children`으로 붙인다.
|
|
- 위치는 호스트가 정한다: 문서 흐름(`style`) 또는 `anchorRef`(기준 요소 아래 고정 Overlay). 열고 닫는 상태도 호스트가 가진다(조건부 렌더링).
|
|
- 여러 Popup을 동시에 띄워도 Escape는 하나씩 닫힌다(카탈로그 3절).
|
|
|
|
## 6. 파일 열기 + 도구 막대 — [ExampleF-ViewerToolbar.tsx](./examples/ExampleF-ViewerToolbar.tsx)
|
|
|
|
**일반 모드 Header에 도구 막대를 더한 구성**입니다. 예제 파일명·주석의 "viewer"는 과거 이름이며 SW 뷰어 모드가 아닙니다. "뷰어"·"도면 보기" 화면은 7(`SWViewer*`)을 씁니다.
|
|
|
|
```tsx
|
|
<SWHeader documentTitle={name} documents={documents} documentOpenPlacement="header" documentOpenLabel="파일 열기" onDocumentOpen={open}
|
|
controls={[{ id: "document" }, { id: "layer" }, { id: "color" }, { id: "popupGuide" }]}
|
|
hiddenControls={["documentNew", "layerFreeze", "layerLock"]}
|
|
tools={<SWToolGroup label="보기" selection="single">
|
|
<SWToolButton pressed={view === "model"} onClick={showModel}>Model</SWToolButton>
|
|
<SWToolButton pressed={view === "layout"} onClick={showLayout}>Layout</SWToolButton>
|
|
</SWToolGroup>} />
|
|
```
|
|
|
|
- 파일 열기는 `tools`에 직접 만들지 않는다. `documentOpenPlacement="header"`가 로고 바로 오른쪽에 파일명과 같은 면으로 그리고 Dropdown의 Open은 숨긴다(중복 없음). 공간이 부족하면 More로 이동한다.
|
|
- 지원하지 않는 기능은 `hiddenControls`로 뺀다(`documentNew`, `documentOpen`, `layerFreeze`, `layerLock`). 가짜 버튼을 두지 않는다.
|
|
- `SWHeader` 없이 같은 순서가 필요하면: `SWHeaderHome` → `SWToolButton appearance="document"` → `SWHeaderDocumentTitle showOpen={false}`(사선 접점·12px 여백은 패키지가 처리). 이 경우 More는 없다.
|
|
|
|
## 7. 뷰어 모드 기본 조합 — [ExampleH-ViewerMode.tsx](./examples/ExampleH-ViewerMode.tsx)
|
|
|
|
SW **뷰어 모드**(도면 보기) 화면입니다. 모드 선택은 [AI_USAGE.md](./AI_USAGE.md) §0, Props·상태는 [VIEWER.md](./VIEWER.md), 토큰은 [DESIGN_RULES.md](./DESIGN_RULES.md) §11. 이 패턴에는 1~6의 일반 모드 컴포넌트(`SWHeader`, `SWPopup`, `SWToolButton` …)를 섞지 않습니다(예외: 창 최소화·닫기 버전의 `trailing` 끝 `SWHeaderWindowControls`, VIEWER.md §2.1).
|
|
|
|
```tsx
|
|
const groups: SWViewerHeaderGroup[] = [
|
|
{ id: "space", label: "도면 공간", surface: "field", hidden: !fileName, items: [
|
|
{ type: "tabs", id: "space", label: "모델과 배치 선택", collapseBelow: 1180, value: space, onValueChange: setSpace, options },
|
|
] },
|
|
{ id: "view", label: "화면 보기", surface: "field", items: [
|
|
// 실제 가용 폭: 공간이 있으면 표시 → 아이콘만 → More (숫자는 순서만 정함).
|
|
{ type: "button", id: "fit", label: "전체 보기", icon: icons.fit, iconOnlyBelow: 560, collapseBelow: 1180, disabled: !fileName },
|
|
{ type: "switch", id: "depth", label: "2.5D", checked: depth, collapseBelow: 1180, onCheckedChange: setDepth },
|
|
] },
|
|
{ id: "panels", label: "패널", placement: "end", items: [
|
|
{ type: "toggle", id: "options", label: "설정", icon: icons.options, pressed: optionsOpen, iconOnlyBelow: 900, onPressedChange: setOptionsOpen },
|
|
] },
|
|
]
|
|
|
|
<SWViewerHeader documentTitle={fileName} groups={groups} onCommand={runCommand}
|
|
onDocumentOpen={openFile} documentOpenLabel="파일 열기"
|
|
documents={samples} activeDocumentId={sampleId} onDocumentSelect={loadSample}
|
|
trailing={<>
|
|
<SWViewerStatus state={fileName ? "ready" : "idle"} />
|
|
<SWViewerHeaderButton iconOnly icon={icons.info} label="정보" onClick={() => setAbout(true)} />
|
|
</>} />
|
|
<SWViewerPanel title="옵션" open={optionsOpen} onClose={() => setOptionsOpen(false)} width={340} height="auto"
|
|
footer={<SWViewerButton variant="primary" onClick={apply}>적용</SWViewerButton>}>
|
|
<SWViewerSection title="표시">
|
|
<SWViewerField label="그리드">{id => <SWViewerSwitch id={id} checked={grid} onCheckedChange={setGrid} />}</SWViewerField>
|
|
<SWViewerField label="줌 속도" layout="stacked" below={<SWViewerSlider aria-label="줌 속도" value={zoom} min={0.2} max={3} step={0.1} onValueChange={setZoom} />}>
|
|
{id => <SWViewerInput id={id} type="number" value={zoom} onChange={e => setZoom(Number(e.currentTarget.value))} />}
|
|
</SWViewerField>
|
|
</SWViewerSection>
|
|
</SWViewerPanel>
|
|
<SWViewerPanel modal title="정보" open={about} onClose={() => setAbout(false)} width={360} height="auto" resizable={false} collapsible={false}
|
|
footer={<SWViewerButton variant="primary" onClick={() => setAbout(false)}>확인</SWViewerButton>}>…</SWViewerPanel>
|
|
```
|
|
|
|
| 구성 | 사용 |
|
|
|---|---|
|
|
| 파일 열기·샘플 | 로고 오른쪽 파일 열기 버튼 `onDocumentOpen`·`documentOpenLabel`(`SWHeader` `documentOpenPlacement="header"` 면 + 테두리 아이콘, 공간이 부족하면 아이콘만, More로 가지 않음), 파일명 클릭 `documents`·`activeDocumentId`·`onDocumentSelect`(`SWHeader` 파일명 목록, Open·+ New 없음) |
|
|
| Header 메뉴 | `groups` 데이터: 도면 공간(`tabs`, `surface: "field"`), 화면 보기(`button`·`switch`, `field`), 패널 토글(`toggle`, `placement: "end"`) |
|
|
| 오른쪽 끝 | `trailing`: `SWViewerStatus` + `SWViewerHeaderButton iconOnly` |
|
|
| 도구 창 | 비모달 `SWViewerPanel`(드래그·크기 조절·접기 기본). 여러 개면 `inset`·`defaultPosition`으로 겹치지 않게 |
|
|
| 확인·정보 | `SWViewerPanel modal` + `footer`의 `SWViewerButton variant="primary"` |
|
|
| 패널 내용 | `SWViewerSection` > `SWViewerField` > `SWViewerSwitch` / `SWViewerSlider` / `SWViewerInput`, 목록은 `surface="plain"` + 프로그램 목록, 설명 `SWViewerHint`. **레이어 표시·숨김은 눈(`Eye`/`EyeOff`) 아이콘 버튼, Switch 금지**(VIEWER.md §4.1, 예제 H) |
|
|
| 반응형 | 고정 너비가 아니라 Header의 실제 가용 폭으로 3단계: 기본(아이콘+텍스트) → `iconOnlyBelow` 항목 모두 아이콘만(툴팁 유지) → `collapseBelow` 항목을 하나씩 More로(값이 큰 것, 같으면 오른쪽 먼저). 넓어지면 역순 복귀. 패널 토글은 `collapseBelow` 없이 아이콘까지만 |
|
|
| 우클릭 메뉴 | `SWViewerMenu` + `anchorPoint` |
|
|
|
|
- 상태(파일명·패널 열림·탭 값)는 프로그램이 가지고, 명령은 각 Callback 또는 `onCommand` 한 곳에서 받는다. 도면 영역 높이·렌더러 크기는 앱 레이아웃 담당(VIEWER.md §7).
|
|
- 없는 기능은 메뉴를 만들지 않거나 `disabled` + 미연결 보고. 패키지에 없는 UI는 [AI_USAGE.md](./AI_USAGE.md) §0.4 뷰어 열을 따른다.
|
|
|
|
## 8. 로고 클릭 메뉴 (일반 모드) — [ExampleG-HomeMenu.tsx](./examples/ExampleG-HomeMenu.tsx)
|
|
|
|
SW **일반 모드** `SWHeader` 전용입니다. 패턴 7(뷰어 모드)에는 홈 메뉴를 넣지 않습니다.
|
|
|
|
```tsx
|
|
const sections: SWHomeMenuSectionData[] = [
|
|
{ id: "working", title: "작업중인 파일", items: [
|
|
{ id: "save", label: "저장", icon: <SaveIcon />, toggle: false, onSelect: save },
|
|
] },
|
|
{ id: "open", title: "다른 파일 열기", items: [
|
|
{ id: "find-open", label: "찾아열기", icon: <OpenIcon />, toggle: false, onSelect: openFile },
|
|
{ id: "recent", label: "최근파일", icon: <RecentIcon />, panel: <SWRecentFiles files={recent} onFileOpen={openRecent} /> },
|
|
] },
|
|
]
|
|
<SWHeader documentTitle={name} documents={documents} onDocumentOpen={openFile} onDocumentNew={newFile}
|
|
homeMenu={{ sections, onItemSelect }} onHomeMenuOpenChange={(open, reason) => log(open, reason)} />
|
|
```
|
|
|
|
1. 카탈로그 §5의 데이터로 메뉴를 쓴다. 화면마다 타일·섹션·제목을 따로 만들지 않는다.
|
|
2. 메뉴 데이터 변경: 항목 추가·삭제는 `items` 배열, 숨김은 `hidden`, 비활성은 `disabled`, 실행만 하는 버튼은 `toggle: false`, 켜고 끄는 버튼은 기본값. Active를 직접 제어하려면 `homeMenu.activeIds` + `onActiveChange`.
|
|
3. 최근파일 패널은 선택 기능이다. 필요한 타일에만 `panel: <SWRecentFiles … />`를 준다. 파일을 열면 메뉴가 닫힌다(`closeOnOpen`).
|
|
4. 로고 클릭은 메뉴만 연다. `onHomeClick`의 홈 이동은 `homeMenu`가 있으면 실행되지 않는다. 메뉴 항목에 홈 이동이 필요하면 항목 `onSelect`에서 한다.
|
|
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)`로 둡니다.
|
|
|
|
## 11. 테마(토큰) 설정 — [ExampleJ-Theme.tsx](./examples/ExampleJ-Theme.tsx)
|
|
|
|
일반·뷰어 모드 공통입니다. 토큰 기본값은 가장 바깥 SW 범위에서만 선언되고 안쪽 SW 요소는 상속합니다([AI_USAGE.md](./AI_USAGE.md) §3.1).
|
|
|
|
```tsx
|
|
const appSWTheme = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-serif' } as CSSProperties // 프로그램이 정한 값만
|
|
|
|
<SWTheme style={appSWTheme}> {/* 가장 바깥 범위: 여기서만 지정 */}
|
|
<SWViewerHeader … /> {/* Dropdown·More까지 상속 */}
|
|
<SWViewerPanel …>…</SWViewerPanel> {/* 패널·안쪽 컨트롤 상속 */}
|
|
</SWTheme>
|
|
|
|
{createPortal(<SWTheme style={appSWTheme}>…</SWTheme>, document.body)} {/* Portal은 같은 객체로 다시 감쌈 */}
|
|
```
|
|
|
|
- 일반 모드도 같습니다: `<SWTheme style={appSWTheme}>` 안의 `SWHeader`(Dropdown·창 이동 Tooltip)·`SWFooter`(`SWFooterCommandLine`·명령 등록 안내)가 같은 값을 씁니다.
|
|
- 기본 글꼴은 Noto Sans KR입니다(2026-10-20 확정). 기본값을 쓸 때는 글꼴 토큰을 지정하지 않습니다. Web UI 글꼴 토큰(`--hm-font-family-base`)을 SW 화면에 넣지 않습니다.
|
|
- `hm-sw-*` class 선택자로 토큰을 덮거나 `!important`, 컴포넌트마다 같은 값 반복 지정을 하지 않습니다.
|
|
|
|
## 9. 하지 말 것
|
|
|
|
- `hm-sw-*` class를 소비 코드에 쓰거나 CSS로 덮어쓰기(토큰 변경도 class 선택자가 아니라 가장 바깥 SW 범위의 `style` — 11)
|
|
- `dist` 구현·`styles.css` 블록 복사로 같은 컴포넌트 재작성
|
|
- `@hm-design/sw/dist/...`·`src/...` 내부 경로 Import
|
|
- 문서에 없는 Props 추측, 새 색·크기 값 도입
|
|
- 같은 화면에 `SWHeader`와 조합 Header를 겹쳐 두 개의 Header 만들기
|
|
- 같은 화면에 `SWHeader`와 `SWViewerHeader`를 함께 두기, 일반 모드 화면에 `SWViewer*` 사용, 뷰어 화면에서 `SWHeader`·`SWPopup` 재스타일(AI_USAGE §0.3)
|