feat(sw): prepare reusable AI-friendly component package

- Ship an AI guide in the package (ai/: usage rules, component catalog,
  design rules, composition patterns) via package.json files.
- Add examples A-D that import only public exports: default header,
  viewer header, feature header, and a consumer-side search extension
  that reuses SWHeaderInput and the public SW tokens.
- Add SW-only checks: docs/API consistency, tarball consumer project
  (React 19 + TS + Vite typecheck/build) and a browser check (27/27).
- No runtime source or public API change; README gains a pointer section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shkim4andClaude Opus 5.5 committed 2026-10-19 11:22:07 +09:00
1 parent 50bf5bda8e
commit e27385e0f0
26 files changed
+1349 -1

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
# @hm-design/sw — AI 코딩 도구 사용 안내
이 폴더(`node_modules/@hm-design/sw/ai/`)는 다른 React 프로젝트에서 AI 코딩 도구가 SW 패키지를 **재사용·조합·확장**할 때 먼저 읽는 안내서입니다. 패키지 자체에는 AI 기능이 없습니다. 아래 문서는 실제 공개 API와 CSS에서 확인한 값만 적습니다.
| 문서 | 내용 |
|---|---|
| [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) | 공식 공개 컴포넌트·Props·Callback·크기·CSS 의존성 |
| [DESIGN_RULES.md](./DESIGN_RULES.md) | SW 전용 토큰·Typography·간격·Hover/Focus/Selected·Overlay·Motion·반응형 |
| [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) | 완성형·간소화·기능 중심·확장 Header 조합 방법 |
| [examples/](./examples) | 공개 API만 쓰는 예제 A~D(독립 소비 프로젝트에서 Typecheck·Build·브라우저 검증) |
## 1. 작업 순서 (필수)
1. **먼저 검색한다.** 요청 기능을 [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)의 공개 컴포넌트에서 찾는다.
2. **있으면 재사용한다(1순위).** 완성형 Header가 필요하면 `SWHeader` 하나만 쓴다. 기존 Props(`hiddenControls`, `menuOrder`, `controls`, 각 데이터 배열·Callback)로 해결되는지 먼저 본다.
3. **없으면 조합한다(2순위).** 개별 공개 컴포넌트(`SWHeaderHome`, `SWHeaderDocumentTitle`, `SWHeaderLayerMenu` …)를 `<SWTheme>` 안에 배치한다. 배치용 CSS만 소비 프로젝트에 작성한다([COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md)).
4. **조합으로도 안 될 때만 새로 만든다(3순위).** 새 컴포넌트는 **소비 프로젝트**에 작성하고, 공개 컴포넌트(예: `SWHeaderInput`)를 내부에 재사용하며, 외형은 공개 토큰 `--hm-sw-header-*`만 읽는다([DESIGN_RULES.md](./DESIGN_RULES.md), 예제 D).
5. **새 디자인 값을 만들지 않는다.** 크기·색·간격은 토큰과 DESIGN_RULES의 확인값만 쓴다. 값이 없으면 추측하지 말고 사용자에게 확인한다.
6. **존재하지 않는 API를 추측하지 않는다.** 카탈로그와 `dist/index.d.ts`에 없는 Export·Props는 없는 것이다.
7. **패키지 코드를 복사하지 않는다.** `node_modules/@hm-design/sw/dist`의 구현이나 `hm-sw-*` CSS 블록을 복사해 같은 컴포넌트를 다시 만들지 않는다.
8. **실제로 검증한다.** 소비 프로젝트에서 `tsc`, Production Build, 브라우저 렌더링(Dropdown 열기·Callback·Focus)을 확인한 뒤 완료로 보고한다. 실행하지 않은 검증은 PASS로 적지 않는다.
## 2. 설치·Import 규칙
```tsx
import { SWHeader, SWTheme, SWHeaderLayerMenu } from "@hm-design/sw"
import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
```
- 공식 진입점은 `@hm-design/sw`(JS·타입)와 `@hm-design/sw/styles.css` 두 개뿐이다. `package.json`의 `exports`가 다른 경로를 막는다.
- **금지:** `@hm-design/sw/src/...`, `@hm-design/sw/dist/components/...` 같은 내부 경로 Import. `dist`에 내부 모듈의 `.d.ts`가 보여도 공개 API가 아니다.
- Peer: `react`, `react-dom` `^19.0.0`. 런타임 dependency 없음. `@hm-design/ui`·조직도 패키지와 무관하다.
- SVG·PNG 자산은 JS에 data URI로 포함되어 별도 설정이 없다.
- 글꼴 파일은 포함하지 않는다. 앱에서 Noto Sans KR(400/500/700)을 로드한다(없으면 Malgun Gothic → Apple SD Gothic Neo).
- CSS는 전역(`html`, `body`, `*`)을 건드리지 않는다. `body` margin 등 페이지 기본값은 소비 앱이 정한다.
- TypeScript에서 CSS side-effect import 오류가 나면 Vite의 `vite/client` 타입을 포함한다.
## 3. 스타일 규칙
- `SWHeader` 밖에서 하위 컴포넌트를 쓰면 반드시 `<SWTheme>`로 감싼다. 토큰·글꼴·버튼 초기화가 이 범위에서만 적용된다.
- 소비 코드는 `hm-sw-*` class를 붙이거나 재정의하지 않는다. 배치는 자기 class(`className`)와 `style`로 한다.
- 외형 변경은 `--hm-sw-header-*` 토큰을 `SWTheme`/`SWHeader`의 `style`로 바꾸는 방법만 쓴다. 사용자 확정 면(파일명·객체 수정·속성 입력)은 기본값 유지를 권장한다.
- Light/Dark 분기는 없다. SW 디자인은 하나다.
## 4. 판단 기록 남기기
새 UI를 만들 때 결과 보고에 다음을 남긴다.
| 요청 항목 | 판단 | 근거 |
|---|---|---|
| 예: 레이어 | 재사용 | `SWHeaderLayerMenu` |
| 예: 검색 | 신규(소비 프로젝트) | 카탈로그 MISSING. `SWHeaderInput` 재사용 + 토큰 기반 표면 |
새 컴포넌트를 SW 패키지 공식 컴포넌트로 승격할지는 별도 검토 대상이다. 임의로 패키지에 추가하지 않는다.
+75
View File
@@ -0,0 +1,75 @@
# @hm-design/sw 컴포넌트 카탈로그
기준: `@hm-design/sw` 0.1.0, `dist/index.d.ts` 공개 Export(2026-10-19). 모든 Import는 `from "@hm-design/sw"`이며 CSS는 `@hm-design/sw/styles.css` 한 개입니다. 이 표에 없는 이름은 공개 API가 아닙니다.
- **단독 사용:** `SWHeader` 밖에서는 `<SWTheme>` 안에 둡니다(토큰·글꼴·버튼 초기화).
- **상태 소유:** 값은 Props, 사용자 행동은 Callback입니다. 컴포넌트는 CAD 데이터를 바꾸지 않습니다. 호스트가 Callback을 받아 Props를 갱신합니다.
- **열림 상태:** Dropdown은 `open` / `onOpenChange`(Controlled)를 주지 않으면 내부 상태로 동작합니다.
- **공통 키보드:** Click·Enter·Space 열기/닫기(Hover로는 열리지 않음), ArrowDown 열고 첫(현재) 항목 Focus, Arrow 이동, Escape·바깥 클릭·Tab 이탈 닫기(Escape는 Trigger로 Focus 복귀).
- **검증 표시:** ✅ = 2026-10-19 독립 소비 프로젝트(Tarball 설치)에서 브라우저 검증, ◻ = 이번 소비 검증 범위 밖(기존 Storybook 검증 기록만 있음).
## 1. 완성형 템플릿
### `SWHeader` ✅
SW 공식 기준 Header(Figma 26:8944, 1920 × 49). 로고 · 파일 · 객체 수정 영역(그리기모드, 레이어/색상/선/문자/치수스타일, 상세보기) · 창 영역(인쇄·K, 창 이동, 최소화, 종료)과 반응형 More, 속성Bar, 속성창을 포함합니다.
| 구분 | Props / Callback |
|---|---|
| 필수 | `documentTitle: string` |
| 구성 | `hiddenControls?: SWHeaderHideableControlId[]`(로고 외 부품 제거, `home` 제거 시 정적 로고), `menuOrder?: SWHeaderMenuId[]`(5개 메뉴끼리 순서), `controls?: SWHeaderControlConfig[]`(객체 영역 전체 allowlist·순서·label) |
| 로고 | `logo?: ReactNode`, `logoSrc?: string`, `logoAlt?: string`, `onHomeClick` |
| 파일 | `documents`, `activeDocumentId`, `documentMenuOpen`, `onDocumentMenuOpenChange`, `onDocumentSelect`, `onDocumentClose`, `onDocumentNew`, `onDocumentOpen`, `onDocumentClick`, `documentIcon`, `documentNewLabel`, `documentOpenLabel` |
| 레이어 | `layer`, `layers`, `activeLayerId`, `layerMenuOpen`, `onLayerMenuOpenChange`, `onLayerSelect`, `onLayerVisibilityChange`, `onLayerFreezeChange`, `onLayerLockChange`, `onLayerDetail`, `onLayerOpen` |
| 색상 | `color`, `colors`, `userColors`, `colorMode`, `colorMenuOpen`, `onColorMenuOpenChange`, `onColorSelect`, `onColorModeChange`, `onColorInput`, `onColorPick`, `onColorDetail`, `onColorOpen` |
| 선스타일 | `lineStyle`, `lineTypes`, `lineWeights`, `activeLineTypeId`, `activeLineWeightId`, `lineStyleMenuOpen`, `onLineStyleMenuOpenChange`, `onLineTypeSelect`, `onLineWeightSelect`, `onLineWeightValueChange`, `onLineScaleChange`, `onLineStyleDetail`, `onLineStyleOpen` |
| 문자/치수 | `textStyle`, `textStyles`, `activeTextStyleId`, `textStyleMenuOpen`, `onTextStyleMenuOpenChange`, `onTextStyleSelect`, `onTextStyleDetail`, `onTextStyleOpen` / `dimensionStyle`, `dimensionStyles`, `activeDimensionStyleId`, `dimensionStyleMenuOpen`, `onDimensionStyleMenuOpenChange`, `onDimensionStyleSelect`, `onDimensionStyleDetail`, `onDimensionStyleOpen` |
| 상세 | `propertyBar`, `propertyBarOpen`, `defaultPropertyBarOpen`, `onPropertyBarOpenChange`, `onPropertyBarToggle`, `propertyPanel`, `propertyPanelOpen`, `onPropertyPanelOpenChange`, `onPropertyPanelToggle` |
| 창 | `onPrint`, `onPopupGuide`, `onWindowMoveStart(PointerEvent)`, `onMinimize`, `onClose` |
| 기타 | `activeMenu`, `disabled`(창 버튼 제외), `drawingModeLabel`, `tools?: ReactNode`(객체 영역 메뉴 뒤), `<header>` HTML 속성·`ref` |
- 데이터 배열(`layers`, `colors`, `lineTypes`/`lineWeights`, `textStyles`, `dimensionStyles`, `documents`)을 주지 않으면 해당 필드는 Dropdown 없는 버튼입니다.
- 반응형(More·대표 필드 전환·창 영역 축소)은 **`SWHeader` 안에서만** 동작합니다(CSS container `hm-sw-header`).
```tsx
<SWHeader documentTitle={name} logoSrc={logo} logoAlt="제품명" layers={layers} activeLayerId={id} onLayerSelect={l => setId(l.id)} onClose={closeWindow} />
```
## 2. 개별 컨트롤 (조합용)
| 컴포넌트 | 용도 | 주요 Props / Callback | 크기(Figma) | 상태 | 검증 |
|---|---|---|---|---|---|
| `SWTheme` | 단독 사용 범위(토큰·글꼴·초기화). 배치는 정하지 않음 | `div` HTML 속성, `className`, `style`(토큰 override) | — | — | ✅ |
| `SWHeaderHome` | 로고 슬롯 + 홈 버튼 | `logo`, `logoSrc`, `logoAlt`, `onClick`, `interactive`(false = 정적 로고) | 80 × 48, 로고 슬롯 32 × 17, 오른쪽 −17px로 다음 파일 면과 겹침 | Hover 배경 교체, Focus | ✅ |
| `SWHeaderDocumentTitle` | 파일명 + 문서 Dropdown(+ New / Open) | `title`(필수), `documents`, `activeDocumentId`, `menuOpen`, `onMenuOpenChange`, `onDocumentSelect`, `onDocumentClose`, `onNew`, `onOpen`, `onClick`, `documentIcon`, `newLabel`, `openLabel`, `label` | 높이 48, 폭 190–310(반응형 clamp), 메뉴 310 | 텍스트만 Hover #FFC600, 열림 화살표 회전 | ✅ |
| `SWHeaderLayerMenu` | 레이어 필드 + Dropdown | `value`(필수), `layers`(필수), `activeLayerId`, `open`, `onOpenChange`, `onClick`, `selected`, `disabled`, `onLayerSelect`, `onVisibilityChange`, `onFreezeChange`, `onLockChange`, `onDetail`, `label` | 필드 234 × 38, 메뉴 234 | Hover 링, Selected, Expanded, 행 Hover | ✅ |
| `SWHeaderColorMenu` | 색상 필드 + Dropdown(ByLayer/ByBlock, 팔레트, 사용자 색, 추출, RGB 입력) | `value`, `colors`(필수), `userColors`, `mode`, `open`, `onOpenChange`, `onClick`, `selected`, `disabled`, `onColorSelect`, `onModeChange`, `onColorInput`, `onPick`, `onDetail`, `label` | 필드 192 × 38 | 칩 선택 시 열림 유지 | ✅ |
| `SWHeaderLineStyleMenu` | 선스타일 필드 + 3열 Dropdown | `value`, `lineTypes`, `lineWeights`, `activeLineTypeId`, `activeLineWeightId`, `open`, `onOpenChange`, `onClick`, `selected`, `disabled`, `onLineTypeSelect`, `onLineWeightSelect`, `onLineWeightValueChange`, `onScaleChange`, `onDetail`, `label` | 필드 284 × 38, 열 182·142·142(필드 중앙 정렬) | 열별 선택, 열림 유지 | ✅ |
| `SWHeaderTextStyleMenu` | 문자스타일 필드 + 목록 | `value`, `textStyles`(필수), `activeTextStyleId`, `onTextStyleSelect`, `open`, `onOpenChange`, `onClick`, `selected`, `disabled`, `onDetail`, `label` | 필드 164 × 38 | 선택 시 닫힘 | ✅ |
| `SWHeaderDimensionStyleMenu` | 치수스타일 필드 + 목록 | `value`, `dimensionStyles`(필수), `activeDimensionStyleId`, `onDimensionStyleSelect`, 열림·`onDetail`·`label` 동일 | 필드 164 × 38 | 선택 시 닫힘 | ✅ |
| `SWHeaderLayerControl` · `SWHeaderColorControl` · `SWHeaderLineStyle` · `SWHeaderTextStyle` · `SWHeaderDimensionStyle` | Dropdown 없는 필드 버튼(호스트가 자체 팝업을 열 때) | `value`(필수), `label`, `selected`, `disabled`, `onClick`, `expanded`, `controls`, `buttonRef` / `SWHeaderLayerControl`은 `state`(표시·동결·잠금) | 위 필드 크기와 같음 | Hover 링, Selected | ◻ |
| `SWHeaderActions` | 인쇄·K 버튼 pill | `onPrint`, `onPopupGuide`, `disabled`, `showPrint`, `showPopupGuide`(하나만 남기면 pill이 축소) | pill 85 × 42(버튼 40 × 38) | 아이콘 Hover, Hover 링, K 가이드 미리보기(반복 Motion, Reduced Motion 정적) | ✅ |
| `SWHeaderWindowControls` | 창 이동·최소화·종료 | `onWindowMoveStart`, `onMinimize`, `onClose`, `showWindowMove`, `showMinimize`, `showClose` | 이동 88 × 48, 버튼 32 × 48 | 창 이동 Hover Motion 1.4s, 종료 Hover 자산 | ◻ |
| `SWHeaderProperties` | 상세보기 / 속성Bar · 속성창 토글 | `barOpen`, `panelOpen`, `panelId`, `disabled`, `onBarToggle`, `onPanelToggle`, `showCaption`, `showBar`, `showPanel` | 157 × 38 | 텍스트 Hover #FFC600 | ◻ |
| `SWHeaderPropertyBar` | 28px 속성 편집 띠 | `selection`, `properties`, `onPropertyChange`, `readOnly`, `disabled`, `div` 속성 | 높이 28, 콘텐츠 폭 | Input 상태 | ◻ |
| `SWHeaderPropertyPanel` | 속성창(13개 필드 타입, 그룹, 형상 Slot) | `selection`, `properties`, `groups`, `onPropertyChange`, `menus`, `expandedGroupIds`/`defaultExpandedGroupIds`/`onExpandedGroupsChange`, `toolActive`/`onToolActiveChange`, `toolLabel`, `onOverviewClick`, `shapeContent`, `onClose`, `id`, `anchorRef`, `style` | 고정 위치 Overlay(Portal·Dim 없음) | 4가지 선택 상태 | ◻ |
| `SWHeaderInput` | 수정 가능한 SW Input | `value`(필수), `onValueChange`, `onCommit`(Enter·Blur, 유효 + 변경 시), `onCancel`(Escape 복원), `valueType`(`text`/`number`), `parse`, `readOnly`, `disabled`, `commitOnUnmount`, `input` 속성 | 58 × 20 기준, Hover/편집 24px | Default/Hover/편집/ReadOnly/Disabled, IME 조합 중 Enter 무시 | ✅ |
| `parseSWHeaderColor` · `parseSWHeaderNumber` | Input 검증 도우미 | `r,g,b` / `#rrggbb`, 숫자(`min`, `max`) | — | — | ◻ |
| `SWHeaderLinePreview` · `swHeaderLinePatterns` | 70 × 2 선 미리보기, CENTER/HIDDEN 패턴 | `pattern?: number[]` | 70 × 2 | — | ◻(패턴 상수만 예제 데이터에서 사용) |
## 3. 공개 타입
`SWHeaderProps`, 각 컴포넌트 `*Props`, `SWThemeProps`, `SWHeaderMenuId`, `SWHeaderControlId`, `SWHeaderControlConfig`, `SWHeaderFrameControlId`, `SWHeaderHideableControlId`, `SWHeaderDocument`, `SWHeaderLayer`, `SWHeaderLayerValue`, `SWHeaderColorValue`, `SWHeaderColorOption`, `SWHeaderColorMode`, `SWHeaderColorInputValue`, `SWHeaderLineStyleValue`, `SWHeaderLineType`, `SWHeaderLineWeight`, `SWHeaderTextStyleValue`, `SWHeaderTextStyleOption`, `SWHeaderDimensionStyleValue`, `SWHeaderDimensionStyleOption`, `SWHeaderInputValueType`, `SWHeaderProperty`, `SWHeaderPropertyType`, `SWHeaderPropertyPicker`, `SWHeaderPropertyMenus`, `SWHeaderPropertyGroup`, `SWHeaderPropertySelection`, `SWHeaderPropertyShapeContent`, `SWHeaderPropertyShapeContext`.
## 4. 공개하지 않은 것 (사용 금지)
`SWHeaderMore`, `SWHeaderField`/`SWHeaderFieldText`, `SWHeaderKGuide`, `useSWHeaderDropdown`, `SWHeaderMenuDetail`, `SWHeaderColorChip`, `SWHeaderLayerRows`, `SWHeaderColorPanelBody`, `SWHeaderLineStyleColumns`, 속성창 내부 Picker, `useSWHeaderOverlay` 등은 내부 구현입니다. 필요하면 공개 컴포넌트로 대체하거나, 부족한 API로 보고합니다.
## 5. 알려진 단독 사용 제약
| 항목 | 내용 | 대응 |
|---|---|---|
| 반응형 More | `SWHeader` 전용. 조합 Header에서는 More·필드 이동이 없음 | 조합 Header의 폭은 소비 앱이 정함(필요 없는 컨트롤을 빼거나 가로 공간 확보) |
| `SWHeaderDocumentTitle` 왼쪽 테두리 | 앞의 로고와 겹치는 전제로 왼쪽 테두리가 없음 | 로고 없이 첫 칸이면 소비 레이아웃 wrapper에 1px 테두리(예제 C) |
| `SWHeaderDocumentTitle` 폭 | `clamp(190px, 100cqw − 1610px, 310px)`; Header 밖에서는 cqw가 뷰포트 기준 | flex 부모 안에 둔다. 1920 뷰포트에서 310px |
| 창 영역 배경 | 배경·테두리는 `SWHeader`의 창 영역에 있음 | 소비 레이아웃이 토큰으로 표면 작성(예제 B·C) |
| Dropdown 위치 | `SWHeader` 안이면 헤더, 밖이면 뷰포트 안으로 좌우 보정 | 부모에 `overflow: hidden`을 두지 않는다 |
+82
View File
@@ -0,0 +1,82 @@
# SW 조합 패턴
모든 패턴은 공개 Export만 사용합니다. 전체 코드는 [examples/](./examples)에 있으며, 설치된 패키지에서 꺼낸 그대로 독립 React 19 + TypeScript + Vite 프로젝트의 `tsc`·Production Build·브라우저 검증을 통과했습니다(2026-10-19).
## 0. 어떤 패턴을 고를까
| 요청 | 패턴 |
|---|---|
| 기존 SW Header 전체(일부 부품 제거·메뉴 순서 변경 포함) | 1. 완성형 |
| 기존 컨트롤 일부만, 다른 배치·순서 | 2. 조합 Header |
| 기존 Dropdown을 툴바·패널 등 다른 곳에 | 3. 개별 컨트롤 |
| 패키지에 없는 기능 | 4. 확장(소비 프로젝트 신규 컴포넌트) |
## 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>
```
- 간소화 Viewer: [ExampleB-ViewerHeader.tsx](./examples/ExampleB-ViewerHeader.tsx) — 로고 → 파일 → 레이어 → 색상 → K.
- 기능 중심: [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. 하지 말 것
- `hm-sw-*` class를 소비 코드에 쓰거나 CSS로 덮어쓰기
- `dist` 구현·`styles.css` 블록 복사로 같은 컴포넌트 재작성
- `@hm-design/sw/dist/...`·`src/...` 내부 경로 Import
- 문서에 없는 Props 추측, 새 색·크기 값 도입
- 같은 화면에 `SWHeader`와 조합 Header를 겹쳐 두 개의 Header 만들기
+94
View File
@@ -0,0 +1,94 @@
# SW 디자인 규칙
출처: `@hm-design/sw` `styles.css`(원본 `src/theme/sw.css`)의 토큰과 규칙, Figma 이지빔 SW `rj6wpsfk24lOWvWiVRAANP` node `26:8944`와 사용자 확정값. 이 문서에 없는 값은 확정값이 아닙니다. 새 컴포넌트에 임의 값을 만들지 말고 아래 토큰을 `var(--…)`로 읽으세요.
- 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의됩니다. 새 컴포넌트는 그 안에 렌더링합니다.
- SW는 `@hm-design/ui` 토큰을 쓰지 않습니다. UI 패키지 토큰을 복제하거나 섞지 않습니다.
- Light/Dark 분기 없음. 전역 선택자(`html`, `body`, `*`) 없음.
## 1. 크기·간격
| 항목 | 값 | 토큰 |
|---|---|---|
| Header 높이 / 면 높이 | 49px / 48px | `--hm-sw-header-height` / `--hm-sw-header-bar-height` |
| 면 사이 간격 | 2px | (Header bar `gap`) |
| 필드 높이 | 38px(면 안 위아래 5px) | `--hm-sw-header-field-height` |
| 필드 사이 간격 | 4px | `--hm-sw-header-gap` |
| 로고 슬롯 | 32 × 17 (홈 80 × 48 안 12, 16) | `--hm-sw-header-logo-width` / `-height` |
| 필드 폭 | 레이어 234, 색상 192, 선스타일 284, 문자·치수스타일 164 | — |
| 상세보기 / 창 영역 | 157 × 38 / 240 × 48(pill 85 × 42, 창 이동 88, 버튼 32 × 2) | — |
| 필드 내부 | padding 0 10px 0 11px, 텍스트 사이 12px | — |
## 2. 색·표면
| 역할 | 값 | 토큰 |
|---|---|---|
| Header 배경 | `#060606` | `--hm-sw-header-background` |
| 기본 글자 / 제목 | `#F6F4F2` / `#F0F7F5` | `--hm-sw-header-text-color` / `--hm-sw-header-title-color` |
| Selected·On·Hover 글자 | `#FFC600` | `--hm-sw-header-selected-color` |
| 화살표 | `#47AF91` | `--hm-sw-header-arrow-color` |
| 파일명 면 | 세로 gradient, 테두리 `#0E4C3A` | `--hm-sw-header-document-background`, `--hm-sw-header-document-border-color` |
| 객체 수정 면 | gradient, 테두리 `#135643`, radius 6px | `--hm-sw-header-object-background`, `-object-border-color`, `-object-radius` |
| 속성 필드 면 | 검정 45–40% gradient, 테두리 `#08251C`, radius 3px, shadow `0 1px 4px rgba(9,39,30,.7)` | `--hm-sw-header-property-background`, `-property-border-color`, `-property-radius`, `-property-shadow` |
| 인쇄·K pill | `rgba(0,0,0,.8)`, 테두리 `#092920` | `--hm-sw-header-pill-background`, `-pill-border-color` |
| 문서 Dropdown | `#103127`, 테두리 `#041611` | `--hm-sw-header-document-menu-*` |
| 필드 Dropdown(레이어·색상·선·문자·치수) | `#27241D`, 테두리 `#000`, radius 3px, 행 Hover `#62533D` + 글자 `#FF5C00` | `--hm-sw-header-layer-menu-*`, `--hm-sw-header-layer-row-hover-*` |
| Input | 글자 `#E60403`, 편집 `#0F0D0A` | `--hm-sw-header-input-*` |
| Tooltip | `rgba(0,0,0,.8)` / `#FFF` | `--hm-sw-header-tooltip-*` |
사용자 확정 면 3개(파일명·객체 수정·속성 필드)는 기본값 유지를 권장합니다.
## 3. Typography
| 항목 | 값 |
|---|---|
| 글꼴 | Noto Sans KR → Malgun Gothic → Apple SD Gothic Neo (Figma Dotum 대체, 의도된 결정). 폰트 파일은 앱이 로드 |
| 기본 / 라벨 | 14px / 12px (`--hm-sw-header-font-size` / `--hm-sw-header-label-size`) |
| Weight | 400, 500, 700만 사용(600 없음). 값·제목·토글 700 |
| 자간 | 값 0.04em, 라벨 0.02em, 파일명 0.06em |
| Text Stroke | Figma Stroke가 있는 텍스트만 검정 외곽선: 14·16px `--hm-sw-header-text-outline`(1.3px), 12px `--hm-sw-header-label-outline`(1px). 자기 색 Stroke 없음. More·Tooltip·`+ New`/`Open` 제외 |
| 말줄임 | 값은 한 줄 `ellipsis`, `title`로 전체 표시 |
## 4. 상태
| 상태 | 규칙 |
|---|---|
| Hover(테두리 있는 버튼·필드·More·인쇄·K) | 1px 테두리가 노란 세로 gradient로 바뀜(`--hm-sw-header-hover-border-gradient`, 폭 `--hm-sw-header-hover-border-width`). 크기·배경·그림자 변화 없음, 자기 Dropdown이 열린 필드는 Hover 없음 |
| Hover(파일명, 속성Bar/속성창 토글) | 글자만 `#FFC600`. 링·배경 없음 |
| Hover(필드 미리보기) | 텍스트·선/폰트/치수 미리보기 `#FFC600` |
| Selected / On | 글자·단색 아이콘 `#FFC600`(`data-selected="true"`), 배경·테두리 변화 없음 |
| Expanded(Dropdown 열림) | 필드 테두리 `#081813`, 아래 모서리 직각, 필드+목록을 감싸는 1px 노란 링 |
| Focus | 2px `#F0F7F5` outline, offset −2px(`:focus-visible`) |
| Disabled | opacity 0.4, 커서 기본. 창 버튼은 `disabled` 영향 없음 |
| 목록 행 | Hover와 Selected가 같은 강조(`#62533D` + `#FF5C00`) |
## 5. Overlay·Dropdown
- 필드 Dropdown은 필드 바로 아래(`top: 100%`)에 겹쳐 열립니다. Portal·Dim 없음, `z-index` 30. 화면(또는 Header) 밖으로 나가면 좌우만 보정합니다. 부모에 `overflow: hidden`을 두지 않습니다.
- 목록 최대 높이 240px(8행), 넘치면 내부 스크롤. 색상 팔레트 118px.
- 속성창·K 가이드는 `position: fixed` Overlay(뷰포트 안 보정, Portal·Dim 없음).
- 한 번에 하나의 Dropdown(SWHeader). 단독 사용 시 각 컴포넌트가 바깥 클릭으로 스스로 닫힙니다.
## 6. Motion
| 대상 | 규칙 |
|---|---|
| 창 이동 Hover | 1.4s 반복(커서 상승 → 약 444ms 문구 → 오른쪽 위 이동 → 약 888ms부터 유지). Drag(`:active`) 중 정지. Reduced Motion: 원본 아이콘과 정적 Tooltip |
| K 가이드 | 원본 이미지 Pan/Zoom 반복(400ms 대기 후 1022·1022·511·511ms 전환). Reduced Motion 정적 |
| 레이어 토글 | 배경·아이콘 300ms ease-out |
| 기타 | Hover·Selected는 즉시 전환(Transition 없음) |
## 7. 반응형 (`SWHeader` 전용)
Container `hm-sw-header` 기준 Breakpoint: **< 1800** 선/문자/치수스타일 → More, **< 1180** 그리기모드 숨김, **< 1080** 상세보기 → More·창 이동 24px(창 영역 176), **< 900** 색상 → More, **< 640** 인쇄·K → More·창 이동 숨김(창 영역 64), **< 520** 레이어 상태 아이콘 숨김. More에서 고른 필드는 첫 칸(레이어 자리)에 표시되고 Dropdown이 바로 열립니다(UI 상태만, 데이터 Callback 없음).
조합 Header(개별 컴포넌트)에는 이 규칙이 적용되지 않습니다. 필요한 컨트롤만 배치하고 폭 정책은 소비 앱이 정합니다.
## 8. 새 컴포넌트 체크리스트
1. `<SWTheme>`(또는 `SWHeader`의 `tools`) 안에서 렌더링한다.
2. 표면·크기·색은 위 토큰만 쓴다. 필드형이면 38px·속성 필드 면·12px 라벨 + 14px 700 값.
3. Hover/Focus/Disabled/Selected를 §4 규칙대로 구현한다(테두리형은 gradient 링, 텍스트형은 글자만 `#FFC600`).
4. 입력이 필요하면 `SWHeaderInput`을 재사용한다.
5. 값은 Props, 동작은 Callback. CAD 데이터를 내부에서 바꾸지 않는다.
6. `hm-sw-*` class를 새로 쓰거나 덮어쓰지 않는다(자기 접두사 사용, 예: `app-`).
@@ -0,0 +1,40 @@
// Example A — reuse (1st priority): the complete SW Header, unchanged. Existing projects keep this code.
import { useState } from "react"
import { SWHeader, type SWHeaderColorMode, type SWHeaderColorValue } from "@hm-design/sw"
import { colors, dimensionStyles, documents, layers, lineTypes, lineWeights, textStyles } from "./sample-data"
export function ExampleADefaultHeader({ onEvent }: { onEvent?: (name: string) => void }) {
const [activeDocumentId, setActiveDocumentId] = useState(documents[0].id)
const [activeLayerId, setActiveLayerId] = useState("0")
const [color, setColor] = useState<SWHeaderColorValue>({ value: "ByLayer", swatch: "#ffffff" })
const [colorMode, setColorMode] = useState<SWHeaderColorMode | null>("byLayer")
const log = (name: string) => () => onEvent?.(name)
return (
<SWHeader
documentTitle={documents.find(item => item.id === activeDocumentId)?.name ?? ""}
logoAlt="예제 SW"
documents={documents}
activeDocumentId={activeDocumentId}
onDocumentSelect={document => { setActiveDocumentId(document.id); onEvent?.(`document:${document.id}`) }}
onDocumentNew={log("document:new")}
onDocumentOpen={log("document:open")}
layers={layers}
activeLayerId={activeLayerId}
onLayerSelect={layer => { setActiveLayerId(layer.id); onEvent?.(`layer:${layer.id}`) }}
color={color}
colors={colors}
colorMode={colorMode}
onColorSelect={option => { setColor({ value: option.label ?? option.color, swatch: option.color }); setColorMode(null); onEvent?.(`color:${option.id}`) }}
onColorModeChange={mode => { setColorMode(mode); onEvent?.(`colorMode:${mode}`) }}
lineTypes={lineTypes}
lineWeights={lineWeights}
textStyles={textStyles}
dimensionStyles={dimensionStyles}
onHomeClick={log("home")}
onPrint={log("print")}
onPopupGuide={log("popupGuide")}
onMinimize={log("minimize")}
onClose={log("close")}
/>
)
}
@@ -0,0 +1,50 @@
// Example B — composition (2nd priority): a simplified viewer header
// Logo → File → Layer → Color → K
// Only public components; the layout is the consumer's (example-layout.css) and <SWTheme> provides tokens.
import { useState } from "react"
import {
SWHeaderActions, SWHeaderColorMenu, SWHeaderDocumentTitle, SWHeaderHome, SWHeaderLayerMenu, SWTheme,
type SWHeaderColorMode, type SWHeaderColorValue,
} from "@hm-design/sw"
import { colors, documents, layers } from "./sample-data"
import "./example-layout.css"
export function ExampleBViewerHeader({ onEvent }: { onEvent?: (name: string) => void }) {
const [activeDocumentId, setActiveDocumentId] = useState(documents[0].id)
const [activeLayerId, setActiveLayerId] = useState("0")
const [color, setColor] = useState<SWHeaderColorValue>({ value: "ByLayer", swatch: "#ffffff" })
const [colorMode, setColorMode] = useState<SWHeaderColorMode | null>("byLayer")
return (
<SWTheme className="app-sw-bar" role="banner" aria-label="뷰어 헤더">
<SWHeaderHome logoAlt="뷰어" onClick={() => onEvent?.("home")} />
<SWHeaderDocumentTitle
title={documents.find(item => item.id === activeDocumentId)?.name ?? ""}
documents={documents}
activeDocumentId={activeDocumentId}
onDocumentSelect={document => { setActiveDocumentId(document.id); onEvent?.(`document:${document.id}`) }}
onNew={() => onEvent?.("document:new")}
onOpen={() => onEvent?.("document:open")}
/>
<div className="app-sw-surface" role="toolbar" aria-label="보기 설정">
<SWHeaderLayerMenu
value={{ name: "0" }}
layers={layers}
activeLayerId={activeLayerId}
onLayerSelect={layer => { setActiveLayerId(layer.id); onEvent?.(`layer:${layer.id}`) }}
onVisibilityChange={(layer, next) => onEvent?.(`layer-visible:${layer.id}:${next}`)}
/>
<SWHeaderColorMenu
value={color}
colors={colors}
mode={colorMode}
onColorSelect={option => { setColor({ value: option.label ?? option.color, swatch: option.color }); setColorMode(null); onEvent?.(`color:${option.id}`) }}
onModeChange={mode => { setColorMode(mode); onEvent?.(`colorMode:${mode}`) }}
/>
</div>
<div className="app-sw-window">
{/* K only: the existing actions pill with print removed. */}
<SWHeaderActions showPrint={false} onPopupGuide={() => onEvent?.("popupGuide")} />
</div>
</SWTheme>
)
}
@@ -0,0 +1,46 @@
// Example C — composition (2nd priority): a feature-focused header in a different order
// File → Line style → Text style → Dimension style → Print
import { useState } from "react"
import {
SWHeaderActions, SWHeaderDimensionStyleMenu, SWHeaderDocumentTitle, SWHeaderLineStyleMenu, SWHeaderTextStyleMenu, SWTheme,
type SWHeaderDimensionStyleValue, type SWHeaderLineStyleValue, type SWHeaderTextStyleValue,
} from "@hm-design/sw"
import { dimensionStyles, lineTypes, lineWeights, textStyles } from "./sample-data"
import "./example-layout.css"
export function ExampleCFeatureHeader({ onEvent }: { onEvent?: (name: string) => void }) {
const [lineStyle, setLineStyle] = useState<SWHeaderLineStyleValue>({ name: "ByLayer", weight: "ByLayer", scale: "1.00" })
const [textStyle, setTextStyle] = useState<SWHeaderTextStyleValue>({ font: "Arial", name: "Standard" })
const [dimensionStyle, setDimensionStyle] = useState<SWHeaderDimensionStyleValue>({ name: "Standard" })
return (
<SWTheme className="app-sw-bar" role="banner" aria-label="스타일 헤더">
{/* No logo before the file well, so the consumer layout gives it its own left edge. */}
<div className="app-sw-file-edge">
<SWHeaderDocumentTitle title="C0020101-006-평면및종단면도(06)" onClick={() => onEvent?.("document")} />
</div>
<div className="app-sw-surface" role="toolbar" aria-label="스타일">
<SWHeaderLineStyleMenu
value={lineStyle}
lineTypes={lineTypes}
lineWeights={lineWeights}
onLineTypeSelect={type => { setLineStyle(current => ({ ...current, name: type.name })); onEvent?.(`lineType:${type.id}`) }}
onLineWeightSelect={weight => { setLineStyle(current => ({ ...current, weight: weight.name })); onEvent?.(`lineWeight:${weight.id}`) }}
onScaleChange={scale => { setLineStyle(current => ({ ...current, scale })); onEvent?.(`lineScale:${scale}`) }}
/>
<SWHeaderTextStyleMenu
value={textStyle}
textStyles={textStyles}
onTextStyleSelect={style => { setTextStyle({ font: style.font, name: style.name }); onEvent?.(`textStyle:${style.id}`) }}
/>
<SWHeaderDimensionStyleMenu
value={dimensionStyle}
dimensionStyles={dimensionStyles}
onDimensionStyleSelect={style => { setDimensionStyle({ name: style.name }); onEvent?.(`dimensionStyle:${style.id}`) }}
/>
</div>
<div className="app-sw-window">
<SWHeaderActions showPopupGuide={false} onPrint={() => onEvent?.("print")} />
</div>
</SWTheme>
)
}
@@ -0,0 +1,30 @@
// Example D — existing Layer / Color components next to a NEW consumer control (SWSearchControl).
import { useState } from "react"
import { SWHeaderColorMenu, SWHeaderLayerMenu, SWTheme, type SWHeaderColorValue } from "@hm-design/sw"
import { colors, layers } from "./sample-data"
import { SWSearchControl } from "./SWSearchControl"
import "./example-layout.css"
export function ExampleDSearchExtension({ onEvent }: { onEvent?: (name: string) => void }) {
const [activeLayerId, setActiveLayerId] = useState("0")
const [color, setColor] = useState<SWHeaderColorValue>({ value: "ByLayer", swatch: "#ffffff" })
const [query, setQuery] = useState("")
return (
<SWTheme className="app-sw-bar" role="banner" aria-label="검색 헤더">
<div className="app-sw-surface" role="toolbar" aria-label="객체 찾기">
<SWHeaderLayerMenu
value={{ name: "0" }}
layers={layers}
activeLayerId={activeLayerId}
onLayerSelect={layer => { setActiveLayerId(layer.id); onEvent?.(`layer:${layer.id}`) }}
/>
<SWHeaderColorMenu
value={color}
colors={colors}
onColorSelect={option => { setColor({ value: option.label ?? option.color, swatch: option.color }); onEvent?.(`color:${option.id}`) }}
/>
<SWSearchControl value={query} onValueChange={setQuery} onSearch={text => onEvent?.(`search:${text}`)} />
</div>
</SWTheme>
)
}
@@ -0,0 +1,41 @@
// Example D extension — a NEW control written in the consuming project (3rd priority).
// Decision record: the package has no search component (MISSING). Its editable SWHeaderInput covers the
// text entry (Enter / blur commit, Escape cancel, IME), so it is reused; only the field surface and the
// search icon button are new. They read the public SW tokens (sw-search-control.css) — no copied
// package code, no hm-sw-* class, no new design values. Not registered as a package component.
import { SWHeaderInput } from "@hm-design/sw"
import "./sw-search-control.css"
export interface SWSearchControlProps {
/** Committed query (host state). */
value: string
/** A new query was committed with Enter or by leaving the input. */
onValueChange?: (value: string) => void
/** Search with the current query (icon button or a committed query). */
onSearch?: (query: string) => void
label?: string
disabled?: boolean
}
export function SWSearchControl({ value, onValueChange, onSearch, label = "검색", disabled }: SWSearchControlProps) {
return (
<div className="app-sw-search" role="search" data-disabled={disabled || undefined}>
<span className="app-sw-search__text">
<span className="app-sw-search__label" aria-hidden="true">{label}</span>
<SWHeaderInput
aria-label={`${label}어`}
value={value}
placeholder="객체 이름"
disabled={disabled}
onCommit={next => { onValueChange?.(next); onSearch?.(next) }}
/>
</span>
<button type="button" className="app-sw-search__button" aria-label={label} disabled={disabled} onClick={() => onSearch?.(value)}>
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<circle cx="7" cy="7" r="4.75" fill="none" stroke="currentColor" strokeWidth="1.5" />
<path d="M10.5 10.5 14 14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</button>
</div>
)
}
@@ -0,0 +1,36 @@
/* Consumer layout for the composed examples (B, C, D). It belongs to the consuming project: it only
arranges public SW components and reads the public --hm-sw-header-* tokens provided by <SWTheme>.
It never styles hm-sw-* classes and adds no new design values. */
.app-sw-bar {
display: flex;
align-items: stretch;
gap: 2px;
box-sizing: border-box;
height: var(--hm-sw-header-height);
padding-bottom: 1px; /* 48px surfaces in the 49px header, like SWHeader */
background: var(--hm-sw-header-background);
}
/* Object (editing) surface, the same tokens as the SWHeader object area. */
.app-sw-surface {
display: flex;
align-items: center;
gap: var(--hm-sw-header-gap);
flex: 1 1 0;
min-width: 0;
padding: 4px;
border: 1px solid var(--hm-sw-header-object-border-color);
border-radius: var(--hm-sw-header-object-radius);
background: var(--hm-sw-header-object-background);
}
/* Window-area surface for print / K / window buttons. */
.app-sw-window {
display: flex;
flex: 0 0 auto;
background: var(--hm-sw-header-document-background);
box-shadow: inset 0 0 0 1px var(--hm-sw-header-document-border-color); /* 1px edge without moving the parts */
}
/* File well placed first (no home overlap): its own left edge. */
.app-sw-file-edge {
display: flex;
border-left: 1px solid var(--hm-sw-header-document-border-color);
}
+52
View File
@@ -0,0 +1,52 @@
// Sample host data for the examples. Every type comes from the public package API.
import {
swHeaderLinePatterns,
type SWHeaderColorOption, type SWHeaderDimensionStyleOption, type SWHeaderDocument, type SWHeaderLayer,
type SWHeaderLineType, type SWHeaderLineWeight, type SWHeaderTextStyleOption,
} from "@hm-design/sw"
export const documents: SWHeaderDocument[] = [
{ id: "doc-1", name: "C0020101-006-평면및종단면도(06)" },
{ id: "doc-2", name: "C0020101-007-횡단면도(07)" },
]
export const layers: SWHeaderLayer[] = [
{ id: "0", name: "0", color: "#ffffff" },
{ id: "center", name: "CENTER", color: "#ff0000" },
{ id: "hidden", name: "HIDDEN", color: "#ffe500", visible: false },
{ id: "dim", name: "DIM", color: "#00ff00", locked: true },
]
export const colors: SWHeaderColorOption[] = [
{ id: "white", color: "#ffffff", label: "흰색" },
{ id: "red", color: "#ff0000", label: "빨강" },
{ id: "green", color: "#00ff00", label: "초록" },
{ id: "yellow", color: "#ffff00", label: "노랑" },
{ id: "blue", color: "#0000ff", label: "파랑" },
{ id: "magenta", color: "#ff00ff", label: "자홍" },
{ id: "cyan", color: "#00ffff", label: "청록" },
]
export const lineTypes: SWHeaderLineType[] = [
{ id: "bylayer", name: "ByLayer" },
{ id: "continuous", name: "Continuous" },
{ id: "center", name: "CENTER", pattern: swHeaderLinePatterns.center },
{ id: "hidden", name: "HIDDEN", pattern: swHeaderLinePatterns.hidden },
]
export const lineWeights: SWHeaderLineWeight[] = [
{ id: "bylayer", name: "ByLayer" },
{ id: "byblock", name: "ByBlock" },
{ id: "default", name: "ByDefault", value: "0.25" },
{ id: "user", name: "User", value: "0.50", editable: true },
]
export const textStyles: SWHeaderTextStyleOption[] = [
{ id: "standard", name: "Standard", font: "Arial" },
{ id: "dotum", name: "Dotum", font: "돋움" },
]
export const dimensionStyles: SWHeaderDimensionStyleOption[] = [
{ id: "standard", name: "Standard" },
{ id: "iso", name: "ISO-25" },
]
@@ -0,0 +1,48 @@
/* Consumer extension styles. Every value is a public SW token (see ../DESIGN_RULES.md); the
38px field height, 12px label / 14px bold value and 1px gradient hover ring follow the SW field rules. */
.app-sw-search {
position: relative;
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
height: var(--hm-sw-header-field-height);
padding: 0 6px 0 11px;
border: 1px solid var(--hm-sw-header-property-border-color);
border-radius: var(--hm-sw-header-property-radius);
background: var(--hm-sw-header-property-background);
box-shadow: var(--hm-sw-header-property-shadow);
}
/* Hover: the bordered-control rule — the 1px border becomes the yellow gradient; nothing moves. */
.app-sw-search::after {
content: "";
position: absolute;
inset: calc(-1 * var(--hm-sw-header-hover-border-width));
padding: var(--hm-sw-header-hover-border-width);
border-radius: inherit;
background: var(--hm-sw-header-hover-border-gradient);
/* Ring only: the content box is cut out (prefixed first, the standard declaration last wins). */
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
opacity: 0;
pointer-events: none;
}
.app-sw-search:hover:not([data-disabled])::after { opacity: 1; }
.app-sw-search__text { display: flex; flex-direction: column; min-width: 0; }
.app-sw-search__label {
height: 14px;
font-size: var(--hm-sw-header-label-size);
line-height: 14px;
letter-spacing: 0.02em;
text-shadow: var(--hm-sw-header-label-outline);
}
.app-sw-search__button {
display: grid;
place-items: center;
width: 24px;
height: 24px;
border-radius: var(--hm-sw-header-property-radius);
}
/* Text-only hover / selected color, like the SW toggles. */
.app-sw-search__button:hover:not(:disabled) { color: var(--hm-sw-header-selected-color); }