Combine Task A (e8409ef, SW 0.2.0 integrated improvement) with Task B
(396539d, Figma-based SWHomeMenu / SWHomeMenuSection / SWHomeMenuItem /
SWRecentFiles). Task B's 84 new files are taken unchanged except for the
public-import Storybook update and joining the popup Escape stack.
- Export the home menu from @hm-design/sw
- SWHeader homeMenu / homeMenuOpen / defaultHomeMenuOpen /
onHomeMenuOpenChange: hidden by default, the logo toggles it (onHomeClick
is not called while homeMenu is set), placed from the measured header,
closes with / closes the document and field dropdowns
- Logo aria-expanded / aria-controls; menu layer CSS (z-index 20)
- AI catalog / patterns / design rules, example G, README, usage guide
- Storybook SW/Home Menu uses @hm-design/sw; integration story
Verified: SW typecheck, tsc -b, SW build, Storybook build, AI docs 22/22,
28/28 existing SW shots pixel-identical, regression 96 PASS / 0 FAIL,
home menu checks 22/22. Not run: tarball consumer install.
10 KiB
10 KiB
SW 조합 패턴
모든 패턴은 공개 Export만 사용합니다. 전체 코드는 examples/에 있으며, 설치된 패키지에서 꺼낸 그대로 독립 React 19 + TypeScript + Vite 프로젝트의 tsc·Production Build·브라우저 검증을 통과했습니다(2026-10-19).
0. 어떤 패턴을 고를까
| 요청 | 패턴 |
|---|---|
| 기존 SW Header 전체(일부 부품 제거·메뉴 순서 변경 포함) | 1. 완성형 |
| 기존 컨트롤 일부만, 다른 배치·순서 | 2. 조합 Header |
| 기존 Dropdown을 툴바·패널 등 다른 곳에 | 3. 개별 컨트롤 |
| 패키지에 없는 기능 | 4. 확장(소비 프로젝트 신규 컴포넌트) |
| SW Popup 디자인의 설정창·정보창·사용자 정의 폼 | 5. Popup(외형 고정, 콘텐츠 자유) |
| 로고 옆 파일 열기, 뷰어 도구 막대(Model/Layout, Fit, 2.5D) | 6. 파일 열기 + 도구 막대 |
| 로고를 누르면 열리는 2단 메뉴(저장·내보내기·찾아열기·최근파일 등) | 7. 로고 클릭 메뉴 |
1. 완성형 SW Header — ExampleA-DefaultHeader.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). 이 CSS는 --hm-sw-header-* 토큰만 읽고 hm-sw-* class를 건드리지 않습니다.
<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>
- 간소화 Viewer: ExampleB-ViewerHeader.tsx — 로고 → 파일 → 레이어 → 색상 → K.
- 기능 중심: 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을 다른 레이아웃에서
<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, ExampleD-SearchExtension.tsx
요청 예: "레이어와 색상은 기존 SW 컴포넌트, 검색 버튼은 SW 디자인으로 새로."
- 카탈로그 검색: 검색 컴포넌트 없음(MISSING).
- 조합 검토: 텍스트 입력·확정·취소·IME는
SWHeaderInput으로 충족 → 재사용. 필드 면과 검색 아이콘 버튼은 공개 컴포넌트가 없음 → 신규. - 신규 부분은 소비 프로젝트에 작성: 38px 속성 필드 면, 12px 라벨, gradient Hover 링, 아이콘 버튼은 글자색 Hover
#FFC600. 모든 값은 토큰(sw-search-control.css). - 기존 컨트롤과 같은 면(
app-sw-surface)에 나란히 배치. - 검증: 레이어 필드와 높이·위치·테두리·radius·배경·그림자·라벨 크기 일치, Hover 링, Enter 확정 →
onSearch, Escape 복원.
새 컨트롤은 패키지 공식 컴포넌트로 자동 등록하지 않습니다. 여러 프로젝트에서 반복되면 SW 패키지 승격을 별도로 검토합니다.
5. Popup — 외형 고정, 콘텐츠 자유 — ExampleE-CustomPopup.tsx
요청 예: "HMDesign SW Popup 디자인을 사용해서 내부에 새로운 설정 폼을 만들어 줘."
- 카탈로그 3절에서
SWPopup을 찾는다(프레임 = 속성창 외형·닫기·Escape·스크롤). - 내용은
children으로 구성한다. 행 모양은SWPopupSection+SWPopupRow, 값 입력은SWHeaderInput. - SW에 없는 컨트롤(Switch, Slider, Select, Button 등)은
@hm-design/ui를children안에 그대로 둔다. 구조 제한은 없다. - 프레임(폭, 제목줄, 닫기, 표면색)을 다시 만들지 않고,
hm-sw-property*class를 쓰지 않는다.
<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>
<MyProjectFields />
</SWPopup>
</SWTheme>
- 데이터 기반 속성창이 필요하면
SWHeaderPropertyPanel(properties/groups)을 쓰고, 추가 내용은 같은 컴포넌트의children으로 붙인다. - 위치는 호스트가 정한다: 문서 흐름(
style) 또는anchorRef(기준 요소 아래 고정 Overlay). 열고 닫는 상태도 호스트가 가진다(조건부 렌더링). - 여러 Popup을 동시에 띄워도 Escape는 하나씩 닫힌다(카탈로그 3절).
6. 파일 열기 + 도구 막대 — ExampleF-ViewerToolbar.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. 로고 클릭 메뉴 — ExampleG-HomeMenu.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)} />
- 카탈로그 5절의 데이터로 메뉴를 쓴다. 화면마다 타일·섹션·제목을 따로 만들지 않는다.
- 메뉴 데이터 변경: 항목 추가·삭제는
items배열, 숨김은hidden, 비활성은disabled, 실행만 하는 버튼은toggle: false, 켜고 끄는 버튼은 기본값. Active를 직접 제어하려면homeMenu.activeIds+onActiveChange. - 최근파일 패널은 선택 기능이다. 필요한 타일에만
panel: <SWRecentFiles … />를 준다. 파일을 열면 메뉴가 닫힌다(closeOnOpen). - 로고 클릭은 메뉴만 연다.
onHomeClick의 홈 이동은homeMenu가 있으면 실행되지 않는다. 메뉴 항목에 홈 이동이 필요하면 항목onSelect에서 한다. - New / Open은 문서 Dropdown에 그대로 둔다(
documentOpenPlacement기본"menu"). 로고 오른쪽에 별도 Open 버튼을 두지 않는다. 메뉴의 찾아열기·새파일은 같은 handler를 연결한다. - 위치·폭은
SWHeader가 정한다. 소비 코드에서 메뉴 위치를 계산하거나 겹치는 wrapper를 만들지 않는다.
8. 하지 말 것
hm-sw-*class를 소비 코드에 쓰거나 CSS로 덮어쓰기dist구현·styles.css블록 복사로 같은 컴포넌트 재작성@hm-design/sw/dist/...·src/...내부 경로 Import- 문서에 없는 Props 추측, 새 색·크기 값 도입
- 같은 화면에
SWHeader와 조합 Header를 겹쳐 두 개의 Header 만들기