fix(sw): refine process header empty title states and UI guidance
- AI_GUIDE stage 5 "Header 전용 스타일의 적용 범위": header-only styles stay in the header (SW SWHeader / SWProcessHeader / SWViewerHeader, Web GNB / LNBMobileHeader / DashboardHeader); no header CSS or tokens on body UI, no global overrides, layout-only joins, package-shared tokens unchanged, whole-screen use only on explicit request. Synced to the 4 package copies; referenced from stage 6, SW and Web AI_USAGE §3 - SWProcessHeader: with documentTitle "" the file name keeps #AEDBCC on hover and a default cursor (root data-document-untitled). The button, focus and onDocumentClick are kept; a real file name keeps the yellow hover and pointer. Visual vs. click difference recorded as a UX item - Step enablement documented as implemented: only an explicit disabled (step, group, process, header) turns steps off; project / file state never does; selection is activeStepId + onStepSelect (also for host previous / next); no activeStepId and empty groups described - Regression check tests/process-header-states.check.mjs (15/15); typecheck:sw, build:sw, check:ai-docs pass; public-api keeps its 3 existing failures, none new
This commit is contained in:
1 parent
6c61edadc1
commit
0086d66a94
14 files changed
+177
-9
No files matched your search
@@ -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단계. 결과 보고 형식
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -191,7 +191,7 @@ 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, 두 타입 공통. 우선순위 `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개, 최대화 숨김) | ◻ |
|
||||
| `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). 그룹은 자연 폭까지·좁으면 비례 축소 | ◻ |
|
||||
|
||||
@@ -199,6 +199,7 @@ SW **뷰어 모드** 전용 Export입니다(2026-10-19 추가, 참고 DWG/DXF
|
||||
- **사다리꼴(그룹 면) 배치 확정(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 값은 `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단 타일 디자인이 다름).
|
||||
|
||||
@@ -278,7 +278,7 @@ const groups: SWProcessGroup[] = [
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user