Merge pull request 'Fix/sw step header middle background' (#9) from fix/sw-step-header-middle-background into main
Deploy Storybook / deploy (push) Successful in 1m16s
Deploy Storybook / deploy (push) Successful in 1m16s
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
commit
7ed7d50499
30 files changed
+481
-63
No files matched your search
@@ -1,5 +1,23 @@
|
||||
# Working Context
|
||||
|
||||
## 2026-11-05 — Step Header 프로세스 영역 양 끝 직각
|
||||
|
||||
- 사용자 결정: `SWProcessHeader appearance="multi"`만 프로세스 영역(파일명 옆·창 영역 옆) 위 모서리를 직각으로(`default`는 Strana 6px·덮개 5px 유지 — 처음 두 타입 모두로 들었다가 사용자 정정). `sw-process-header.css` multi 블록에 바탕 `::before`·양 끝 덮개 `border-radius: 0` 1줄. 단계 탭 캡슐·화살표·사다리꼴 면·색·높이 변경 없음. Header 최외곽은 원래 직각.
|
||||
- 문서: DESIGN_RULES §14 프로세스 바탕 행, Process 명세 §9.2. 회귀 검사 `process-header-states.check.mjs`에 default 6/5 유지·multi 0 검사 추가. 미Commit.
|
||||
|
||||
## 2026-11-05 — Header 스타일 적용 범위·빈 파일명 Hover·Step 활성화 규칙
|
||||
|
||||
- 단일 기준: `docs/ai/AI_GUIDE.md` 5단계 "Header 전용 스타일의 적용 범위"(4개 패키지 사본 sync, sw 사본 LF 유지). 참조만: AI_GUIDE 6단계 5번, `packages/sw/ai/AI_USAGE.md` §3, `packages/ui/ai/AI_USAGE.md` §3.
|
||||
- `SWProcessHeader`: `documentTitle=""`이면 루트 `data-document-untitled`, 파일명 면 Hover 색 변화 없음·`cursor: default`(`sw-process-header.css` 빈 상태 블록). 버튼·`onDocumentClick`·크기·위치·색은 유지 — 테스트 앱 백업(`D:\신규 헤더 테스트_backup_*`)과 Storybook Live Multi Step이 빈 상태 클릭을 로그로 사용 중이라 클릭은 제거하지 않음.
|
||||
- Step 활성화: 코드 변경 없음. `disabled: true`(단계·그룹·process·Header)만 비활성, 파일명·프로젝트 상태 무관을 `COMPONENT_CATALOG.md` §11 "단계 활성화"에 명시, `COMPOSITION_PATTERNS.md` §12-6 문구 수정. 회귀 검사 `packages/sw/tests/process-header-states.check.mjs`.
|
||||
- 보류: 도구 아이콘 5개 Export(출처·권한 미확인, Figma에 Iconify `mage:` 흔적), `public-api.approved.json`, Step 선택 애니메이션, 테스트 프로젝트 생성. 미Commit.
|
||||
|
||||
## 2026-11-05 — 시안 기반 Header 도구 아이콘·창 제어 규칙 (B안, 문서만)
|
||||
|
||||
- 단일 기준: `packages/sw/ai/AI_USAGE.md` §5(기본 구성 vs 시안 재현, 아이콘 1~3순위, Storybook 전용 vs 공개, 상태 처리, 창 제어 `hiddenControls`, 예시, 보고).
|
||||
- 참조만 추가: `docs/ai/AI_GUIDE.md` 아이콘 칸(+ 4개 패키지 사본 sync, sw 사본은 LF 유지), `COMPOSITION_PATTERNS.md` §12·§9, `COMPONENT_CATALOG.md` §4·§11, `DESIGN_RULES.md` §9.
|
||||
- 보류: Figma 도구 아이콘 5개(help·view-3d·controls·menu-list·exit) Export — 출처·권한 미확인. 제안 구조 `swProcessToolIcons`와 전제 조건은 `docs/sw/sw-process-header-design-spec.md` §13. 소스·Props·CSS·공개 API·Fixture 변경 없음.
|
||||
|
||||
## 2026-11-03 — SWProcessHeader 1차 구현 (Strana Main Header)
|
||||
|
||||
- 작업 브랜치: `feature/sw-process-header`(main 6ca92c5에서 분기). 미Commit·미Push — Commit·Push는 사용자 별도 지시 대상.
|
||||
|
||||
+15
-2
@@ -141,7 +141,7 @@
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | VIEWER.md `SWViewerHeader`·디자인 변경 절 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | VIEWER.md 디자인 변경 절(52px Header 등 실측값) | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | VIEWER.md `SWViewerPanel` 절(프레임은 SWPopup과 같음) | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon` | Core 내장 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 사용자·프로젝트 제공 에셋 → 패키지 공개 Export 중 같은 기능 → 없으면 미지원 보고(외부 라이브러리·AI 생성 SVG·Storybook 파일로 대체 금지). 시안 기반 Header 도구 아이콘·창 제어 버튼은 `@hm-design/sw` `ai/AI_USAGE.md` §5 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon`. 선택 순서는 SW 일반 모드와 같음(`AI_USAGE.md` §5) | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | 전용 디자인 없음 — 패널 본문 스크롤은 `SWViewerPanel` | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | VIEWER.md `SWViewerHeader` 절 상태 표현 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | `SWViewerPanel`(+ `modal`) + `SWViewerSection`/`SWViewerField` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
@@ -178,6 +178,19 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
3. 패키지가 실제로 공유하는 부분은 혼합이 아닙니다: `SWViewerPanel`은 `SWPopup` 프레임을 그대로 렌더링하고, 두 Header는 파일명·객체 수정·속성 면 토큰(`--hm-sw-header-document-*`·`object-*`·`property-*`)을 공유합니다. 소비 코드가 이 공유를 흉내 내 다른 모드 부품을 섞어도 된다는 뜻은 아닙니다.
|
||||
4. 프로그램이 두 모드를 모두 가지면 모드 전환 시 Header 컴포넌트를 바꿔 끼웁니다(파일·패널 상태는 프로그램 상태로 유지). 세부는 `@hm-design/sw/ai/AI_USAGE.md` §0과 `VIEWER.md`.
|
||||
|
||||
### Header 전용 스타일의 적용 범위 (2026-11-05)
|
||||
|
||||
화면 상단 Header 컴포넌트 — SW `SWHeader`·`SWProcessHeader`·`SWViewerHeader`, Web `GNB`·`LNBMobileHeader`·`DashboardHeader` — 의 전용 스타일은 **그 Header 영역 안에서만** 씁니다. 전용 스타일은 Header의 class(`hm-sw-header__*`·`hm-sw-process-*`·`hm-sw-viewer-header__*`, Web Header의 `data-slot`)와 Header 모양을 정하는 값(배경·gradient·테두리·글자 외곽선·높이·간격)입니다. `DialogHeader`·`CardHeader`처럼 다른 컴포넌트 안의 머리 부품은 여기서 말하는 Header가 아닙니다. 이 규칙은 아래를 정할 뿐 기존 Header의 레이아웃·스타일·공개 API를 바꾸지 않습니다.
|
||||
|
||||
1. Header 밖의 본문·패널·카드·버튼·입력 필드와 다른 컴포넌트에 Header 전용 class·CSS·값을 붙이거나 복사하지 않습니다. "Header 스타일로" 같은 요청도 Header 영역에만 적용하고, 별도 요청이 없으면 Header 밖 기존 UI의 디자인과 동작은 그대로 둡니다.
|
||||
2. 화면 전체 토큰·테마(Web Runtime Theme `--hm-sem-*`, SW 가장 바깥 범위의 `style` 토큰, 위 표의 범위)와 Header 전용 스타일을 구분합니다. Header를 맞추려고 화면 전체 테마 값을 바꾸지 않습니다.
|
||||
3. Header를 구현하려고 전역 CSS(`html`·`body`·`*`, 공통 reset)를 바꾸거나 기존 본문 스타일을 덮어쓰지 않습니다.
|
||||
4. Header와 본문의 경계에서는 배치에 필요한 연결만 소비 프로젝트 class로 처리합니다(Header 높이만큼 본문 시작 위치, sticky·z-index, 남는 높이를 본문에 주는 flex 등).
|
||||
5. 공식 컴포넌트 내부 스타일을 소비 프로젝트에서 Override하지 않습니다(위 혼합 규칙 3). 바꿀 수 있는 것은 각 패키지가 공개한 Props·토큰뿐입니다.
|
||||
6. 시안에 본문 디자인이 따로 있으면 그 영역의 패키지(위 혼합 규칙 1)의 공식 컴포넌트로 구현합니다. Header와 같은 패키지의 부품을 Header 밖에 둘 때도 그 부품의 공식 사용법(SW는 `<SWTheme>` 안)을 따르고 Header 모양을 덧입히지 않습니다.
|
||||
7. 패키지가 공식으로 공유하는 부분은 위반이 아닙니다(예: `SWPopup`·`<SWTheme>` 부품이 `--hm-sw-header-*` 토큰을 읽음, 위 SW 분리 3). 소비 코드가 이를 흉내 내 Header 스타일을 다른 곳에 쓰면 안 됩니다.
|
||||
8. 사용자가 화면 전체에 Header 디자인을 적용하라고 **명시한 경우에만** 범위를 넓힙니다. 이때도 공식 컴포넌트·공개 토큰으로만 맞추고 Header class를 복사하지 않으며, 결과 보고에 넓힌 범위를 적습니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
@@ -186,7 +199,7 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW 일반 모드는 대상 창 폭(Header는 반응형 More 포함), SW 뷰어 모드는 대상 창 폭을 줄였다 늘리며 실제 공간 부족에 따른 단계(`iconOnlyBelow` 항목 아이콘만 → `collapseBelow` 항목 More 이동, 숫자는 px 경계가 아니라 순서) 전후, OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지(§5)
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지, Header 전용 스타일이 Header 밖에 쓰이지 않았는지(§5, "Header 전용 스타일의 적용 범위")
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
@@ -321,10 +321,10 @@ SW Header는 여러 SW 프로그램이 공통으로 사용합니다. 따라서 *
|
||||
|
||||
| 항목 | 기준 | 구분 |
|
||||
|---|---|---|
|
||||
| 로고 영역 | 홈 버튼(80 × 48) 안 x 12 / y 16, 32 × 17 (Figma `bi_egbim` frame). 토큰 `--hm-sw-header-logo-width` / `-logo-height` | 공통 디자인(확정) |
|
||||
| 로고 영역 | 홈 버튼 안 x 12 / 세로 가운데, 높이 17 · 폭은 로고 내용 폭(2026-10-23부터, Figma `bi_egbim` frame 32 × 17은 예시이며 이때 홈 80 × 48). 홈 = 12 + 로고 + 36, 사선 배경이 로고 폭을 따라 늘거나 줄며 고정 폭·로고별 예외 없음. 토큰 `--hm-sw-header-logo-width`(기본 `auto`) / `-logo-height` | 공통 디자인(확정) |
|
||||
| 홈 배경 | `home-bg.svg` 사선 녹색 면 + 0–65px 파일명 gradient 바탕 | 공통 디자인(확정) |
|
||||
| 로고 이미지 | `logoSrc`(이미지 URL) 또는 `logo`(React 요소)로 각 프로그램이 전달. 비율이 달라도 영역 안에 `contain`으로 맞추고 넘치지 않음 | 프로그램별 결정 |
|
||||
| 로고 미전달 | 로고를 그리지 않음. 홈 배경·파일명·객체 수정 영역 위치는 그대로 | 공통 동작 |
|
||||
| 로고 이미지 | `logoSrc`(이미지 URL) 또는 `logo`(React 요소)로 각 프로그램이 전달. 이미지·SVG는 높이 17·원본 비율로 표시하고 그 폭만큼 홈이 늘어남(잘리지 않음). 커스텀 글자 요소는 한 줄·`line-height: 17px`로 전달(로고 칸은 상자만 17로 제한, CSS가 강제하지 않음) | 프로그램별 결정 |
|
||||
| 로고 미전달 | 로고를 그리지 않음. 빈 로고 자리 32px 유지(홈 80 × 48), 홈 배경·파일명·객체 수정 영역 위치는 그대로 | 공통 동작 |
|
||||
| 접근성 | `logoAlt` 전달 시 홈 버튼 이름 "<logoAlt> 홈", 없으면 "홈" | 공통 동작 |
|
||||
|
||||
- Figma 26:8944의 이지빔 로고(`bi_egbim`)는 기준 화면의 예시이며 공통 컴포넌트에 넣지 않습니다(2026-10-16 사용자 지시로 패키지에서 제거).
|
||||
@@ -642,7 +642,7 @@ SW Header는 Figma Node 26:8944를 기준으로 구현한다. 사용자가 확
|
||||
5. 선택은 글자·단색 아이콘 `#FFC600`만 바꾸고 배경·테두리·그림자를 추가하지 않는다. Focus 표시를 지우지 않는다.
|
||||
6. 기능이 화면에서 빠질 때는 반드시 더보기로 옮긴다. 필드를 줄여 값을 자르지 말고 기능을 옮긴다. 헤더 가로 스크롤을 만들지 않는다.
|
||||
7. Figma Assets는 `packages/sw/src/assets/`의 원본 SVG를 쓴다. 임시 Figma URL·스크린샷 이미지를 쓰지 않는다.
|
||||
8. 로고는 확정 디자인이 아니다. 공통 컴포넌트에 특정 SW 로고·브랜드명·파일 경로를 넣지 않고, 각 프로그램이 `logoSrc` 또는 `logo`와 `logoAlt`로 전달한다. 로고 영역의 위치·크기는 바꾸지 않는다.
|
||||
8. 로고는 확정 디자인이 아니다. 공통 컴포넌트에 특정 SW 로고·브랜드명·파일 경로를 넣지 않고, 각 프로그램이 `logoSrc` 또는 `logo`와 `logoAlt`로 전달한다. 로고 영역의 위치(x 12·세로 가운데)와 높이 17은 바꾸지 않고, 폭은 로고 내용을 따른다(고정 폭·로고별 예외를 추가하지 않는다).
|
||||
9. Hover 테두리는 §11.1의 노란 gradient 1px을 사용한다. 배경·크기·그림자를 바꾸지 않고, 새 버튼도 같은 토큰과 대상 목록 규칙을 따른다. 텍스트·아이콘 예외는 §11.1·§11.2·§11.5를 따른다(5개 필드의 텍스트와 지정 미리보기는 §11.5, 최소화·종료는 Figma 전용 hover, 인쇄·팝업 가이드는 기존 테두리 + 지정 fill 변경). SVG는 원본의 해당 foreground fill만 바꾼 로컬 Variant를 사용하며 CSS filter를 쓰지 않는다.
|
||||
|
||||
## 17. 향후 공통화 검토 참고
|
||||
@@ -655,6 +655,8 @@ SW Header는 Figma Node 26:8944를 기준으로 구현한다. 사용자가 확
|
||||
| 유사하지만 의미 다름 | 선택 노랑 `#FFC600`(HM 팝업 제목 노랑과 같은 값이나 역할 다름), 녹색 계열(HM `green-*` palette와 값 비교 필요) |
|
||||
| 공통화 후보 | Noto Sans KR·400/500/700 정책, 더보기 메뉴(HM `DropdownMenu`와 기능 동일 — 스타일은 SW 전용), Focus 정책, 말줄임·no-scroll 반응형 원칙 |
|
||||
| 비교 필요 | 필드 38px vs HM control height, radius 3/6 vs HM radius scale, Hover 색 |
|
||||
| class 역할 분리 검토(2026-11-05) | 인쇄 버튼 class `hm-sw-header__action--print`가 `SWToolButton appearance="pill"`(`tools`, `SWProcessHeader`, `SWProcessSplitButton`)에도 붙어 pill 외형을 겸합니다. 분리 여부는 별도 구현 과제이며 영향 범위·원칙은 `packages/sw/ai/DESIGN_RULES.md` §15.3 |
|
||||
| 원문 확인·툴팁 | 메뉴 타이틀(필드 12px 라벨)은 말줄임되지만 `title`이 없고 버튼 `aria-label`만 원문입니다. 인쇄·창 버튼은 `aria-label`만 있습니다. 공통 원칙·개선 필요 항목은 DESIGN_RULES §15.1 · §15.2 |
|
||||
|
||||
## 추가 수정 기준 — 속성창·K 가이드 (2026-10-16)
|
||||
|
||||
@@ -696,7 +698,7 @@ SW Header는 Figma Node 26:8944를 기준으로 구현한다. 사용자가 확
|
||||
|
||||
## 3차 기준 — 구성요소 표시·메뉴 순서·속성창 상태·형상 Slot (2026-10-16)
|
||||
|
||||
- **로고 필수:** 로고 슬롯(32×17, 홈 배경 위 12,16)은 모든 구성에서 표시합니다. `home` 제거 시 같은 배경·위치의 정적 로고(Hover 없음)입니다.
|
||||
- **로고 필수:** 로고 슬롯(높이 17·로고 폭, 홈 배경 위 x 12·세로 가운데)은 모든 구성에서 표시합니다. `home` 제거 시 같은 배경·위치의 정적 로고(Hover 없음)입니다.
|
||||
- **개별 제거:** `hiddenControls`로 홈·파일·그리기모드·5개 메뉴·상세보기 caption·속성Bar·속성창·인쇄·K·창 이동·최소화·종료를 각각 제거합니다. 남은 요소는 기본 크기를 유지하고 제거한 공간은 남기지 않습니다(구현값: 상세보기 블록 일부 제거 시 폭 auto·최소 64px, 인쇄/K pill과 창 버튼은 남은 버튼 폭, 창 영역 폭 auto). 아무것도 제거하지 않으면 기존 1920px 디자인과 동일합니다.
|
||||
- **창 이동 Tooltip(구현값):** 최소화·종료 중 하나 이상을 제거하면 창 영역 오른쪽 밖은 잘리고(Header 밖 노출 방지), 1080px 미만 24px 이동 영역의 Tooltip은 이동 영역 왼쪽에 표시합니다. 기본 구성의 1400ms Motion·위치는 변경하지 않았습니다.
|
||||
- **메뉴 순서:** `menuOrder`는 5개 유사 메뉴끼리만 자리를 바꿉니다. 기본은 레이어 → 색상 → 선스타일 → 문자스타일 → 치수스타일(Figma 26:8944)입니다.
|
||||
|
||||
@@ -38,7 +38,7 @@ Strana Grid의 별 비율을 CSS flex 비율로 옮겼습니다. 숨김 단계·
|
||||
|
||||
| 영역 | Strana | 구현 |
|
||||
|---|---|---|
|
||||
| 홈 | 106 × 48, 오른쪽 17px이 파일명 위로 겹침 | **SWHeader 우선(2026-11-03):** `SWHeaderHome` 그대로 = 12 + 로고 칸(높이 17, 로고 비율 폭) + 36, 32 × 17 로고 → 80. 오른쪽 15px이 파일명과 겹친 뒤 2px 간격(SWHeader bar gap, `margin-right: -15px`). `logoText`는 18px 700, 58px 칸(→ 106) |
|
||||
| 홈 | 106 × 48, 오른쪽 17px이 파일명 위로 겹침 | **SWHeader 우선(2026-11-03):** `SWHeaderHome` 그대로 = 12 + 로고 칸(높이 17, 로고 비율 폭) + 36, 32 × 17 로고 → 80. 오른쪽 15px이 파일명과 겹친 뒤 2px 간격(SWHeader bar gap, `margin-right: -15px`). `logoText`는 18px 700·`--hm-sw-header-text-color`·SW 외곽선·한 줄(줄바꿈 없음), 최소 58px 칸 가운데(→ 홈 최소 106). 글자가 더 넓으면 칸이 글자 폭만큼 넓어지고 홈(클릭 영역)·사선 배경·파일명 시작 위치가 따라감(2026-11-05, 잘림 없음). 홈은 줄지 않으므로(`flex: 0 0 auto`) 늘어난 폭만큼 파일명(최소 100)과 가운데 영역이 flex 비율대로 줄어듦. 참고 실측(Noto Sans KR, 글꼴에 따라 다름): StrAna 59.29 → 홈 107.29, Accessway 93.72 → 홈 141.72 |
|
||||
| 파일명 | `286*`, 최소 100, 최대 286, 글자 시작 22 | 폭만 Strana: `flex: 286 1 0`, `min-width: 100px`, `max-width: 286px`. 글자는 SWHeader 그대로(x 65 시작, padding 5 / 12 → 글자 x 70, 14px 700·자간 0.84px·SW 외곽선·세로 가운데), SW 화살표 숨김 |
|
||||
| 가운데 | `1260*` | `flex: 1260 1 0` |
|
||||
| 프로세스 | `1003*` | `flex: 1003 1 0` |
|
||||
@@ -229,6 +229,7 @@ Hanmac 열이 기본값입니다. 다른 테마는 Header의 `style`(가장 바
|
||||
| 폭 측정(파일명 / 프로세스 / Layer 칸 / 창 영역) | 1920: 280.8 / 984.8 / 252.3 / 313 = Strana 별 비율 계산값. 1100: 129.1 / 416.9 / 152(최소) / 313. 1366: 178.3 / 625.4 / 160.3 / 313. 2560: 286(최대) / 1615 / 257(최대) / 313. 넘침 없음. 홈 80(SWHeader 기준, 2026-11-03) 이후 파일명 / 프로세스: 1100 133.7 / 437, 1366 182.9 / 641.4, 1920 285.4 / 1000.8, 2560 286 / 1639.7, 창 영역 312, 넘침 없음 |
|
||||
| Strana 캡처 비교(1.5배, 나란히) | Hanmac 1920 Modeling · Results, 1366, 1100 — 배치·면·발광·화살표·탭·Layer·도구·창 버튼 일치. 차이: 글자 외곽선 두께, 파일명 자간, 좁은 폭의 말줄임(§5) |
|
||||
| 동작 | 탭 클릭·Arrow·Home·End(비활성 건너뜀), 같은 탭 재클릭 시 Callback 없음, 전구·필드 분리, 최대화/복원 전환, 로고 열림 모양, 도구 눌림 모양, Hover(종료 `#E40000`, 최소화, Layer 링, 탭 글자), Focus outline |
|
||||
| 로고 폭(2026-11-05, Chromium, `multi`, 1440·1100) | 기준: 글자 잘림 0, 홈 = 12 + 로고 칸 + 36, 사선 배경 = 홈 + 12(왼쪽 −8 · 오른쪽 +4, 홈 끝을 따라감), 홈·파일명 겹침 15, Header 49, 단계 클릭·Enter 동작 유지. 참고 실측: Accessway 홈 106 → 141.72에서 가운데 영역 1440 1024.2 → 992.5(−31.7), 1100 728 → 696.89(−31.11), 파일명 151.47 → 146.78 / 107.67 → 103.06. 더 긴 글자(187.44)는 파일명이 최소 100에서 멈춤. 이미지 로고(32 × 17) Story는 `default`·`multi` 모두 홈 80 그대로. 커스텀 `logo`는 코드 경로만 확인(같은 로고 칸) |
|
||||
| 기존 `SWHeader` | 변경 전후 빌드에서 `SW/Header` Overview · Responsive More 출력 HTML 동일, `dist/styles.css`의 기존 내용 동일(새 규칙만 추가) |
|
||||
|
||||
Strana 비교 캡처는 `D:\Strana\_verification\header-rework-20261029\renders\Hanmac-{1100,1366,1920}-*.png`(최종 Commit 7173e82 · b7f412c 이전 렌더)입니다. 분할 버튼 변경(b7f412c) 이후 최신 캡처와 다른 테마(Dark · Light · HighContrast)의 화면 비교는 미확인입니다. Firefox · Safari는 확인하지 않았습니다.
|
||||
@@ -250,7 +251,7 @@ Strana 비교 캡처는 `D:\Strana\_verification\header-rework-20261029\renders\
|
||||
### 9.2 `default`와 공유한 것 / 다른 것
|
||||
|
||||
- **사다리꼴 배치:** 시작·끝 그룹에만(§4 확정 규칙). 873:17421처럼 가운데 그룹이 켜지면 도형 없이 밝은 바탕만 보입니다(2026-11-03 수정 — 이전 구현은 켜진 가운데 그룹에도 발광 면을 그렸음).
|
||||
- **같은 구현(값 대조 일치):** 프로세스 바탕(ProcessBase gradient · DocBorder · 위 모서리 6), 비활성 덮개(`#004430` 90%, 꼭지·홈 모양 포함), 활성 발광 면·비활성 바깥 면(같은 경로), 화살표(선·채움·그늘 같은 값, Dim 25% / 50%), 홈 도형, 파일명 면 gradient, 창 이동 아이콘·줄무늬 색, 캡션 버튼 글리프, 선택·키보드·Callback.
|
||||
- **같은 구현(값 대조 일치):** 프로세스 바탕(ProcessBase gradient · DocBorder. 위 모서리는 2026-11-05부터 `multi`만 직각 — 사용자 결정, `default`는 6 유지), 비활성 덮개(`#004430` 90%, 꼭지·홈 모양 포함), 활성 발광 면·비활성 바깥 면(같은 경로), 화살표(선·채움·그늘 같은 값, Dim 25% / 50%), 홈 도형, 파일명 면 gradient, 창 이동 아이콘·줄무늬 색, 캡션 버튼 글리프, 선택·키보드·Callback.
|
||||
- **multi 전용 CSS:** 49px 면, 파일명 최대 189·단색 테두리(로고·파일명 글자는 SWHeader 기준으로 default와 공통), SW 기본 pill(41 · 40 × 37), 창 이동 위치, 그룹 내용(제목 위 · 탭 25), 탭 4상태, 발광 면 위치(왼쪽 50 / 반전 48).
|
||||
- **새로 만든 것:** 진행 완료 상태(`data-progress="done"`, multi만), `documentEmptyLabel`, `labelHidden`, `appearance`. 컴포넌트 DOM은 하나이며 multi는 탭의 층 9개 대신 면 하나만 그립니다.
|
||||
- **Figma 도구 아이콘 5개**(help · view-3d · controls · menu-list · exit-arrow)는 Story fixture(`src/sw/assets/process-header/tool-*.svg`, Figma 원본)에만 둡니다.
|
||||
@@ -325,3 +326,36 @@ Strana 비교 캡처는 `D:\Strana\_verification\header-rework-20261029\renders\
|
||||
|
||||
- **Popup 하단 잘림(2026-11-03, 수정):** 재현 조건 = Popup 아래 공간이 부족한 화면(예: Storybook Canvas 높이 400px의 Multi Step). 공용 위치 훅 `useSWFixedDropdownPosition`이 아래 공간이 없으면 필드 위로 뒤집는데(`flipped`), 이 선택기는 꼬리·패널을 항상 아래쪽 모양으로 그려 Header 위에 겹치고 패널 하단이 Header에 가려졌습니다(아이콘 SVG·`viewBox`·`overflow`·세로 정렬은 정상). 수정: `SWProcessSplitButton`이 뒤집힘을 쓰지 않고 항상 버튼 아래에 붙습니다(좌우 보정은 훅 그대로). 아래 공간이 부족하면 화면 끝에서 잘리지만 페이지 스크롤에 따라 위치가 갱신되어 스크롤하면 전체가 보입니다. Default·Multi 공통(같은 컴포넌트), CSS 변경 없음.
|
||||
- **아이콘 하단 잘림(2026-11-03, 수정):** 사용자 Chrome(배율 100%, 화면 1920 × 1080, Storybook 미리보기 1620 × 871)에서 재현. Responsive 1366 · 1100 행의 Popup 첫 열림에서 6개 틀이 21줄로 그려져 판 아래 밝은 테두리가 사라짐(아래가 잘린 것처럼 보임). 원인: 틀 상자 31.6 × 21.73(소수)이 화면 픽셀로 맞춰질 때 위치에 따라 21줄 또는 22줄이 되고, SVG 판은 크기가 고정이라 21줄일 때 아래 밝은 줄이 덮임. 패널 하단 테두리(`#926E03`)는 수정 전에도 그려져 있었음. 수정: 틀 상자를 32 × 22 정수로(그림 SVG·패널·위치 변경 없음). 수정 후 같은 Chrome에서 1366 · 1100 행, Default · Multi Step, 1920 고정 폭 Header 모두 틀 22줄 · 아래 밝은 줄 표시 확인.
|
||||
|
||||
## 12. UX 검토 항목 — 외부 소비 프로젝트 요청 (2026-11-05, 정책 변경 아님)
|
||||
|
||||
외부 소비 프로젝트의 개선 요청을 이 Header 고유 항목과 SW Header 공통 원칙으로 나눈 기록입니다. 공통 원칙(말줄임 원문 확인, 아이콘 버튼 이름·툴팁, class 역할, 글꼴별 폭)은 `packages/sw/ai/DESIGN_RULES.md` §15를 따릅니다. 아래 "현재 동작"은 그대로 유지하며, 정책 변경이 필요한 항목은 사용자 결정 전까지 바꾸지 않습니다. 이 절은 §8·§9의 과거 측정·검증 기록을 재판정하지 않습니다.
|
||||
|
||||
| 항목 | 현재 동작(유지) | 사용자 경험상 문제 | 구분 |
|
||||
|---|---|---|---|
|
||||
| 단계가 많을 때 말줄임 | `multi` 1920 5그룹 11탭은 말줄임 없음, 1366에서는 11탭 중 10개 `…`(§9 측정, §11.1 개정 후 선택 탭 포함) | 짧게 잘린 단계명끼리 구분하기 어려움 | 별도 검토 |
|
||||
| 단계·그룹 원문 확인 | 단계 탭 `aria-label` = 전체 이름, 그룹 이름은 `aria-describedby`로 연결. 탭·`multi` 그룹 제목에 `title` 없음(파일명·Layer 값은 `title` 있음) | 마우스·터치 사용자는 잘린 이름의 원문을 볼 수 없음 | 개선 필요(구현 별도) |
|
||||
| 선택 단계 최소 폭 | 선택 탭도 다른 탭과 같은 비율로 줄어듦(§11.1, 선택 탭 우선 배정 폐기) | 선택 단계가 `…`가 되면 현재 단계를 알아보기 어려움 | 정책 결정 필요(폐기 사유인 옆 탭 24 · 27px 문제와 함께 검토) |
|
||||
| 권장 화면 폭 | 보장 폭 1100px 이상. 단계 수·이름 길이별 권장 폭 기준은 없음 | 소비 프로젝트가 단계 구성에 필요한 폭을 판단하기 어려움 | 별도 검토(측정 기준 정의 필요) |
|
||||
| 넘침 처리 | 그룹 접기·좌우 스크롤·More 없음(2026-11-03 확정) | 단계가 많으면 축소 외 수단이 없음 | 확정 정책 — 변경하려면 사용자 결정 필요 |
|
||||
| 도구 버튼 툴팁 | `SWProcessSplitButton`은 `label`을 `title`로 전달. 호스트 `tools`의 `SWToolButton pill`은 2026-11-05부터 공백이 아닌 문자열 `children`이 기본 `title`(명시 `title` 우선, `title=""` 끔, 숫자·JSX 제외). 이 Header의 Story 도구는 이미 `title`을 전달해 DOM 변화 없음 | 아이콘만 보여 의미를 알기 어려울 수 있음(키보드 Focus·터치에서는 `title`이 보이지 않음 — 미해결) | 공통 원칙(DESIGN_RULES §15.2) — 자동 툴팁 구현됨 |
|
||||
| 텍스트 로고 폭 | `main @ 06527ed` 기준: `logoText` 58px 고정 칸이라 더 긴 글자는 양쪽으로 넘쳐 홈 왼쪽에서 잘림. 2026-11-05 변경 기준: 최소 58px · 글자 폭만큼 확장(§3 홈 행, §8 로고 폭 행) | 글꼴에 따라 글자 폭·홈 폭이 달라짐(§3 실측은 Noto Sans KR 기준) | 공통 원칙(DESIGN_RULES §1 · §15.4). 로고 이름·글꼴별 고정 폭 예외를 두지 않음 |
|
||||
| 파일 생성 전 파일명 면 | 2026-11-05부터 `documentTitle=""`이면 Hover 색 변화 없음(`#AEDBCC` 유지)·`cursor: default`. 버튼·키보드 Focus·`onDocumentClick`은 그대로(기존 사용처가 빈 상태 클릭을 받음) | 클릭할 수 없어 보이지만 실제로는 클릭·Focus가 됨(보이는 모양과 동작이 다름) | 별도 검토 — 클릭·Focus 제거는 사용처 확인과 사용자 결정 필요 |
|
||||
|
||||
## 13. Figma 도구 아이콘 공개 Export — 보류 및 향후 제안 (2026-11-05)
|
||||
|
||||
2026-11-05 사용자 결정(B안): 시안 기반 아이콘 선택·창 제어 버튼 생략 규칙은 `packages/sw/ai/AI_USAGE.md` §5에 단일 기준으로 정의하고, 아래 아이콘의 공개 Export는 **보류**합니다. 소스·Props·CSS·공개 API·`public-api.approved.json`은 바꾸지 않았고 Fixture 파일도 그대로입니다.
|
||||
|
||||
| 기능 | Fixture(Storybook 전용) | Figma 프레임 이름 | 상태 |
|
||||
|---|---|---|---|
|
||||
| 도움말 | `src/sw/assets/process-header/tool-help(-hover).svg` | help | 공개 API 아님 |
|
||||
| 3D 보기 | `tool-view-3d(-hover).svg` | view-3d | 〃 |
|
||||
| 설정 | `tool-controls(-hover).svg` | controls(라벨 "설정"은 Story 기준, Figma 원본 라벨 미확인) | 〃 |
|
||||
| 메뉴 | `tool-menu-list(-hover).svg` | menu-list | 〃 |
|
||||
| 나가기 | `tool-exit(-hover).svg` | exit-arrow | 〃 |
|
||||
|
||||
- **확인된 사실:** Figma StrAna-SW(`4y8lTK1E1hAGYiMcYZwuNe`) 원본 내보내기(adbb034, §9.2). 기본 `#B6D0C9` / Hover `#FFC600` 두 장, 24 × 24, `<img>`로 쓰면 SVG 내부 id 충돌 없음. `SWToolButton`의 `icon`·`hoverIcon`과 그대로 호환(Process Header에서는 `pressed`도 Hover 그림).
|
||||
- **미확인:** 원작자·사용 권한·라이선스. 프레임 이름의 " 1" 접미사는 외부 SVG를 Figma로 가져왔을 가능성을 시사(추정).
|
||||
- **Export 전제 조건:** ① 5개 아이콘의 출처·사용 권한(공개 패키지 배포 포함) 사용자 확인, ② 아래 구조와 이름의 별도 승인, ③ `public-api.approved.json` 갱신 승인.
|
||||
- **제안 구조(미구현, 승인 전 문서화·사용 금지):** `swFooterToolIcons` 선례와 같은 방식. `packages/sw/src/components/sw-process-header/icons.tsx`에 `swProcessToolIcons: Record<SWProcessToolIconName, { icon: ReactElement; hoverIcon: ReactElement }>`(기능 키 `help` · `view3d` · `settings` · `menu` · `exit`, `<img width={24} height={24} alt="" />`), SVG 10개는 같은 폴더 `assets/`로 이동(data URI 번들), `sw-process-header/index.ts`에서 Export. 사용: `<SWToolButton appearance="pill" {...swProcessToolIcons.help} onClick={openHelp}>도움말</SWToolButton>`. 지원 범위는 `SWProcessHeader`의 `tools`(두 appearance), `SWHeader` `tools`는 별도 검증.
|
||||
- **Export 시 함께 할 일:** Story를 공개 Export로 전환하고 Fixture 중복 제거, `COMPONENT_CATALOG.md` §11 Export 추가, `AI_USAGE.md` §5.2 공개 목록 갱신·§5.3 Storybook 전용 목록에서 제외, `check:ai-docs` · `typecheck:sw` · `build:sw` · `public-api` 검사 · Tarball 소비 프로젝트 Import·Hover/Pressed 확인.
|
||||
@@ -141,7 +141,7 @@
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | VIEWER.md `SWViewerHeader`·디자인 변경 절 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | VIEWER.md 디자인 변경 절(52px Header 등 실측값) | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | VIEWER.md `SWViewerPanel` 절(프레임은 SWPopup과 같음) | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon` | Core 내장 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 사용자·프로젝트 제공 에셋 → 패키지 공개 Export 중 같은 기능 → 없으면 미지원 보고(외부 라이브러리·AI 생성 SVG·Storybook 파일로 대체 금지). 시안 기반 Header 도구 아이콘·창 제어 버튼은 `@hm-design/sw` `ai/AI_USAGE.md` §5 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon`. 선택 순서는 SW 일반 모드와 같음(`AI_USAGE.md` §5) | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | 전용 디자인 없음 — 패널 본문 스크롤은 `SWViewerPanel` | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | VIEWER.md `SWViewerHeader` 절 상태 표현 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | `SWViewerPanel`(+ `modal`) + `SWViewerSection`/`SWViewerField` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
@@ -178,6 +178,19 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
3. 패키지가 실제로 공유하는 부분은 혼합이 아닙니다: `SWViewerPanel`은 `SWPopup` 프레임을 그대로 렌더링하고, 두 Header는 파일명·객체 수정·속성 면 토큰(`--hm-sw-header-document-*`·`object-*`·`property-*`)을 공유합니다. 소비 코드가 이 공유를 흉내 내 다른 모드 부품을 섞어도 된다는 뜻은 아닙니다.
|
||||
4. 프로그램이 두 모드를 모두 가지면 모드 전환 시 Header 컴포넌트를 바꿔 끼웁니다(파일·패널 상태는 프로그램 상태로 유지). 세부는 `@hm-design/sw/ai/AI_USAGE.md` §0과 `VIEWER.md`.
|
||||
|
||||
### Header 전용 스타일의 적용 범위 (2026-11-05)
|
||||
|
||||
화면 상단 Header 컴포넌트 — SW `SWHeader`·`SWProcessHeader`·`SWViewerHeader`, Web `GNB`·`LNBMobileHeader`·`DashboardHeader` — 의 전용 스타일은 **그 Header 영역 안에서만** 씁니다. 전용 스타일은 Header의 class(`hm-sw-header__*`·`hm-sw-process-*`·`hm-sw-viewer-header__*`, Web Header의 `data-slot`)와 Header 모양을 정하는 값(배경·gradient·테두리·글자 외곽선·높이·간격)입니다. `DialogHeader`·`CardHeader`처럼 다른 컴포넌트 안의 머리 부품은 여기서 말하는 Header가 아닙니다. 이 규칙은 아래를 정할 뿐 기존 Header의 레이아웃·스타일·공개 API를 바꾸지 않습니다.
|
||||
|
||||
1. Header 밖의 본문·패널·카드·버튼·입력 필드와 다른 컴포넌트에 Header 전용 class·CSS·값을 붙이거나 복사하지 않습니다. "Header 스타일로" 같은 요청도 Header 영역에만 적용하고, 별도 요청이 없으면 Header 밖 기존 UI의 디자인과 동작은 그대로 둡니다.
|
||||
2. 화면 전체 토큰·테마(Web Runtime Theme `--hm-sem-*`, SW 가장 바깥 범위의 `style` 토큰, 위 표의 범위)와 Header 전용 스타일을 구분합니다. Header를 맞추려고 화면 전체 테마 값을 바꾸지 않습니다.
|
||||
3. Header를 구현하려고 전역 CSS(`html`·`body`·`*`, 공통 reset)를 바꾸거나 기존 본문 스타일을 덮어쓰지 않습니다.
|
||||
4. Header와 본문의 경계에서는 배치에 필요한 연결만 소비 프로젝트 class로 처리합니다(Header 높이만큼 본문 시작 위치, sticky·z-index, 남는 높이를 본문에 주는 flex 등).
|
||||
5. 공식 컴포넌트 내부 스타일을 소비 프로젝트에서 Override하지 않습니다(위 혼합 규칙 3). 바꿀 수 있는 것은 각 패키지가 공개한 Props·토큰뿐입니다.
|
||||
6. 시안에 본문 디자인이 따로 있으면 그 영역의 패키지(위 혼합 규칙 1)의 공식 컴포넌트로 구현합니다. Header와 같은 패키지의 부품을 Header 밖에 둘 때도 그 부품의 공식 사용법(SW는 `<SWTheme>` 안)을 따르고 Header 모양을 덧입히지 않습니다.
|
||||
7. 패키지가 공식으로 공유하는 부분은 위반이 아닙니다(예: `SWPopup`·`<SWTheme>` 부품이 `--hm-sw-header-*` 토큰을 읽음, 위 SW 분리 3). 소비 코드가 이를 흉내 내 Header 스타일을 다른 곳에 쓰면 안 됩니다.
|
||||
8. 사용자가 화면 전체에 Header 디자인을 적용하라고 **명시한 경우에만** 범위를 넓힙니다. 이때도 공식 컴포넌트·공개 토큰으로만 맞추고 Header class를 복사하지 않으며, 결과 보고에 넓힌 범위를 적습니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
@@ -186,7 +199,7 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW 일반 모드는 대상 창 폭(Header는 반응형 More 포함), SW 뷰어 모드는 대상 창 폭을 줄였다 늘리며 실제 공간 부족에 따른 단계(`iconOnlyBelow` 항목 아이콘만 → `collapseBelow` 항목 More 이동, 숫자는 px 경계가 아니라 순서) 전후, OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지(§5)
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지, Header 전용 스타일이 Header 밖에 쓰이지 않았는지(§5, "Header 전용 스타일의 적용 범위")
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | VIEWER.md `SWViewerHeader`·디자인 변경 절 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | VIEWER.md 디자인 변경 절(52px Header 등 실측값) | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | VIEWER.md `SWViewerPanel` 절(프레임은 SWPopup과 같음) | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon` | Core 내장 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 사용자·프로젝트 제공 에셋 → 패키지 공개 Export 중 같은 기능 → 없으면 미지원 보고(외부 라이브러리·AI 생성 SVG·Storybook 파일로 대체 금지). 시안 기반 Header 도구 아이콘·창 제어 버튼은 `@hm-design/sw` `ai/AI_USAGE.md` §5 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon`. 선택 순서는 SW 일반 모드와 같음(`AI_USAGE.md` §5) | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | 전용 디자인 없음 — 패널 본문 스크롤은 `SWViewerPanel` | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | VIEWER.md `SWViewerHeader` 절 상태 표현 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | `SWViewerPanel`(+ `modal`) + `SWViewerSection`/`SWViewerField` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
@@ -178,6 +178,19 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
3. 패키지가 실제로 공유하는 부분은 혼합이 아닙니다: `SWViewerPanel`은 `SWPopup` 프레임을 그대로 렌더링하고, 두 Header는 파일명·객체 수정·속성 면 토큰(`--hm-sw-header-document-*`·`object-*`·`property-*`)을 공유합니다. 소비 코드가 이 공유를 흉내 내 다른 모드 부품을 섞어도 된다는 뜻은 아닙니다.
|
||||
4. 프로그램이 두 모드를 모두 가지면 모드 전환 시 Header 컴포넌트를 바꿔 끼웁니다(파일·패널 상태는 프로그램 상태로 유지). 세부는 `@hm-design/sw/ai/AI_USAGE.md` §0과 `VIEWER.md`.
|
||||
|
||||
### Header 전용 스타일의 적용 범위 (2026-11-05)
|
||||
|
||||
화면 상단 Header 컴포넌트 — SW `SWHeader`·`SWProcessHeader`·`SWViewerHeader`, Web `GNB`·`LNBMobileHeader`·`DashboardHeader` — 의 전용 스타일은 **그 Header 영역 안에서만** 씁니다. 전용 스타일은 Header의 class(`hm-sw-header__*`·`hm-sw-process-*`·`hm-sw-viewer-header__*`, Web Header의 `data-slot`)와 Header 모양을 정하는 값(배경·gradient·테두리·글자 외곽선·높이·간격)입니다. `DialogHeader`·`CardHeader`처럼 다른 컴포넌트 안의 머리 부품은 여기서 말하는 Header가 아닙니다. 이 규칙은 아래를 정할 뿐 기존 Header의 레이아웃·스타일·공개 API를 바꾸지 않습니다.
|
||||
|
||||
1. Header 밖의 본문·패널·카드·버튼·입력 필드와 다른 컴포넌트에 Header 전용 class·CSS·값을 붙이거나 복사하지 않습니다. "Header 스타일로" 같은 요청도 Header 영역에만 적용하고, 별도 요청이 없으면 Header 밖 기존 UI의 디자인과 동작은 그대로 둡니다.
|
||||
2. 화면 전체 토큰·테마(Web Runtime Theme `--hm-sem-*`, SW 가장 바깥 범위의 `style` 토큰, 위 표의 범위)와 Header 전용 스타일을 구분합니다. Header를 맞추려고 화면 전체 테마 값을 바꾸지 않습니다.
|
||||
3. Header를 구현하려고 전역 CSS(`html`·`body`·`*`, 공통 reset)를 바꾸거나 기존 본문 스타일을 덮어쓰지 않습니다.
|
||||
4. Header와 본문의 경계에서는 배치에 필요한 연결만 소비 프로젝트 class로 처리합니다(Header 높이만큼 본문 시작 위치, sticky·z-index, 남는 높이를 본문에 주는 flex 등).
|
||||
5. 공식 컴포넌트 내부 스타일을 소비 프로젝트에서 Override하지 않습니다(위 혼합 규칙 3). 바꿀 수 있는 것은 각 패키지가 공개한 Props·토큰뿐입니다.
|
||||
6. 시안에 본문 디자인이 따로 있으면 그 영역의 패키지(위 혼합 규칙 1)의 공식 컴포넌트로 구현합니다. Header와 같은 패키지의 부품을 Header 밖에 둘 때도 그 부품의 공식 사용법(SW는 `<SWTheme>` 안)을 따르고 Header 모양을 덧입히지 않습니다.
|
||||
7. 패키지가 공식으로 공유하는 부분은 위반이 아닙니다(예: `SWPopup`·`<SWTheme>` 부품이 `--hm-sw-header-*` 토큰을 읽음, 위 SW 분리 3). 소비 코드가 이를 흉내 내 Header 스타일을 다른 곳에 쓰면 안 됩니다.
|
||||
8. 사용자가 화면 전체에 Header 디자인을 적용하라고 **명시한 경우에만** 범위를 넓힙니다. 이때도 공식 컴포넌트·공개 토큰으로만 맞추고 Header class를 복사하지 않으며, 결과 보고에 넓힌 범위를 적습니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
@@ -186,7 +199,7 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW 일반 모드는 대상 창 폭(Header는 반응형 More 포함), SW 뷰어 모드는 대상 창 폭을 줄였다 늘리며 실제 공간 부족에 따른 단계(`iconOnlyBelow` 항목 아이콘만 → `collapseBelow` 항목 More 이동, 숫자는 px 경계가 아니라 순서) 전후, OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지(§5)
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지, Header 전용 스타일이 Header 밖에 쓰이지 않았는지(§5, "Header 전용 스타일의 적용 범위")
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | VIEWER.md `SWViewerHeader`·디자인 변경 절 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | VIEWER.md 디자인 변경 절(52px Header 등 실측값) | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | VIEWER.md `SWViewerPanel` 절(프레임은 SWPopup과 같음) | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon` | Core 내장 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 사용자·프로젝트 제공 에셋 → 패키지 공개 Export 중 같은 기능 → 없으면 미지원 보고(외부 라이브러리·AI 생성 SVG·Storybook 파일로 대체 금지). 시안 기반 Header 도구 아이콘·창 제어 버튼은 `@hm-design/sw` `ai/AI_USAGE.md` §5 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon`. 선택 순서는 SW 일반 모드와 같음(`AI_USAGE.md` §5) | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | 전용 디자인 없음 — 패널 본문 스크롤은 `SWViewerPanel` | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | VIEWER.md `SWViewerHeader` 절 상태 표현 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | `SWViewerPanel`(+ `modal`) + `SWViewerSection`/`SWViewerField` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
@@ -178,6 +178,19 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
3. 패키지가 실제로 공유하는 부분은 혼합이 아닙니다: `SWViewerPanel`은 `SWPopup` 프레임을 그대로 렌더링하고, 두 Header는 파일명·객체 수정·속성 면 토큰(`--hm-sw-header-document-*`·`object-*`·`property-*`)을 공유합니다. 소비 코드가 이 공유를 흉내 내 다른 모드 부품을 섞어도 된다는 뜻은 아닙니다.
|
||||
4. 프로그램이 두 모드를 모두 가지면 모드 전환 시 Header 컴포넌트를 바꿔 끼웁니다(파일·패널 상태는 프로그램 상태로 유지). 세부는 `@hm-design/sw/ai/AI_USAGE.md` §0과 `VIEWER.md`.
|
||||
|
||||
### Header 전용 스타일의 적용 범위 (2026-11-05)
|
||||
|
||||
화면 상단 Header 컴포넌트 — SW `SWHeader`·`SWProcessHeader`·`SWViewerHeader`, Web `GNB`·`LNBMobileHeader`·`DashboardHeader` — 의 전용 스타일은 **그 Header 영역 안에서만** 씁니다. 전용 스타일은 Header의 class(`hm-sw-header__*`·`hm-sw-process-*`·`hm-sw-viewer-header__*`, Web Header의 `data-slot`)와 Header 모양을 정하는 값(배경·gradient·테두리·글자 외곽선·높이·간격)입니다. `DialogHeader`·`CardHeader`처럼 다른 컴포넌트 안의 머리 부품은 여기서 말하는 Header가 아닙니다. 이 규칙은 아래를 정할 뿐 기존 Header의 레이아웃·스타일·공개 API를 바꾸지 않습니다.
|
||||
|
||||
1. Header 밖의 본문·패널·카드·버튼·입력 필드와 다른 컴포넌트에 Header 전용 class·CSS·값을 붙이거나 복사하지 않습니다. "Header 스타일로" 같은 요청도 Header 영역에만 적용하고, 별도 요청이 없으면 Header 밖 기존 UI의 디자인과 동작은 그대로 둡니다.
|
||||
2. 화면 전체 토큰·테마(Web Runtime Theme `--hm-sem-*`, SW 가장 바깥 범위의 `style` 토큰, 위 표의 범위)와 Header 전용 스타일을 구분합니다. Header를 맞추려고 화면 전체 테마 값을 바꾸지 않습니다.
|
||||
3. Header를 구현하려고 전역 CSS(`html`·`body`·`*`, 공통 reset)를 바꾸거나 기존 본문 스타일을 덮어쓰지 않습니다.
|
||||
4. Header와 본문의 경계에서는 배치에 필요한 연결만 소비 프로젝트 class로 처리합니다(Header 높이만큼 본문 시작 위치, sticky·z-index, 남는 높이를 본문에 주는 flex 등).
|
||||
5. 공식 컴포넌트 내부 스타일을 소비 프로젝트에서 Override하지 않습니다(위 혼합 규칙 3). 바꿀 수 있는 것은 각 패키지가 공개한 Props·토큰뿐입니다.
|
||||
6. 시안에 본문 디자인이 따로 있으면 그 영역의 패키지(위 혼합 규칙 1)의 공식 컴포넌트로 구현합니다. Header와 같은 패키지의 부품을 Header 밖에 둘 때도 그 부품의 공식 사용법(SW는 `<SWTheme>` 안)을 따르고 Header 모양을 덧입히지 않습니다.
|
||||
7. 패키지가 공식으로 공유하는 부분은 위반이 아닙니다(예: `SWPopup`·`<SWTheme>` 부품이 `--hm-sw-header-*` 토큰을 읽음, 위 SW 분리 3). 소비 코드가 이를 흉내 내 Header 스타일을 다른 곳에 쓰면 안 됩니다.
|
||||
8. 사용자가 화면 전체에 Header 디자인을 적용하라고 **명시한 경우에만** 범위를 넓힙니다. 이때도 공식 컴포넌트·공개 토큰으로만 맞추고 Header class를 복사하지 않으며, 결과 보고에 넓힌 범위를 적습니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
@@ -186,7 +199,7 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW 일반 모드는 대상 창 폭(Header는 반응형 More 포함), SW 뷰어 모드는 대상 창 폭을 줄였다 늘리며 실제 공간 부족에 따른 단계(`iconOnlyBelow` 항목 아이콘만 → `collapseBelow` 항목 More 이동, 숫자는 px 경계가 아니라 순서) 전후, OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지(§5)
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지, Header 전용 스타일이 Header 밖에 쓰이지 않았는지(§5, "Header 전용 스타일의 적용 범위")
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
+123
-1
@@ -45,7 +45,7 @@ SW 패키지에는 Header·Popup 계열이 두 벌 있습니다. 화면마다 **
|
||||
| 로고(홈) 버튼 메뉴 | `SWHeader`의 `homeMenu`(`SWHomeMenu` 데이터, 최근파일 `SWRecentFiles`, Figma 26:10591). 기본 숨김 → 로고 클릭 열기 → 다시 클릭 닫기 | **사용하지 않음.** `SWViewerHeader`에는 홈 메뉴 Props가 없고 로고 클릭은 `onHomeClick`만 호출합니다. 파일 열기는 로고 오른쪽 버튼(`onDocumentOpen`·`documentOpenLabel`), 샘플 목록은 파일명 클릭 목록(`documents`·`activeDocumentId`·`onDocumentSelect`)이며 `groups` 항목으로 만들지 않습니다([VIEWER.md](./VIEWER.md) §2) |
|
||||
| 하단 Footer | `SWFooter`(+ 바 안 명령창 `command={<SWFooterCommandLine />}`, 좌표 `status`, `SWFooterLayoutTabs`, 부품 `SWFooter*`), [FOOTER.md](./FOOTER.md) | **사용하지 않음.** 뷰어 화면에는 하단 명령 막대가 없습니다. 도면 공간 탭은 `SWViewerHeader`의 `tabs` 항목 |
|
||||
| 버튼·탭·토글 | `SWToolButton` / `SWToolGroup` | Header 항목 `type: "button" \| "toggle" \| "tabs" \| "switch"`, 패널 안 `SWViewerButton` |
|
||||
| 아이콘 | 패키지 자산(필드 안 아이콘) | 프로그램 아이콘 16px, 직접 그린 아이콘은 `SWViewerIcon`으로 감쌈 |
|
||||
| 아이콘 | 패키지 자산(필드 안 아이콘). 시안의 도구 아이콘 선택은 §5.2 | 프로그램 아이콘 16px, 직접 그린 아이콘은 `SWViewerIcon`으로 감쌈. 시안의 도구 아이콘 선택은 §5.2 |
|
||||
| 범위 | 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·§10·§11, [DESIGN_RULES.md](./DESIGN_RULES.md) §1~10·§12·§13·§14, [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 1~6·8·10·12, [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) |
|
||||
@@ -116,6 +116,7 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
|
||||
- 소비 코드는 `hm-sw-*` class를 붙이거나 재정의하지 않는다. 배치는 자기 class(`className`)와 `style`로 한다.
|
||||
- 뷰어 모드 컴포넌트(`SWViewerHeader`·`SWViewerMenu`·`SWViewerPanel`)는 자체 `hm-sw-theme` 범위를 가지므로 `<SWTheme>`로 감싸지 않아도 된다. 소비 코드는 `hm-sw-viewer-*` class도 붙이거나 재정의하지 않는다.
|
||||
- 외형 변경은 §3.1의 방법만 쓴다. 사용자 확정 면(파일명·객체 수정·속성 입력)은 기본값 유지를 권장한다.
|
||||
- Header(`SWHeader`·`SWProcessHeader`·`SWViewerHeader`) 전용 스타일은 Header 영역에만 쓴다. 본문·패널에 Header 모양을 적용하지 않는다([AI_GUIDE.md](./AI_GUIDE.md) 5단계 "Header 전용 스타일의 적용 범위").
|
||||
- Light/Dark 분기는 없다. SW 디자인은 하나다.
|
||||
|
||||
### 3.1 테마(토큰) 설정 — 공식 방법 (2026-10-20)
|
||||
@@ -148,3 +149,124 @@ SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-viewer-*`, `--hm-sw
|
||||
| 예: 뷰어 측정 패널 | 신규 콘텐츠(소비 프로젝트) | `SWViewerPanel` children에 `SWViewerField` + `SWViewerInput`. 토큰에 없는 값은 "AI 구성 값 — 디자인 확인 필요" |
|
||||
|
||||
새 컴포넌트를 SW 패키지 공식 컴포넌트로 승격할지는 별도 검토 대상이다. 임의로 패키지에 추가하지 않는다.
|
||||
|
||||
## 5. 디자인 시안 기반 Header 구현 — 도구 아이콘·창 제어 버튼 판단 (2026-11-05)
|
||||
|
||||
화면 이미지·Figma·캡처 같은 **디자인 시안**을 보고 SW Header(`SWHeader`·`SWProcessHeader`·`SWViewerHeader`)의 도구 버튼과 창 제어 버튼을 구현할 때 따르는 **단일 기준**입니다. 다른 문서는 이 절을 요약·참조만 합니다. 크기·색·상태 값은 [DESIGN_RULES.md](./DESIGN_RULES.md)(§9·§14, 뷰어는 [VIEWER.md](./VIEWER.md))를 따르며 이 절에서 다시 정하지 않습니다.
|
||||
|
||||
### 5.1 컴포넌트 기본 구성과 시안 재현은 다릅니다
|
||||
|
||||
| 구분 | 뜻 | 바꾸는 방법 |
|
||||
|---|---|---|
|
||||
| 컴포넌트 기본 구성 | Props를 생략했을 때 컴포넌트가 그리는 구성(예: `SWProcessHeader`는 `hiddenControls`가 없으면 창 이동·최소화·최대화/복원·종료를 모두 그림). 기존 소비 프로젝트가 의존하는 패키지 기본값 | 바꾸지 않습니다. 패키지 기본값·CSS를 고치거나 `hm-sw-*` class로 숨기지 않습니다 |
|
||||
| 시안 재현 | 시안에 **보이는** 구성만 화면에 두는 것 | 공개 Props(`hiddenControls`·`tools`·`trailing` 등)로만 맞춥니다 |
|
||||
|
||||
- 카탈로그 예시·예제 파일·Storybook에 어떤 버튼이나 아이콘이 있다는 사실은 시안에 그것을 만들 근거가 아닙니다.
|
||||
- **기존 화면 작업**(기존 화면을 HMDesign으로 옮기기, 이미 HMDesign을 쓰는 화면에 새 시안 적용)에는 [AI_GUIDE.md](./AI_GUIDE.md) 4단계 "기존 화면 전환 시 원본 보존"·"디자인 적용 시 변경 범위 제한"이 우선합니다. 원본에 있는 버튼·아이콘·창 제어 기능을 "시안에 없다"는 이유만으로 지우거나 `hiddenControls`를 더해 숨기지 않고, 원본에 없는 창 제어 버튼을 시안만 근거로 더하지 않습니다. 시안과 원본이 다르면 결과 보고의 "원본 대비 변경"에 확인 대상으로 적습니다. §5.5의 생략·추가 규칙은 신규 화면 기준입니다.
|
||||
- 사용자가 창 제어·도구 버튼 구성 변경을 **명시적으로 요청**했으면 요청한 버튼에 한해 공개 Props로 바꾸고, "원본 대비 변경"의 근거를 "사용자 승인"으로 적습니다. 요청 범위 밖의 버튼은 그대로 둡니다.
|
||||
|
||||
### 5.2 도구 아이콘 선택 우선순위
|
||||
|
||||
| 순위 | 조건 | 할 일 |
|
||||
|---|---|---|
|
||||
| 1 | 사용자가 이번 작업에 제공했거나 사용을 승인한 이미지·SVG, 또는 소비 프로젝트에 이미 등록된 공식 아이콘이 있음 | 그 에셋을 씁니다. 상태별 그림이 있으면 기본은 `icon`, Hover 그림은 `hoverIcon`에 넣습니다(§5.4) |
|
||||
| 2 | 제공된 에셋이 없고, `@hm-design/sw`가 **공개 Export**하는 아이콘·부품 중 **기능과 의미가 같은 것**이 있음 | 그것을 재사용합니다. 시안과 모양이 조금 달라도 기능이 같고 그 아이콘의 사용 위치·크기·상태 규칙 안이면 HMDesign 것을 우선합니다 |
|
||||
| 3 | 1·2 모두 없음 | 대체하지 않고 **미지원으로 보고**합니다(아래) |
|
||||
|
||||
- **시안 속 아이콘의 사용 권한:** Figma 등 시안에 그려져 있다는 사실만으로 사용 권한이 있다고 보지 않습니다. 시안에서 아이콘을 추출해 쓰려면 사용자가 그 에셋을 제공했거나 사용을 명시적으로 승인했는지 먼저 확인하고, 확인 전에는 1순위로 쓰지 않습니다(2·3순위로 판단). 이 승인은 해당 소비 프로젝트에서의 사용에만 해당하며, HMDesign 패키지 공식 등록·공개 배포 권한은 별도로 확인합니다(§5.3).
|
||||
|
||||
**2순위 — 현재 공개된 아이콘·아이콘 포함 부품**(목록 밖 이름은 [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)와 `dist/index.d.ts`에서 확인):
|
||||
|
||||
| 공개 Export | 기능 | 사용 위치 |
|
||||
|---|---|---|
|
||||
| `SWHeader` 기본 부품(인쇄 `print`, K 가이드 `popupGuide`, 창 이동·최소화·종료) | 인쇄, 단축키 안내, 창 제어 | `SWHeader` 안(`hiddenControls`로 개별 제거). 아이콘만 꺼내 쓰는 API는 없습니다 |
|
||||
| `SWHeaderActions` | 인쇄·K 가이드 버튼 pill(`onPrint`·`onPopupGuide`·`showPrint`·`showPopupGuide`·`disabled`) | `<SWTheme>` 안 SW 일반 모드 조합 Header([COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 2). 컴포넌트 단위로만 재사용합니다 — 인쇄·K 아이콘 단독 Export는 없어 `SWToolButton`의 `icon`에 넣을 수 없습니다. `SWHeader`(이미 내장)·`SWProcessHeader` `tools`·뷰어 화면([VIEWER.md](./VIEWER.md) §2.1)에는 넣지 않습니다(검증된 조합 아님) |
|
||||
| `SWHeaderWindowControls` | 창 이동·최소화·종료 | SW 일반 모드 조합 Header, 뷰어 `trailing` 끝(최소화·닫기만, `showWindowMove={false}`, VIEWER.md §2.1) |
|
||||
| `SWProcessSplitButton` | 화면 분할 | `SWProcessHeader`의 `tools` |
|
||||
| `SWLayerVisibilityIcon` | 레이어 표시·숨김 전구 | 뷰어 레이어 Popup(VIEWER.md §4.1) |
|
||||
| `swFooterToolIcons` · `swFooterAuxIcons` | Footer 명령(Line·Circle …)·보조기능(객체스냅 …) | `SWFooter` 칸([FOOTER.md](./FOOTER.md)). 크기·상태 색이 Header 도구 버튼과 달라 Header pill에는 쓰지 않습니다 |
|
||||
|
||||
- 모양이 비슷하다는 이유만으로 기능이 다른 아이콘을 쓰지 않습니다(예: `swFooterToolIcons.rect`를 최대화 아이콘으로 쓰기 금지).
|
||||
- `SWViewerIcon`은 외곽선만 더하는 감싸기 부품이며 그림을 제공하지 않습니다.
|
||||
- **Header 도구 버튼용 범용 아이콘(도움말·3D 보기·설정·메뉴·나가기 등)은 현재 공개 Export가 없습니다.** 이런 아이콘이 시안에 있고 1순위 에셋도 없으면 3순위입니다.
|
||||
|
||||
**3순위 — 공식 아이콘이 없을 때:**
|
||||
|
||||
1. 외부 아이콘 라이브러리(`lucide-react` 등 — Web 패키지 의존성이지만 SW 화면에 쓰지 않음), AI가 그린 SVG, Storybook 파일 복사로 대신하지 않습니다.
|
||||
2. 코드를 쓰기 전에 필요한 아이콘·기능·필요한 상태(기본·Hover 등)를 사용자에게 알리고 에셋 제공 또는 HMDesign 공식 아이콘 등록이 필요하다고 요청합니다.
|
||||
3. 답을 기다리지 않고 진행해야 하면 그 도구 버튼을 넣지 않거나, 사용자가 승인한 임시 표시만 쓰고 결과 보고에 "미지원 아이콘 — 에셋 필요"로 적습니다. 승인 없이 임시 아이콘을 최종 디자인으로 확정하지 않습니다.
|
||||
|
||||
### 5.3 Storybook 전용 에셋과 공개 에셋의 차이
|
||||
|
||||
| 구분 | 공개 에셋·API | Storybook 전용 에셋(Fixture) |
|
||||
|---|---|---|
|
||||
| 위치 | `@hm-design/sw` 진입점 Export(카탈로그에 이름이 있음). SVG는 JS에 data URI로 포함 | HMDesign 저장소의 Story 데모 파일. 설치 패키지(`dist`·`ai`)에 포함되지 않음 |
|
||||
| 보장 | 버전 호환·사용 규칙 문서화 대상 | 데모용. 출처·사용 권한·유지를 보장하지 않음 |
|
||||
| 소비 프로젝트 | Import해서 사용 | Import·복사·경로 참조 금지(Git Submodule로 저장소 파일이 보여도 같음) |
|
||||
|
||||
- Storybook 화면에 보인다는 것은 공개 API라는 뜻이 아닙니다. Storybook은 조합·외형 참고용입니다([AI_GUIDE.md](./AI_GUIDE.md) 4단계 "Git Submodule 사용 규칙").
|
||||
- **2026-11-05 현재 Storybook 전용(공개 API 아님):** `SW / Step Header` Story의 도구 아이콘 — Multi Step의 도움말·3D 보기·설정·메뉴·나가기, Default의 View Property·Sub 창. 출처·사용 권한 확인 전이라 패키지에 등록하지 않았습니다. 시안에 이 아이콘이 있으면 1순위(사용자·프로젝트 제공)로 받거나 3순위로 보고합니다.
|
||||
|
||||
### 5.4 아이콘 상태 처리 — 기존 공개 API 방식 그대로
|
||||
|
||||
- `SWToolButton appearance="pill"`의 `icon`(기본 그림)과 `hoverIcon`(상태 그림)만 씁니다. Disabled 전용 아이콘 Prop은 없습니다. 상태별 현재 동작(패키지 CSS 기준)은 버튼이 놓인 곳에 따라 다릅니다.
|
||||
|
||||
| 상태 | `SWProcessHeader`의 `tools` 안 | 그 밖의 `pill`(`SWHeader`의 `tools`, `<SWTheme>` 조합) |
|
||||
|---|---|---|
|
||||
| Default | `icon`. `currentColor` = `#B6D0C9`(`--hm-sw-process-tool-icon-color`) | `icon`. `currentColor` = 상속 글자색 `--hm-sw-header-text-color`(`#F6F4F2`), `#B6D0C9` 아님 |
|
||||
| Hover | 노란 링 + `currentColor` `#FFC600` + `hoverIcon`이 있으면 그 그림 | 노란 링 + `hoverIcon`이 있으면 그 그림. `currentColor` 색은 바뀌지 않음 |
|
||||
| Pressed(`pressed` Prop, `aria-pressed`) | Hover와 같은 모양(노란 링 + `#FFC600` + `hoverIcon`) | `currentColor`만 `#FFC600`. 노란 링·`hoverIcon` 전환 없음 |
|
||||
| Disabled | Hover 반응 없음, 기본 그림 유지, 투명도 0.4 | 같음 |
|
||||
|
||||
- 누르는 순간(`:active`)의 별도 모양은 없습니다. 위 Pressed는 `pressed` Prop 상태입니다.
|
||||
- 색이 고정된 이미지는 기본·Hover 두 장을 줍니다. Hover 그림이 제공되지 않으면 `hoverIcon`을 생략하며(Hover에도 기본 그림 유지), 제공된 그림의 색을 바꿔 Hover 그림을 만들지 않습니다. `currentColor` 한 장으로 상태 색을 따르는 방식은 `SWProcessHeader`의 `tools`에서만 `#B6D0C9` → `#FFC600`이 됩니다. 그 밖의 `pill`에서 시안 색이 인쇄 버튼처럼 `#B6D0C9` → `#FFC600`이면 기본·Hover 두 장 이미지를 씁니다(값은 [DESIGN_RULES.md](./DESIGN_RULES.md) §9·§14).
|
||||
- 위 표와 다른 상태 모양이 시안에 있으면 CSS로 맞추지 않고 지원 범위 밖으로 보고합니다.
|
||||
- 그림은 장식이므로 `<img alt="" />`로 두고 버튼 이름은 `children`(문자열이면 기본 툴팁, DESIGN_RULES §15.2)으로 줍니다.
|
||||
- 상태를 맞추려고 `hm-sw-*` class나 패키지 CSS를 덮지 않습니다.
|
||||
|
||||
### 5.5 창 제어 버튼(창 이동·최소화·최대화·닫기)
|
||||
|
||||
1. 시안에 **명확히 보이는** 창 제어 버튼만 구현합니다(웹 화면은 4번, 기존 화면은 §5.1이 우선).
|
||||
2. 시안에 없고 별도 요구도 없으면 만들지 않는 것이 기본입니다. 컴포넌트 기본 구성(§5.1)에 있어도 공개 숨김 API로 뺍니다(신규 화면 기준, 기존 화면은 §5.1).
|
||||
3. 시안의 오른쪽이 잘렸거나 일부가 보이지 않으면 보이지 않는 버튼의 존재를 확정하지 않습니다. 코드를 쓰기 전에 묻고, 답 없이 진행해야 하면 보이는 범위만 구현한 뒤 결과 보고에 "확인 필요(시안 잘림)"로 적습니다.
|
||||
4. 브라우저에서 쓰는 웹 화면에서는 운영체제 창 제어 버튼을 새로 만들지 않는 것이 기본입니다. 시안에 창 제어 버튼이 보여도 실제로 연결할 창 동작(최소화·최대화·닫기를 처리하는 셸·Callback 요구)이 확인되지 않으면 임의로 구현하지 않습니다. 코드를 쓰기 전에 묻고, 답 없이 진행해야 하면 신규 창 제어 버튼 생성을 보류하고(아래 공개 숨김 API로 생략) 결과 보고에 "보류 — 창 동작 요구 확인 필요"로 적습니다. 빈 Callback·임의 `window.close()` 연결 같은 가짜 동작을 만들지 않습니다. 기존 화면에 이미 있는 창 제어 기능은 이 규칙을 이유로 별도 승인 없이 제거하지 않습니다(§5.1).
|
||||
5. 데스크톱 프로그램(설치형·WebView 셸 등)처럼 창 제어가 필수인 환경은 플랫폼 요구와 사용자 명세를 우선합니다. 시안에 없는데 플랫폼상 필요해 보이면 임의로 더하지 않고 확인합니다. 실제 창 이동·최소화·최대화·종료는 프로그램 담당입니다(Callback 연결).
|
||||
6. 숨김·생략은 아래 공개 API로만 합니다. 적절한 공개 API가 없으면 CSS로 감추는 우회 대신 지원 여부와 제약을 보고합니다.
|
||||
|
||||
| Header | 기본 구성 | 시안에 없을 때(공개 API) |
|
||||
|---|---|---|
|
||||
| `SWProcessHeader` | 창 이동·최소화·최대화/복원·종료 표시 | `hiddenControls`에 `"windowMove"`·`"minimize"`·`"maximize"`·`"close"` 중 없는 것. 넷 다 숨기고 `tools`도 없으면 창 영역 자체가 렌더링되지 않음 |
|
||||
| `SWHeader` | 창 이동·최소화·종료 표시 | `hiddenControls`에 `"windowMove"`·`"minimize"`·`"close"` 중 없는 것 |
|
||||
| `SWViewerHeader` | 창 제어 없음 | `trailing`에 `SWHeaderWindowControls`를 넣지 않음. 시안에 최소화·닫기가 있으면 `trailing` 맨 끝에 `showWindowMove={false}`로만 둠(VIEWER.md §2.1 승인 구성). 창 이동은 뷰어에서 지원하지 않으므로 시안에 있어도 넣지 않고 미지원으로 보고 |
|
||||
| `<SWTheme>` 안 조합 Header | — | `SWHeaderWindowControls`를 두지 않거나 `showWindowMove`·`showMinimize`·`showClose`로 일부만 표시 |
|
||||
|
||||
### 5.6 공식 구현 예시 (공개 API만)
|
||||
|
||||
```tsx
|
||||
import { SWHeader, SWProcessHeader, SWToolButton } from "@hm-design/sw"
|
||||
// 1순위 에셋: 사용자·소비 프로젝트가 제공한 파일(HMDesign 패키지·Storybook 파일이 아님)
|
||||
import helpIcon from "./assets/help.svg"
|
||||
import helpIconHover from "./assets/help-hover.svg"
|
||||
|
||||
// 시안: 창 제어 버튼 없음, 도구는 도움말 하나
|
||||
<SWProcessHeader appearance="multi" documentTitle={projectName} logoText="StrAna" onHomeClick={toggleFileMenu}
|
||||
process={{ groups, activeStepId: step, onStepSelect: s => setStep(s.id) }}
|
||||
tools={<SWToolButton appearance="pill" icon={<img src={helpIcon} alt="" />} hoverIcon={<img src={helpIconHover} alt="" />} onClick={openHelp}>도움말</SWToolButton>}
|
||||
hiddenControls={["windowMove", "minimize", "maximize", "close"]} />
|
||||
|
||||
// 시안: 최소화·닫기만 있음(창 이동·최대화 없음)
|
||||
<SWProcessHeader documentTitle={fileName} process={processProps}
|
||||
hiddenControls={["windowMove", "maximize"]} onMinimize={minimize} onClose={closeWindow} />
|
||||
|
||||
// 시안: 완성형 SWHeader에 창 제어 버튼 없음
|
||||
<SWHeader documentTitle={fileName} {...headerProps} hiddenControls={["windowMove", "minimize", "close"]} />
|
||||
```
|
||||
|
||||
### 5.7 결과 보고에 남길 것
|
||||
|
||||
§4 표에 다음 판단을 함께 적습니다.
|
||||
|
||||
| 요청 항목 | 판단 | 근거 |
|
||||
|---|---|---|
|
||||
| 예: 도움말 아이콘 | 1순위 사용 / 2순위 재사용 / 3순위 미지원 | 사용자 제공 파일명 / 재사용한 공개 Export 이름 / "공개 아이콘 없음 — 에셋 제공 또는 공식 등록 필요(기능·필요 상태)" |
|
||||
| 예: 창 제어 버튼 | 구현 / 생략 / 보류 / 원본 유지 / 확인 필요 | 시안에 보인 버튼 목록, 사용한 `hiddenControls` 값, 시안 잘림·웹/데스크톱·플랫폼 요구 여부, 기존 화면이면 원본 구성과 사용자 승인 여부 |
|
||||
@@ -23,7 +23,7 @@ SW 공식 기준 Header(Figma 26:8944, 1920 × 49). 로고 · 파일 · 객체
|
||||
| 필수 | `documentTitle: string` |
|
||||
| 구성 | `hiddenControls?: SWHeaderHideableControlId[]`(로고 외 부품 제거, `home` 제거 시 정적 로고. 하위 항목 `documentNew`·`documentOpen`·`layerFreeze`·`layerLock`도 제거 — 레이아웃 변화 없음), `menuOrder?: SWHeaderMenuId[]`(5개 메뉴끼리 순서), `controls?: SWHeaderControlConfig[]`(객체 영역 전체 allowlist·순서·label) |
|
||||
| 로고 오른쪽 | `documentOpenPlacement?: "menu" \| "header"`(기본 `menu` = 기존 Dropdown 하단. `header` = 로고 바로 오른쪽에 파일명과 같은 면의 Open 버튼, Dropdown의 Open은 숨김. 둘 다 `onDocumentOpen`·`documentOpenLabel`), `leading?: ReactNode`(Open 뒤·파일명 앞 추가 부품, 예: `SWToolButton appearance="document"`) |
|
||||
| 로고 | `logo?: ReactNode`, `logoSrc?: string`, `logoAlt?: string`, `onHomeClick`(`homeMenu`가 없을 때만 호출). 로고는 높이 17px·원본 비율이고 홈과 사선 배경이 로고 폭을 따라 늘거나 줍니다. 로고가 없어도 홈과 사선 배경, `onHomeClick`·`homeMenu` 동작은 그대로입니다(빈 로고 자리 32px을 유지해 80 × 48, 2026-10-23). `SWViewerHeader`는 로고가 없으면 로고 면 전체를 그리지 않습니다(이때 `onHomeClick`·`logoMenu`는 누를 곳이 없음) |
|
||||
| 로고 | `logo?: ReactNode`, `logoSrc?: string`, `logoAlt?: string`, `onHomeClick`(`homeMenu`가 없을 때만 호출). 로고는 높이 17px·원본 비율이고 홈과 사선 배경이 로고 폭을 따라 늘거나 줍니다(고정 폭 없음, 긴 로고도 잘리지 않음). 글자 로고는 `logo`에 요소로 전달하고 한 줄·`line-height: 17px`로 맞춥니다(공통 원칙 [DESIGN_RULES.md](./DESIGN_RULES.md) §1). 로고가 없어도 홈과 사선 배경, `onHomeClick`·`homeMenu` 동작은 그대로입니다(빈 로고 자리 32px을 유지해 80 × 48, 2026-10-23). `SWViewerHeader`는 로고가 없으면 로고 면 전체를 그리지 않습니다(이때 `onHomeClick`·`logoMenu`는 누를 곳이 없음) |
|
||||
| 로고 클릭 메뉴 | `homeMenu?: SWHeaderHomeMenu`(= `SWHomeMenu` Props에서 열림 상태 제외, 5절), `homeMenuOpen`, `defaultHomeMenuOpen`(기본 false), `onHomeMenuOpenChange(open, reason: SWHeaderHomeMenuChangeReason)` |
|
||||
| 파일 | `documents`, `activeDocumentId`, `documentMenuOpen`, `onDocumentMenuOpenChange`, `onDocumentSelect`, `onDocumentClose`, `onDocumentNew`, `onDocumentOpen`, `onDocumentClick`, `documentIcon`, `documentNewLabel`, `documentOpenLabel` |
|
||||
| 레이어 | `layer`, `layers`, `activeLayerId`, `layerMenuOpen`, `onLayerMenuOpenChange`, `onLayerSelect`, `onLayerVisibilityChange`, `onLayerFreezeChange`, `onLayerLockChange`, `onLayerDetail`, `onLayerOpen` |
|
||||
@@ -89,10 +89,13 @@ SW 공식 기준 Header(Figma 26:8944, 1920 × 49). 로고 · 파일 · 객체
|
||||
|
||||
| 컴포넌트 | 용도 | 주요 Props | 모양(기존 기준) | 검증 |
|
||||
|---|---|---|---|---|
|
||||
| `SWToolButton` | 버튼·토글 | `appearance`(`text` 기본 / `pill` / `document`), `pressed`(토글·선택), `children`(글자. `pill`은 접근성 이름으로만 사용), `icon`·`hoverIcon`(`pill`), `button` 속성 | `text` = 속성Bar·속성창 글자 토글(700, Hover·선택 #FFC600, 행 19px). `pill` = 인쇄·K 40 × 38(노란 Hover 링, 아이콘 24 × 23 영역, `hoverIcon` 생략 시 Hover에도 기본 아이콘 유지). `document` = 파일명 면(48px, 글자만 Hover #FFC600). Home 바로 뒤에 두면 로고 사선 접점을 그대로 공유 | ✅ |
|
||||
| `SWToolButton` | 버튼·토글 | `appearance`(`text` 기본 / `pill` / `document`), `pressed`(토글·선택), `children`(글자. `pill`은 화면에 보이지 않는 접근성 이름이며 문자열이면 기본 `title`), `icon`·`hoverIcon`(`pill`), `title`(명시 시 우선, `""` = 툴팁 없음), `button` 속성 | `text` = 속성Bar·속성창 글자 토글(700, Hover·선택 #FFC600, 행 19px). `pill` = 인쇄·K 40 × 38(노란 Hover 링, 아이콘 24 × 23 영역, `hoverIcon` 생략 시 Hover에도 기본 아이콘 유지). `document` = 파일명 면(48px, 글자만 Hover #FFC600). Home 바로 뒤에 두면 로고 사선 접점을 그대로 공유 | ✅ |
|
||||
| `SWToolGroup` | 버튼 묶음 | `appearance`(`text` 기본 = 상세보기 묶음, `pill` = 인쇄·K pill), `label`(`text`의 12px 캡션·접근성 이름), `selection`(`single` = radiogroup, 화살표·Home·End로 이동·선택), `div` 속성 | 버튼 사이 1px 구분선(`text`), 내용 폭 pill | ✅ |
|
||||
|
||||
- 탭(Model / Layout)은 별도 컴포넌트 없이 `SWToolGroup selection="single"` + `SWToolButton pressed`입니다.
|
||||
- `pill`은 공백이 아닌 문자열 `children`을 기본 툴팁(`title`)으로 씁니다. 명시한 `title`이 우선하고 `title=""`이면 툴팁이 없습니다. 숫자·JSX `children`은 변환하지 않으므로 필요하면 `title`을 전달합니다. `text`·`document`는 전달한 `title`만 사용합니다([DESIGN_RULES.md](./DESIGN_RULES.md) §15.2).
|
||||
- `pill`의 `icon`·`hoverIcon`에 넣을 아이콘은 [AI_USAGE.md](./AI_USAGE.md) §5.2 순서로 고릅니다(패키지는 Header 도구용 범용 아이콘을 Export하지 않음).
|
||||
- `pill` 버튼은 SWHeader 인쇄 버튼과 같은 `hm-sw-header__action--print` class를 가집니다. 이 class로 인쇄 버튼을 찾거나 덮지 않습니다([DESIGN_RULES.md](./DESIGN_RULES.md) §15.3).
|
||||
- 일반 모드의 상태 표시(로드 상태 등) 전용 SW 디자인은 아직 없습니다(디자인 확정 후 검토). 뷰어 모드는 `SWViewerStatus`(§9)가 있으며 일반 모드 Header에 가져오지 않습니다.
|
||||
|
||||
## 5. 로고 클릭 메뉴 — Figma 2단 Header 메뉴 (SW 일반 모드 전용)
|
||||
@@ -188,16 +191,18 @@ SW **뷰어 모드** 전용 Export입니다(2026-10-19 추가, 참고 DWG/DXF
|
||||
|
||||
| 컴포넌트 | 용도 | 주요 Props | 크기 | 검증 |
|
||||
|---|---|---|---|---|
|
||||
| `SWProcessHeader` | 49px Header: 로고 → 파일명 → 프로세스 영역 · Layer 면 → 창 영역(도구 pill · 창 이동 · 최소화 · 최대화/복원 · 종료) | `appearance`(`"default"` Strana · `"multi"` StrAna-SW 다중 Step, 기본 `default`, `process.appearance`가 없으면 프로세스 영역에도 적용), `documentTitle`(필수, 문서가 없으면 `""`), `documentEmptyLabel`(제목이 비었을 때 파일명 면 오른쪽 위 10px 표시, 예: `프로젝트 생성 전`), `onDocumentClick`, `logo` / `logoText`(글자 로고) / `logoSrc`(SWHeader 로고 칸: 높이 17·로고 비율 폭, 32 × 17 → 홈 80, 두 타입 공통. 글자 로고는 58px 칸 → 홈 106), `logoAlt`(기본 `logoText`), `onHomeClick`, `homeMenuOpen`(로고 메뉴는 호스트 소유), `process: SWProcessBarProps`(필수), `layer?: SWProcessHeaderLayer`(`label` 기본 `레이어`, `value`, `visible`, `open`, `disabled`), `onLayerClick`, `onLayerVisibilityChange(next)`, `tools?: ReactNode`(`SWToolButton appearance="pill"`, Header가 pill로 감쌈), `toolsLabel`(기본 `도구`), `onWindowMoveStart`, `maximized`(기본 false = 최대화 글리프, true = 복원 글리프), `onMinimize`, `onMaximizeToggle`, `onClose`, `hiddenControls?: SWProcessHeaderHideableControlId[]`(`layer`·`tools`·`windowMove`·`minimize`·`maximize`·`close`), `disabled`(프로세스·Layer만, 로고·파일명·도구·창 버튼 제외), `<header>` HTML 속성·`ref` | 높이 49. `default`: 면 48, 파일명 286 : 가운데 1260(프로세스 1003 : Layer 257) 비율, 파일명 100~286, Layer 칸 152~257, 창 영역 약 312(도구 3개). `multi`: 면 49, 파일명 189 : 프로세스 1278, 파일명 100~189, SW pill(버튼 40 × 37), 창 영역 367(도구 5개, 최대화 숨김) | ◻ |
|
||||
| `SWProcessSplitButton` | 화면 분할 도구(Strana 화면 분할, StrAna-SW 6758:12510): `tools` pill 안 버튼 + 233 × 36 선택 패널(칸 6개). 열리면 버튼이 어두운 면 + 금색 위·양옆으로 바뀌고 Header 끝까지 이어진 뒤 패널이 Header 바로 아래에 붙음 | `value: SWProcessSplitType`(필수, 버튼 아이콘·선택 칸), `onValueChange(value)`(다른 칸 선택, 패널은 열린 채), `open` / `defaultOpen` / `onOpenChange(open)`, `label`(기본 `화면 분할`), `typeLabels`, `disabled`. 키보드: Enter·Space 열기/닫기, ArrowDown 열고 선택 칸으로, 방향키·Home·End 선택, Escape 닫고 버튼으로, Tab 이탈·바깥 클릭 닫기. 실제 창 분할은 호스트 | 버튼 40 × 38(`multi` 37), 패널 233 × 36, 칸 38.5 × 34 | ◻ |
|
||||
| `SWProcessHeader` | 49px Header: 로고 → 파일명 → 프로세스 영역 · Layer 면 → 창 영역(도구 pill · 창 이동 · 최소화 · 최대화/복원 · 종료) | `appearance`(`"default"` Strana · `"multi"` StrAna-SW 다중 Step, 기본 `default`, `process.appearance`가 없으면 프로세스 영역에도 적용), `documentTitle`(필수, 문서가 없으면 `""`), `documentEmptyLabel`(제목이 비었을 때 파일명 면 오른쪽 위 10px 표시, 예: `프로젝트 생성 전`. `documentTitle=""`이면 파일명 면에 Hover 색 변화·손가락 커서가 없음 — 크기·위치·색과 `onDocumentClick` 호출은 그대로, 2026-11-05), `onDocumentClick`, `logo`(커스텀 요소) / `logoText`(글자 로고) / `logoSrc`(SWHeader 로고 칸: 높이 17·로고 비율 폭, 32 × 17 → 홈 80, 두 타입 공통. 우선순위 `logo` > `logoText` > `logoSrc`, 셋 다 같은 로고 칸에 들어가 홈 = 12 + 로고 폭 + 36. 글자 로고는 최소 58px 칸 → 홈 최소 106, 더 긴 글자는 글자 폭만큼 넓어짐·잘림 없음), `logoAlt`(기본 `logoText`), `onHomeClick`, `homeMenuOpen`(로고 메뉴는 호스트 소유), `process: SWProcessBarProps`(필수), `layer?: SWProcessHeaderLayer`(`label` 기본 `레이어`, `value`, `visible`, `open`, `disabled`), `onLayerClick`, `onLayerVisibilityChange(next)`, `tools?: ReactNode`(`SWToolButton appearance="pill"`, Header가 pill로 감쌈), `toolsLabel`(기본 `도구`), `onWindowMoveStart`, `maximized`(기본 false = 최대화 글리프, true = 복원 글리프), `onMinimize`, `onMaximizeToggle`, `onClose`, `hiddenControls?: SWProcessHeaderHideableControlId[]`(`layer`·`tools`·`windowMove`·`minimize`·`maximize`·`close`), `disabled`(프로세스·Layer만, 로고·파일명·도구·창 버튼 제외), `<header>` HTML 속성·`ref` | 높이 49. `default`: 면 48, 파일명 286 : 가운데 1260(프로세스 1003 : Layer 257) 비율, 파일명 100~286, Layer 칸 152~257, 창 영역 약 312(도구 3개). `multi`: 면 49, 파일명 189 : 프로세스 1278, 파일명 100~189, SW pill(버튼 40 × 37), 창 영역 367(도구 5개, 최대화 숨김) | ◻ |
|
||||
| `SWProcessSplitButton` | 화면 분할 도구(Strana 화면 분할, StrAna-SW 6758:12510): `tools` pill 안 버튼 + 233 × 36 선택 패널(칸 6개). 열리면 버튼이 어두운 면 + 금색 위·양옆으로 바뀌고 Header 끝까지 이어진 뒤 패널이 Header 바로 아래에 붙음 | `value: SWProcessSplitType`(필수, 버튼 아이콘·선택 칸), `onValueChange(value)`(다른 칸 선택, 패널은 열린 채), `open` / `defaultOpen` / `onOpenChange(open)`, `label`(기본 `화면 분할`, 버튼 접근성 이름·`title`·패널 이름), `typeLabels`, `disabled`. 키보드: Enter·Space 열기/닫기, ArrowDown 열고 선택 칸으로, 방향키·Home·End 선택, Escape 닫고 버튼으로, Tab 이탈·바깥 클릭 닫기. 실제 창 분할은 호스트 | 버튼 40 × 38(`multi` 37), 패널 233 × 36, 칸 38.5 × 34 | ◻ |
|
||||
| `SWProcessBar` | 프로세스 영역: 그룹(번호·이름·단계 탭) + 그룹 사이 화살표, 활성 그룹 발광 면, 비활성 덮개. 단계 하나만 선택(radiogroup) | `appearance`(`"default"` · `"multi"`, 기본 `default`), `groups: SWProcessGroup[]`(필수, `id`·`number`·`label`·`labelHidden`(이름을 보조기술용으로만 둠, `multi`는 제목 줄 높이 유지)·`steps: SWProcessStep[]`(`id`·`label`·`disabled`)·`disabled`), `activeStepId`, `activeGroupId`(기본 = `activeStepId`가 속한 그룹), `onStepSelect(step, group)`(이미 선택된 단계를 다시 누르면 호출 안 함), `disabled`, `aria-label`(기본 `프로세스`), `div` 속성 | `default`: 높이 48, 탭 32(넓을 때 127). `multi`: 높이 49, 제목 12px 위 + 탭 25(글자 폭 + 25, 간격 0). 그룹은 자연 폭까지·좁으면 비례 축소 | ◻ |
|
||||
|
||||
- **`default` 확정 디자인은 2그룹**(01 Modeling: Structure · Loads / 02 Analysis Result: Results)입니다. 1그룹·3그룹 이상은 렌더링되지만 디자인 미확정입니다.
|
||||
- **사다리꼴(그룹 면) 배치 확정(2026-11-03):** 시작(첫 그룹)과 끝(마지막 그룹)에만 둡니다(켜졌으면 발광 면, 아니면 비활성 면). 가운데 그룹은 켜져 있어도 도형 없이 밝은 바탕만 보입니다. 그룹 수와 관계없이, `default`·`multi` 공통입니다. 그룹 사이 화살표·비활성 덮개·선택 상태는 그대로입니다.
|
||||
- **`multi`**(Figma StrAna-SW 873:16481 · 873:16254 · 873:17421): 그룹 수·단계 수는 데이터대로이며, 켜진 그룹보다 앞 그룹은 진행 완료(제목 700 흰색·밝은 탭), 뒤 그룹은 미진행(제목 500 `#C3EBDF`·70% 탭)으로 표시합니다. 바탕·덮개·발광·화살표·선택·키보드는 `default`와 같은 구현입니다.
|
||||
- **키보드:** 탭 사이 Arrow(좌우·상하)·Home·End 이동 + 선택(비활성 단계 건너뜀), Tab은 선택된 단계 하나로만 들어갑니다.
|
||||
- **단계 활성화(현재 구현, 2026-11-05 명시):** 단계는 기본으로 모두 선택할 수 있습니다. `step.disabled: true`, `group.disabled: true`, `process.disabled` 또는 Header `disabled`를 명시했을 때만 비활성입니다(생략·`false`는 활성). 프로젝트 생성 여부·파일명 유무(`documentTitle`, `documentEmptyLabel`)는 활성화에 영향을 주지 않으므로, 새 프로젝트라는 이유로 `disabled`를 넣지 않습니다. 단계 선택은 호스트가 `activeStepId`를 바꾸는 방식이며(`onStepSelect`에서 갱신, 이전·다음 버튼도 같은 값을 바꿈), 그룹의 켜짐·진행 완료 모양은 `activeStepId`로 정해집니다. `activeStepId`가 없으면 선택된 단계와 켜진 그룹 없이 모든 그룹이 켜지지 않은 모양이지만 단계는 그대로 선택할 수 있고, `groups`가 비면 단계 없이 프로세스 바탕만 그립니다. 컴포넌트는 단계를 만들거나 자동 선택하지 않습니다.
|
||||
- **반응형:** 숨김·More·스크롤 없음. 보장 폭 1100px 이상이며 더 좁으면 Header 안에서 잘립니다. 탭 글자·파일명·Layer 이름은 `…` 말줄임. 공간이 부족하면 선택된 탭도 다른 탭과 같은 비율로 줄어듭니다(2026-11-03 개정): `default`는 모든 탭이 같은 폭, `multi`는 글자 폭에 비례하며 이름이 들어가지 않으면 `…`. 선택이 바뀌어도 탭 폭은 그대로입니다. 그룹은 이름 + 모든 탭 여백보다 좁아지지 않아 겹치지 않습니다. 그룹 사이 화살표·간격은 고정입니다. **넘침 정책 확정(2026-11-03): 그룹 접기·좌우 스크롤·More 메뉴를 추가하지 않습니다.**
|
||||
- **범위 밖(호스트 소유):** 로고 메뉴 본체, Layer Popup, 실제 화면 분할 실행, 도구 아이콘(소비 측이 `tools`에 넣는 요소). 화면 분할 선택 UI는 `SWProcessSplitButton`(2026-11-03 추가). `SWHomeMenu`는 이 Header에 쓰지 않습니다(2단 타일 디자인이 다름).
|
||||
- **원문 확인(현재 구현):** 파일명·Layer 값은 `title`로 전체 이름을 보여 줍니다. 단계 탭 이름과 `multi` 그룹 제목은 말줄임되어도 `title`이 없고 접근성 이름(탭 `aria-label`, 그룹 이름 `aria-describedby`)만 전체 이름입니다. 개선은 별도 과제입니다([DESIGN_RULES.md](./DESIGN_RULES.md) §15.1). 호스트 `tools`의 `SWToolButton pill`은 문자열 `children`이 기본 툴팁이 되며, 숫자·JSX `children`이면 호스트가 `title`을 전달합니다(§4).
|
||||
- **범위 밖(호스트 소유):** 로고 메뉴 본체, Layer Popup, 실제 화면 분할 실행, 도구 아이콘(소비 측이 `tools`에 넣는 요소). 도움말·3D 보기·설정·메뉴·나가기 같은 Header 도구 아이콘은 공개 Export가 없고, Storybook `SW / Step Header`의 아이콘은 공개 API가 아닙니다. 시안의 아이콘 선택과 창 제어 버튼 생략(`hiddenControls`)은 [AI_USAGE.md](./AI_USAGE.md) §5를 따르며, 창 버튼 기본 구성(모두 표시)은 바뀌지 않습니다. 화면 분할 선택 UI는 `SWProcessSplitButton`(2026-11-03 추가). `SWHomeMenu`는 이 Header에 쓰지 않습니다(2단 타일 디자인이 다름).
|
||||
- **SWHeader와 같은 상호작용(2026-11-03):** 창 이동은 `SWHeaderWindowControls`의 창 이동(Hover 1.4s Motion·툴팁)을 그대로 씁니다. 글자 외곽선·Hover·Focus도 SWHeader 규칙(DESIGN_RULES §14). 도구 아이콘이 이미지면 `SWToolButton`의 `hoverIcon`으로 노란 그림(`#B6D0C9 → #FFC600`)을 함께 주고, 단색 SVG면 `currentColor`로 칠합니다.
|
||||
- **단독 사용:** `SWProcessBar`만 쓸 때는 `<SWTheme>` 안에 둡니다. `SWProcessHeader`는 루트에 `hm-sw-theme`이 있어 `<SWTheme>`이 필요 없습니다.
|
||||
- **공개 타입:** `SWProcessHeaderProps`, `SWProcessHeaderLayer`, `SWProcessHeaderHideableControlId`, `SWProcessBarProps`, `SWProcessGroup`, `SWProcessStep`, `SWProcessAppearance`(`"default" | "multi"`), `SWProcessSplitType`(`single` · `columns` · `rows` · `topWide` · `leftTall` · `grid` = Strana Type01~06), `SWProcessSplitButtonProps`.
|
||||
|
||||
@@ -273,14 +273,16 @@ const groups: SWProcessGroup[] = [
|
||||
onMinimize={minimize} onMaximizeToggle={toggleMaximize} onClose={closeWindow} />
|
||||
```
|
||||
|
||||
1. 도구 아이콘은 프로그램이 그린 24 × 24 요소입니다. 버튼 상태 색(기본 `#B6D0C9`, Hover·눌림 `#FFC600`)을 따르려면 `currentColor`로 칠하고, 색이 고정된 이미지 아이콘이면 `hoverIcon`에 `#FFC600` 그림을 함께 줍니다(SW 인쇄 버튼과 같은 방식).
|
||||
1. 도구 아이콘은 프로그램이 그린 24 × 24 요소입니다. 버튼 상태 색(기본 `#B6D0C9`, Hover·눌림 `#FFC600`)을 따르려면 `currentColor`로 칠하고, 색이 고정된 이미지 아이콘이면 `hoverIcon`에 `#FFC600` 그림을 함께 줍니다(SW 인쇄 버튼과 같은 방식). 시안의 아이콘을 고를 때는 [AI_USAGE.md](./AI_USAGE.md) §5.2 순서(제공 에셋 → 공개 Export → 미지원 보고)를 따릅니다. Storybook `SW / Step Header`의 도구 아이콘은 공개 API가 아닙니다(§5.3).
|
||||
2. 화면 분할은 `SWProcessSplitButton`을 `tools`의 원하는 자리에 둡니다(두 appearance 공통). 고른 값으로 실제 창을 나누는 일은 프로그램이 합니다. 분할 아이콘·선택 패널을 직접 그리지 않습니다.
|
||||
3. 프로세스 영역만 다른 곳에 둘 때는 `<SWTheme>` 안에 `SWProcessBar`를 둡니다.
|
||||
4. 다른 테마(Strana Dark 등)는 11처럼 가장 바깥 범위 `style`에 `--hm-sw-process-*`·`--hm-sw-header-*` 토큰을 지정합니다.
|
||||
5. `SWHeader`의 메뉴(레이어·색상·선/문자/치수스타일 Dropdown)를 이 Header에 넣지 않습니다. 필요하면 `SWHeader`를 씁니다.
|
||||
6. **그룹·단계가 많은 다중 Step 화면**(StrAna-SW Figma 873:16481 등)은 `appearance="multi"`입니다. 같은 `process` 데이터 구조를 쓰고, 제목이 없는 그룹은 `labelHidden`, 아직 열 수 없는 단계는 `disabled`, 문서가 없을 때는 `documentTitle=""` + `documentEmptyLabel`입니다. Figma 프레임에는 Layer 필드와 최대화 버튼이 없으므로 `layer`를 주지 않고 `hiddenControls={["maximize"]}`를 씁니다. 그룹·단계가 많아도 데이터를 줄이거나 접기·스크롤·More를 만들지 않습니다(넘침 정책: 선택 단계 포함 비례 축소 + `…`, 2026-11-03 개정).
|
||||
6. **그룹·단계가 많은 다중 Step 화면**(StrAna-SW Figma 873:16481 등)은 `appearance="multi"`입니다. 같은 `process` 데이터 구조를 쓰고, 제목이 없는 그룹은 `labelHidden`, 호스트가 막아야 한다고 명시한 단계만 `disabled: true`(생략하면 선택 가능, 새 프로젝트·파일명 없음은 비활성 조건이 아님 — [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) 11절 "단계 활성화"), 문서가 없을 때는 `documentTitle=""` + `documentEmptyLabel`입니다. Figma 프레임에는 Layer 필드와 최대화 버튼이 없으므로 `layer`를 주지 않고 `hiddenControls={["maximize"]}`를 씁니다. 그룹·단계가 많아도 데이터를 줄이거나 접기·스크롤·More를 만들지 않습니다(넘침 정책: 선택 단계 포함 비례 축소 + `…`, 2026-11-03 개정).
|
||||
7. **창 제어 버튼은 시안에 보이는 것만** 둡니다. 위 예시의 창 이동·최소화·종료는 그 Figma 프레임에 있기 때문이며 모든 화면의 기본이 아닙니다. 시안에 없는 버튼은 `hiddenControls`(`"windowMove"`·`"minimize"`·`"maximize"`·`"close"`)로 빼고, 잘린 시안·웹/데스크톱 구분은 [AI_USAGE.md](./AI_USAGE.md) §5.5, 예시는 §5.6을 따릅니다. 기존 화면의 창 버튼은 시안에 없다는 이유만으로 빼지 않습니다(AI_USAGE §5.1). 컴포넌트 기본값은 바꾸지 않습니다.
|
||||
|
||||
```tsx
|
||||
// helpIcon / helpIconHover: 사용자·소비 프로젝트가 제공한 에셋(패키지에 도움말 아이콘 Export 없음, AI_USAGE §5.2)
|
||||
<SWProcessHeader appearance="multi" documentTitle={projectName ?? ""} documentEmptyLabel="프로젝트 생성 전"
|
||||
logoSrc={logoUrl} logoAlt={productName} onHomeClick={toggleFileMenu}
|
||||
process={{ groups: [
|
||||
@@ -298,5 +300,7 @@ const groups: SWProcessGroup[] = [
|
||||
- `dist` 구현·`styles.css` 블록 복사로 같은 컴포넌트 재작성
|
||||
- `@hm-design/sw/dist/...`·`src/...` 내부 경로 Import
|
||||
- 문서에 없는 Props 추측, 새 색·크기 값 도입
|
||||
- Storybook 데모 아이콘·파일을 Import·복사하거나, 공식 아이콘이 없을 때 외부 아이콘 라이브러리·AI 생성 SVG로 대체([AI_USAGE.md](./AI_USAGE.md) §5.2·§5.3)
|
||||
- 시안에 없는 창 제어 버튼을 예시·기본 구성에 있다는 이유로 만들기([AI_USAGE.md](./AI_USAGE.md) §5.5)
|
||||
- 같은 화면에 `SWHeader`와 조합 Header를 겹쳐 두 개의 Header 만들기
|
||||
- 같은 화면에 `SWHeader`와 `SWViewerHeader`를 함께 두기, 일반 모드 화면에 `SWViewer*` 사용, 뷰어 화면에서 `SWHeader`·`SWPopup` 재스타일(AI_USAGE §0.3)
|
||||
@@ -17,6 +17,7 @@
|
||||
| 필드 높이 | 38px(면 안 위아래 5px) | `--hm-sw-header-field-height` |
|
||||
| 필드 사이 간격 | 4px | `--hm-sw-header-gap` |
|
||||
| 로고 슬롯 | 높이 17, 폭은 로고 원본 비율(`-width` 기본 `auto`, Figma 예시 32 × 17). 홈 = 왼쪽 12 + 로고 + 36(사선), 로고는 그 안 콘텐츠 영역 가로·세로 가운데. 사선 배경(Figma 68 × 48)은 로고 폭만큼 늘거나 줄며 사선 모양·Hover는 그대로(9-slice). 로고가 없어도 홈과 사선 배경·동작은 그대로(빈 로고 자리 32px 유지, 80 × 48 · 사선 68 × 48, 파일명 위치 동일). `SWViewerHeader`는 로고가 없으면 로고 면 전체 없음(2026-10-23) | `--hm-sw-header-logo-width` / `-height` |
|
||||
| 로고 확장 공통 원칙(`SWHeader`·`SWViewerHeader`·`SWProcessHeader`, 2026-11-05) | 세 Header는 같은 로고 칸(`SWHeaderLogo`)과 같은 사선 배경(9-slice)을 씁니다. 홈(로고 면)의 폭·클릭 영역·사선 끝은 로고 내용 폭이 정하며 긴 로고도 잘리지 않고 홈은 줄지 않습니다(옆 영역이 각 Header 규칙대로 줄어듦). 로고·홈에 고정 폭이나 특정 로고 이름별 예외를 추가하지 않습니다. 이미지(`logoSrc`)·SVG는 높이 17·원본 비율, 커스텀 `logo` 요소는 자기 폭 그대로이며 칸은 상자만 17로 제한하므로 글자 요소는 `line-height: 17px`·한 줄로 전달합니다(CSS가 강제하지 않음). 로고 관련 변경 시 `logo`·`logoSrc`(Process는 `logoText`도) 경로와 옆 영역 폭을 Header별로 확인합니다. 높이·여백·겹침·로고 없음 동작·Hover는 Header마다 다르므로 통일하지 않습니다(§11 뷰어, §14 Process) | 위와 같음 |
|
||||
| 필드 폭 | 레이어 234, 색상 192, 선스타일 284, 문자·치수스타일 164 | — |
|
||||
| 상세보기 / 창 영역 | 157 × 38 / 240 × 48(pill 85 × 42, 창 이동 88, 버튼 32 × 2) | — |
|
||||
| 필드 내부 | padding 0 10px 0 11px, 텍스트 사이 12px | — |
|
||||
@@ -49,7 +50,7 @@
|
||||
| Weight | 400, 500, 700만 사용(600 없음). SWHeader 안의 필드 값(레이어·색상·선스타일·가중치·스케일·문자/치수스타일 값)은 400(14px · 자간 0.56px), Composed Controls(SWHeader 밖 단독 메뉴)의 필드 값은 700(2026-10-20). 파일명·제목·토글·버튼·메뉴 글자 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`로 전체 표시 |
|
||||
| 말줄임 | 값은 한 줄 `ellipsis`, `title`로 전체 표시. 적용 범위·키보드·터치 기준과 Header별 현재 구현은 §15.1 |
|
||||
|
||||
## 4. 상태
|
||||
|
||||
@@ -123,9 +124,12 @@
|
||||
| appearance | 기존 기준 | 상태 |
|
||||
|---|---|---|
|
||||
| `text` | 속성Bar·속성창 토글: 19px 행, 14px 700, 자간 0.04em, 묶음 위 12px 캡션, 버튼 사이 1px 구분선 | Hover·`pressed` 글자 `#FFC600`, 테두리·배경 변화 없음 |
|
||||
| `pill` | 인쇄·K: pill(`rgba(0,0,0,.8)`, 1px `#092920`, radius 4px) 안 40 × 38 버튼, 아이콘 영역 24 × 23 | Hover 노란 gradient 링 + `hoverIcon`(생략 시 기본 아이콘 유지). 아이콘 색은 기존 인쇄 아이콘 `#B6D0C9` → Hover `#FFC600`(`--hm-sw-header-selected-color`) |
|
||||
| `pill` | 인쇄·K: pill(`rgba(0,0,0,.8)`, 1px `#092920`, radius 4px) 안 40 × 38 버튼, 아이콘 영역 24 × 23 | Hover 노란 gradient 링 + `hoverIcon`(생략 시 기본 아이콘 유지). 아이콘 색은 기존 인쇄 아이콘 `#B6D0C9` → Hover `#FFC600`(`--hm-sw-header-selected-color`) — 인쇄 이미지 두 장의 색입니다. `currentColor` 아이콘은 상속 글자색이며 Hover에서 바뀌지 않고 `pressed`만 `#FFC600`(링·`hoverIcon` 전환 없음). Process Header `tools`는 §14, 상태 비교는 [AI_USAGE.md](./AI_USAGE.md) §5.4 |
|
||||
| `document` | 파일명 면: 48px, gradient 표면, 위·오른쪽·아래 1px gradient 테두리(`--hm-sw-header-document-border-gradient`), 글자 `#F0F7F5` 700 · 자간 0.06em | 글자만 Hover `#FFC600`. Home 바로 뒤면 로고 사선 접점 공유, 그 뒤 파일명은 왼쪽 여백 12px |
|
||||
|
||||
- 아이콘 전용 `pill`의 이름·툴팁 기준은 §15.2, `pill`이 인쇄 버튼 class를 공유하는 구조는 §15.3입니다.
|
||||
- 이 절은 상태 값만 정합니다. 디자인 시안의 아이콘을 어디서 가져올지(제공 에셋 → 공개 Export → 미지원 보고)와 시안에 없는 창 제어 버튼 생략은 [AI_USAGE.md](./AI_USAGE.md) §5입니다(§14 Process Header 도구 pill에도 적용).
|
||||
|
||||
## 10. 새 컴포넌트 체크리스트
|
||||
|
||||
1. `<SWTheme>`(또는 `SWHeader`의 `tools`) 안에서 렌더링한다.
|
||||
@@ -221,8 +225,9 @@
|
||||
|
||||
| 항목 | 값 | 토큰 |
|
||||
|---|---|---|
|
||||
| 배치 | 홈·파일명 글자 = SWHeader 그대로(두 타입 공통, 2026-11-03): 홈 12 + 로고(32 × 17 → 80) + 36, 파일명은 홈과 15 겹친 뒤 2 간격(x 65), padding 5 / 12, 14px 700·자간 0.06em·SW 외곽선, 화살표 없음(글자 로고 18px 700 → 홈 106) → 프로세스 → Layer 면(객체 면, 양옆 1) → 창 영역(파일명 면 + 테두리 1px) | SW 토큰 |
|
||||
| 프로세스 바탕 | 세로 `#0C5941 → #19BF8D 10.1% → #109069 18.27% → #0C5941`, 파일명 테두리 1px, 위 모서리 6 | `--hm-sw-process-base-background`, `-base-border-gradient` |
|
||||
| 배치 | 홈·파일명 글자 = SWHeader 그대로(두 타입 공통, 2026-11-03): 홈 12 + 로고(32 × 17 → 80) + 36, 파일명은 홈과 15 겹친 뒤 2 간격(x 65), padding 5 / 12, 14px 700·자간 0.06em·SW 외곽선, 화살표 없음(글자 로고 18px 700, 최소 58px 칸 → 홈 최소 106, 더 긴 글자는 글자 폭만큼 넓어짐) → 프로세스 → Layer 면(객체 면, 양옆 1) → 창 영역(파일명 면 + 테두리 1px) | SW 토큰 |
|
||||
| 로고 폭(Process 고유, 2026-11-05) | 공통 원칙은 §1 "로고 확장 공통 원칙". Process 고유: `logoText`만 최소 58px 칸(→ 홈 최소 106), 넓어진 홈만큼 파일명(최소 100)·가운데 영역이 flex 비율로 줄어듦. 로고 변경 시 `default`·`multi`를 각각 확인하고 단계 말줄임 정책은 그대로 둡니다 | SW 토큰 |
|
||||
| 프로세스 바탕 | 세로 `#0C5941 → #19BF8D 10.1% → #109069 18.27% → #0C5941`, 파일명 테두리 1px, 위 모서리 6. `multi`는 양 끝 직각(2026-11-05 — 단계 탭 모양과 별개) | `--hm-sw-process-base-background`, `-base-border-gradient` |
|
||||
| 비활성 덮개 | `rgba(0, 68, 48, .902)`: 비활성 그룹, 화살표 꼭지(앞 그룹 비활성)·홈(뒤 그룹 비활성), 비활성 바깥 그룹 쪽 남는 폭 | `--hm-sw-process-dim` |
|
||||
| 그룹 글자 | 번호 12px 500 `#69C2A6`, 이름 12px(최대 56, 줄바꿈) 활성 700 `#FFF` / 비활성 500 `#C3EBDF`, 위 10.67(한 줄)·8.67(두 줄), 그룹 좌우 20, 이름 뒤 12 | `--hm-sw-process-number-color`, `-group-color`, `-group-active-color` |
|
||||
| 활성 그룹 면 | 기운 변 도형, 3층 면(85·80·45%), 발광 `drop-shadow(0 0 30px rgba(88,181,153,.85))`, 가장자리 빛 3px Blur 5, 화살표 쪽 76px에 걸쳐 사라짐. 첫 그룹 왼쪽 40px 밖, 마지막 그룹 좌우 반전·44px 밖 | `--hm-sw-process-glow-*` |
|
||||
@@ -255,3 +260,44 @@
|
||||
| 탭 | 높이 25, 글자 폭 + 25(padding 13 / 12), 14px 700, 간격 0. 모서리: 단독 50, 첫 50 0 0 50, 마지막 0 50 50 0, 가운데 0. 좁으면 선택 탭 포함 비례 축소 + `…`. 화살표 칸·그룹 사이 36 고정. 그룹 접기·좌우 스크롤·More 없음(넘침 정책 확정 2026-11-03) | — |
|
||||
| 탭 상태 | 기본(미진행) 70% `rgba(52,143,114,.8) → rgba(2,50,35,.8)` · `#0C3125`. 밝음(켜진·진행 완료) 100% `rgba(68,178,143,.8) → rgba(0,61,41,.8)` · `#0A5940`. 선택 `#113228 → #28735C`(아래 → 위) · `#001E15` · 안쪽 그림자, 글자 `#FFC600`. Disabled 50% 속성 필드 면 · `#021912`, 글자 `#7D9F91`. 공통 그림자 `1px 1px 1px rgba(31,148,113,.7)`. Hover 글자 `#FFC600`, Focus 2px `#F0F7F5` | `--hm-sw-process-multi-tab-*`, `--hm-sw-header-property-background` |
|
||||
- 새 프로세스 UI는 위 토큰을 `var(--…)`로 읽고 `hm-sw-process-*` class를 쓰거나 덮어쓰지 않습니다.
|
||||
|
||||
## 15. Header 공통 UX·레이아웃 원칙 (`SWHeader` · `SWViewerHeader` · `SWProcessHeader`, 2026-11-05)
|
||||
|
||||
세 Header와 그 안의 SW 도구 버튼에 공통으로 적용합니다. 크기·반응형·말줄임 시점 같은 Header별 값은 각 절(§7 SWHeader, §11·[VIEWER.md](./VIEWER.md) 뷰어, §14 Process)과 디자인 명세를 따르며 이 절은 그 정책을 바꾸지 않습니다. "현재 구현"은 소스 기준 사실이고, "개선 필요"는 아직 구현되지 않은 권고입니다(구현 완료가 아님).
|
||||
|
||||
### 15.1 말줄임 텍스트의 원문 확인
|
||||
|
||||
- 말줄임 때문에 전체 내용을 알 수 없는 텍스트(파일명·단계명·그룹명·값 등)에는 원문을 확인할 수단을 둡니다. 기본 수단은 그 요소나 버튼의 `title`이고, SW 툴팁(창 이동·K 가이드 등)은 해당 컴포넌트에 이미 있는 경우에만 씁니다. 모든 텍스트에 툴팁을 일괄로 붙이지 않습니다.
|
||||
- 기존 접근성 이름(`aria-label`, 연결된 label)은 원문 그대로 유지합니다. 툴팁은 접근성 이름을 대신하지 않습니다.
|
||||
- `title`은 마우스 Hover에서만 보이고 키보드 Focus·터치에서는 표시되지 않습니다. 키보드·터치 사용자에게도 원문이 필요한 곳은 다른 확인 경로(목록·More·Popup 등)나 Focus 표시를 컴포넌트별로 검토합니다. 현재 공통 구현은 없습니다.
|
||||
- 잘렸을 때만 `title`을 붙이는 방식은 `SWFooterToolButton`에 선례가 있습니다(Hover·Focus 때 실제 폭을 측정하고, 전달된 `title`이 없을 때만 적용). 적용 여부는 컴포넌트별로 판단합니다.
|
||||
|
||||
| Header | 현재 구현: 원문 `title` 있음 | 개선 필요: 말줄임되지만 `title` 없음(접근성 이름은 원문) |
|
||||
|---|---|---|
|
||||
| `SWHeader` | 파일명, 문서 목록 항목(`title ?? name`), 필드 값, 문자스타일 글꼴명, 레이어 목록 이름, 속성Bar 객체명 | 메뉴 타이틀(필드 12px 라벨, 버튼 `aria-label`) |
|
||||
| `SWViewerHeader` | 파일명(`documentTooltip`, 기본 이름), 상태 표시(`detail`), 아이콘만 남은 버튼·토글(`title ?? label`) | Model / Layout 탭 이름(`option.title`을 줄 때만), 아이콘만 남은 `menu` 항목(`item.title`을 줄 때만), 말줄임된 일반 항목 라벨 |
|
||||
| `SWProcessHeader` | 파일명, Layer 값(`layer.value`) | 단계 탭 이름, `multi` 그룹 제목(그룹 이름은 `aria-describedby`로 연결) |
|
||||
|
||||
### 15.2 아이콘 전용 도구 버튼
|
||||
|
||||
- 아이콘만 보이는 버튼은 의미를 식별할 수 있는 접근성 이름이 있어야 합니다. `SWToolButton appearance="pill"`은 `children`을 시각적으로 숨긴 글자(`hm-sw-tool__label`)로 그려 접근성 이름으로 씁니다(또는 `aria-label`).
|
||||
- **현재 구현(`SWToolButton` 자동 툴팁, 2026-11-05):** `appearance="pill"`만 적용합니다. 우선순위는 ① 명시한 `title`(`title=""`이면 툴팁 없음 — 공식 비활성화 방법) → ② 공백이 아닌 문자열 `children`을 원문 그대로 `title`로 사용 → ③ 툴팁 없음입니다. 빈 문자열·공백만 있는 문자열·숫자·JSX·배열(`{label} 보기`처럼 값을 이어 쓴 JSX 포함) `children`은 문자열로 변환하지 않아 툴팁이 생기지 않으므로, 이때는 템플릿 문자열(`` {`${label} 보기`} ``)이나 `title`을 씁니다. `text`·`document`는 전달한 `title`만 그대로 붙입니다(자동 툴팁 없음). 접근성 이름(`children`·`aria-label`·`aria-labelledby`)과 클릭·키보드 동작, DOM·class는 그대로입니다. `aria-label`과 `children`이 다르면 툴팁은 `children`, 접근성 이름은 `aria-label`입니다.
|
||||
- `SWProcessSplitButton`은 `label`(기본 `화면 분할`)을 `children`과 `title`에 함께 전달합니다. SWHeader의 인쇄·창 버튼과 Process 창 버튼은 `SWToolButton`이 아니며 `aria-label`만 있고 `title`이 없습니다(창 이동은 SW 툴팁).
|
||||
- Hover 툴팁은 보조 수단이며 접근성 이름·Focus 표시·키보드 동작을 대신하지 않습니다. 자동 툴팁도 네이티브 `title`이라 키보드 Focus·터치에서는 보이지 않습니다(§15.1, 미해결). 툴팁을 더할 때 기존 `aria-*`와 클릭·Enter·Space 동작을 바꾸지 않습니다.
|
||||
|
||||
### 15.3 CSS 클래스 역할 구분
|
||||
|
||||
- 여러 버튼이 공유하는 외형(공통)과 특정 기능 전용 스타일은 class 이름으로 구분합니다. 기능 이름이 붙은 class를 다른 기능의 버튼이 재사용하면 그 class의 변경·선택자가 모든 사용처에 영향을 주므로, 변경 전에 사용처(TSX·CSS·테스트·QA baseline)를 검색합니다.
|
||||
- **현재 구조:** `hm-sw-header__action--print`는 SWHeader 인쇄 버튼(`SWHeader.tsx`, `SWHeaderActions.tsx`)과 `SWToolButton appearance="pill"`(→ `SWHeader`·`SWProcessHeader`의 `tools`, `SWProcessSplitButton`)에 함께 붙습니다. pill 버튼의 1px 테두리·radius·파일명 면 배경은 이 class에서 오고, `> img` 아이콘 위치 규칙은 `<img>`를 직접 자식으로 둔 인쇄 버튼에만 맞습니다(pill은 `.hm-sw-tool__icon`). Process는 `.hm-sw-tool__pill-button`으로 자기 값을 덮습니다. `tests/header-composition.browser.mjs`(인쇄 버튼 수)와 `docs/sw/detail-button-qa/baseline.json`도 이 class를 기준으로 삼습니다.
|
||||
- 소비 코드는 이 class로 인쇄 버튼을 찾거나 덮지 않습니다(§10-6). 공통 pill 외형 class와 인쇄 전용 class의 분리는 class·선택자·테스트 baseline이 함께 바뀌는 별도 구현 과제입니다.
|
||||
|
||||
### 15.4 글꼴에 따른 실제 텍스트 폭
|
||||
|
||||
- 로고 폭 원칙은 §1 "로고 확장 공통 원칙"을 따르며 여기서 반복하지 않습니다. 이 절은 글꼴 차이만 다룹니다.
|
||||
- 텍스트 로고·파일명·탭·필드 값처럼 폭이 내용에 따라 정해지는 영역은 실제 렌더링 글꼴에 따라 폭이 다릅니다. §3의 SW 기본 글꼴은 앱이 글꼴 파일을 로드할 때 기준이며, 소비 프로젝트가 Noto Sans KR을 로드하지 않았거나 다른 글꼴을 지정하면 대체 글꼴로 그려져 글자 폭·말줄임 시점·홈 폭이 문서 실측값과 달라질 수 있습니다.
|
||||
- 문서의 px 실측(예: §14·[COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)의 Process 로고 칸 값, HMDesign 저장소 Process 명세의 StrAna·Accessway 값)은 해당 글꼴·브라우저·당시 Commit 기준의 검증 사례이며 고정 규격이나 디자인 토큰이 아닙니다. 새 로고 이름이나 글꼴마다 고정 폭·예외를 추가하지 않고, 내용 폭을 따르는 기존 구조와 Header별 최소 폭으로 처리합니다.
|
||||
- 폭을 검증할 때는 글꼴이 실제로 로드됐는지 확인하고, 다른 글꼴 환경의 결과는 글꼴 이름을 함께 기록합니다.
|
||||
|
||||
### 15.5 공통 규칙으로 강제하지 않는 사항
|
||||
|
||||
- 단계 수·이름 길이에 따른 말줄임, 선택 단계 최소 폭, 권장 화면 폭, 단계 축소·More·스크롤은 `SWProcessHeader` 고유 검토 항목입니다(현재 동작은 [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) 11절 반응형, 검토 기록은 HMDesign 저장소 `docs/sw/sw-process-header-design-spec.md` §12). 다른 Header에 같은 기준을 적용하지 않습니다.
|
||||
@@ -159,6 +159,6 @@ const groups: SWViewerHeaderGroup[] = [
|
||||
|
||||
## 7. 디자인 변경 시 주의
|
||||
|
||||
- 크기(52px Header, 34px 버튼, 40px 토글·스위치 칸, 41~42px field 면)와 색은 참고 뷰어 실측값이고(Model / Layout 탭은 2026-10-23 Figma 7652:40344 모양, 높이는 field 그룹과 같은 42px·버튼 37.5px), 로고 면 64px는 2026-10-23 `SWHeader` 로고 기준입니다. 파일명·객체 수정·속성 면 배경은 SWHeader와 같은 사용자 확정 토큰(`--hm-sw-header-document-*`, `object-*`, `property-*`)을 공유하므로 바꾸면 두 Header가 함께 바뀝니다.
|
||||
- 크기(52px Header, 34px 버튼, 40px 토글·스위치 칸, 41~42px field 면)와 색은 참고 뷰어 실측값이고(Model / Layout 탭은 2026-10-23 Figma 7652:40344 모양, 높이는 field 그룹과 같은 42px·버튼 37.5px), 로고 면은 2026-10-23 `SWHeader` 로고 기준이며 폭은 고정값이 아니라 로고 내용 폭을 따릅니다(Header 테두리 1px 안쪽에서 11 + 로고 + 36, 로고 위치는 SWHeader와 같은 x 12. 32 × 17 예시 로고에서 79px은 측정 예시, 공통 원칙은 [DESIGN_RULES.md](./DESIGN_RULES.md) §1). 파일명·객체 수정·속성 면 배경은 SWHeader와 같은 사용자 확정 토큰(`--hm-sw-header-document-*`, `object-*`, `property-*`)을 공유하므로 바꾸면 두 Header가 함께 바뀝니다.
|
||||
- **높이:** 패키지는 뷰어(도면) 영역 높이를 정하지 않습니다. Header는 52px 한 줄, 팝업만 최대 90vh입니다. 화면 높이는 앱의 상위 레이아웃이 정하고(예: `html, body, #root { height: 100% }` → 세로 flex, Header 다음 도면 영역 `flex: 1; min-height: 0`), 도면 영역 크기 변화에 맞춰 렌더러 크기를 바꾸는 것은 프로그램 담당입니다(ResizeObserver 권장). Storybook 예시의 고정 높이는 미리보기 프레임 값일 뿐 컴포넌트 값이 아닙니다.
|
||||
- Light/Dark 분기는 없습니다. 참고 뷰어와의 알려진 차이: 도면 공간(tabs) field 면 높이 42px(참고 41px — 참고 Tabs 루트가 39px인 구현 부산물), 크기 조절은 네이티브 `resize` 대신 같은 위치의 모서리 Grip(모든 브라우저 동작), 패널 Escape 닫기 추가, 로고 기본 표시 없음(사용자 결정).
|
||||
@@ -348,7 +348,8 @@ export interface SWHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"
|
||||
defaultHomeMenuOpen?: boolean
|
||||
onHomeMenuOpenChange?: (open: boolean, reason: SWHeaderHomeMenuChangeReason) => void
|
||||
/**
|
||||
* Logo image URL of the SW program, contained in the 32 x 17 logo slot.
|
||||
* Logo image URL of the SW program, 17px high at its own ratio in the logo slot (32 x 17 is the Figma example, not a
|
||||
* fixed size); the home and its slant follow the logo width (12 + logo + 36).
|
||||
* The logo is decided by each program: none is rendered when omitted.
|
||||
*/
|
||||
logoSrc?: string
|
||||
|
||||
@@ -52,10 +52,14 @@ export const SWProcessHeader = forwardRef<HTMLElement, SWProcessHeaderProps>(fun
|
||||
const captions = (["minimize", "maximize", "close"] as const).filter(id => !hidden.has(id))
|
||||
const windowShown = toolsShown || !hidden.has("windowMove") || captions.length > 0
|
||||
const documentEmpty = documentTitle === "" && !!documentEmptyLabel
|
||||
// No file yet (documentTitle ""): the file name does not look clickable (no hover color, default cursor). The button
|
||||
// and `onDocumentClick` stay as they were (2026-11-05: hosts may still listen to it); only the look changes.
|
||||
const documentUntitled = documentTitle === ""
|
||||
|
||||
return (
|
||||
<header ref={ref} className={`hm-sw-theme hm-sw-process-header${className ? ` ${className}` : ""}`}
|
||||
data-appearance={appearance === "multi" ? "multi" : undefined} data-document-empty={documentEmpty || undefined} {...rest}>
|
||||
data-appearance={appearance === "multi" ? "multi" : undefined} data-document-empty={documentEmpty || undefined}
|
||||
data-document-untitled={documentUntitled || undefined} {...rest}>
|
||||
<div className="hm-sw-process-header__bar">
|
||||
<SWHeaderHome logo={logoNode} logoSrc={logoSrc} logoAlt={logoAlt ?? logoText} onClick={onHomeClick} aria-expanded={homeMenuOpen} />
|
||||
<SWHeaderDocumentTitle title={documentEmpty ? documentEmptyLabel : documentTitle} onClick={onDocumentClick} />
|
||||
|
||||
@@ -84,7 +84,7 @@ export interface SWProcessHeaderProps extends Omit<HTMLAttributes<HTMLElement>,
|
||||
onDocumentClick?: () => void
|
||||
/** Logo element in the SWHeader logo slot (17px high, as wide as the logo: 32 x 17 -> home 80px). Takes precedence over `logoText` and `logoSrc`. */
|
||||
logo?: ReactNode
|
||||
/** Text logo (e.g. `StrAna`): 18px 700 with the SW text outline in a 58px place (home 106px). */
|
||||
/** Text logo (e.g. `StrAna`): 18px 700 with the SW text outline in a place of at least 58px (home at least 106px); a longer text widens the place and the home to its own width, never cut. */
|
||||
logoText?: string
|
||||
/** Logo image URL, contained (ratio kept, centered) in the logo slot. */
|
||||
logoSrc?: string
|
||||
|
||||
@@ -27,7 +27,7 @@ interface GroupContext { selection: "none" | "single" }
|
||||
const SWToolGroupContext = createContext<GroupContext | null>(null)
|
||||
|
||||
export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(function SWToolButton(
|
||||
{ appearance = "text", pressed, children, icon, hoverIcon, className, type = "button", ...rest }, ref,
|
||||
{ appearance = "text", pressed, children, icon, hoverIcon, className, type = "button", title, ...rest }, ref,
|
||||
) {
|
||||
const group = useContext(SWToolGroupContext)
|
||||
const radio = group?.selection === "single"
|
||||
@@ -39,15 +39,19 @@ export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(fun
|
||||
// The wrapper is the Header flex item; [data-tool] keeps it at its content width (not the file-name clamp).
|
||||
return (
|
||||
<div className="hm-sw-header__document" data-tool="">
|
||||
<button ref={ref} type={type} className={classes("hm-sw-header__document-button")} data-selected={pressed ? "true" : undefined} {...state} {...rest}>
|
||||
<button ref={ref} type={type} className={classes("hm-sw-header__document-button")} data-selected={pressed ? "true" : undefined} title={title} {...state} {...rest}>
|
||||
<span className="hm-sw-header__document-name">{children}</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (appearance === "pill") {
|
||||
// Default tooltip (2026-11-05): the pill shows only its icon, so a non-blank string `children` (its accessible name)
|
||||
// is also the native `title`, as written. An explicit `title` wins and `title=""` turns it off; numbers and JSX
|
||||
// children are never converted. The accessible name is unchanged (children / aria-label / aria-labelledby).
|
||||
const tooltip = title ?? (typeof children === "string" && children.trim() !== "" ? children : undefined)
|
||||
return (
|
||||
<button ref={ref} type={type} className={classes("hm-sw-header__action hm-sw-header__action--print hm-sw-tool__pill-button")} data-selected={pressed ? "true" : undefined} {...state} {...rest}>
|
||||
<button ref={ref} type={type} className={classes("hm-sw-header__action hm-sw-header__action--print hm-sw-tool__pill-button")} data-selected={pressed ? "true" : undefined} title={tooltip} {...state} {...rest}>
|
||||
{/* hm-sw-header__icon is the "hide on hover" half of the sw.css icon swap: only with a hover picture to show. */}
|
||||
{icon !== undefined && <span className={hoverIcon !== undefined ? "hm-sw-header__icon hm-sw-tool__icon" : "hm-sw-tool__icon"} aria-hidden="true">{icon}</span>}
|
||||
{hoverIcon !== undefined && <span className="hm-sw-header__icon-hover hm-sw-tool__icon" aria-hidden="true">{hoverIcon}</span>}
|
||||
@@ -56,7 +60,7 @@ export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(fun
|
||||
)
|
||||
}
|
||||
return (
|
||||
<button ref={ref} type={type} className={classes("hm-sw-header__toggle hm-sw-tool__text-button")} data-selected={pressed ? "true" : undefined} {...state} {...rest}>
|
||||
<button ref={ref} type={type} className={classes("hm-sw-header__toggle hm-sw-tool__text-button")} data-selected={pressed ? "true" : undefined} title={title} {...state} {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -13,8 +13,9 @@ import "../../theme/sw-viewer.css"
|
||||
|
||||
export interface SWViewerHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
||||
/**
|
||||
* Logo image URL of the program (same prop as SWHeader), contained in the SWHeader 32 x 17 logo slot, 12px from
|
||||
* the header's left edge and vertically centered on the slanted green cap.
|
||||
* Logo image URL of the program (same prop as SWHeader), 17px high at its own ratio in the SWHeader logo slot (32 x 17
|
||||
* is an example, not a fixed size), 12px from the header's left edge and vertically centered on the slanted green
|
||||
* cap, which follows the logo width.
|
||||
* The logo belongs to each program: nothing is rendered when omitted (no default text or icon) and there is no cap
|
||||
* either (2026-10-23, also with `onHomeClick` / `logoMenu`, which then have no trigger).
|
||||
*/
|
||||
|
||||
@@ -111,12 +111,15 @@
|
||||
/* Logo and file name follow SWHeader (2026-11-03, both appearances): SWHeaderHome as it is (logo slot as wide as the
|
||||
logo, 32 x 17 example -> home 80, 12px padding, slant), the file name 2px after the home overlap (SWHeader bar gap)
|
||||
with the SW padding 5 / 12 and font. Only the file-name width stays per appearance. The text logo (`logoText`) is
|
||||
18px 700 in a 58px place; a wider word overflows that place evenly on both sides, like the Strana centered content. */
|
||||
18px 700 in a place of at least 58px (-> home 106); a wider word widens the place to its own width (2026-11-05, was
|
||||
a fixed 58px that a longer word overflowed on both sides and the home cut on the left), so the home, its slant and
|
||||
the file name follow it like any wider logo. */
|
||||
.hm-sw-process-header__bar > .hm-sw-header__home { margin-right: -15px; }
|
||||
.hm-sw-process-header__wordmark {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 58px;
|
||||
width: max-content;
|
||||
min-width: 58px;
|
||||
color: var(--hm-sw-header-text-color);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
@@ -467,7 +470,11 @@
|
||||
text-align: right;
|
||||
text-shadow: var(--hm-sw-header-label-outline);
|
||||
}
|
||||
.hm-sw-process-header[data-document-empty] .hm-sw-header__document-button:not(:hover) .hm-sw-header__document-name { color: var(--hm-sw-process-document-empty-color); }
|
||||
/* 2026-11-05: before a file exists (documentTitle "") the file name does not look clickable: the label keeps its color
|
||||
on hover (was #FFC600 like a file name) and the cursor stays default. Size, place, colors and the click are unchanged;
|
||||
a real file name keeps the SWHeader hover. */
|
||||
.hm-sw-process-header[data-document-empty] .hm-sw-header__document-button .hm-sw-header__document-name { color: var(--hm-sw-process-document-empty-color); }
|
||||
.hm-sw-process-header[data-document-untitled] .hm-sw-header__document-button { cursor: default; }
|
||||
|
||||
/* ---------- appearance="multi" (StrAna-SW multi step, Figma 873:16481 / 873:16254 / 873:17421) ----------
|
||||
Same markup and shared rules as the default; only sizes, the file name, the tool pill and the group content differ.
|
||||
@@ -522,6 +529,10 @@
|
||||
--hm-sw-process-shape-start: 50px;
|
||||
--hm-sw-process-shape-end: 48px;
|
||||
}
|
||||
/* Square ends (2026-11-05 user decision, `multi` only): the base and the end covers meet the file name and the window
|
||||
area with right angles. `default` keeps the Strana top corners 6 (cover 5); the step tabs keep their own shapes. */
|
||||
.hm-sw-process-bar[data-appearance="multi"]::before,
|
||||
.hm-sw-process-bar[data-appearance="multi"] > .hm-sw-process-bar__rest { border-radius: 0; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// SWProcessHeader state regression check (2026-11-05): step enablement, step selection by activeStepId, the "no step"
|
||||
// cases and the file name before a file exists. Server render of the built package (no browser, no Storybook).
|
||||
// Usage: npm run build -w @hm-design/sw && node packages/sw/tests/process-header-states.check.mjs
|
||||
import { readFile } from "node:fs/promises"
|
||||
import { createElement as h } from "react"
|
||||
import { renderToStaticMarkup } from "react-dom/server"
|
||||
import { SWProcessHeader } from "../dist/index.js"
|
||||
|
||||
const results = []
|
||||
const check = (name, ok, detail) => { results.push(ok); console.log(`${ok ? "PASS" : "FAIL"} ${name}${ok || detail === undefined ? "" : ` ${JSON.stringify(detail)}`}`) }
|
||||
const render = props => renderToStaticMarkup(h(SWProcessHeader, { appearance: "multi", documentTitle: "", documentEmptyLabel: "프로젝트 생성 전", ...props }))
|
||||
const tabs = html => [...html.matchAll(/<button[^>]*role="radio"[^>]*>/g)].map(([tag]) => ({
|
||||
id: tag.match(/data-step-id="([^"]*)"/)?.[1], disabled: /\sdisabled=""/.test(tag), checked: /aria-checked="true"/.test(tag), stop: /tabindex="0"/i.test(tag),
|
||||
}))
|
||||
const groupsOf = html => [...html.matchAll(/<div class="hm-sw-process-bar__group"([^>]*)>/g)].map(([, attrs]) => ({
|
||||
active: /data-active="true"/.test(attrs), dim: /data-dim="true"/.test(attrs), done: /data-progress="done"/.test(attrs),
|
||||
}))
|
||||
const count = (list, key) => list.filter(item => item[key]).length
|
||||
|
||||
const GROUPS = [
|
||||
{ id: "g1", number: "01", label: "데이터 구축", steps: [{ id: "input", label: "데이터입력" }, { id: "review", label: "데이터확인", disabled: false }] },
|
||||
{ id: "g2", number: "02", label: "선형 입력", steps: [{ id: "line", label: "선형입력" }] },
|
||||
{ id: "g3", number: "03", label: "해석", steps: [{ id: "model", label: "해석모델" }, { id: "result", label: "성과품", disabled: true }] },
|
||||
]
|
||||
|
||||
// 1. Enablement: only an explicit disabled: true (step, group, process, header) turns steps off.
|
||||
{
|
||||
const t = tabs(render({ process: { groups: GROUPS, activeStepId: "input" } }))
|
||||
check("steps without disabled (omitted or false) are enabled; only disabled: true is off", t.length === 5 && count(t, "disabled") === 1 && t[4].disabled)
|
||||
const group = tabs(render({ process: { groups: GROUPS.map(g => (g.id === "g2" ? { ...g, disabled: true } : g)), activeStepId: "input" } }))
|
||||
check("group.disabled turns only that group's steps off", count(group, "disabled") === 2 && group[2].disabled)
|
||||
check("process.disabled turns every step off", count(tabs(render({ process: { groups: GROUPS, disabled: true } })), "disabled") === 5)
|
||||
check("header disabled turns every step off", count(tabs(render({ process: { groups: GROUPS }, disabled: true })), "disabled") === 5)
|
||||
}
|
||||
|
||||
// 2. Project / file state never decides enablement.
|
||||
{
|
||||
const variants = [
|
||||
render({ process: { groups: GROUPS, activeStepId: "input" } }),
|
||||
render({ documentEmptyLabel: undefined, process: { groups: GROUPS, activeStepId: "input" } }),
|
||||
render({ documentTitle: "Bridge.sa", documentEmptyLabel: undefined, process: { groups: GROUPS, activeStepId: "input" } }),
|
||||
].map(html => tabs(html).map(t => t.disabled).join())
|
||||
check("empty title, no empty label and a real file name give the same enabled steps", new Set(variants).size === 1, variants)
|
||||
}
|
||||
|
||||
// 3. Selection follows activeStepId (what step clicks, keyboard and host previous / next buttons change).
|
||||
{
|
||||
const order = ["input", "review", "line", "model"]
|
||||
const states = order.map(step => { const html = render({ process: { groups: GROUPS, activeStepId: step } }); return { t: tabs(html), g: groupsOf(html) } })
|
||||
check("each activeStepId checks exactly that step, with the single tab stop on it",
|
||||
states.every(({ t }, i) => count(t, "checked") === 1 && t.findIndex(x => x.checked) === [0, 1, 2, 3][i] && count(t, "stop") === 1 && t[[0, 1, 2, 3][i]].stop))
|
||||
const lit = states.map(({ g }) => g.findIndex(x => x.active))
|
||||
check("crossing groups moves the lit group (input/review -> 01, line -> 02, model -> 03)", lit.join() === "0,0,1,2", lit)
|
||||
check("multi: groups before the lit one are done, the others not", states[3].g.map(x => x.done).join() === "true,true,false" && states[0].g.every(x => !x.done))
|
||||
}
|
||||
|
||||
// 4. No step set: nothing is created or selected, the steps stay selectable.
|
||||
{
|
||||
const html = render({ process: { groups: GROUPS } })
|
||||
const t = tabs(html), g = groupsOf(html)
|
||||
check("no activeStepId: no checked step, no lit or done group, every group dim", count(t, "checked") === 0 && g.every(x => !x.active && !x.done && x.dim))
|
||||
check("no activeStepId: steps stay enabled and the first enabled step is the tab stop", count(t, "disabled") === 1 && count(t, "stop") === 1 && t[0].stop)
|
||||
const empty = render({ process: { groups: [] } })
|
||||
check("groups = []: no step tab and no group are rendered (nothing invented)", tabs(empty).length === 0 && groupsOf(empty).length === 0 && /hm-sw-process-bar/.test(empty))
|
||||
}
|
||||
|
||||
// 5. File name before a file exists: marked untitled, still the same button (click kept), CSS removes the clickable look.
|
||||
{
|
||||
const untitled = render({ process: { groups: GROUPS } })
|
||||
const named = render({ documentTitle: "Bridge.sa", documentEmptyLabel: undefined, process: { groups: GROUPS } })
|
||||
check("documentTitle \"\" marks the header data-document-untitled; a real file name does not",
|
||||
/<header[^>]*data-document-untitled="true"/.test(untitled) && !/data-document-untitled/.test(named))
|
||||
check("the file name stays a button in both states (onDocumentClick is still wired)",
|
||||
/<button[^>]*class="hm-sw-header__document-button"/.test(untitled) && /<button[^>]*class="hm-sw-header__document-button"/.test(named))
|
||||
const css = (await readFile(new URL("../dist/styles.css", import.meta.url), "utf8")).replace(/\s+/g, " ")
|
||||
check("styles.css: untitled file name has the default cursor", /\[data-document-untitled\] \.hm-sw-header__document-button ?\{ ?cursor: ?default/.test(css))
|
||||
check("styles.css: the empty label keeps its color on hover (no :not(:hover) scope)",
|
||||
/\[data-document-empty\] \.hm-sw-header__document-button \.hm-sw-header__document-name ?\{ ?color: ?var\(--hm-sw-process-document-empty-color\)/.test(css)
|
||||
&& !/\[data-document-empty\] \.hm-sw-header__document-button:not\(:hover\)/.test(css))
|
||||
// 6. Process-area ends (2026-11-05): `multi` square (base and end covers radius 0), `default` keeps the Strana 6 / 5.
|
||||
const block = selector => css.match(new RegExp(`(?:^|\\}|\\*/) ?${selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} ?\\{([^}]*)\\}`))?.[1] ?? null
|
||||
const base = block(".hm-sw-process-bar::before") ?? ""
|
||||
check("styles.css: default keeps the process base top corners 6 and the end covers 5",
|
||||
/border-radius: ?6px 6px 0 0/.test(base) && /border-top-left-radius: ?5px/.test(block('.hm-sw-process-bar__rest[data-side="start"]') ?? "")
|
||||
&& /border-top-right-radius: ?5px/.test(block('.hm-sw-process-bar__rest[data-side="end"]') ?? ""), { base })
|
||||
check("styles.css: multi sets radius 0 on the process base and both end covers",
|
||||
/\.hm-sw-process-bar\[data-appearance="?multi"?\]::before ?, ?\.hm-sw-process-bar\[data-appearance="?multi"?\] ?> ?\.hm-sw-process-bar__rest ?\{ ?border-radius: ?0 ?;? ?\}/.test(css))
|
||||
}
|
||||
|
||||
const passed = results.filter(Boolean).length
|
||||
console.log(`${passed}/${results.length} PASS`)
|
||||
process.exitCode = passed === results.length ? 0 : 1
|
||||
@@ -91,7 +91,9 @@ try {
|
||||
report.data[`gaps${width}`] = { colGap, colGap2, rowGap }
|
||||
// 2026-10-22 (user request): no Inner scroller; the page scrolls. 2 columns × 2 rows, one column below 720px.
|
||||
// 2026-10-26 (user request): from 1080px, 3 columns — none/all stacked in column 1, circle and expanded beside them.
|
||||
const oneColumn = width < 720, threeColumns = width >= 1080
|
||||
// 2026-11-05: the Story page Inner keeps 60px on each side (story-page.css), so the steps moved to 787px / 1125px
|
||||
// (sw-stories.css .sw-story-compare--grid). It keeps 16px up to 480px (growing to 60px at 768px), so 390px has 358px.
|
||||
const oneColumn = width < 787, threeColumns = width >= 1125
|
||||
if (threeColumns) {
|
||||
const gaps = { stack: b.cell.y - a.cell.bottom, col1: c.cell.x - Math.max(a.cell.right, b.cell.right), col2: d.cell.x - c.cell.right }
|
||||
report.data[`gaps${width}`] = gaps
|
||||
|
||||
@@ -140,8 +140,8 @@ await page.keyboard.press('Escape')
|
||||
report.reference = { more: refMore[0], documents: refDocs[0], viewerItem: refViewerItem[0], viewerLabel: refViewerLabel[0] }
|
||||
|
||||
await goto('sw-viewer--viewer-header')
|
||||
// The 1440px Story frame at full width (as the viewer suites): the header has room for every label.
|
||||
await page.addStyleTag({ content: '.sb-page { --sb-page-max-width: 100vw; }' }); await page.waitForTimeout(200)
|
||||
// The 1440px Story frame at full width (the 1920px page's Inner is 1800px): the header has room for every label.
|
||||
await page.waitForTimeout(200)
|
||||
const vh = page.locator('.hm-sw-viewer-header').first()
|
||||
const vNames = await styles('.hm-sw-viewer-header >> nth=0 >> :is(.hm-sw-header__document-name, .hm-sw-viewer-header__document-name, .hm-sw-viewer-header__label, .hm-sw-viewer-header__tab)')
|
||||
report.viewer = vNames
|
||||
|
||||
@@ -39,7 +39,6 @@ const newPage = async (width = 1440, height = 1000) => {
|
||||
const open = async (page, id) => {
|
||||
await page.goto(`${storybook}/iframe.html?id=${id}&viewMode=story`)
|
||||
await page.waitForSelector('#storybook-root > *')
|
||||
await page.addStyleTag({ content: '.sb-page { --sb-page-max-width: 100vw; }' })
|
||||
await page.evaluate(() => document.fonts.ready)
|
||||
await page.waitForTimeout(300)
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ const ev = async expr => { const r = await send('script.evaluate', { expression:
|
||||
const goto = async story => {
|
||||
await send('browsingContext.navigate', { context, url: `${base}/iframe.html?id=${story}&viewMode=story`, wait: 'complete' })
|
||||
for (let i = 0; i < 40; i++) { if (await ev(`!!document.querySelector('.hm-sw-viewer-header')`)) break; await sleep(250) }
|
||||
// Lift the Story page envelope like the Chromium suites; scroll instantly (the preview sets smooth scrolling).
|
||||
await ev(`(() => { const s = document.createElement('style'); s.textContent = '.sb-page { --sb-page-max-width: 100vw; } html { scroll-behavior: auto !important; }'; document.head.append(s); return true })()`)
|
||||
// Scroll instantly (the preview sets smooth scrolling).
|
||||
await ev(`(() => { const s = document.createElement('style'); s.textContent = 'html { scroll-behavior: auto !important; }'; document.head.append(s); return true })()`)
|
||||
await ev('document.fonts.ready.then(() => true)'); await sleep(400)
|
||||
}
|
||||
const frames = () => ev('new Promise(r => requestAnimationFrame(() => requestAnimationFrame(() => r(true))))')
|
||||
|
||||
@@ -29,7 +29,6 @@ const newPage = async (width = 1920, height = 1000) => {
|
||||
const open = async (page, id) => {
|
||||
await page.goto(story(id))
|
||||
await page.waitForSelector('.hm-sw-viewer-header')
|
||||
await page.addStyleTag({ content: '.sb-page { --sb-page-max-width: 100vw; }' })
|
||||
await page.evaluate(() => document.fonts.ready)
|
||||
await page.waitForTimeout(200)
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ const newPage = async (width = 1440, height = 900) => {
|
||||
page.on('console', message => { if (message.type() === 'error' && !/favicon|404/.test(message.text())) errors.push(`${page.url()}: ${message.text()}`) })
|
||||
return page
|
||||
}
|
||||
// 2026-10-23: waits for a visible header (the Responsive Story's first, 1920px row is hidden below a 2000px page).
|
||||
// Test-only: lift the 1280px Story page envelope so the 1440px frames are measured at full width.
|
||||
const open = async (page, id) => { await page.goto(story(id)); await page.waitForSelector('.hm-sw-viewer-header:visible'); await page.addStyleTag({ content: '.sb-page { --sb-page-max-width: 100vw; }' }); await page.evaluate(() => document.fonts.ready); await page.waitForTimeout(200) }
|
||||
// 2026-10-23: waits for a visible header (the Responsive Story's first, 1920px row is hidden below a 2040px page).
|
||||
// 2026-11-05: the Story page Inner is the page width minus 60px on each side from a 768px viewport (16px up to 480px,
|
||||
// growing in between; story-page.css), so no envelope override is needed.
|
||||
const open = async (page, id) => { await page.goto(story(id)); await page.waitForSelector('.hm-sw-viewer-header:visible'); await page.evaluate(() => document.fonts.ready); await page.waitForTimeout(200) }
|
||||
const log = page => page.locator('[data-callback-log]').first().innerText()
|
||||
const box = locator => locator.boundingBox()
|
||||
|
||||
@@ -293,7 +294,7 @@ if (comparison.reference) {
|
||||
}
|
||||
{
|
||||
// Responsive: measured steps (full → icons only → More), no horizontal overflow; More collects the collapsed items.
|
||||
// 2026-10-23: rows are picked by their data-width (not by position). The Story's 1920px row is hidden below a 2000px
|
||||
// 2026-10-23: rows are picked by their data-width (not by position). The Story's 1920px row is hidden below a 2040px
|
||||
// page (sw-story-responsive-hidden), so the page is 2500px wide and every row is shown at its own maximum width.
|
||||
const page = await newPage(2500, 1200)
|
||||
await open(page, 'responsive')
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | VIEWER.md `SWViewerHeader`·디자인 변경 절 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | VIEWER.md 디자인 변경 절(52px Header 등 실측값) | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | VIEWER.md `SWViewerPanel` 절(프레임은 SWPopup과 같음) | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon` | Core 내장 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 사용자·프로젝트 제공 에셋 → 패키지 공개 Export 중 같은 기능 → 없으면 미지원 보고(외부 라이브러리·AI 생성 SVG·Storybook 파일로 대체 금지). 시안 기반 Header 도구 아이콘·창 제어 버튼은 `@hm-design/sw` `ai/AI_USAGE.md` §5 | 프로그램 제공 16px `currentColor` 아이콘, 외곽선은 `SWViewerIcon`. 선택 순서는 SW 일반 모드와 같음(`AI_USAGE.md` §5) | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | 전용 디자인 없음 — 패널 본문 스크롤은 `SWViewerPanel` | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | VIEWER.md `SWViewerHeader` 절 상태 표현 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | `SWViewerPanel`(+ `modal`) + `SWViewerSection`/`SWViewerField` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
@@ -178,6 +178,19 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
3. 패키지가 실제로 공유하는 부분은 혼합이 아닙니다: `SWViewerPanel`은 `SWPopup` 프레임을 그대로 렌더링하고, 두 Header는 파일명·객체 수정·속성 면 토큰(`--hm-sw-header-document-*`·`object-*`·`property-*`)을 공유합니다. 소비 코드가 이 공유를 흉내 내 다른 모드 부품을 섞어도 된다는 뜻은 아닙니다.
|
||||
4. 프로그램이 두 모드를 모두 가지면 모드 전환 시 Header 컴포넌트를 바꿔 끼웁니다(파일·패널 상태는 프로그램 상태로 유지). 세부는 `@hm-design/sw/ai/AI_USAGE.md` §0과 `VIEWER.md`.
|
||||
|
||||
### Header 전용 스타일의 적용 범위 (2026-11-05)
|
||||
|
||||
화면 상단 Header 컴포넌트 — SW `SWHeader`·`SWProcessHeader`·`SWViewerHeader`, Web `GNB`·`LNBMobileHeader`·`DashboardHeader` — 의 전용 스타일은 **그 Header 영역 안에서만** 씁니다. 전용 스타일은 Header의 class(`hm-sw-header__*`·`hm-sw-process-*`·`hm-sw-viewer-header__*`, Web Header의 `data-slot`)와 Header 모양을 정하는 값(배경·gradient·테두리·글자 외곽선·높이·간격)입니다. `DialogHeader`·`CardHeader`처럼 다른 컴포넌트 안의 머리 부품은 여기서 말하는 Header가 아닙니다. 이 규칙은 아래를 정할 뿐 기존 Header의 레이아웃·스타일·공개 API를 바꾸지 않습니다.
|
||||
|
||||
1. Header 밖의 본문·패널·카드·버튼·입력 필드와 다른 컴포넌트에 Header 전용 class·CSS·값을 붙이거나 복사하지 않습니다. "Header 스타일로" 같은 요청도 Header 영역에만 적용하고, 별도 요청이 없으면 Header 밖 기존 UI의 디자인과 동작은 그대로 둡니다.
|
||||
2. 화면 전체 토큰·테마(Web Runtime Theme `--hm-sem-*`, SW 가장 바깥 범위의 `style` 토큰, 위 표의 범위)와 Header 전용 스타일을 구분합니다. Header를 맞추려고 화면 전체 테마 값을 바꾸지 않습니다.
|
||||
3. Header를 구현하려고 전역 CSS(`html`·`body`·`*`, 공통 reset)를 바꾸거나 기존 본문 스타일을 덮어쓰지 않습니다.
|
||||
4. Header와 본문의 경계에서는 배치에 필요한 연결만 소비 프로젝트 class로 처리합니다(Header 높이만큼 본문 시작 위치, sticky·z-index, 남는 높이를 본문에 주는 flex 등).
|
||||
5. 공식 컴포넌트 내부 스타일을 소비 프로젝트에서 Override하지 않습니다(위 혼합 규칙 3). 바꿀 수 있는 것은 각 패키지가 공개한 Props·토큰뿐입니다.
|
||||
6. 시안에 본문 디자인이 따로 있으면 그 영역의 패키지(위 혼합 규칙 1)의 공식 컴포넌트로 구현합니다. Header와 같은 패키지의 부품을 Header 밖에 둘 때도 그 부품의 공식 사용법(SW는 `<SWTheme>` 안)을 따르고 Header 모양을 덧입히지 않습니다.
|
||||
7. 패키지가 공식으로 공유하는 부분은 위반이 아닙니다(예: `SWPopup`·`<SWTheme>` 부품이 `--hm-sw-header-*` 토큰을 읽음, 위 SW 분리 3). 소비 코드가 이를 흉내 내 Header 스타일을 다른 곳에 쓰면 안 됩니다.
|
||||
8. 사용자가 화면 전체에 Header 디자인을 적용하라고 **명시한 경우에만** 범위를 넓힙니다. 이때도 공식 컴포넌트·공개 토큰으로만 맞추고 Header class를 복사하지 않으며, 결과 보고에 넓힌 범위를 적습니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
@@ -186,7 +199,7 @@ SW 일반 모드와 뷰어 모드는 같은 패키지·같은 CSS 파일이지
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW 일반 모드는 대상 창 폭(Header는 반응형 More 포함), SW 뷰어 모드는 대상 창 폭을 줄였다 늘리며 실제 공간 부족에 따른 단계(`iconOnlyBelow` 항목 아이콘만 → `collapseBelow` 항목 More 이동, 숫자는 px 경계가 아니라 순서) 전후, OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지(§5)
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지, SW면 선택하지 않은 모드의 컴포넌트·토큰이 섞이지 않았는지, Header 전용 스타일이 Header 밖에 쓰이지 않았는지(§5, "Header 전용 스타일의 적용 범위")
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
@@ -54,6 +54,7 @@ import { applyRuntimeTheme, resetRuntimeTheme, loadRuntimeTheme } from "@hm-desi
|
||||
- Light/Dark 분기 없음. `dark:` 분기·Dark Token을 추가하지 않는다.
|
||||
- 테마 변경은 Runtime Theme(`applyRuntimeTheme(root, theme)`, `loadRuntimeTheme(root, url)`)만 쓴다. `theme`은 `{ schemaVersion: 1, tokens: { "color.bg.content": "#…" } }` 형식이며 키는 `runtimeThemeContract`에 있는 것만 유효하다.
|
||||
- 한 페이지의 Navigation은 GNB / LNB / DASHBOARD 중 하나다([COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) §1).
|
||||
- `GNB`·`LNBMobileHeader`·`DashboardHeader`의 전용 스타일은 Header 영역에만 쓴다. 본문 UI에 Header 모양을 적용하지 않는다([AI_GUIDE.md](./AI_GUIDE.md) 5단계 "Header 전용 스타일의 적용 범위").
|
||||
|
||||
## 4. 접근성 기본
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Storybook presentation only. Common page frame for representative Stories; all selectors stay inside .sb-page.
|
||||
Inner (fluid, 60px each side; vertical gutter 16–40) and title (kicker 12 / title 28·36·700 / description, underlined) reuse Foundation / Color.
|
||||
Inner (fluid; 16px each side up to 480px, growing to 60px at 768px; vertical gutter 16–40) and title (kicker 12 / title 28·36·700 / description, underlined) reuse Foundation / Color.
|
||||
Section reuses the Center Graphic / 교량 split: heading + description, a divider, then the specimen. */
|
||||
.sb-page {
|
||||
--sb-page-gutter: clamp(16px, 3vw, 40px);
|
||||
@@ -12,8 +12,10 @@
|
||||
}
|
||||
.sb-page > .sb-page-inner {
|
||||
box-sizing: border-box;
|
||||
/* 2026-11-05 (user request): no max width; the Inner follows the page width and keeps 60px on each side. */
|
||||
width: calc(100% - 120px);
|
||||
/* 2026-11-05 (user request): no max width; the Inner follows the page width. Side margin (each side) = 16px up to a
|
||||
480px viewport (358px Inner at 390px, so a 314px specimen fits), then linear to 60px at 768px (44px over 288px,
|
||||
11 / 72 of the viewport growth), 60px from 768px. The Inner keeps growing (about 0.69px per 1px), with no jump. */
|
||||
width: calc(100% - 2 * clamp(16px, calc(16px + (100vw - 480px) * 11 / 72), 60px));
|
||||
min-width: 0;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
.sw-footer-story-inline { display: inline-flex; flex-direction: column; padding: 0; }
|
||||
.sw-footer-story-width + .sw-footer-story-width { margin-top: 16px; }
|
||||
/* The hidden 1920px row (Responsive More, narrower than 1920px) takes its gap with it. */
|
||||
@container sw-story-page (max-width: 1999.98px) { .sw-story-responsive-hidden + .sw-footer-story-width { margin-top: 0; } }
|
||||
@container sw-story-page (max-width: 2039.98px) { .sw-story-responsive-hidden + .sw-footer-story-width { margin-top: 0; } }
|
||||
.sw-footer-story-aux-compare { display: grid; grid-template-columns: repeat(auto-fill, minmax(560px, 1fr)); gap: 16px; }
|
||||
.sw-footer-story-aux-row { display: flex; align-items: center; gap: 12px; margin: 0; }
|
||||
.sw-footer-story-aux-row figcaption { flex: 0 0 120px; font-size: 12px; color: #3d4a45; }
|
||||
|
||||
+11
-6
@@ -1,12 +1,13 @@
|
||||
/* Story canvas only: frames that simulate a header width. Never styles the header itself. */
|
||||
.sw-story-scroller { max-width: 100%; overflow-x: auto; }
|
||||
/* Responsive More pages: the 1920px row (sw-story-responsive-hidden) shows when the page has 1920px of usable width —
|
||||
the page (container sw-story-page, not the browser) minus its 40px gutters, so 2000px; with less room the whole row —
|
||||
the page (container sw-story-page, not the browser) minus the Inner's 60px side margins (story-page.css,
|
||||
60px from a 768px viewport), so 2040px (2026-11-05; was 2000px with the former 40px gutters); with less room the whole row —
|
||||
title, header, background, margin — is hidden. 2026-10-23: it stays inside the Story column like every other row (the
|
||||
former reach-out of the 80vw Inner, sw-story-responsive-wide, is gone); its preview is at most 1920px wide. */
|
||||
.sw-story-responsive { container-type: inline-size; }
|
||||
.sb-page:has(.sw-story-responsive) { container: sw-story-page / inline-size; }
|
||||
@container sw-story-page (max-width: 1999.98px) { .sw-story-responsive-hidden { display: none; } }
|
||||
@container sw-story-page (max-width: 2039.98px) { .sw-story-responsive-hidden { display: none; } }
|
||||
.sw-story-frame { background: #060606; }
|
||||
/* Shared drawing-area canvas of the SW Stories (2026-10-19): the SW/Viewer black grid — 40px cells, 1px lines of
|
||||
HM green #266651 at 18%, on #0A0A0B. Used behind the SW Header work area and dropdown room and the viewer frames. */
|
||||
@@ -43,18 +44,22 @@
|
||||
/* Property panel 4-state comparison at the panels' own size (314px wide, never shrunk).
|
||||
2026-10-26 (user request): 3 columns — the short panels (none, all) stacked in column 1, the long ones (circle,
|
||||
expanded) side by side in columns 2 and 3. Rows are auto / 1fr so the long panels' height goes to row 2 only and
|
||||
"all" sits 24px under "none". Narrower screens: 2 columns × 2 rows (3 × 314 + 2 gaps + gutters do not fit), and one
|
||||
column below 720px; no Inner scroller and no sideways scroll (2026-10-22). */
|
||||
"all" sits 24px under "none". Narrower screens: 2 columns × 2 rows, and one column below 787px; no Inner scroller
|
||||
and no sideways scroll (2026-10-22). 2026-11-05: each step needs its columns plus the Inner's 60px side margins
|
||||
(120, story-page.css) and a ~15px vertical scrollbar (the media queries read the viewport): 3 columns from 1125px
|
||||
(3 × 314 + 2 × 24 = 990), 2 columns from 787px (2 × 314 + 24 = 652). Before: 1080px / 720px with 16–40px gutters.
|
||||
Both steps sit above 768px, where the margins are 60px. The single column always fits: the margins are 16px up to
|
||||
480px and grow to 60px by 768px (story-page.css), so 375 / 390px screens have 343 / 358px for the 314px panel. */
|
||||
.sw-story-compare-page .sw-story-log { margin-top: 12px; }
|
||||
.sw-story-compare.sw-story-compare--grid { display: grid; grid-template-columns: repeat(2, max-content); align-items: start; gap: 24px; }
|
||||
@media (min-width: 1080px) {
|
||||
@media (min-width: 1125px) {
|
||||
.sw-story-compare.sw-story-compare--grid { grid-template-columns: repeat(3, max-content); grid-template-rows: auto 1fr; }
|
||||
.sw-story-compare--grid > [data-compare="none"] { grid-area: 1 / 1; }
|
||||
.sw-story-compare--grid > [data-compare="all"] { grid-area: 2 / 1; }
|
||||
.sw-story-compare--grid > [data-compare="circle"] { grid-area: 1 / 2 / span 2; }
|
||||
.sw-story-compare--grid > [data-compare="expanded"] { grid-area: 1 / 3 / span 2; }
|
||||
}
|
||||
@media (max-width: 719.98px) { .sw-story-compare.sw-story-compare--grid { grid-template-columns: max-content; } }
|
||||
@media (max-width: 786.98px) { .sw-story-compare.sw-story-compare--grid { grid-template-columns: max-content; } }
|
||||
/* Placement-only exception, same as the Overview Stage: the product clamps a panel to the viewport
|
||||
height; here the scroller provides the room so the 754px panel keeps its size. */
|
||||
.sw-story-compare--grid .hm-sw-property { max-height: none; }
|
||||
|
||||
Reference in new issue
Block a user