feat(sw): add step header with multi-step and split layout support
No files matched your search
@@ -1,5 +1,26 @@
|
||||
# Working Context
|
||||
|
||||
## 2026-11-03 — SWProcessHeader 1차 구현 (Strana Main Header)
|
||||
|
||||
- 작업 브랜치: `feature/sw-process-header`(main 6ca92c5에서 분기). 미Commit·미Push — Commit·Push는 사용자 별도 지시 대상.
|
||||
- 범위: 방안 B(별도 컴포넌트) 승인안. `@hm-design/sw`에 `SWProcessHeader`·`SWProcessBar` 추가(`packages/sw/src/components/sw-process-header/`, `src/theme/sw-process-header.css`), Story `SW/Step Header`(후속 12에서 `SW/Process Header`에서 변경, `src/sw/sw-process-header*.tsx|css`), 이후 `SWProcessSplitButton` 추가(후속 5), 스펙 `docs/sw/sw-process-header-design-spec.md`, SW AI 문서 4종 갱신, `src/index.ts` Export 1줄.
|
||||
- 변경 금지 유지: `SWHeader`·`SWViewerHeader`·`sw.css`·기존 Story·`docs/ai/AI_GUIDE.md`·`packages/ui`·`D:\Strana`.
|
||||
- 1차 제외(호스트 소유·2차 후보): 로고 메뉴 본체, Layer Popup, Dark·Light·HighContrast 화면 비교. (화면 분할은 후속 5에서 추가)
|
||||
- 검증: typecheck:sw · build:sw · check:ai-docs · tsc -b PASS, Storybook 상태·Hover·키보드 확인, Strana Hanmac 1100/1366/1920 캡처 비교, `SW/Header` 출력 HTML·기존 CSS 동일. Firefox·Safari·최신 분할 버튼 캡처는 미확인.
|
||||
- 2026-11-03 후속: Story 설명·이벤트 로그 제거, 로고를 임시 로고(`logoSrc`, `src/sw/assets/sw-header-logo-placeholder.svg`)로 교체, 이 Header 로고 칸 기본 58 × 17(홈 106) 유지 — 후속 7에서 SWHeader 기준(홈 80)으로 대체.
|
||||
- 2026-11-03 후속 2: `appearance="multi"`(StrAna-SW Figma 873:16481·873:16254·873:17421) 추가 — 같은 컴포넌트·DOM, CSS 분기, `documentEmptyLabel`·`labelHidden`·`SWProcessAppearance`, Story 7개, Figma 도구 아이콘 fixture. Default 치수·CSS 회귀 없음 확인. 넘침 정책 확정(말줄임, 접기·스크롤·More 없음; 선택 탭 유지는 후속 10에서 비례 축소로 대체).
|
||||
- 2026-11-03 후속 3: 사다리꼴(그룹 면)은 시작·끝 그룹에만 — 켜진 가운데 그룹의 발광 면 제거(Figma 873:17421 일치), default·multi 공통.
|
||||
- 2026-11-03 후속 4: SWHeader 정합 — multi에도 SW 글자 외곽선, 창 이동을 SWHeaderWindowControls(창 이동만, 1.4s Motion)로 교체(정적 그림·토큰 2개 삭제), Multi 도구 Hover 노란 그림(fixture hoverIcon), 눌림도 노란 아이콘.
|
||||
- 2026-11-03 후속 5: 선택 Step 전체 이름을 default에도 적용(측정한 최소 폭, flex 통일 — 후속 10에서 비례 축소로 대체), Hover 그림 없는 도구 아이콘이 Hover 시 사라지던 문제 수정(범위 안 보완 — 후속 6에서 SWToolButton 공통 수정으로 대체), 화면 분할 `SWProcessSplitButton` 추가(Strana 6758:12510, 연장은 Header 끝까지).
|
||||
- 2026-11-03 후속 6: SWToolButton 공통 아이콘 버그 수정 — pill 기본 아이콘의 `hm-sw-header__icon` class를 `hoverIcon`이 있을 때만 부여(`SWTool.tsx`), SWProcessHeader 범위 보완 CSS 삭제. SWHeader `tools`도 함께 해결, `sw.css`·색·Hover 정책 변경 없음.
|
||||
- 2026-11-03 후속 7: 로고·파일명 영역 SWHeader 기준 통일(default·multi) — 로고 칸 58/55 고정 제거(SWHeader 자동 폭 → 홈 80), 홈과 파일명 간격 SWHeader와 같게(겹침 15 + 2), 파일명 padding 22·33/16·multi 자간 0 제거 → 글자 x 70. 파일명 최대 286/189 유지.
|
||||
- 2026-11-03 후속 8: 화면 분할 Popup 하단 잘림 수정 — 아래 공간이 부족하면 위치 훅이 위로 뒤집어 Header에 가려지던 문제, `SWProcessSplitButton`은 항상 버튼 아래(스크롤 시 따라감). 설계 문서 §11.3.
|
||||
- 2026-11-03 후속 9: Storybook `SW/Process Header` 간소화 — 사이드바 2개(Process Header = Default 섹션 + Multi Step 섹션(Figma 시안 3개, 각자 상태, 노드 ID 미표시) · Responsive), SW 페이지와 같은 제목 + StorySection. 검증용 숨김: Multi Step(시안 1920, data-figma-node) · 화면 분할 · Fixed Widths · Process Bar · Many Groups · Many Steps · States · Theme Override, Figma 시안 3개 Story를 한 페이지로 통합, 캡션·디버그 data 속성 제거.
|
||||
- 2026-11-03 후속 10: 단계 탭 비례 축소 — 선택 탭 최소 폭(`--hm-sw-process-step-min`)·multi 선택 탭 `flex: 0 0 auto` 제거, 그룹 최소 폭은 모든 탭 여백만. 1920·2560 변화 없음.
|
||||
- 2026-11-03 후속 11: 화면 분할 Popup 아이콘 하단 잘림 — 원인은 틀 상자 소수 크기(31.6 × 21.73)의 픽셀 맞춤(배율 100%에서 21줄), 틀 32 × 22로 수정(`sw-process-header.css`). 사용자 Chrome에서 재현·수정 확인.
|
||||
- 2026-11-03 후속 12: Storybook 표시 명칭 `SW/Process Header` → `SW/Step Header`(페이지 제목 `SW Step Header`, 섹션·사이드바 `Step Header`, `Multi Step` 유지). Story ID는 `sw-step-header--*`로 바뀜. 컴포넌트·공개 API(`SWProcessHeader`, `SWProcessBar`, `SWProcessSplitButton`)·파일명은 유지.
|
||||
- 기존 `package-lock.json` 수정과 미추적 `.claude/`는 범위 밖(보존).
|
||||
|
||||
## 2026-10-28 — Command Search 예제
|
||||
|
||||
- 작업 브랜치: `codex/command-framework-options`.
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
# SW Step Header 디자인 명세 (`SWProcessHeader`)
|
||||
|
||||
> 상태: `@hm-design/sw` 1차 구현 기준(2026-11-03). 코드: `packages/sw/src/components/sw-process-header/`, CSS: `packages/sw/src/theme/sw-process-header.css`, Story: `SW / Step Header`(2026-11-03 표시 명칭 변경, 컴포넌트·공개 API 이름 `SWProcessHeader`·`SWProcessBar` 등은 유지).
|
||||
> 원본: Strana Main Header — `D:\Strana\Git_StranaDevDesign` HEAD `edda3cc`(Header 7173e82 + 분할 버튼 b7f412c)의 `HeaderStyles.xaml`, `HeaderBrushes.xaml`, `MainProcessBar.xaml`, `MainWorkspaceWindowView.xaml`, Hanmac `Colors.xaml`. Strana 저장소는 읽기 전용 참고이며 수정하지 않습니다.
|
||||
> 승인: 2026-11-03 사용자 승인(방안 B · 설계 계획서 7절 9개 항목 권장안).
|
||||
|
||||
## 1. 개요
|
||||
|
||||
SW 일반 모드의 **독립 Header**입니다. `SWHeader`(편집 메뉴 Header), `SWViewerHeader`(뷰어 모드)와 같은 층위이며 한 화면에는 Header를 하나만 둡니다. `SWHeader`·`SWViewerHeader`·`sw.css`는 바꾸지 않았습니다.
|
||||
|
||||
| 공개 컴포넌트 | 역할 |
|
||||
|---|---|
|
||||
| `SWProcessHeader` | 49px Header 프레임: 로고 → 파일명 → 가운데(프로세스 · Layer 면) → 창 영역(도구 pill · 창 이동 · 최소화 · 최대화/복원 · 종료) |
|
||||
| `SWProcessBar` | 프로세스 단계 영역(그룹 번호·이름, 단계 탭, 그룹 사이 화살표, 활성 그룹 발광). Header 밖에서는 `SWTheme` 안에서 씁니다 |
|
||||
| `SWProcessSplitButton` | `tools` pill 안 화면 분할 도구(버튼 + 6칸 선택 패널, §11.3) |
|
||||
|
||||
## 2. 구조와 재사용
|
||||
|
||||
```
|
||||
SWProcessHeader <header class="hm-sw-theme hm-sw-process-header"> 49px (면 48 + 아래 1px 배경)
|
||||
├─ SWHeaderHome 재사용 — 9-slice 홈 도형(Strana HomeFace/HomeBorder/HomeShadow와 같은 경로·gradient·그림자)
|
||||
├─ SWHeaderDocumentTitle 재사용 — 파일명 면(DocFace / DocBorder = SW 문서 토큰과 같은 값)
|
||||
├─ __center
|
||||
│ ├─ SWProcessBar 신규
|
||||
│ └─ __layer 면 SW 객체 면 토큰(ObjFace / LayerFaceBorder와 같은 값)
|
||||
│ └─ Layer 필드 신규 내부 — 전구는 SWHeaderLayerToggle(내부) 재사용, Strana 전구와 같은 그림
|
||||
└─ __window DocFace 면 + DocBorder 1px
|
||||
├─ SWToolGroup pill 재사용(`tools` 슬롯을 감쌈) — 테두리·면만 Strana 값으로 범위 안에서 조정
|
||||
├─ 창 이동 재사용 — SWHeaderWindowControls(창 이동만): 줄무늬 · 아이콘 · 1.4s Hover Motion · 툴팁
|
||||
└─ 캡션 버튼 3개 신규 내부 — 최소화 · 최대화/복원 · 종료
|
||||
```
|
||||
|
||||
`SWHeaderLayerControl`·`SWHomeMenu`는 쓰지 않습니다(빈 20px 칸, 2단 타일 메뉴가 Strana와 달라서). `SWHeaderWindowControls`는 창 이동만 쓰고(`showMinimize`·`showClose` false), 최소화·최대화/복원·종료는 이 Header의 캡션 버튼입니다(2026-11-03 변경, §10).
|
||||
|
||||
## 3. 레이아웃·반응형
|
||||
|
||||
Strana Grid의 별 비율을 CSS flex 비율로 옮겼습니다. 숨김 단계·More는 없습니다(Strana와 같음). 보장 폭은 1100px 이상이며 그보다 좁으면 Header 안에서 잘립니다.
|
||||
|
||||
| 영역 | 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) |
|
||||
| 파일명 | `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` |
|
||||
| Layer 면 | `257*`, 최소 152, 최대 257, 양옆 1 | `flex: 257 1 0`, 칸 152 ~ 257, 면은 칸 안 양옆 1px(150 ~ 255, 가상 요소), 높이 48, radius 6 |
|
||||
| Layer 필드 | 면 안 양옆 8, 최대 234 × 38, padding 12 / 10 | 같음(가운데 정렬) |
|
||||
| 창 영역 | Auto: pill(위·왼쪽 3, 높이 42) + 창 이동 88 + 버튼 32 × 3 | 같음(약 313px, 도구 3개 기준) |
|
||||
| 프로세스 그룹 | 자연 폭까지, 좁아지면 자연 폭 비율로 함께 줄어듦, 양옆 남는 폭은 같게 | 그룹 `flex: 0 1 auto`(flex-shrink가 기본 폭에 비례), 양옆 `flex: 1 1 0` |
|
||||
| 단계 탭 | UniformGrid, 넓을 때 127 + 간격 1, 좁으면 0까지 | flex 한 줄 `flex: 1 1 0`(같은 폭), 크기 기준 요소 127px, 선택 탭도 함께 축소(§11.1) |
|
||||
|
||||
## 4. 프로세스 영역 규칙
|
||||
|
||||
| 요소 | 규칙 |
|
||||
|---|---|
|
||||
| 바탕 | ProcessBase 세로 gradient, DocBorder 1px, 위 모서리 6 |
|
||||
| 비활성 덮개 | 비활성 그룹 위 `--hm-sw-process-dim`. 왼쪽 남는 폭 = 첫 그룹, 오른쪽 남는 폭 = 마지막 그룹 밝기. 화살표 칸: 꼭지(앞 그룹 비활성) / 홈(뒤 그룹 비활성) |
|
||||
| 활성 그룹 면 | 기운 왼쪽 변 + 둥근 아래 모서리 도형(ProcessGlowLeft), 화살표 쪽으로 사라짐, 발광 Halo + 가장자리 빛. 첫 그룹은 왼쪽 40px, 마지막 그룹은 좌우 반전·오른쪽 44px 밖으로 |
|
||||
| 비활성 그룹 면 | ProcessIdleLeft 도형, Blur + 어두운 Halo + 테두리 빛, 바깥쪽에서 안쪽으로 사라짐(첫 그룹 79.8 → 198.4, 마지막 그룹 236.7 → 273.1) |
|
||||
| 사다리꼴 배치(확정 2026-11-03) | 그룹 면(사다리꼴)은 프로세스 영역의 **시작(첫 그룹)과 끝(마지막 그룹)에만** 둡니다. 그 그룹이 켜져 있으면 발광 면, 아니면 비활성 면. 가운데 그룹에는 켜져 있어도 도형이 없고 밝은 바탕(덮개 없음)만 보입니다(Figma 873:17421). 그룹 수와 관계없이 같으며 `default`·`multi` 공통. 화살표·비활성 덮개·선택 상태는 그대로 |
|
||||
| 화살표 | 16 × 48, 앞 그룹 활성 = On(채움 #27D9A5), 아니면 Dim(채움 25%, 선 50%) |
|
||||
| 번호 / 이름 | 12px, 줄 높이 14. 번호 500 `#69C2A6`. 이름 최대 56px 줄바꿈, 활성 700 흰색 / 비활성 500 `#C3EBDF`, 폭은 늘 700 기준. 위 여백 10.67(한 줄) / 8.67(두 줄) |
|
||||
| 탭 | 높이 32, padding 0 12 2.67, 16px 700 `#F0F7F5`. 모서리: 단독 16, 첫 탭 16 4 4 16, 마지막 탭 4 16 16 4, 가운데 4 |
|
||||
| 탭 상태 | 기본 층 Opacity 0.7. Hover = 글자 `#FFC600`. Checked = 층 1, 바탕·그늘·광택 교체, 위 5 · 왼쪽 6 안쪽 그림자 0.75, 테두리 2px 0.25, 글자 `#FFC600`. Disabled = 층 0.5, 글자 `#64A491` |
|
||||
|
||||
Strana의 2그룹(01 Modeling: Structure · Loads / 02 Analysis Result: Results)이 확정 디자인입니다. 1그룹·3그룹 이상은 렌더링되지만 디자인 미확정입니다.
|
||||
|
||||
## 5. HM 규칙 적용으로 바뀐 점 (원본 대비 변경)
|
||||
|
||||
| 항목 | Strana 원본 | 구현 | 근거 |
|
||||
|---|---|---|---|
|
||||
| 파일명 | TextBox(말줄임 없음, 편집 가능하나 반영 안 됨) | 버튼 + 말줄임, `title`로 전체 이름 | 승인 7 |
|
||||
| Focus | 표시 없음(FocusVisual null) | SW 2px `#F0F7F5` outline | 승인 7 |
|
||||
| 글자 외곽선 | DropShadow Blur 2 | SW `--hm-sw-header-text-outline`(14·16·18px) / `--hm-sw-header-label-outline`(10·12px). `multi`도 같음(Figma에는 Stroke 없음) | 승인 7, 2026-11-03 요청 |
|
||||
| 창 이동 | 정적 장식(Motion 없음) | SWHeader 창 이동 Motion(1.4s 반복, Hover 해제 즉시 복원, 툴팁 `창 이동`, Drag 중 정지, Reduced Motion 정적) | 2026-11-03 요청(이전 승인 5의 "Motion 없음"을 대체) |
|
||||
| 파일명 자간 | 0 | 0.06em(재사용한 SW 파일명 면) | 재사용 부품 |
|
||||
| Layer 필드 | ToggleButton 안에 전구 ToggleButton 중첩 | 형제 button 2개(전구 · 필드), 보이는 모양 같음 | HTML은 버튼 중첩 불가 |
|
||||
| 탭 3개 이상 가운데 탭 | 정의 없음(첫 탭 외 모두 오른쪽 모서리) | 모서리 4 | 일반화 |
|
||||
| 테마 | 4개(Hanmac · Dark · Light · HighContrast) | Hanmac 값만 기본값, 나머지는 §7 Override | 승인 4, Light/Dark preset 금지 |
|
||||
| 로고 메뉴 · Layer Popup | 있음 | 1차 범위 제외(버튼 상태·Callback만) | 승인 6 |
|
||||
| 화면 분할 버튼 · 선택 Popup | 있음 | `SWProcessSplitButton`으로 추가(2026-11-03, §11). 버튼 아래 연장을 Header 끝까지 늘려 패널이 Header 아래에서 시작(Strana는 3px — 창 버튼 아래 끝을 가림) | 2026-11-03 요청 |
|
||||
| 도구 아이콘 | DesignSystem Geometry | Storybook fixture(`src/sw/sw-process-header-fixture.tsx`)의 TSX 아이콘, 패키지는 `tools` 슬롯. 상태 색은 `currentColor` | 승인 8 |
|
||||
| 좁은 폭의 탭 글자 · Layer 이름 | 잘림(Clip) | `…` 말줄임 | HM 말줄임 규칙(승인 7과 같은 기준) |
|
||||
| 그림 자산 | XAML Geometry · DrawingBrush | 파일 대신 인라인 SVG(TSX)와 CSS — 색을 토큰으로 바꿀 수 있게 | 승인 4(토큰 Override) |
|
||||
| 도구 버튼 눌림 | 노란 링 + 아이콘 `#FFC600` | 같음. `SWToolButton` pill에는 눌림 모양이 없어 이 Header 범위 안에서만 더함(`SWHeader`의 pill은 그대로) | 원본 재현 |
|
||||
| 발광 · Blur | WPF DropShadow 30 / 15, Blur 10 / 8 / 2 | CSS `drop-shadow` 30 / 15px, `blur` 5 / 4 / 1px(반경 → 표준편차) | 근사값 — 1920 캡처 비교로 확인 |
|
||||
|
||||
## 6. 토큰
|
||||
|
||||
새 토큰은 `--hm-sw-process-*`이며 `sw-process-header.css`의 가장 바깥 SW 범위(`sw-footer.css`와 같은 규칙)에 선언합니다. 값이 SW 토큰과 같은 곳은 SW 토큰을 그대로 읽습니다(파일명 면·테두리, 객체 면·테두리, 속성 필드 면·그림자, Hover gradient, 선택 `#FFC600`, 제목·글자, 창 이동 줄무늬, 창 버튼 글리프·Hover 배경).
|
||||
|
||||
전체 목록과 Strana 원본 키는 §7 표의 왼쪽 두 열을 봅니다.
|
||||
|
||||
## 7. Strana 테마 → 토큰 Override 대응표
|
||||
|
||||
Hanmac 열이 기본값입니다. 다른 테마는 Header의 `style`(가장 바깥 SW 범위)에 같은 토큰을 지정합니다. 예: `style={{ "--hm-sw-header-selected-color": "#FFC833" }}`. Strana ARGB `#AARRGGBB`는 CSS `rgba()`로 바꿉니다. 아래 표는 Strana `Styles/Theme/*/Colors.xaml`(HEAD `edda3cc`)에서 생성했습니다.
|
||||
|
||||
<!-- theme-table:start -->
|
||||
| 토큰 | Strana `Color.Header.*` | Hanmac(기본값) | Dark | Light | HighContrast |
|
||||
|---|---|---|---|---|---|
|
||||
| `--hm-sw-header-background` | Background | `#060606` | `#0b0d11` | `#b9c7d7` | `#000000` |
|
||||
| `--hm-sw-header-text-color` | Text | `#f6f4f2` | `#f4f7fa` | `#1c2430` | `#ffffff` |
|
||||
| `--hm-sw-header-title-color · --hm-sw-process-tab-text` | Title | `#f0f7f5` | `#f4f7fa` | `#1c2430` | `#ffffff` |
|
||||
| `--hm-sw-header-selected-color` | Selected | `#ffc600` | `#ffc833` | `#0e54c6` | `#ffff00` |
|
||||
| `--hm-sw-header-text-outline-color` | TextOutline | `#000000` | `#000000` | `#ffffff` | `#000000` |
|
||||
| `--hm-sw-header-hover-border-gradient` | HoverBorderDark | `#a67d03` | `#a67d03` | `#2f5faf` | `#ffff00` |
|
||||
| ″ | HoverBorder | `#f2cc0f` | `#f2cc0f` | `#5c88d6` | `#ffff00` |
|
||||
| ″ | HoverBorderLight | `#fadd4f` | `#fadd4f` | `#8fb0e8` | `#ffff00` |
|
||||
| ″ | HoverBorderDeep | `#926e03` | `#926e03` | `#2f5faf` | `#ffff00` |
|
||||
| `--hm-sw-header-document-background` | DocFaceEdge | `#081813` | `#12151a` | `#c6d4e3` | `#000000` |
|
||||
| ″ | DocFaceDeep | `#09271e` | `#181c23` | `#d0dce8` | `#000000` |
|
||||
| ″ | DocFaceMid | `#0b3227` | `#1e232b` | `#d9e3ef` | `#000000` |
|
||||
| ″ | DocFaceLight | `#0e3d2f` | `#262c36` | `#e3ebf4` | `#000000` |
|
||||
| ″ | DocFaceLow | `#0a2b21` | `#1a1e25` | `#cfdae6` | `#000000` |
|
||||
| `--hm-sw-header-document-border-gradient · --hm-sw-process-base-border-gradient` | DocBorderTop | `#0e4c3a` | `#3a4250` | `#ffffff` | `#ffffff` |
|
||||
| ″ | DocBorderDark | `#041611` | `#0b0d11` | `#afbecd` | `#ffffff` |
|
||||
| ″ | DocBorderBottom | `#18503f` | `#3f4856` | `#9daec0` | `#ffffff` |
|
||||
| `--hm-sw-header-document-border-color` | DocBorderTop | `#0e4c3a` | `#3a4250` | `#ffffff` | `#ffffff` |
|
||||
| `--hm-sw-header-object-background` | ObjFaceEdge | `#144838` | `#1c2027` | `#d3dce6` | `#000000` |
|
||||
| ″ | ObjFaceLow | `#1a4536` | `#20252d` | `#dae2eb` | `#000000` |
|
||||
| ″ | ObjFaceMid | `#1f4134` | `#22272f` | `#dde5ed` | `#000000` |
|
||||
| ″ | ObjFaceDeep | `#10392d` | `#1a1e25` | `#d3dce6` | `#000000` |
|
||||
| ″ | ObjFaceLight | `#296b55` | `#353d48` | `#f0f4f8` | `#000000` |
|
||||
| ″ | ObjFaceUpper | `#1f5a47` | `#2a3039` | `#e7edf3` | `#000000` |
|
||||
| ″ | ObjFaceTop | `#124133` | `#1e232b` | `#d8e0ea` | `#000000` |
|
||||
| `--hm-sw-header-object-border-gradient` | ObjBorderTop | `#135643` | `#313843` | `#c3cfdb` | `#ffffff` |
|
||||
| ″ | ObjBorderDeep | `#0e3d2f` | `#1e232b` | `#afbecd` | `#ffffff` |
|
||||
| ″ | ObjBorderHighlight | `#34daaa` | `#74c4b4` | `#5c88d6` | `#ffffff` |
|
||||
| ″ | ObjBorderDark | `#08241c` | `#12151a` | `#afbecd` | `#ffffff` |
|
||||
| ″ | ObjBorderBottom | `#1f5e4b` | `#2e353f` | `#c3cfdb` | `#ffffff` |
|
||||
| `--hm-sw-header-property-background` | PropertyFace | `rgba(0, 0, 0, 0.451)` | `rgba(0, 0, 0, 0.451)` | `rgba(255, 255, 255, 0.349)` | `rgba(0, 0, 0, 1)` |
|
||||
| ″ | PropertyFaceLight | `rgba(0, 0, 0, 0.4)` | `rgba(0, 0, 0, 0.4)` | `rgba(255, 255, 255, 0.302)` | `rgba(0, 0, 0, 1)` |
|
||||
| `--hm-sw-header-property-shadow` | PropertyShadow | `#09271e` | `#000000` | `#5a6878` | `#000000` |
|
||||
| `--hm-sw-header-stripe-color` | Stripe | `rgba(28, 106, 81, 0.322)` | `rgba(63, 72, 86, 0.322)` | `rgba(47, 95, 175, 0.239)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-header-window-icon-color` | WindowIcon | `#adb0af` | `#adb0af` | `#4a5868` | `#ffffff` |
|
||||
| `--hm-sw-header-window-icon-hover-color` | WindowIconHover | `#ffffff` | `#ffffff` | `#1c2430` | `#000000` |
|
||||
| `--hm-sw-header-window-button-hover-background` | WindowButtonHover | `rgba(0, 0, 0, 0.2)` | `rgba(0, 0, 0, 0.2)` | `rgba(0, 0, 0, 0.122)` | `#ffff00` |
|
||||
| `--hm-sw-header-layer-icon-hover-background` | LayerIconHover | `rgba(0, 0, 0, 0.251)` | `rgba(0, 0, 0, 0.251)` | `rgba(0, 0, 0, 0.149)` | `rgba(255, 255, 255, 0.251)` |
|
||||
| `--hm-sw-process-base-background` | ProcessBaseEdge | `#0c5941` | `#1c2027` | `#c6d4e3` | `#000000` |
|
||||
| ″ | ProcessBaseLight | `#19bf8d` | `#4a5260` | `#f0f4f8` | `#000000` |
|
||||
| ″ | ProcessBaseMid | `#109069` | `#313843` | `#d9e3ef` | `#000000` |
|
||||
| `--hm-sw-process-dim` | ProcessDim | `rgba(0, 68, 48, 0.902)` | `rgba(11, 13, 17, 0.902)` | `rgba(207, 218, 230, 0.902)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-number-color` | ProcessNumber | `#69c2a6` | `#74c4b4` | `#2c5a88` | `#ffffff` |
|
||||
| `--hm-sw-process-group-color` | ProcessGroupText | `#c3ebdf` | `#c9d3de` | `#4a5868` | `#ffffff` |
|
||||
| `--hm-sw-process-group-active-color` | ProcessGroupActive | `#ffffff` | `#ffffff` | `#1c2430` | `#ffff00` |
|
||||
| `--hm-sw-process-glow-face-base` | ProcessGlowFaceTop | `#063b2b` | `#1a1e25` | `#e3ebf4` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowFaceFade | `rgba(13, 101, 74, 0.051)` | `rgba(42, 48, 57, 0.051)` | `rgba(217, 227, 239, 0.051)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowInner | `rgba(24, 134, 101, 0.702)` | `rgba(63, 111, 104, 0.702)` | `rgba(92, 136, 214, 0.702)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowInnerFade | `rgba(24, 134, 101, 0)` | `rgba(63, 111, 104, 0)` | `rgba(92, 136, 214, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-glow-face-rim` | ProcessGlowRim | `#247a60` | `#3f6f68` | `#5c88d6` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowFaceFade | `rgba(13, 101, 74, 0.051)` | `rgba(42, 48, 57, 0.051)` | `rgba(217, 227, 239, 0.051)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-glow-face-sheen` | ProcessGlowSheenTop | `#20322c` | `#1e232b` | `#ffffff` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowSheen | `#3dc79d` | `#74c4b4` | `#8fb0e6` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowSheenFade | `rgba(61, 199, 157, 0.051)` | `rgba(116, 196, 180, 0.051)` | `rgba(143, 176, 230, 0.051)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessGlowTint | `rgba(0, 95, 66, 0.251)` | `rgba(42, 48, 57, 0.251)` | `rgba(92, 136, 214, 0.251)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-glow-halo (× 0.85)` | ProcessGlowHalo | `#58b599` | `#74c4b4` | `#5c88d6` | `#000000` |
|
||||
| `--hm-sw-process-glow-edge (× 0.65)` | ProcessGlowEdge | `#58b599` | `#74c4b4` | `#5c88d6` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-idle-face` | ProcessIdleInset | `rgba(0, 0, 0, 0.502)` | `rgba(0, 0, 0, 0.502)` | `rgba(0, 0, 0, 0.149)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessIdleInsetFade | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessIdleShade | `rgba(23, 42, 26, 0.251)` | `rgba(0, 0, 0, 0.2)` | `rgba(47, 74, 110, 0.078)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-idle-halo (× 0.8)` | ProcessIdleHalo | `#11694f` | `#3a4656` | `#8fb0e6` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-idle-rim` | ProcessIdleRim | `rgba(4, 128, 90, 0.322)` | `rgba(116, 196, 180, 0.302)` | `rgba(255, 255, 255, 0.4)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-arrow-fill` | ProcessArrowFill | `#27d9a5` | `#74c4b4` | `#5c88d6` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-arrow-dim-fill` | ProcessArrowDimFill | `#abc7bf` | `#c9d3de` | `#8fa3b8` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-arrow-fill-fade` | ProcessArrowFillFade | `rgba(16, 109, 81, 0)` | `rgba(42, 48, 57, 0)` | `rgba(217, 227, 239, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-arrow-edge` | ProcessArrowEdge | `#00432e` | `#1a1e25` | `#afbecd` | `#ffffff` |
|
||||
| `--hm-sw-process-arrow-mid` | ProcessArrowMid | `#1c6a51` | `#4a5260` | `#5c88d6` | `#ffffff` |
|
||||
| `--hm-sw-process-arrow-light` | ProcessArrowLight | `#3d9c7e` | `#5e8f87` | `#8fb0e6` | `#ffffff` |
|
||||
| `--hm-sw-process-arrow-shadow` | ProcessArrowShadow | `rgba(0, 0, 0, 0.502)` | `rgba(0, 0, 0, 0.502)` | `rgba(255, 255, 255, 0.502)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-shade · -shade-mid · -shade-fade` | ProcessShade | `rgba(4, 34, 25, 0.878)` | `rgba(11, 13, 17, 0.878)` | `rgba(175, 190, 205, 0.878)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessShadeMid | `rgba(4, 34, 25, 0.706)` | `rgba(11, 13, 17, 0.706)` | `rgba(175, 190, 205, 0.706)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | ProcessShadeFade | `rgba(4, 34, 25, 0)` | `rgba(11, 13, 17, 0)` | `rgba(175, 190, 205, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-glow` | TabGlow | `rgba(31, 148, 113, 0.702)` | `rgba(74, 82, 96, 0.702)` | `rgba(255, 255, 255, 0.702)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-depth` | TabDepthFade | `rgba(0, 61, 41, 0)` | `rgba(26, 30, 37, 0)` | `rgba(198, 212, 227, 0)` | `rgba(0, 0, 0, 1)` |
|
||||
| ″ | TabDepth | `rgba(0, 61, 41, 0.8)` | `rgba(26, 30, 37, 0.8)` | `rgba(198, 212, 227, 0.8)` | `rgba(0, 0, 0, 1)` |
|
||||
| `--hm-sw-process-tab-shade` | TabShade | `rgba(0, 0, 0, 0.4)` | `rgba(0, 0, 0, 0.4)` | `rgba(0, 0, 0, 0.149)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabShadeFade | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-sheen` | TabSheen | `rgba(74, 255, 196, 0.502)` | `rgba(116, 196, 180, 0.502)` | `rgba(255, 255, 255, 0.502)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabSheenFade | `rgba(74, 255, 196, 0)` | `rgba(116, 196, 180, 0)` | `rgba(255, 255, 255, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-border-gradient` | TabBorder | `#001e15` | `#0b0d11` | `#9daec0` | `#ffffff` |
|
||||
| ″ | TabStrokeHighlight | `#17a77b` | `#4a5260` | `#ffffff` | `#ffffff` |
|
||||
| ″ | TabStrokeLow | `#0b533d` | `#262c36` | `#9daec0` | `#ffffff` |
|
||||
| `--hm-sw-process-tab-ring` | TabRing | `rgba(0, 0, 0, 0.251)` | `rgba(0, 0, 0, 0.251)` | `rgba(255, 255, 255, 0.251)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-active-base` | TabActiveBase | `rgba(0, 0, 0, 0.4)` | `rgba(0, 0, 0, 0.4)` | `rgba(255, 255, 255, 0.4)` | `rgba(0, 0, 0, 1)` |
|
||||
| ″ | TabActiveBaseDeep | `rgba(0, 0, 0, 0.451)` | `rgba(0, 0, 0, 0.451)` | `rgba(255, 255, 255, 0.451)` | `rgba(0, 0, 0, 1)` |
|
||||
| `--hm-sw-process-tab-active-shade` | TabActiveShade | `rgba(0, 0, 0, 0.6)` | `rgba(0, 0, 0, 0.6)` | `rgba(92, 136, 214, 0.2)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabShadeFade | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-active-sheen` | TabActiveSheenFade | `rgba(122, 206, 179, 0)` | `rgba(116, 196, 180, 0)` | `rgba(92, 136, 214, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabActiveSheen | `rgba(122, 206, 179, 0.102)` | `rgba(116, 196, 180, 0.102)` | `rgba(92, 136, 214, 0.102)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-inset (끝은 투명)` | TabInset | `rgba(0, 0, 0, 0.659)` | `rgba(11, 13, 17, 0.659)` | `rgba(175, 190, 205, 0.659)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabInsetFade | `rgba(0, 0, 0, 0)` | `rgba(11, 13, 17, 0)` | `rgba(175, 190, 205, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-disabled-shade` | TabShadeFade | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabDisabledShade | `rgba(0, 0, 0, 0.8)` | `rgba(0, 0, 0, 0.8)` | `rgba(0, 0, 0, 0.2)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-disabled-sheen` | TabDisabledSheen | `rgba(130, 181, 164, 0.502)` | `rgba(128, 142, 156, 0.502)` | `rgba(255, 255, 255, 0.502)` | `rgba(0, 0, 0, 0)` |
|
||||
| ″ | TabActiveSheenFade | `rgba(122, 206, 179, 0)` | `rgba(116, 196, 180, 0)` | `rgba(92, 136, 214, 0)` | `rgba(0, 0, 0, 0)` |
|
||||
| `--hm-sw-process-tab-disabled-text` | TabDisabledText | `#64a491` | `#6d7784` | `#8a97a6` | `#a0a0a0` |
|
||||
| `--hm-sw-process-layer-border-gradient` | PropertyBorder | `#08251c` | `#12151a` | `#afbecd` | `#ffffff` |
|
||||
| ″ | ObjBorderDeep | `#0e3d2f` | `#1e232b` | `#afbecd` | `#ffffff` |
|
||||
| ″ | LayerFieldBorderHighlight | `#1e7c60` | `#4a5260` | `#ffffff` | `#ffffff` |
|
||||
| ″ | ObjBorderDark | `#08241c` | `#12151a` | `#afbecd` | `#ffffff` |
|
||||
| ″ | ToolBorderBottom | `#051612` | `#0b0d11` | `#9daec0` | `#ffffff` |
|
||||
| `--hm-sw-process-pill-background` | PillFaceTop | `rgba(0, 0, 0, 0.851)` | `rgba(0, 0, 0, 0.851)` | `rgba(224, 228, 232, 0.851)` | `rgba(0, 0, 0, 1)` |
|
||||
| ″ | PillFace | `rgba(0, 0, 0, 0.8)` | `rgba(0, 0, 0, 0.8)` | `rgba(246, 249, 252, 0.8)` | `rgba(0, 0, 0, 1)` |
|
||||
| `--hm-sw-process-pill-border-gradient` | PillBorder | `#092920` | `#1a1e25` | `#afbecd` | `#ffffff` |
|
||||
| ″ | PillBorderBottom | `#1c6f56` | `#313843` | `#c3cfdb` | `#ffffff` |
|
||||
| `--hm-sw-process-tool-border-gradient` | PropertyBorder | `#08251c` | `#12151a` | `#afbecd` | `#ffffff` |
|
||||
| ″ | ToolBorderHighlight | `#208769` | `#4a5260` | `#ffffff` | `#ffffff` |
|
||||
| ″ | ObjBorderDark | `#08241c` | `#12151a` | `#afbecd` | `#ffffff` |
|
||||
| ″ | ToolBorderBottom | `#051612` | `#0b0d11` | `#9daec0` | `#ffffff` |
|
||||
| `--hm-sw-process-tool-icon-color` | Icon | `#b6d0c9` | `#c9d3de` | `#26303c` | `#ffffff` |
|
||||
| `--hm-sw-process-close-hover-background` | CloseHover | `#e40000` | `#e40000` | `#e40000` | `#ff0000` |
|
||||
| `--hm-sw-process-close-icon-hover` | CloseIconHover | `#ffffff` | `#ffffff` | `#ffffff` | `#ffffff` |
|
||||
| `--hm-sw-process-tool-open-background` | ToolOpenEdge | `#081813` | `#0b0d11` | `#dce4ed` | `#000000` |
|
||||
| ″ | ToolOpenMid | `#0b3227` | `#171b21` | `#e8eef5` | `#000000` |
|
||||
| ″ | ToolOpenCenter | `#0e3d2f` | `#1e232b` | `#f3f6fa` | `#000000` |
|
||||
| `--hm-sw-process-tool-open-edge` | ToolOpenEdge | `#081813` | `#0b0d11` | `#dce4ed` | `#000000` |
|
||||
| `--hm-sw-process-split-panel-background` | SplitPanelFace | `#082018` | `#14171c` | `#ffffff` | `#000000` |
|
||||
| `--hm-sw-process-split-cell-pane` | SplitCellPane | `#1b6b53` | `#3a4656` | `#e3ebf4` | `#000000` |
|
||||
| `--hm-sw-process-split-icon-pane` | IconCell | `#003425` | `#12151a` | `#afbecd` | `#000000` |
|
||||
|
||||
그라데이션 토큰은 여러 키의 조합입니다. 같은 위치(stop)에 테마 값을 넣어 토큰 전체를 지정합니다. 다음 키는 토큰이 아닙니다: `HomeFaceBottom`, `HomeFaceDeep`, `HomeFaceMid`, `HomeFaceTop`, `HomeBorderTop`, `HomeBorderLight`, `HomeBorderDark`, `HomeBorderBottom`, `HomeShadow`, `IconOutline`, `WindowMoveIcon`, `LayerBulbBase`, `LayerBulbBar`, `LayerBulbOnLight`, `LayerBulbOnMid`, `LayerBulbOnDark`, `LayerBulbOffLight`, `LayerBulbOffMid`, `LayerBulbOffDark`(SW 홈·전구·창 이동 그림 자산 — 테마 Override 대상 아님, Hanmac 그림), `IconScreen`, `IconScreenDeep`, `IconMonitorScreen`(Strana 도구 아이콘 전용 — 도구 아이콘은 소비 측이 그림).
|
||||
<!-- theme-table:end -->
|
||||
|
||||
## 8. 검증
|
||||
|
||||
2026-11-03 1차 구현(Chromium, Storybook 6024).
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| 폭 측정(파일명 / 프로세스 / 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 |
|
||||
| 기존 `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는 확인하지 않았습니다.
|
||||
|
||||
## 9. `appearance="multi"` — StrAna-SW 다중 Step (2026-11-03)
|
||||
|
||||
원본: Figma StrAna-SW `4y8lTK1E1hAGYiMcYZwuNe`, 노드 `873:16481` · `873:16254` · `873:17421`(각 1920 × 49). Story: `SW / Step Header › Step Header`(Step Header 섹션 = `default` + Multi Step 섹션 = 세 시안, 노드 ID는 화면에 표시하지 않고 `data-figma-node`로만 유지), `Responsive`, 검증용(숨김) `Multi Step`(세 시안 1920) · `화면 분할` · `Fixed Widths` · `Process Bar` · `Many Groups` · `Many Steps` · `States` · `Theme Override`.
|
||||
|
||||
### 9.1 시안 비교
|
||||
|
||||
세 시안은 **같은 구조의 상태 차이**입니다. 그래서 별도 컴포넌트 없이 `SWProcessHeader` / `SWProcessBar`의 `appearance="multi"` 하나로 구현했습니다.
|
||||
|
||||
| 시안 | 파일명 면 | 켜진 그룹 · 선택 단계 | 그룹 상태 |
|
||||
|---|---|---|---|
|
||||
| 873:16481 | 파일 없음: `프로젝트 생성 전` 10px 700 `#AEDBCC` 오른쪽 위 | 01 데이터 구축 · 데이터입력 | 2·3 미진행(70%), 4·5 비활성(회색) |
|
||||
| 873:16254 | 파일명 14px 700 `#F0F7F5`, 말줄임. 위치·여백은 SWHeader 우선(글자 x 70, padding 5 / 12, 자간 0.84px) | 01 데이터 구축 · 데이터입력 | 같음 |
|
||||
| 873:17421 | 파일 없음 | 03 구조물 상세 및 계획 · 본체계획 | 1·2 진행 완료(덮개 + 밝은 탭), 4·5 비활성 |
|
||||
|
||||
### 9.2 `default`와 공유한 것 / 다른 것
|
||||
|
||||
- **사다리꼴 배치:** 시작·끝 그룹에만(§4 확정 규칙). 873:17421처럼 가운데 그룹이 켜지면 도형 없이 밝은 바탕만 보입니다(2026-11-03 수정 — 이전 구현은 켜진 가운데 그룹에도 발광 면을 그렸음).
|
||||
- **같은 구현(값 대조 일치):** 프로세스 바탕(ProcessBase gradient · DocBorder · 위 모서리 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 원본)에만 둡니다.
|
||||
|
||||
### 9.3 넘침 처리 — 확정 정책(2026-11-03 사용자 승인)
|
||||
|
||||
| 항목 | 결정 | 근거 |
|
||||
|---|---|---|
|
||||
| 그룹 폭 | 자연 폭(제목·탭 중 넓은 쪽 + 좌우 10)까지, 좁으면 자연 폭 비율로 함께 축소. 남는 폭은 양쪽에 같게 | `default`와 같은 규칙 |
|
||||
| 탭 | 글자 폭 + 25, 좁으면 선택 탭 포함 글자 폭 비율로 축소 + `…`(2026-11-03 개정) | 옆 탭이 비정상적으로 좁아지지 않게 |
|
||||
| 화살표 · 그룹 사이 36 | 고정 | Figma |
|
||||
| 실제 넘침 | 그룹 접기·좌우 스크롤·More 메뉴를 두지 않음. 더 좁으면 영역 안에서 잘림 | 확정(Figma에 정의 없음) |
|
||||
|
||||
측정(1920): 5그룹 11탭은 말줄임 없음. 1366에서는 11탭 중 10개, 8그룹 · 16탭(1920)에서는 15개가 말줄임됩니다. 겹침 · 영역 밖 탭 0. (개정 후 1366에서는 선택 탭도 다른 탭과 함께 줄어 `…`가 될 수 있습니다.)
|
||||
|
||||
### 9.4 Figma와 남은 차이
|
||||
|
||||
- 873:17421의 켜진 그룹 미선택 탭은 70% · `#0C3125` · `rgba(76,167,138,.8)`, 873:16481은 100% · `#0A5940` · `rgba(68,178,143,.8)`로 시안끼리 다릅니다. 873:16481 값으로 통일했습니다.
|
||||
- 873:17421의 선택 탭에만 있는 안쪽 2px 링(`rgba(0,0,0,.25)`)은 넣지 않았습니다(873:16481 기준).
|
||||
- 비활성 글자는 `#87AD9D`(1개 탭) / `#7D9F91`(3개 탭) 두 값 중 `#7D9F91`로 통일했습니다.
|
||||
- 873:17421의 `선형입력` 위치(599)는 873:16481(592.8)과 다릅니다. 구현은 그룹 간격 36 규칙(592)입니다.
|
||||
- Figma 로고 자리는 글자 `LOGO`이며, Story는 임시 로고 이미지입니다(로고·파일명 글자는 SWHeader와 같은 크기·위치).
|
||||
- ~~창 이동 아이콘 밝기 차이~~: SWHeader 창 이동 자산으로 바꾼 뒤 Figma와 같은 밝기(2026-11-03 해소).
|
||||
- 글자 외곽선: Figma multi 시안의 글자에는 Stroke가 없으나 SW 규칙에 맞춰 외곽선을 적용했습니다(의도된 차이, §10).
|
||||
- 장치 배율 1.5에서 1px 테두리 반올림으로 창 영역이 366.3px로 측정됩니다(Figma 367).
|
||||
|
||||
## 10. SWHeader 스타일 정합 (2026-11-03)
|
||||
|
||||
`SWHeader`의 실제 구현(sw.css)과 브라우저 렌더를 기준으로 맞췄습니다. `SWHeader`·`SWViewerHeader`·`sw.css`는 바꾸지 않았고, 모두 `default`·`multi` 공통입니다.
|
||||
|
||||
| 항목 | SWHeader | Process Header(적용 결과) | 구현 |
|
||||
|---|---|---|---|
|
||||
| 글자 Stroke | 14·16px 검정 1.3px, 12px 1px(16방향 text-shadow), 말줄임 글자는 `overflow-clip-margin` 2px | 같음: 파일명·탭(선택·비선택·비활성)·Layer 값 1.3px, 그룹 제목·번호·Layer 라벨·파일 없음 표시 1px | SW 토큰 재사용. `multi`의 `text-shadow: none` 제거, 탭·제목 말줄임 글자는 clip margin 공통 |
|
||||
| 창 이동 | 88 × 48, 줄무늬, 아이콘 → Hover 시 창·커서 1.4s 반복 + 툴팁, Drag 중 정지, 해제 즉시 복원, Reduced Motion 정적 | 같음(SWHeader 부품 그대로). `multi`만 Figma 위치로 줄무늬 82 × 39(3, 5)·아이콘 3px 위 | `SWHeaderWindowControls`(창 이동만) 재사용, 정적 그림·CSS·토큰 2개 삭제 |
|
||||
| 도구 버튼 Hover | 노란 gradient 링 + 노란 아이콘(`hoverIcon` 또는 색) | 같음. Default fixture 아이콘은 `currentColor`, Multi Figma 아이콘은 `#B6D0C9 → #FFC600` Hover 그림(SW 인쇄 버튼 방식) | SWToolButton `hoverIcon`, 눌림(pressed)도 링 + 노란 아이콘 |
|
||||
| 로고 · 파일명 · 탭 Hover | 로고 금색 테두리, 파일명 글자 `#FFC600` | 같음, 탭 글자 `#FFC600`, 비활성 탭은 변화 없음 | 재사용 |
|
||||
| Layer 필드 Hover | 필드 노란 링 + 글자 `#FFC600`, 전구는 어두운 배경 | 같음 | 재사용 |
|
||||
| 최소화 · 최대화 Hover | 배경 `rgba(0,0,0,.2)` + 흰 글리프(노랑 아님) | 같음 | SW 토큰 |
|
||||
| 종료 Hover | `#E40000` + 흰 X | 같음(유지) | 확정 디자인 |
|
||||
| Focus | 2px `#F0F7F5`, -2px | 같음(탭·도구·캡션 버튼) | SW 공통 규칙 |
|
||||
| 선택 · Disabled | — | 선택 탭 `#FFC600`(Hover와 같은 색이지만 면이 다름), Disabled는 Hover·Focus 없음 | 기존 확정 디자인 |
|
||||
|
||||
## 11. 선택 Step 표시 통일 · 아이콘 · 화면 분할 (2026-11-03)
|
||||
|
||||
### 11.1 선택 Step 폭(두 appearance 공통, 2026-11-03 개정)
|
||||
|
||||
- **개정:** 선택된 탭도 다른 탭과 같은 비율로 줄어듭니다(이전 "선택 탭 전체 이름 유지" 폐기 — 선택 탭에 폭을 먼저 주느라 옆 탭이 24 · 27px까지 좁아지던 문제). 공간이 충분하면 전체 이름, 부족하면 모든 탭이 기본 폭 비율로 줄고 `…`입니다(`default` 같은 폭, `multi` 글자 폭 비례). 선택이 바뀌어도 탭 폭은 그대로입니다.
|
||||
- 그룹 최소 폭 = 좌우 여백 + (`default`만) 이름 + 모든 탭 여백 + 간격(측정). 그 위에서는 그룹이 비례로 줄고, 아래로는 줄지 않아 겹치지 않습니다.
|
||||
- 탭 배치는 두 appearance 모두 flex 한 줄입니다(이전 `default`의 grid 대체). 넓을 때 `default` 탭은 127 균등 그대로입니다.
|
||||
- 측정(개정 후, 고정 폭, `*` = 선택): `default` 1100 `*61 61 | 45`(이전 `*98 25 | 45`), 1366 `*124 124 | 124`, 1920 · 2560 `*127 127 | 127`(변화 없음). `multi` 1100 셋째 그룹 `*34 34 37 37`(이전 `*77 25 25 25`), 1366 `*54 54 61 61`(이전 `*77 47 53 53`), 1920 · 2560 `*77 77 89 89`(변화 없음). 겹침 0. 그룹 최소 폭의 합이 프로세스 영역보다 크면 영역 끝에서 잘립니다.
|
||||
|
||||
### 11.2 도구 아이콘이 사라지던 문제
|
||||
|
||||
- 원인: `sw.css`의 `:is(.hm-sw-header__action, …):hover:not(:disabled) > .hm-sw-header__icon { visibility: hidden }`는 Hover 그림(`hoverIcon`)이 있다고 가정합니다. `hoverIcon` 없이 `currentColor` 아이콘만 준 `SWToolButton`은 Hover 순간 아이콘이 비었습니다(Default 도구 3개).
|
||||
- 해결(2026-11-03 공통 수정): `SWToolButton`(pill)이 `hm-sw-header__icon` class를 `hoverIcon`이 있을 때만 기본 아이콘에 붙입니다. `hoverIcon`이 없으면 기본 아이콘이 Hover에도 그대로 보이고 색만 `#FFC600`입니다. `SWHeader`의 `tools`도 같은 컴포넌트라 함께 해결되었고, 이 Header 범위의 보완 CSS 규칙은 삭제했습니다(`sw.css` 변경 없음). 눌림은 Hover 그림이 있을 때만 그 그림으로 바뀝니다. Disabled·열림에서도 아이콘이 유지되며 크기 24 × 24로 같습니다.
|
||||
|
||||
### 11.3 화면 분할(`SWProcessSplitButton`)
|
||||
|
||||
원본: Strana `ViewportSplitSelector.xaml`, `Styles/Controls/ViewportSplitStyles.xaml`, `HeaderStyles.xaml`(ToolMenu · ToolOpenTail · ToolMenuStep · Icon.ToolLayout), MainWorkspaceWindowView 분할 Popup(HEAD `edda3cc`).
|
||||
|
||||
| 항목 | Strana | 구현 |
|
||||
|---|---|---|
|
||||
| 버튼 | 도구 버튼 40 × 38, 아이콘 = 현재 분할 모양 | `SWToolButton` pill + 분할 아이콘(Strana Geometry, 틀은 `currentColor`) |
|
||||
| 열림 | 면 ToolOpenFace, 금색 위·양옆, 아래 열림, Hover 링 없음, 아이콘 노랑 | 같음 |
|
||||
| 연장 | 버튼 아래 3px(버튼 아래 1px 겹침) | 버튼 아래 1px 겹침, 길이는 Header 끝까지(최소 3px) — 패널이 창 버튼을 가리지 않게 |
|
||||
| 패널 | 233 × 36, 면 `#082018`, 금색 양옆·아래, radius 0 2 2 2, 그림자 0 2 4 60% | 같음, Header 바로 아래·버튼 왼쪽, 화면 밖이면 좌우만 보정(SW 필드 Dropdown 위치 훅) |
|
||||
| 위 선 | 버튼 오른쪽 끝 ~ 패널 끝 금색 | 같음 |
|
||||
| 칸 | 38 × 34 × 6(Type01~06), Hover·선택 = 창 노랑, Disabled 40% | 38.5 × 34(233 / 6), 같은 그림·상태. 틀 상자는 32 × 22 정수(Strana 31.6 × 21.73, 그림 29.6 × 19.73 그대로) |
|
||||
| 동작 | Popup StaysOpen=False(바깥 클릭 닫힘), 칸 선택 시 열린 채 | 같음 + Escape 닫고 버튼으로, Tab 이탈 닫힘, ArrowDown 열기, 방향키 선택(radiogroup) |
|
||||
|
||||
값은 Props(`value`, `open`), 동작은 Callback(`onValueChange`, `onOpenChange`)이며 창을 실제로 나누는 일은 호스트가 합니다. `tools` 안에 두므로 버튼 순서는 호스트가 정하고 `default`·`multi`가 같은 컴포넌트를 씁니다. Figma 다중 Step 시안 3개에는 화면 분할 버튼이 없어 해당 Story에는 넣지 않았습니다(`Step Header` 페이지의 Step Header 섹션과 검증용 `화면 분할` Story에서 Default·Multi Step을 확인).
|
||||
|
||||
- **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줄 · 아래 밝은 줄 표시 확인.
|
||||
@@ -48,15 +48,16 @@ SW 패키지에는 Header·Popup 계열이 두 벌 있습니다. 화면마다 **
|
||||
| 아이콘 | 패키지 자산(필드 안 아이콘) | 프로그램 아이콘 16px, 직접 그린 아이콘은 `SWViewerIcon`으로 감쌈 |
|
||||
| 범위 | 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, [DESIGN_RULES.md](./DESIGN_RULES.md) §1~10·§12·§13, [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 1~6·8·10, [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) |
|
||||
| 읽을 문서 | [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) |
|
||||
|
||||
- **프로세스 단계 Header**(로고 · 파일명 · 01 Modeling → 02 Analysis Result 같은 단계 탭 · Layer 필드 · 도구 pill · 최소화 · 최대화/복원 · 종료, Strana Main Header) 요청은 일반 모드의 독립 Header `SWProcessHeader`입니다(카탈로그 §11, DESIGN_RULES §14, PATTERNS 12). 그룹·단계가 많은 다중 Step 화면(StrAna-SW)은 같은 컴포넌트의 `appearance="multi"`입니다(별도 컴포넌트를 만들지 않음). `SWHeader` 대신 쓰며 같은 화면에 함께 두지 않습니다.
|
||||
- **하단 명령 막대(Footer)** 요청은 일반 모드입니다: [FOOTER.md](./FOOTER.md) §1 순서로 `SWFooter`가 있는지 확인하고, 명령·분류·보조기능·문구·아이콘만 Props로 바꿉니다. Footer 외형을 새로 그리지 않습니다.
|
||||
- **일반 모드 Header에 도구 막대를 더한 구성**(로고 옆 파일 열기 + Model/Layout·Fit·2.5D 버튼을 `SWHeader`에 추가)은 **일반 모드**입니다: `SWHeader`의 `documentOpenPlacement`·`leading`·`tools` + `SWToolButton` / `SWToolGroup`(PATTERNS 6, 예제 F). "뷰어"·"도면 보기" 화면을 요청받았다면 이 구성이 아니라 뷰어 모드(`SWViewer*`)를 씁니다.
|
||||
- 모드 진입·종료, 파일 처리, 도면 렌더링, 메뉴 명령 실행은 어느 모드든 **프로그램 담당**입니다.
|
||||
|
||||
### 0.3 모드 혼합 금지
|
||||
|
||||
- **한 화면에 Header 하나.** `SWHeader`와 `SWViewerHeader`를 같은 화면에 함께 두지 않습니다. 두 모드를 오가는 프로그램은 상태를 유지한 채 Header 컴포넌트만 바꿉니다(VIEWER.md §1).
|
||||
- **한 화면에 Header 하나.** `SWHeader`·`SWProcessHeader`·`SWViewerHeader` 중 둘 이상을 같은 화면에 함께 두지 않습니다. 두 모드를 오가는 프로그램은 상태를 유지한 채 Header 컴포넌트만 바꿉니다(VIEWER.md §1).
|
||||
- **뷰어 화면:** `SWHeader`에 뷰어 메뉴를 넣거나 `SWHeader`·`SWPopup`을 뷰어처럼 재스타일하지 않습니다. 뷰어 팝업은 `SWViewerPanel`입니다. 예외: 창 최소화·닫기 버전은 `SWViewerHeader`의 `trailing` 맨 끝에 `SWHeaderWindowControls`(`showWindowMove={false}`)를 그대로 둡니다(VIEWER.md §2.1, 2026-10-21).
|
||||
- **일반 화면:** `SWViewerHeader`·`SWViewerMenu`·`SWViewerPanel`과 패널 컨트롤(`SWViewerSection` …)을 쓰지 않습니다.
|
||||
- **로고(홈) 메뉴는 SW 일반 모드 전용:** `SWHomeMenu`·`SWHomeMenuSection`·`SWHomeMenuItem`·`SWRecentFiles`·`useSWHomeMenuClose`와 `SWHeader`의 `homeMenu`·`homeMenuOpen`·`defaultHomeMenuOpen`·`onHomeMenuOpenChange`는 일반 모드 `SWHeader` 화면에서만 씁니다. 뷰어 화면에는 홈 메뉴를 만들지 않고(`SWViewerHeader` 옆·`custom` 항목·`SWViewerPanel` 안에 넣거나 비슷하게 다시 만들지 않음), 뷰어 Header와 기능은 그대로 둡니다.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- **검증 표시:** ✅ = 2026-10-19 독립 소비 프로젝트(Tarball 설치)에서 브라우저 검증, ◻ = 소비 검증 범위 밖(Storybook 검증 기록만 있음).
|
||||
- **먼저 모드:** SW 일반 모드(편집) / SW 뷰어 모드(도면 보기)를 [AI_USAGE.md](./AI_USAGE.md) §0으로 정합니다. **뷰어 모드는 §9(`SWViewer*`)만** 쓰고(예외: 창 최소화·닫기 버전의 `trailing` 끝 `SWHeaderWindowControls`, [VIEWER.md](./VIEWER.md) §2.1), 아래 §1~8은 일반 모드용입니다. 두 모드를 한 화면에 섞지 않습니다.
|
||||
- **무엇을 쓸까(일반 모드):** Header 전체 → `SWHeader`(1). Header 부품 조합 → 개별 컨트롤(2). 팝업·설정창·정보창 → `SWPopup`(3). 도구 막대·탭·토글 → `SWToolButton` / `SWToolGroup`(4). 로고 클릭 2단 메뉴·최근파일 → `SWHeader homeMenu` / `SWHomeMenu`(5). 게이지바(투명도 등)는 `SWSlider`, 선택 목록(단위 등)은 `SWSelect`(3). Switch·Badge·Progress·Dialog(모달)·Label·FilterBar는 SW에 없습니다 → `@hm-design/ui`를 그대로 씁니다(필요하면 `SWPopup` children 안에).
|
||||
- **무엇을 쓸까(일반 모드, 프로세스 단계 Header):** 로고 · 파일명 · 프로세스 단계 탭(01 Modeling → 02 Analysis Result 등) · Layer 필드 · 도구 pill · 창 이동 · 최소화 · 최대화/복원 · 종료 구성(Strana Main Header) → `SWProcessHeader`, 프로세스 영역만 → `SWProcessBar`(11). `SWHeader`와 같은 화면에 함께 두지 않습니다(Header는 화면에 하나).
|
||||
- **무엇을 쓸까(일반 모드 하단):** 하단 명령 막대(보조기능·명령 칸·분류 탭·명령어 전체보기) → `SWFooter`, 도면 공간 탭(Model/Layout) → `SWFooterLayoutTabs`, 명령창 → `SWFooterCommandLine`(10, [FOOTER.md](./FOOTER.md)).
|
||||
- **무엇을 쓸까(뷰어 모드):** Header → `SWViewerHeader`(`groups` + `trailing`), Dropdown·우클릭 → `SWViewerMenu`, 팝업·설정창·모달 → `SWViewerPanel` + `SWViewerSection`/`SWViewerField`/`SWViewerSwitch`/`SWViewerInput`/`SWViewerButton`, 옵션 패널 게이지 → `SWSlider appearance="property"`, 상태 → `SWViewerStatus`(9).
|
||||
|
||||
@@ -88,7 +89,7 @@ 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 영역). `document` = 파일명 면(48px, 글자만 Hover #FFC600). Home 바로 뒤에 두면 로고 사선 접점을 그대로 공유 | ✅ |
|
||||
| `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 바로 뒤에 두면 로고 사선 접점을 그대로 공유 | ✅ |
|
||||
| `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`입니다.
|
||||
@@ -180,3 +181,24 @@ SW **뷰어 모드** 전용 Export입니다(2026-10-19 추가, 참고 DWG/DXF
|
||||
- **상수·아이콘:** `SW_FOOTER_MODEL`(`"model"`, Model 탭의 `value`), `swFooterToolIcons`(`line`·`pline`·`arc`·`circle`·`ellipse`·`rect`·`poly`·`point`·`hatch`·`ncopy` → `{ icon, activeIcon }`), `swFooterAuxIcons`(`objectSnap`·`selectionEffect`·`orthogonal`·`gridSnap`·`gridBackground`·`lineWeight`, 인라인 SVG·채움 `currentColor`).
|
||||
- **공개 타입:** `SWFooterProps`, `SWFooterSideAction`, `SWFooterSideButtonProps`, `SWFooterAuxGroupProps`, `SWFooterAuxButtonProps`, `SWFooterToolButtonProps`, `SWFooterAddSlotProps`, `SWFooterGuideProps`, `SWFooterGuideContent`, `SWFooterMenuProps`, `SWFooterMenuItem`, `SWFooterLayoutTabsProps`, `SWFooterCommandLineProps`, `SWFooterTool`(`id`, `label`, `icon`, `activeIcon`, `title`, `ariaLabel`, `disabled`), `SWFooterCategory`(`id`, `label`, `tools`, `disabled`), `SWFooterAuxItem`(`id`, `label`, `icon`, `pressed`, `title`, `disabled`), `SWFooterLayout`(`id`, `label`, `locked`, `shape`), `SWFooterToolIconName`, `SWFooterAuxIconName`.
|
||||
- **검증 표시:** Footer Export는 Storybook(`SW/Footer`)과 저장소 안 Import·Build 검증만 있고 독립 소비 프로젝트(Tarball) 브라우저 검증 기록이 없어 ◻입니다.
|
||||
|
||||
## 11. Process Header (`SWProcessHeader` · `SWProcessBar`, SW 일반 모드)
|
||||
|
||||
프로세스 단계 Header(2026-11-03 추가). Strana Main Header 디자인을 SW 일반 모드의 **독립 Header**로 옮긴 것으로, `SWHeader`·`SWViewerHeader`와 같은 층위입니다. 한 화면에는 Header 하나만 둡니다. 토큰·상태는 [DESIGN_RULES.md](./DESIGN_RULES.md) §14, 조합은 [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) 12.
|
||||
|
||||
| 컴포넌트 | 용도 | 주요 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 | ◻ |
|
||||
| `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은 선택된 단계 하나로만 들어갑니다.
|
||||
- **반응형:** 숨김·More·스크롤 없음. 보장 폭 1100px 이상이며 더 좁으면 Header 안에서 잘립니다. 탭 글자·파일명·Layer 이름은 `…` 말줄임. 공간이 부족하면 선택된 탭도 다른 탭과 같은 비율로 줄어듭니다(2026-11-03 개정): `default`는 모든 탭이 같은 폭, `multi`는 글자 폭에 비례하며 이름이 들어가지 않으면 `…`. 선택이 바뀌어도 탭 폭은 그대로입니다. 그룹은 이름 + 모든 탭 여백보다 좁아지지 않아 겹치지 않습니다. 그룹 사이 화살표·간격은 고정입니다. **넘침 정책 확정(2026-11-03): 그룹 접기·좌우 스크롤·More 메뉴를 추가하지 않습니다.**
|
||||
- **범위 밖(호스트 소유):** 로고 메뉴 본체, Layer Popup, 실제 화면 분할 실행, 도구 아이콘(소비 측이 `tools`에 넣는 요소). 화면 분할 선택 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`.
|
||||
- **검증 표시:** Storybook(`SW/Step Header`)과 저장소 안 Typecheck·Build 검증만 있고 독립 소비 프로젝트(Tarball) 브라우저 검증 기록이 없어 ◻입니다.
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 0. 어떤 패턴을 고를까
|
||||
|
||||
먼저 SW 모드를 정합니다([AI_USAGE.md](./AI_USAGE.md) §0). 1~6과 8·10은 **SW 일반 모드**, 7은 **SW 뷰어 모드**이며 한 화면에서 섞지 않습니다. 11(테마 설정)은 두 모드 공통입니다.
|
||||
먼저 SW 모드를 정합니다([AI_USAGE.md](./AI_USAGE.md) §0). 1~6과 8·10·12는 **SW 일반 모드**, 7은 **SW 뷰어 모드**이며 한 화면에서 섞지 않습니다. 11(테마 설정)은 두 모드 공통입니다.
|
||||
|
||||
| 요청 | 모드 | 패턴 |
|
||||
|---|---|---|
|
||||
@@ -18,6 +18,7 @@
|
||||
| 일반 모드 `SWHeader`에서 로고를 누르면 열리는 2단 메뉴(저장·내보내기·찾아열기·최근파일 등) | 일반 | 8. 로고 클릭 메뉴 |
|
||||
| 화면 하단 명령 막대(Line·Arc 등 명령, 분류 탭, 보조기능 토글, 명령창), Model/Layout 탭 | 일반 | 10. Footer |
|
||||
| 글꼴 등 SW 토큰을 화면 전체(Header·Dropdown·More·Panel·명령창)에 같은 값으로 바꾸기, Portal 요소 포함 | 일반·뷰어 공통 | 11. 테마(토큰) 설정 |
|
||||
| 프로세스 단계(01 Modeling → 02 Analysis Result 등) 탭이 있는 Header, Strana Main Header | 일반 | 12. 프로세스 단계 Header |
|
||||
|
||||
## 1. 완성형 SW Header — [ExampleA-DefaultHeader.tsx](./examples/ExampleA-DefaultHeader.tsx)
|
||||
|
||||
@@ -248,6 +249,49 @@ const appSWTheme = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-serif'
|
||||
- 기본 글꼴은 Noto Sans KR입니다(2026-10-20 확정). 기본값을 쓸 때는 글꼴 토큰을 지정하지 않습니다. Web UI 글꼴 토큰(`--hm-font-family-base`)을 SW 화면에 넣지 않습니다.
|
||||
- `hm-sw-*` class 선택자로 토큰을 덮거나 `!important`, 컴포넌트마다 같은 값 반복 지정을 하지 않습니다.
|
||||
|
||||
## 12. 프로세스 단계 Header (일반 모드)
|
||||
|
||||
`SWHeader` 대신 쓰는 독립 Header입니다(한 화면에 Header 하나). 상태는 모두 프로그램이 갖고 Callback으로 갱신합니다. 로고 메뉴·Layer Popup·도구 Popup은 프로그램이 직접 엽니다([COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) §11).
|
||||
|
||||
```tsx
|
||||
const groups: SWProcessGroup[] = [
|
||||
{ id: "modeling", number: "01", label: "Modeling", steps: [{ id: "structure", label: "Structure" }, { id: "loads", label: "Loads" }] },
|
||||
{ id: "analysis", number: "02", label: "Analysis Result", steps: [{ id: "results", label: "Results" }] },
|
||||
]
|
||||
|
||||
<SWProcessHeader
|
||||
documentTitle={fileName} logoSrc={logoUrl} logoAlt={productName} onHomeClick={toggleFileMenu} homeMenuOpen={fileMenuOpen}
|
||||
process={{ groups, activeStepId: step, onStepSelect: s => setStep(s.id) }}
|
||||
layer={{ label: "Layer", value: layerName, visible: layerVisible, open: layerPopupOpen }}
|
||||
onLayerClick={toggleLayerPopup} onLayerVisibilityChange={setLayerVisible}
|
||||
tools={<>
|
||||
{/* ViewPropertyIcon: the program's own icon (the package ships no product icons). */}
|
||||
<SWToolButton appearance="pill" icon={<ViewPropertyIcon />} pressed={viewProperty} onClick={toggleViewProperty}>View Property</SWToolButton>
|
||||
<SWProcessSplitButton value={split} onValueChange={setSplit} open={splitOpen} onOpenChange={setSplitOpen} />
|
||||
</>}
|
||||
onWindowMoveStart={startWindowDrag} maximized={maximized}
|
||||
onMinimize={minimize} onMaximizeToggle={toggleMaximize} onClose={closeWindow} />
|
||||
```
|
||||
|
||||
1. 도구 아이콘은 프로그램이 그린 24 × 24 요소입니다. 버튼 상태 색(기본 `#B6D0C9`, Hover·눌림 `#FFC600`)을 따르려면 `currentColor`로 칠하고, 색이 고정된 이미지 아이콘이면 `hoverIcon`에 `#FFC600` 그림을 함께 줍니다(SW 인쇄 버튼과 같은 방식).
|
||||
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 개정).
|
||||
|
||||
```tsx
|
||||
<SWProcessHeader appearance="multi" documentTitle={projectName ?? ""} documentEmptyLabel="프로젝트 생성 전"
|
||||
logoSrc={logoUrl} logoAlt={productName} onHomeClick={toggleFileMenu}
|
||||
process={{ groups: [
|
||||
{ id: "data", label: "데이터 구축", steps: [{ id: "input", label: "데이터입력" }, { id: "check", label: "데이터확인" }] },
|
||||
{ id: "alignment", label: "선형 입력", labelHidden: true, steps: [{ id: "alignment", label: "선형입력" }] },
|
||||
{ id: "analysis", label: "해석 및 성과품", labelHidden: true, disabled: true, steps: [{ id: "model", label: "해석모델" }] },
|
||||
], 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={["maximize"]} onWindowMoveStart={startWindowDrag} onMinimize={minimize} onClose={closeWindow} />
|
||||
```
|
||||
|
||||
## 9. 하지 말 것
|
||||
|
||||
- `hm-sw-*` class를 소비 코드에 쓰거나 CSS로 덮어쓰기(토큰 변경도 class 선택자가 아니라 가장 바깥 SW 범위의 `style` — 11)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- 기본값은 가장 바깥 SW 범위에서만 선언되고 안쪽 SW 범위(Dropdown·More·Panel·명령창)는 상속합니다(2026-10-20). 값을 바꿀 때는 가장 바깥 범위의 `style`만 씁니다 — [AI_USAGE.md](./AI_USAGE.md) §3.1.
|
||||
- SW는 `@hm-design/ui` 토큰을 쓰지 않습니다. UI 패키지 토큰을 복제하거나 섞지 않습니다.
|
||||
- Light/Dark 분기 없음. 전역 선택자(`html`, `body`, `*`) 없음.
|
||||
- **모드:** §1~10과 §12·§13은 SW **일반 모드**(`SWHeader`·`SWPopup`·`SWToolButton`·`SWHomeMenu`·`SWFooter`) 기준입니다. SW **뷰어 모드**(`SWViewer*`)의 토큰·상태는 §11과 [VIEWER.md](./VIEWER.md)를 봅니다. 모드 선택·혼합 금지는 [AI_USAGE.md](./AI_USAGE.md) §0.
|
||||
- **모드:** §1~10과 §12·§13·§14는 SW **일반 모드**(`SWHeader`·`SWPopup`·`SWToolButton`·`SWHomeMenu`·`SWFooter`·`SWProcessHeader`) 기준입니다. SW **뷰어 모드**(`SWViewer*`)의 토큰·상태는 §11과 [VIEWER.md](./VIEWER.md)를 봅니다. 모드 선택·혼합 금지는 [AI_USAGE.md](./AI_USAGE.md) §0.
|
||||
|
||||
## 1. 크기·간격
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
| 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`) |
|
||||
| `document` | 파일명 면: 48px, gradient 표면, 위·오른쪽·아래 1px gradient 테두리(`--hm-sw-header-document-border-gradient`), 글자 `#F0F7F5` 700 · 자간 0.06em | 글자만 Hover `#FFC600`. Home 바로 뒤면 로고 사선 접점 공유, 그 뒤 파일명은 왼쪽 여백 12px |
|
||||
|
||||
## 10. 새 컴포넌트 체크리스트
|
||||
@@ -214,3 +214,44 @@
|
||||
|
||||
- Figma에 없는 값: 실제 화면에서 가져온 값(바 안 명령창 배치, 좌표, ▲ 패널, 분류 ◀ ▶, 보조기능 아이콘만)과 AI 구성 값 — 디자인 확인 필요(측면 버튼 Hover 글자 `#FF5C00`·주황 테두리, 분류 행 `+` Hover, Disabled 40%, 명령창 최소 280px, 칸 영역 비율 0.57(두 실제 화면 재현 값), 보조기능 아이콘 전환 1279px, Layout 탭 이름 편집 입력의 동작).
|
||||
- 새 하단 UI는 위 토큰을 `var(--…)`로 읽고 `hm-sw-footer*` class를 쓰거나 덮어쓰지 않습니다.
|
||||
|
||||
## 14. Process Header (`SWProcessHeader` · `SWProcessBar`) — SW 일반 모드 전용
|
||||
|
||||
원본 `src/theme/sw-process-header.css`(2026-11-03). Strana Main Header(Hanmac 테마)의 값입니다. 새 토큰은 `--hm-sw-process-*`(`.hm-sw-header`, `.hm-sw-theme` 범위)이고, 값이 같은 곳은 SW Header 토큰을 그대로 읽습니다(파일명 면·테두리, 객체 면·테두리, 속성 필드 면·그림자, Hover gradient, 선택 `#FFC600`, 제목·글자, 창 이동 줄무늬, 창 버튼 글리프 `#ADB0AF`·Hover 배경). Light/Dark preset은 없으며 다른 Strana 테마는 가장 바깥 범위 `style`에 같은 토큰을 지정합니다([AI_USAGE.md](./AI_USAGE.md) §3.1).
|
||||
|
||||
| 항목 | 값 | 토큰 |
|
||||
|---|---|---|
|
||||
| 배치 | 홈·파일명 글자 = 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` |
|
||||
| 비활성 덮개 | `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-*` |
|
||||
| 비활성 바깥 그룹 면 | Blur 4 면 + `drop-shadow(0 0 15px rgba(17,105,79,.8))` + 테두리 빛 1.5px, 바깥에서 안쪽으로 사라짐(79.8→198.4 / 반전 236.7→273.1) | `--hm-sw-process-idle-*` |
|
||||
| 사다리꼴 배치(확정 2026-11-03) | 그룹 면(사다리꼴)은 프로세스 영역의 **시작(첫 그룹)과 끝(마지막 그룹)에만** 둡니다. 그 그룹이 켜져 있으면 발광 면, 아니면 비활성 면. 가운데 그룹에는 켜져 있어도 도형이 없고 밝은 바탕(덮개 없음)만 보입니다(Figma 873:17421). 그룹 수와 관계없이 같으며 `default`·`multi` 공통. 화살표·비활성 덮개·선택 상태는 그대로 |
|
||||
| 화살표 | 16 × 48. On 채움 `#27D9A5`, Dim 채움 `#ABC7BF` 25%·선 50%, 그늘이 다음 그룹 위로 20% | `--hm-sw-process-arrow-*`, `-shade*` |
|
||||
| 탭 | 높이 32, padding 0 12 2.67, 16px 700 `#F0F7F5`, 간격 1, 넓을 때 127. 모서리: 단독 16 / 첫 16 4 4 16 / 마지막 4 16 16 4 / 가운데 4 | `--hm-sw-process-tab-*` |
|
||||
| 선택 탭 폭(공통, 2026-11-03 개정) | 선택된 탭도 다른 탭과 같은 비율로 줄어듦(우선 배정 없음). 넓을 때 `default`는 127 균등 그대로. 좁으면 모든 탭이 여백 폭까지 줄고 `…`. 그룹은 이름 + 모든 탭 여백보다 좁아지지 않음(측정) | — |
|
||||
| 화면 분할 도구 | 버튼 = SW pill 버튼(Hover 링·노란 아이콘). 열림 = 면 `--hm-sw-process-tool-open-background` + 금색 위·양옆(`--hm-sw-header-hover-border-gradient`), 링 없음, 아이콘 `#FFC600`. 버튼 아래 연장(면 `#081813` + 금색 양옆)이 Header 끝까지, 패널 233 × 36(면 `#082018`, 금색 양옆·아래, 위 오른쪽·아래 radius 2, 그림자 `0 2px 4px rgba(0,0,0,.6)`)이 Header 바로 아래 버튼 왼쪽에, 버튼 오른쪽부터 패널 끝까지 금색 위 선. 칸 38.5 × 34: 밝은 틀 32 × 22(`#B6D0C9`, 검정 1px, 정수 크기 — 배율 100%에서 아래 밝은 줄 유지) 안에 그림 29.6 × 19.73 + 초록 창(`#1B6B53`, 안쪽 검정 30% 1px) + 왼쪽 위 광택, Hover·선택 = 창만 `#FFC600`. 버튼 아이콘 창 `#003425` | `--hm-sw-process-split-*`, `--hm-sw-process-tool-open-*` |
|
||||
| 탭 상태 | 기본 층 70%. Hover 글자 `#FFC600`. 선택 = 층 100%, 바탕 `--hm-sw-process-tab-active-base`, 위 5·왼쪽 6 안쪽 그림자 75%, 테두리 2px 25%, 글자 `#FFC600`. Disabled = 층 50%, 글자 `#64A491`(SW 기본 40% 대신). Focus 2px `#F0F7F5` | `--hm-sw-process-tab-*` |
|
||||
| Layer 필드 | 최대 234 × 38, 면 안 양옆 8, padding 12 / 10, 속성 필드 면 + 테두리 `#08251C → #0E3D2F → #1E7C60 → #08241C → #051612`, 전구 20 + 8 + 라벨 12/14 · 값 14/16(400). Hover·열림 = 노란 gradient 링 + 글자 `#FFC600`, Disabled 40% | `--hm-sw-process-layer-border-gradient` |
|
||||
| 도구 pill | 위·왼쪽 3, 높이 42, radius 4, 면 `rgba(0,0,0,.851)`(위 2px) → `.8`(4px), 테두리 `#092920 → #1C6F56`. 버튼 40 × 38 간격 1, 파일명 면 + 테두리 `#08251C → #208769 → #08241C → #051612`, 아이콘 24 `#B6D0C9`. Hover·눌림 = 노란 링 + 아이콘 `#FFC600` | `--hm-sw-process-pill-*`, `-tool-*` |
|
||||
| 창 이동 | SWHeader 창 이동 그대로(`SWHeaderWindowControls` 창 이동만): 88 × 48, 줄무늬 82 × 37(3, 6), 아이콘 (28, 13), Hover 1.4s Motion + 툴팁, Drag 중 정지, 해제 즉시 복원 | §6, `--hm-sw-header-stripe-color` |
|
||||
| 캡션 버튼 | 32 × 48 × 3. Hover 배경 `rgba(0,0,0,.2)` + 흰 글리프, 종료 Hover `#E40000` + 흰 글리프, Disabled 40% | `--hm-sw-header-window-*`, `--hm-sw-process-close-hover-background`, `-close-icon-hover` |
|
||||
|
||||
- 글자 외곽선은 두 appearance 모두 SW 규칙(`--hm-sw-header-text-outline` 14·16·18px, `--hm-sw-header-label-outline` 10·12px)입니다. Strana 원본(DropShadow Blur 2), multi Figma(Stroke 없음)와 다른 의도된 차이입니다(2026-11-03).
|
||||
- 도구 아이콘은 어떤 상태에서도 사라지지 않습니다: `hoverIcon`이 없는 `currentColor` 아이콘은 Hover에도 그대로 보이고 색만 `#FFC600`(`SWToolButton`은 `hoverIcon`이 있을 때만 Hover 아이콘 교체에 참여, SWHeader `tools`와 공통, 2026-11-03).
|
||||
- Hover·Focus는 SWHeader와 같습니다: 도구 버튼 노란 링 + 노란 아이콘(`hoverIcon` 또는 `currentColor`, 눌림도 같은 모양), 로고 금색 테두리, 파일명·탭 글자 `#FFC600`, 최소화·최대화 어두운 배경 + 흰 글리프, 종료 `#E40000`, Focus 2px `#F0F7F5`.
|
||||
- 파일 없음(`documentEmptyLabel`): 파일명 면 오른쪽 위 10px 700 `#AEDBCC`(`--hm-sw-process-document-empty-color`), 자간 −0.1px, 위 7px. 두 appearance 공통.
|
||||
|
||||
**`appearance="multi"`**(StrAna-SW Figma 873:16481 · 873:16254 · 873:17421, 2026-11-03). 바탕·덮개·발광·비활성 면·화살표·선택·키보드는 위 `default` 규칙과 같은 구현입니다. 아래만 다릅니다.
|
||||
|
||||
| 항목 | 값 | 토큰 |
|
||||
|---|---|---|
|
||||
| 프레임 | 면 49(아래 1px 없음), 파일명 최대 189(테두리 단색 `#0E4C3A`, 로고·글자 위치는 default·SWHeader와 같음), 프로세스 1278 비율, 창 영역 테두리 단색 | `--hm-sw-header-bar-height`, `--hm-sw-header-document-border-color` |
|
||||
| 도구 pill | SW pill 그대로(`#092920`, `rgba(0,0,0,.8)`, 안쪽 그림자) 높이 41, 위 4 · 왼쪽 5 · 오른쪽 2, 버튼 40 × 37(단색 `#08251C`) | `--hm-sw-header-pill-*` |
|
||||
| 창 이동 | SWHeader 창 이동(Motion 포함) 위치만: 줄무늬 82 × 39(3, 5), 아이콘·Motion 3px 위(28, 10) | `--hm-sw-header-stripe-color` |
|
||||
| 그룹 | 좌우 10, 위 3.8: 제목 줄 14(12px, 번호와 간격 2, 한 줄 `…`) → 1.9 → 탭 25(위 19.7). 화살표 칸 16, 그룹 내용 사이 36. 발광 면 왼쪽 50 · 반전 오른쪽 48 밖 | — |
|
||||
| 그룹 상태 | 켜진 그룹 = 제목 700 흰색, 진행 완료(켜진 그룹 앞) = 같은 제목 + 밝은 탭(바탕은 덮개), 미진행 = 제목 500 `#C3EBDF` | — |
|
||||
| 탭 | 높이 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를 쓰거나 덮어쓰지 않습니다.
|
||||
@@ -0,0 +1,167 @@
|
||||
import { useId } from "react"
|
||||
|
||||
/*
|
||||
* Internal art of SWProcessHeader / SWProcessBar (not exported from the package). Geometry is the Strana Main Header
|
||||
* (HeaderStyles.xaml, HEAD edda3cc); colors are --hm-sw-process-* / --hm-sw-header-* tokens, so a theme override
|
||||
* through the outermost SW scope recolors them. Inline SVG instead of asset files for that reason.
|
||||
*/
|
||||
|
||||
const stop = (token: string) => ({ stopColor: `var(${token})` })
|
||||
|
||||
/** Glow surface of the lit group (Header.ContentControl.ProcessShape). Face / halo / fade are CSS. */
|
||||
function GlowShape() {
|
||||
return (
|
||||
<>
|
||||
<span className="hm-sw-process-bar__glow-halo">
|
||||
<span className="hm-sw-process-bar__glow-face"><i /><i /><i /></span>
|
||||
</span>
|
||||
{/* Edge light: 3px stroke, 10 blur, fading at the bottom (Header.Geometry.ProcessGlowLeftEdge at -1.5). */}
|
||||
<svg className="hm-sw-process-bar__glow-edge" width="54" height="48" viewBox="0 0 54 48" overflow="visible">
|
||||
<path d="M37.6,0 L42.04,36.63 C42.53,40.65 45.94,43.66 49.98,43.66 H50.5" />
|
||||
</svg>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** Idle surface of an unlit outer group (Header.ContentControl.ProcessShapeIdle). */
|
||||
function IdleShape() {
|
||||
return (
|
||||
<>
|
||||
<span className="hm-sw-process-bar__idle-halo">
|
||||
<span className="hm-sw-process-bar__idle-blur"><span className="hm-sw-process-bar__idle-face" /></span>
|
||||
</span>
|
||||
<svg className="hm-sw-process-bar__idle-rim" width="100%" height="48" overflow="visible">
|
||||
<path d="M47.153,-4.95264 C42.095,-4.95264 38.306,-0.31753 39.313,4.63925 L46.064,37.889 C46.821,41.6176 50.099,44.2971 53.904,44.2971 H60" />
|
||||
<rect x="60" y="43.5471" width="100%" height="1.5" />
|
||||
</svg>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** Group surface behind the name and tabs. `mirrored`: the last group (Strana ScaleX −1, 44px outside). */
|
||||
export function SWProcessShape({ kind, mirrored }: { kind: "active" | "idle"; mirrored: boolean }) {
|
||||
return (
|
||||
<span className="hm-sw-process-bar__shape" data-kind={kind} data-mirrored={mirrored || undefined} aria-hidden="true">
|
||||
{kind === "active" ? <GlowShape /> : <IdleShape />}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrow between two groups (Header.Control.ProcessArrowOn / Dim, 16 x 48) and the dim cover of its cell:
|
||||
* the tip is dim while the group before is unlit, the notch while the group after is unlit.
|
||||
*/
|
||||
export function SWProcessArrow({ on, dimTip, dimNotch }: { on: boolean; dimTip: boolean; dimNotch: boolean }) {
|
||||
const id = useId()
|
||||
return (
|
||||
<span className="hm-sw-process-bar__arrow" aria-hidden="true">
|
||||
{(dimTip || dimNotch) && (
|
||||
<svg className="hm-sw-process-bar__arrow-dim" width="16" height="48" viewBox="0 0 16 48" preserveAspectRatio="none">
|
||||
{dimTip && <path d="M0,0 L13,24 L0,48 Z" />}
|
||||
{dimNotch && <path d="M0,0 H16 V48 H0 L12.25,24 Z" />}
|
||||
</svg>
|
||||
)}
|
||||
<svg className="hm-sw-process-bar__arrow-art" width="16" height="48" viewBox="0 0 16 48" overflow="visible">
|
||||
<defs>
|
||||
<linearGradient id={`${id}s`} gradientUnits="userSpaceOnUse" x1="0.1" y1="0" x2={on ? 22.5 : 50.5} y2="0">
|
||||
<stop offset="0" style={stop("--hm-sw-process-shade")} />
|
||||
{!on && <stop offset="0.12" style={stop("--hm-sw-process-shade-mid")} />}
|
||||
<stop offset="1" style={stop("--hm-sw-process-shade-fade")} />
|
||||
</linearGradient>
|
||||
<linearGradient id={`${id}f`} x1="1" y1="0.5" x2="0" y2="0.5">
|
||||
<stop offset="0" style={stop(on ? "--hm-sw-process-arrow-fill" : "--hm-sw-process-arrow-dim-fill")} />
|
||||
<stop offset="1" style={stop("--hm-sw-process-arrow-fill-fade")} />
|
||||
</linearGradient>
|
||||
<linearGradient id={`${id}l`} x1="0" y1="1" x2="0" y2="0">
|
||||
<stop offset="0" style={stop("--hm-sw-process-arrow-edge")} />
|
||||
<stop offset="0.45" style={stop("--hm-sw-process-arrow-mid")} />
|
||||
<stop offset="0.7" style={stop("--hm-sw-process-arrow-light")} />
|
||||
<stop offset="1" style={stop("--hm-sw-process-arrow-edge")} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path transform="translate(3.01 0)" d="M0,0 H106.926 V47.8735 H0 L12.5175,23.9368 Z" fill={`url(#${id}s)`} opacity="0.2" />
|
||||
<path d="M11.9375,24.2236 L0,1.24 V47.2072 Z" fill={`url(#${id}f)`} opacity={on ? undefined : 0.25} />
|
||||
<g opacity={on ? undefined : 0.5} fill="none" strokeWidth="1">
|
||||
<path className="hm-sw-process-bar__arrow-shadow" transform="translate(1 0)" d="M1.285,0.2236 L13.285,24.2236 L1.285,48.2236" />
|
||||
<path d="M1.285,0.2236 L13.285,24.2236 L1.285,48.2236" stroke={`url(#${id}l)`} />
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const CAPTION_GLYPHS = {
|
||||
minimize: "M11,23 H21 V25 H11 Z",
|
||||
maximize: "M11,19 H21 V29 H11 Z M12,21 V28 H20 V21 Z",
|
||||
restore: "M13,18 H22 V27 H21 V20 H13 Z M11,21 H20 V30 H11 Z M12,23 V29 H19 V23 Z",
|
||||
close: "M16 22.5854L20.5 18.0854L21.9142 19.4996L17.4142 23.9996L21.915 28.5004L20.5008 29.9146L16 25.4138L11.4992 29.9146L10.085 28.5004L14.5858 23.9996L10.0858 19.4996L11.5 18.0854L16 22.5854Z",
|
||||
} as const
|
||||
|
||||
/** Caption glyph (Header.Geometry.Caption*) in the 32 x 48 button, filled with the button color. */
|
||||
export function SWProcessCaptionGlyph({ kind }: { kind: keyof typeof CAPTION_GLYPHS }) {
|
||||
return (
|
||||
<svg width="32" height="48" viewBox="0 0 32 48" aria-hidden="true">
|
||||
<path d={CAPTION_GLYPHS[kind]} fill="currentColor" fillRule="evenodd" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
/** Viewport split layouts in the Strana order (ViewportSplitType Type01 … Type06). */
|
||||
export const SW_PROCESS_SPLIT_TYPES = ["single", "columns", "rows", "topWide", "leftTall", "grid"] as const
|
||||
|
||||
type Rect = readonly [x: number, y: number, width: number, height: number]
|
||||
|
||||
// Header button icon panes (Header.Geometry.ToolLayout.T1 … T5, default 2 x 2), in the 24 x 24 icon.
|
||||
const W = 6.538, H = 3.448, FW = 15.11, FH = 8.87, X1 = 4.477, X2 = 13.049, Y1 = 7.597, Y2 = 13.019
|
||||
const ICON_PANES: Record<(typeof SW_PROCESS_SPLIT_TYPES)[number], readonly Rect[]> = {
|
||||
single: [[X1, Y1, FW, FH]],
|
||||
columns: [[X1, Y1, W, FH], [X2, Y1, W, FH]],
|
||||
rows: [[X1, Y1, FW, H], [X1, Y2, FW, H]],
|
||||
topWide: [[X1, Y1, FW, H], [X1, Y2, W, H], [X2, Y2, W, H]],
|
||||
leftTall: [[X1, Y1, W, FH], [X2, Y1, W, H], [X2, Y2, W, H]],
|
||||
grid: [[X1, Y1, W, H], [X2, Y1, W, H], [X1, Y2, W, H], [X2, Y2, W, H]],
|
||||
}
|
||||
|
||||
/** Split button icon (Header.Icon.ToolLayout): the frame takes the button color (currentColor), panes stay dark. */
|
||||
export function SWProcessSplitIcon({ type }: { type: (typeof SW_PROCESS_SPLIT_TYPES)[number] }) {
|
||||
return (
|
||||
<svg className="hm-sw-process-split__icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||||
<rect x="2.485" y="5.557" width="19.038" height="12.888" rx="0.481" fill="currentColor" />
|
||||
{ICON_PANES[type].map(([x, y, w, h], index) => <rect key={index} className="hm-sw-process-split__icon-pane" x={x} y={y} width={w} height={h} />)}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
// Selector cell panes (Geometry.SplitCell.T1 … T6) in the 29.6 x 19.73 frame, radius 1.
|
||||
const CELL_PANES: Record<(typeof SW_PROCESS_SPLIT_TYPES)[number], readonly Rect[]> = {
|
||||
single: [[0.82, 0.82, 27.954, 18.088]],
|
||||
columns: [[0.82, 0.82, 13.566, 18.088], [15.2, 0.82, 13.566, 18.088]],
|
||||
rows: [[0.82, 0.82, 27.99, 8.633], [0.82, 10.26, 27.99, 8.633]],
|
||||
topWide: [[0.82, 0.82, 27.99, 8.633], [0.82, 10.26, 13.566, 8.633], [15.2, 10.26, 13.566, 8.633]],
|
||||
leftTall: [[0.82, 0.82, 13.566, 18.088], [15.2, 0.81, 13.566, 8.633], [15.2, 10.28, 13.566, 8.633]],
|
||||
grid: [[0.83, 0.81, 13.566, 8.633], [15.2, 0.81, 13.566, 8.633], [0.83, 10.26, 13.566, 8.633], [15.2, 10.28, 13.566, 8.633]],
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector cell picture (Button.SplitCell): light frame with a black outline, panes with a 1px inner edge (drawn on a
|
||||
* line 0.5 inside, so the edge stays inside the pane), and the top-left sheen.
|
||||
*/
|
||||
export function SWProcessSplitCell({ type }: { type: (typeof SW_PROCESS_SPLIT_TYPES)[number] }) {
|
||||
const id = useId()
|
||||
return (
|
||||
<span className="hm-sw-process-split__frame" aria-hidden="true">
|
||||
<svg viewBox="0 0 29.599 19.733" width="29.599" height="19.733">
|
||||
<defs>
|
||||
<linearGradient id={`${id}s`} gradientUnits="userSpaceOnUse" x1="0.2785" y1="1.8744" x2="10.2688" y2="18.0923">
|
||||
<stop offset="0" stopColor="#fff" />
|
||||
<stop offset="0.4295" stopColor="#fff" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{CELL_PANES[type].map(([x, y, w, h], index) => (
|
||||
<rect key={index} className="hm-sw-process-split__pane" x={x + 0.5} y={y + 0.5} width={w - 1} height={h - 1} rx="0.5" />
|
||||
))}
|
||||
<path transform="translate(2.18 1.9)" d="M20.1195,0 C7.69482,0 1.52955,8.77004 0,13.1551 V1 C0,0.4477 0.4477,0 1,0 H20.1195 Z" fill={`url(#${id}s)`} opacity="0.25" />
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { forwardRef, Fragment, useId, useLayoutEffect, useRef, type KeyboardEvent } from "react"
|
||||
import "../../theme/sw-process-header.css"
|
||||
import { moveRovingFocus } from "../shared/roving-focus"
|
||||
import { SWProcessArrow, SWProcessShape } from "./SWProcessArt"
|
||||
import type { SWProcessBarProps } from "./types"
|
||||
|
||||
type StepShape = "single" | "start" | "middle" | "end"
|
||||
const stepShape = (index: number, count: number): StepShape =>
|
||||
count === 1 ? "single" : index === 0 ? "start" : index === count - 1 ? "end" : "middle"
|
||||
|
||||
/**
|
||||
* Process area of the Strana Main Header (MainProcessBar.xaml) and the StrAna-SW multi step header (`appearance="multi"`):
|
||||
* groups with a number, a name and step tabs, an arrow between groups, the lit group on a glow surface. One checked step
|
||||
* for the whole bar (radio group, arrow keys move and select). Groups keep their natural width and shrink together in
|
||||
* proportion; the room left over is shared on both sides. Both appearances share this markup; only the group content
|
||||
* (name beside or above the tabs, tab look) differs in CSS. Data in props, selection through `onStepSelect`.
|
||||
*/
|
||||
export const SWProcessBar = forwardRef<HTMLDivElement, SWProcessBarProps>(function SWProcessBar(
|
||||
{ appearance = "default", groups, activeStepId, activeGroupId, onStepSelect, disabled, className, onKeyDown, "aria-label": ariaLabel, ...rest }, forwardedRef,
|
||||
) {
|
||||
const id = useId()
|
||||
const rootRef = useRef<HTMLDivElement | null>(null)
|
||||
const multi = appearance === "multi"
|
||||
const litGroupId = activeGroupId ?? groups.find(group => group.steps.some(step => step.id === activeStepId))?.id
|
||||
// multi only: the groups before the lit one are done (Figma 873:17421: bright name and tabs on the dim surface).
|
||||
const litIndex = groups.findIndex(group => group.id === litGroupId)
|
||||
const enabledIds = groups.flatMap(group => disabled || group.disabled ? [] : group.steps.filter(step => !step.disabled).map(step => step.id))
|
||||
const tabStop = activeStepId !== undefined && enabledIds.includes(activeStepId) ? activeStepId : enabledIds[0]
|
||||
|
||||
// Measured, so fonts and labels can change:
|
||||
// - a two-line group name sits 2px higher (Strana: 8.67 instead of 10.67);
|
||||
// - each group keeps room for its name (beside the tabs) and every tab down to its padding (the checked one too, so
|
||||
// it shrinks with the others). Groups still shrink in proportion above that minimum and never overlap below it.
|
||||
useLayoutEffect(() => {
|
||||
const root = rootRef.current
|
||||
if (!root) return
|
||||
const px = (value: string) => parseFloat(value) || 0
|
||||
const update = () => {
|
||||
root.querySelectorAll<HTMLElement>(".hm-sw-process-bar__name-text").forEach(name => {
|
||||
const label = name.closest<HTMLElement>(".hm-sw-process-bar__label")
|
||||
if (label) label.dataset.lines = name.getBoundingClientRect().height > 15 ? "2" : "1"
|
||||
})
|
||||
root.querySelectorAll<HTMLElement>(".hm-sw-process-bar__group").forEach(group => {
|
||||
const style = getComputedStyle(group)
|
||||
const steps = group.querySelector<HTMLElement>(".hm-sw-process-bar__steps")
|
||||
const label = group.querySelector<HTMLElement>(".hm-sw-process-bar__label")
|
||||
if (!steps) return
|
||||
const tabs = Array.from(steps.children) as HTMLElement[]
|
||||
let min = px(style.paddingLeft) + px(style.paddingRight) + px(getComputedStyle(steps).columnGap) * Math.max(tabs.length - 1, 0)
|
||||
tabs.forEach(tab => {
|
||||
const tabStyle = getComputedStyle(tab)
|
||||
min += px(tabStyle.paddingLeft) + px(tabStyle.paddingRight) + px(tabStyle.borderLeftWidth) + px(tabStyle.borderRightWidth)
|
||||
})
|
||||
// Beside the tabs (default) the name takes its own width; above them (multi) it ellipsizes.
|
||||
if (label && style.flexDirection !== "column") min += label.offsetWidth + px(getComputedStyle(label).marginRight)
|
||||
group.style.minWidth = `${Math.ceil(min)}px`
|
||||
})
|
||||
}
|
||||
update()
|
||||
const observer = new ResizeObserver(update)
|
||||
root.querySelectorAll(".hm-sw-process-bar__label").forEach(node => observer.observe(node))
|
||||
return () => observer.disconnect()
|
||||
}, [groups, activeStepId, appearance])
|
||||
|
||||
const keys = (event: KeyboardEvent<HTMLDivElement>) => {
|
||||
onKeyDown?.(event)
|
||||
if (event.defaultPrevented) return
|
||||
moveRovingFocus(event, Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>('button[role="radio"]:not(:disabled)')), { vertical: true, select: true })
|
||||
}
|
||||
|
||||
const lit = (index: number) => groups[index]?.id === litGroupId
|
||||
const last = groups.length - 1
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={node => {
|
||||
rootRef.current = node
|
||||
if (typeof forwardedRef === "function") forwardedRef(node)
|
||||
else if (forwardedRef) forwardedRef.current = node
|
||||
}}
|
||||
role="radiogroup"
|
||||
aria-label={ariaLabel ?? "프로세스"}
|
||||
aria-disabled={disabled || undefined}
|
||||
className={`hm-sw-process-bar${className ? ` ${className}` : ""}`}
|
||||
data-appearance={multi ? "multi" : undefined}
|
||||
onKeyDown={keys}
|
||||
{...rest}
|
||||
>
|
||||
<span className="hm-sw-process-bar__rest" data-side="start" data-dim={!lit(0) || undefined} aria-hidden="true" />
|
||||
{groups.map((group, index) => {
|
||||
const active = lit(index)
|
||||
const outer = index === 0 || index === last
|
||||
const labelId = `${id}-group-${index}`
|
||||
return (
|
||||
<Fragment key={group.id}>
|
||||
{index > 0 && <SWProcessArrow on={lit(index - 1)} dimTip={!lit(index - 1)} dimNotch={!active} />}
|
||||
<div className="hm-sw-process-bar__group" data-active={active || undefined} data-dim={!active || undefined}
|
||||
data-progress={multi && litIndex >= 0 && index < litIndex ? "done" : undefined}>
|
||||
{/* Trapezoids only at the start and the end of the area (glow when that group is lit, idle otherwise);
|
||||
a lit middle group has the bright base without a shape (Figma 873:17421). */}
|
||||
{outer && <SWProcessShape kind={active ? "active" : "idle"} mirrored={index === last && index > 0} />}
|
||||
<span className="hm-sw-process-bar__label" id={labelId} data-label-hidden={group.labelHidden || undefined}>
|
||||
{group.number !== undefined && <span className="hm-sw-process-bar__number">{group.number}</span>}
|
||||
<span className="hm-sw-process-bar__name">
|
||||
{/* The place is always the 700 width, so the bar never moves when the group lights up. */}
|
||||
<span className="hm-sw-process-bar__name-reserve" aria-hidden="true">{group.label}</span>
|
||||
<span className="hm-sw-process-bar__name-text">{group.label}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="hm-sw-process-bar__steps">
|
||||
{group.steps.map((step, stepIndex) => {
|
||||
const checked = step.id === activeStepId
|
||||
const off = disabled || group.disabled || step.disabled
|
||||
return (
|
||||
<button
|
||||
key={step.id}
|
||||
type="button"
|
||||
role="radio"
|
||||
className="hm-sw-process-bar__step"
|
||||
data-shape={stepShape(stepIndex, group.steps.length)}
|
||||
aria-checked={checked}
|
||||
aria-label={step.label}
|
||||
aria-describedby={labelId}
|
||||
tabIndex={step.id === tabStop ? 0 : -1}
|
||||
disabled={off}
|
||||
onClick={() => { if (!checked) onStepSelect?.(step, group) }}
|
||||
>
|
||||
<span className="hm-sw-process-bar__step-box" aria-hidden="true">
|
||||
<i data-layer="glow" /><i data-layer="glow2" /><i data-layer="depth" /><i data-layer="shade" /><i data-layer="sheen" />
|
||||
<i data-layer="frame" /><i data-layer="inset-top" /><i data-layer="inset-left" /><i data-layer="ring" />
|
||||
</span>
|
||||
<span className="hm-sw-process-bar__step-text">{step.label}</span>
|
||||
<span className="hm-sw-process-bar__step-size" aria-hidden="true" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
<span className="hm-sw-process-bar__rest" data-side="end" data-dim={!lit(last) || undefined} aria-hidden="true" />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,87 @@
|
||||
import { forwardRef } from "react"
|
||||
import "../../theme/sw-process-header.css"
|
||||
import { SWHeaderDocumentTitle } from "../sw-header/components/SWHeaderDocumentTitle"
|
||||
import { SWHeaderHome } from "../sw-header/components/SWHeaderHome"
|
||||
import { SWHeaderLayerToggle } from "../sw-header/components/SWHeaderLayerIcons"
|
||||
import { SWHeaderWindowControls } from "../sw-header/components/SWHeaderWindowControls"
|
||||
import { SWToolGroup } from "../sw-tool/SWTool"
|
||||
import { SWProcessBar } from "./SWProcessBar"
|
||||
import { SWProcessCaptionGlyph } from "./SWProcessArt"
|
||||
import type { SWProcessHeaderLayer, SWProcessHeaderProps } from "./types"
|
||||
|
||||
/** Layer field: bulb (the SWHeader layer toggle) and the field button as siblings, one look (Strana LayerField). */
|
||||
function SWProcessLayerField({ layer, disabled, onClick, onVisibilityChange }: {
|
||||
layer: SWProcessHeaderLayer
|
||||
disabled: boolean
|
||||
onClick?: () => void
|
||||
onVisibilityChange?: (nextVisible: boolean) => void
|
||||
}) {
|
||||
const label = layer.label ?? "레이어"
|
||||
return (
|
||||
<div className="hm-sw-process-header__layer-field" data-open={layer.open || undefined} data-disabled={disabled || undefined}>
|
||||
{/* Bulb first: the tab order follows what is seen; the field button lies under it (z-index). */}
|
||||
<SWHeaderLayerToggle kind="visibility" layer={{ id: "layer", name: layer.value, visible: layer.visible ?? true, disabled }}
|
||||
onChange={onVisibilityChange ? (_layer, next) => onVisibilityChange(next) : undefined} />
|
||||
<button type="button" className="hm-sw-process-header__layer-button" title={layer.value} aria-label={`${label}: ${layer.value}`}
|
||||
aria-expanded={layer.open} disabled={disabled} onClick={onClick} />
|
||||
<span className="hm-sw-process-header__layer-text" aria-hidden="true">
|
||||
<span className="hm-sw-process-header__layer-label">{label}</span>
|
||||
<span className="hm-sw-process-header__layer-value">{layer.value}</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* SW general-mode header of the Strana Main Header (49px): logo → file name → process area and layer surface →
|
||||
* window area (tool pill, the SWHeader window move with its hover motion, minimize, maximize / restore, close). An independent header
|
||||
* beside SWHeader and SWViewerHeader; one header per screen. Every state is a prop and every action a callback; the
|
||||
* logo menu, the layer popup and the tool popups belong to the host. Reuses SWHeaderHome, SWHeaderDocumentTitle,
|
||||
* the SWHeader layer bulb and the SWToolGroup pill; the rest is sw-process-header.css. `appearance="multi"` is the
|
||||
* StrAna-SW multi step design on the same markup (CSS only: frame sizes, file name, tool pill, process group content).
|
||||
*/
|
||||
export const SWProcessHeader = forwardRef<HTMLElement, SWProcessHeaderProps>(function SWProcessHeader({
|
||||
appearance = "default", documentTitle, documentEmptyLabel, onDocumentClick, logo, logoText, logoSrc, logoAlt, onHomeClick, homeMenuOpen,
|
||||
process, layer, onLayerClick, onLayerVisibilityChange, tools, toolsLabel = "도구",
|
||||
onWindowMoveStart, maximized = false, onMinimize, onMaximizeToggle, onClose, hiddenControls, disabled = false,
|
||||
className, ...rest
|
||||
}, ref) {
|
||||
const hidden = new Set(hiddenControls ?? [])
|
||||
const logoNode = logo ?? (logoText ? <span className="hm-sw-process-header__wordmark">{logoText}</span> : undefined)
|
||||
const toolsShown = tools !== undefined && tools !== null && tools !== false && !hidden.has("tools")
|
||||
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
|
||||
|
||||
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}>
|
||||
<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} />
|
||||
<div className="hm-sw-process-header__center">
|
||||
<SWProcessBar appearance={appearance} {...process} disabled={disabled || process.disabled} />
|
||||
{layer && !hidden.has("layer") && (
|
||||
<div className="hm-sw-process-header__layer">
|
||||
<SWProcessLayerField layer={layer} disabled={disabled || !!layer.disabled} onClick={onLayerClick} onVisibilityChange={onLayerVisibilityChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{windowShown && (
|
||||
<div className="hm-sw-process-header__window">
|
||||
{toolsShown && <SWToolGroup appearance="pill" aria-label={toolsLabel} className="hm-sw-process-header__tools">{tools}</SWToolGroup>}
|
||||
{/* The SWHeader window move as it is: stripes, icon, 1.4s hover motion and tooltip (minimize / close are ours). */}
|
||||
{!hidden.has("windowMove") && <SWHeaderWindowControls onWindowMoveStart={onWindowMoveStart} showMinimize={false} showClose={false} />}
|
||||
{captions.map(id => (
|
||||
<button key={id} type="button" className="hm-sw-process-header__caption" data-kind={id}
|
||||
aria-label={id === "minimize" ? "최소화" : id === "close" ? "종료" : maximized ? "이전 크기로 복원" : "최대화"}
|
||||
onClick={id === "minimize" ? onMinimize : id === "close" ? onClose : onMaximizeToggle}>
|
||||
<SWProcessCaptionGlyph kind={id === "maximize" && maximized ? "restore" : id} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,126 @@
|
||||
import { forwardRef, useEffect, useId, useRef, useState, type FocusEvent, type KeyboardEvent } from "react"
|
||||
import "../../theme/sw-process-header.css"
|
||||
import { moveRovingFocus } from "../shared/roving-focus"
|
||||
import { useOutsidePointerDown } from "../shared/use-outside-pointer"
|
||||
import { useSWFixedDropdownPosition } from "../sw-header/components/SWHeaderDropdown"
|
||||
import { SWToolButton } from "../sw-tool/SWTool"
|
||||
import { SW_PROCESS_SPLIT_TYPES, SWProcessSplitCell, SWProcessSplitIcon } from "./SWProcessArt"
|
||||
import type { SWProcessSplitButtonProps, SWProcessSplitType } from "./types"
|
||||
|
||||
const TYPE_LABELS: Record<SWProcessSplitType, string> = {
|
||||
single: "1면", columns: "좌우 2분할", rows: "상하 2분할", topWide: "상단 1면 + 하단 2분할", leftTall: "좌측 1면 + 우측 2분할", grid: "4분할",
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport split tool for the `tools` pill of SWProcessHeader (both appearances). The button is the SW pill button
|
||||
* (hover ring, focus, disabled); open, it takes the Strana open look and the selector hangs from it: a 3px extension of
|
||||
* the button, the 233 x 36 panel under its left edge and a gold line from its right edge to the panel end. The selector
|
||||
* is a fixed overlay kept on screen (the SW field dropdown position), so the header clipping never cuts it.
|
||||
* Keyboard: Enter / Space toggle, ArrowDown opens on the checked cell, arrows / Home / End move and choose (radio group),
|
||||
* Escape closes back to the button, Tab out and a click outside close.
|
||||
*/
|
||||
export const SWProcessSplitButton = forwardRef<HTMLButtonElement, SWProcessSplitButtonProps>(function SWProcessSplitButton(
|
||||
{ value, onValueChange, open: openProp, defaultOpen = false, onOpenChange, label = "화면 분할", typeLabels, disabled = false }, forwardedRef,
|
||||
) {
|
||||
const [internalOpen, setInternalOpen] = useState(defaultOpen)
|
||||
const open = !disabled && (openProp ?? internalOpen)
|
||||
const rootRef = useRef<HTMLSpanElement>(null)
|
||||
const triggerRef = useRef<HTMLButtonElement | null>(null)
|
||||
const popupRef = useRef<HTMLDivElement>(null)
|
||||
const focusCellRef = useRef(false)
|
||||
const popupId = useId()
|
||||
|
||||
const setOpen = (next: boolean) => {
|
||||
if (openProp === undefined) setInternalOpen(next)
|
||||
onOpenChange?.(next)
|
||||
}
|
||||
const close = (restoreFocus: boolean) => {
|
||||
setOpen(false)
|
||||
if (restoreFocus) triggerRef.current?.focus()
|
||||
}
|
||||
|
||||
useOutsidePointerDown(open, rootRef, () => setOpen(false))
|
||||
const position = useSWFixedDropdownPosition(open, triggerRef, popupRef, "start")
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !focusCellRef.current || !position) return
|
||||
focusCellRef.current = false
|
||||
popupRef.current?.querySelector<HTMLButtonElement>('[role="radio"][tabindex="0"]')?.focus()
|
||||
})
|
||||
|
||||
// The button extension reaches the header bottom, so the panel starts under the header and never covers the
|
||||
// window buttons next to the pill (Strana 3px; at least 3px when used outside a header).
|
||||
const trigger = triggerRef.current?.getBoundingClientRect()
|
||||
const headerBottom = triggerRef.current?.closest(".hm-sw-process-header")?.getBoundingClientRect().bottom
|
||||
const tail = open && position && trigger && headerBottom !== undefined ? Math.max(3, Math.round(headerBottom - trigger.bottom + 1)) : 3
|
||||
// The selector hangs from the button (tail), so it never takes the hook's flip above the field: flipped, it was
|
||||
// drawn over the header with its panel bottom cut. With no room below it stays under the button; the hook follows
|
||||
// the page scroll, which brings it into view.
|
||||
const top = position && trigger ? (position.flipped ? trigger.bottom : position.top) - 1 : 0
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent<HTMLSpanElement>) => {
|
||||
if (event.key === "Escape" && open) {
|
||||
event.stopPropagation()
|
||||
close(true)
|
||||
return
|
||||
}
|
||||
if (event.target === triggerRef.current) {
|
||||
if (event.key === "ArrowDown" && !open) {
|
||||
event.preventDefault()
|
||||
focusCellRef.current = true
|
||||
setOpen(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
moveRovingFocus(event, Array.from(popupRef.current?.querySelectorAll<HTMLButtonElement>('[role="radio"]') ?? []), { vertical: true, select: true })
|
||||
}
|
||||
const onBlur = (event: FocusEvent<HTMLSpanElement>) => {
|
||||
if (open && !rootRef.current?.contains(event.relatedTarget as Node | null)) setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<span ref={rootRef} className="hm-sw-process-split" data-open={open || undefined} onKeyDown={onKeyDown} onBlur={onBlur}>
|
||||
<SWToolButton
|
||||
ref={node => {
|
||||
triggerRef.current = node
|
||||
if (typeof forwardedRef === "function") forwardedRef(node)
|
||||
else if (forwardedRef) forwardedRef.current = node
|
||||
}}
|
||||
appearance="pill"
|
||||
icon={<SWProcessSplitIcon type={value} />}
|
||||
title={label}
|
||||
aria-expanded={open}
|
||||
aria-controls={open ? popupId : undefined}
|
||||
disabled={disabled}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{label}
|
||||
</SWToolButton>
|
||||
{open && (
|
||||
<div
|
||||
ref={popupRef}
|
||||
id={popupId}
|
||||
className="hm-sw-process-split__popup"
|
||||
style={position
|
||||
? { left: position.left, top, ["--hm-sw-process-split-offset" as string]: `${-position.offset}px`, ["--hm-sw-process-split-tail" as string]: `${tail}px` }
|
||||
: { visibility: "hidden" }}
|
||||
>
|
||||
<span className="hm-sw-process-split__tail" aria-hidden="true" />
|
||||
<div className="hm-sw-process-split__panel" role="radiogroup" aria-label={label}>
|
||||
{SW_PROCESS_SPLIT_TYPES.map(type => {
|
||||
const checked = type === value
|
||||
return (
|
||||
<button key={type} type="button" role="radio" className="hm-sw-process-split__cell" aria-checked={checked}
|
||||
aria-label={typeLabels?.[type] ?? TYPE_LABELS[type]} tabIndex={checked ? 0 : -1}
|
||||
onClick={() => { if (!checked) onValueChange?.(type) }}>
|
||||
<SWProcessSplitCell type={type} />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<span className="hm-sw-process-split__step" aria-hidden="true" />
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
export { SWProcessHeader } from "./SWProcessHeader"
|
||||
export { SWProcessBar } from "./SWProcessBar"
|
||||
export { SWProcessSplitButton } from "./SWProcessSplitButton"
|
||||
export type {
|
||||
SWProcessHeaderProps, SWProcessHeaderLayer, SWProcessHeaderHideableControlId,
|
||||
SWProcessBarProps, SWProcessGroup, SWProcessStep, SWProcessAppearance, SWProcessSplitType, SWProcessSplitButtonProps,
|
||||
} from "./types"
|
||||
@@ -0,0 +1,151 @@
|
||||
import type { HTMLAttributes, PointerEventHandler, ReactNode } from "react"
|
||||
|
||||
/** One tab of a process group (e.g. `Structure`). */
|
||||
export interface SWProcessStep {
|
||||
/** Stable key, returned with the selection. */
|
||||
id: string
|
||||
label: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Process area design (2026-11-03).
|
||||
* - `default`: the Strana Main Header — name beside 32px tabs of equal width (127 at most), two groups confirmed.
|
||||
* - `multi`: StrAna-SW multi step (Figma 873:16481 / 873:16254 / 873:17421) — title above 25px tabs as wide as their
|
||||
* label, any number of groups; groups before the lit one show as done.
|
||||
* Both share the area surface, dim covers, group surfaces, arrows, selection and keyboard.
|
||||
*/
|
||||
export type SWProcessAppearance = "default" | "multi"
|
||||
|
||||
/**
|
||||
* One group of the process area (e.g. `01 Modeling`). `default` confirms two groups; `multi` takes any count. In both,
|
||||
* the group surfaces (trapezoids) stand only at the start and the end of the area — never on a middle group, lit or not.
|
||||
*/
|
||||
export interface SWProcessGroup {
|
||||
id: string
|
||||
/** Number shown before the name, e.g. `01`. */
|
||||
number?: string
|
||||
/** Group name: `default` wraps it at 56px (two lines move it 2px up), `multi` keeps one line above the tabs. */
|
||||
label: string
|
||||
/**
|
||||
* The name stays for assistive technology only (each step is described by it). `multi` keeps its row, so the tabs
|
||||
* line up with the other groups (Figma groups without a title). Default false.
|
||||
*/
|
||||
labelHidden?: boolean
|
||||
steps: readonly SWProcessStep[]
|
||||
/** Disables every step of the group. */
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface SWProcessBarProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "onChange"> {
|
||||
/** Design of the area. Default `default`. */
|
||||
appearance?: SWProcessAppearance
|
||||
groups: readonly SWProcessGroup[]
|
||||
/** The checked step (one for the whole bar). None checked when omitted. */
|
||||
activeStepId?: string
|
||||
/** The lit group (glow surface, bright text). Defaults to the group of `activeStepId`. */
|
||||
activeGroupId?: string
|
||||
/** A step other than the checked one was chosen (click, or arrow keys). The host updates `activeStepId`. */
|
||||
onStepSelect?: (step: SWProcessStep, group: SWProcessGroup) => void
|
||||
/** Disables every step. */
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/** Layer field of the header (bulb + label + current layer). The host owns the layer data and its popup. */
|
||||
export interface SWProcessHeaderLayer {
|
||||
/** Field label. Default `레이어`. */
|
||||
label?: string
|
||||
/** Current layer name (ellipsis when narrow). */
|
||||
value: string
|
||||
/** Bulb on (colored) when true. Default true. */
|
||||
visible?: boolean
|
||||
/** The host popup is open: the field shows its open look (yellow ring and text) and `aria-expanded`. */
|
||||
open?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/** Parts that can be removed. Removed parts take no space; the logo, file name and process area always show. */
|
||||
export type SWProcessHeaderHideableControlId = "layer" | "tools" | "windowMove" | "minimize" | "maximize" | "close"
|
||||
|
||||
export interface SWProcessHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title" | "children"> {
|
||||
/**
|
||||
* Header design, also used by the process area unless `process.appearance` is set. `default`: Strana Main Header
|
||||
* (48px surfaces, layer field, Strana tool pill). `multi`: StrAna-SW multi step (49px surfaces, 189px file name,
|
||||
* SW tool pill 40 x 37). Logo and file-name text follow SWHeader in both. Default `default`.
|
||||
*/
|
||||
appearance?: SWProcessAppearance
|
||||
/** File name in the document area (ellipsis when narrow, full name as tooltip). Empty while no document is open. */
|
||||
documentTitle: string
|
||||
/**
|
||||
* Shown instead of the file name while `documentTitle` is empty (e.g. `프로젝트 생성 전`): 10px 700 at the top right
|
||||
* of the file-name surface (Figma 873:16481). Still the same button (`onDocumentClick`).
|
||||
*/
|
||||
documentEmptyLabel?: string
|
||||
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). */
|
||||
logoText?: string
|
||||
/** Logo image URL, contained (ratio kept, centered) in the logo slot. */
|
||||
logoSrc?: string
|
||||
/** Accessible product name; the logo button is announced as "<logoAlt> 홈". Defaults to `logoText`. */
|
||||
logoAlt?: string
|
||||
/** Logo click. The menu it opens belongs to the host. */
|
||||
onHomeClick?: () => void
|
||||
/** The host logo menu is open: the logo keeps its hover look and `aria-expanded`. */
|
||||
homeMenuOpen?: boolean
|
||||
/** Process area data and selection (`SWProcessBar` props). */
|
||||
process: SWProcessBarProps
|
||||
/** Layer field. Omitted: no layer surface. */
|
||||
layer?: SWProcessHeaderLayer
|
||||
/** Layer field click (open the host layer popup and set `layer.open`). */
|
||||
onLayerClick?: () => void
|
||||
/** Bulb click. Without it the bulb is a status icon only. */
|
||||
onLayerVisibilityChange?: (nextVisible: boolean) => void
|
||||
/** Tool buttons of the dark pill, e.g. `SWToolButton appearance="pill"` (40 x 38 each, `multi` 40 x 37, 1px apart). */
|
||||
tools?: ReactNode
|
||||
/** Accessible name of the tool pill. Default `도구`. */
|
||||
toolsLabel?: string
|
||||
/** Pointer down on the window-move area (the host moves the window). */
|
||||
onWindowMoveStart?: PointerEventHandler<HTMLDivElement>
|
||||
/** Window maximized: the maximize button shows the restore glyph. Default false. */
|
||||
maximized?: boolean
|
||||
onMinimize?: () => void
|
||||
/** Maximize / restore button. The host toggles the window and updates `maximized`. */
|
||||
onMaximizeToggle?: () => void
|
||||
onClose?: () => void
|
||||
/** Parts to remove. Default: none. */
|
||||
hiddenControls?: readonly SWProcessHeaderHideableControlId[]
|
||||
/** Disables the process area and the layer field (logo, file name, host tools and window buttons stay). */
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport split layout (Strana ViewportSplitType, in this order): `single` Type01 one pane, `columns` Type02 left /
|
||||
* right, `rows` Type03 top / bottom, `topWide` Type04 one top + two bottom, `leftTall` Type05 one left + two right,
|
||||
* `grid` Type06 2 x 2.
|
||||
*/
|
||||
export type SWProcessSplitType = "single" | "columns" | "rows" | "topWide" | "leftTall" | "grid"
|
||||
|
||||
/**
|
||||
* Viewport split tool of the process header pill (Strana Main Header 화면 분할, StrAna-SW 6758:12510): a pill button
|
||||
* showing the current layout and, under it, a 233 x 36 selector of the six layouts. Choosing a layout only reports it;
|
||||
* splitting the program window is the host's job.
|
||||
*/
|
||||
export interface SWProcessSplitButtonProps {
|
||||
/** Current layout: the button icon and the checked cell. */
|
||||
value: SWProcessSplitType
|
||||
/** A cell other than the current one was chosen (click, or arrow keys). The selector stays open, like Strana. */
|
||||
onValueChange?: (value: SWProcessSplitType) => void
|
||||
/** Controlled open state of the selector; uncontrolled when undefined. */
|
||||
open?: boolean
|
||||
/** Uncontrolled initial state. Default false. */
|
||||
defaultOpen?: boolean
|
||||
/** The selector opened or closed (button, Escape, a click outside, Tab out). */
|
||||
onOpenChange?: (open: boolean) => void
|
||||
/** Accessible name of the button and the selector. Default `화면 분할`. */
|
||||
label?: string
|
||||
/** Accessible names of the cells. Defaults: 1면, 좌우 2분할, 상하 2분할, 상단 1면 + 하단 2분할, 좌측 1면 + 우측 2분할, 4분할. */
|
||||
typeLabels?: Partial<Record<SWProcessSplitType, string>>
|
||||
disabled?: boolean
|
||||
}
|
||||
@@ -48,7 +48,8 @@ export const SWToolButton = forwardRef<HTMLButtonElement, SWToolButtonProps>(fun
|
||||
if (appearance === "pill") {
|
||||
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}>
|
||||
{icon !== undefined && <span className="hm-sw-header__icon hm-sw-tool__icon" aria-hidden="true">{icon}</span>}
|
||||
{/* 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>}
|
||||
{children !== undefined && <span className="hm-sw-tool__label">{children}</span>}
|
||||
</button>
|
||||
|
||||
@@ -21,3 +21,6 @@ export * from "./components/sw-viewer"
|
||||
|
||||
// SW footer (2026-10-20): Figma 26:10097 bottom bar, command slots, category row, layout tabs and command line.
|
||||
export * from "./components/sw-footer"
|
||||
|
||||
// SW process header (2026-11-03): the Strana Main Header as an independent SW general-mode header and its process area.
|
||||
export * from "./components/sw-process-header"
|
||||
@@ -0,0 +1,645 @@
|
||||
/* @hm-design/sw — SW Process Header (Strana Main Header, D:\Strana\Git_StranaDevDesign HEAD edda3cc: HeaderStyles.xaml,
|
||||
HeaderBrushes.xaml, MainProcessBar.xaml, Hanmac Colors.xaml; 2026-11-03). SW general mode, an independent header
|
||||
beside SWHeader / SWViewerHeader. Only --hm-sw-process-* tokens and hm-sw-process-* classes are new; values equal to
|
||||
an SW token read that token. Never touches global selectors or SWHeader rules; the reused SW parts (home, file name,
|
||||
tool pill) are adjusted only inside .hm-sw-process-header. Declared on the outermost SW scope only, like sw.css. */
|
||||
|
||||
:is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *) {
|
||||
/* Process area base (Header.Border.ProcessBase): ProcessBase gradient, DocBorder 1px, top corners 6. */
|
||||
--hm-sw-process-base-background: linear-gradient(180deg, #0c5941 0%, #19bf8d 10.1%, #109069 18.27%, #0c5941 100%);
|
||||
--hm-sw-process-base-border-gradient: var(--hm-sw-header-document-border-gradient);
|
||||
/* Cover over an unlit group, its arrow side and its outer rest (ProcessDim #E6004430). */
|
||||
--hm-sw-process-dim: rgba(0, 68, 48, 0.902);
|
||||
|
||||
/* Group number and name (12px, line 14). */
|
||||
--hm-sw-process-number-color: #69c2a6;
|
||||
--hm-sw-process-group-color: #c3ebdf;
|
||||
--hm-sw-process-group-active-color: #fff;
|
||||
|
||||
/* Lit group surface (ProcessGlowFace: three layer groups at 85 / 80 / 45%, over the shape bounds -4.95 .. 44.16),
|
||||
halo (DropShadow 30, 85%) and edge light (3px, Blur 10, 65%). */
|
||||
--hm-sw-process-glow-face-base: linear-gradient(0deg, rgba(24, 134, 101, 0.702) 0%, rgba(24, 134, 101, 0) 40%), linear-gradient(180deg, #063b2b 0%, rgba(13, 101, 74, 0.051) 96.03%);
|
||||
--hm-sw-process-glow-face-rim: linear-gradient(180deg, rgba(13, 101, 74, 0.051) 87.76%, #247a60 96.05%);
|
||||
--hm-sw-process-glow-face-sheen: linear-gradient(rgba(0, 95, 66, 0.251), rgba(0, 95, 66, 0.251)), linear-gradient(180deg, #20322c 6.94%, #3dc79d 21.41%, rgba(61, 199, 157, 0.051) 96.05%);
|
||||
--hm-sw-process-glow-halo: rgba(88, 181, 153, 0.85);
|
||||
--hm-sw-process-glow-edge: rgba(88, 181, 153, 0.65);
|
||||
/* Unlit outer group surface (ProcessShapeIdleFace, Blur 8, DropShadow 15 #11694F 80%, rim 1.5px Blur 2). */
|
||||
--hm-sw-process-idle-face: linear-gradient(180deg, rgba(0, 0, 0, 0.502) 10.08%, rgba(0, 0, 0, 0) 26.37%), linear-gradient(180deg, rgba(23, 42, 26, 0.251) 0%, rgba(23, 42, 26, 0.251) 62%, rgba(0, 0, 0, 0) 90%);
|
||||
--hm-sw-process-idle-halo: rgba(17, 105, 79, 0.8);
|
||||
--hm-sw-process-idle-rim: rgba(4, 128, 90, 0.322);
|
||||
|
||||
/* Arrow between groups (16 x 48): fill, stroke stops, line shadow, shade over the next group. */
|
||||
--hm-sw-process-arrow-fill: #27d9a5;
|
||||
--hm-sw-process-arrow-dim-fill: #abc7bf;
|
||||
--hm-sw-process-arrow-fill-fade: rgba(16, 109, 81, 0);
|
||||
--hm-sw-process-arrow-edge: #00432e;
|
||||
--hm-sw-process-arrow-mid: #1c6a51;
|
||||
--hm-sw-process-arrow-light: #3d9c7e;
|
||||
--hm-sw-process-arrow-shadow: rgba(0, 0, 0, 0.502);
|
||||
--hm-sw-process-shade: rgba(4, 34, 25, 0.878);
|
||||
--hm-sw-process-shade-mid: rgba(4, 34, 25, 0.706);
|
||||
--hm-sw-process-shade-fade: rgba(4, 34, 25, 0);
|
||||
|
||||
/* Step tab (Header.RadioButton.ProcessTab, 32px): stacked layers, 70% unchecked / 100% checked / 50% disabled. */
|
||||
--hm-sw-process-tab-text: var(--hm-sw-header-title-color);
|
||||
--hm-sw-process-tab-glow: rgba(31, 148, 113, 0.702);
|
||||
--hm-sw-process-tab-depth: linear-gradient(180deg, rgba(0, 61, 41, 0) 0%, rgba(0, 61, 41, 0.8) 100%);
|
||||
--hm-sw-process-tab-shade: linear-gradient(0deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 25%);
|
||||
--hm-sw-process-tab-sheen: linear-gradient(180deg, rgba(74, 255, 196, 0.502) 0%, rgba(74, 255, 196, 0) 40%);
|
||||
--hm-sw-process-tab-border-gradient: linear-gradient(180deg, #001e15 8.98%, #17a77b 22.9%, #0b533d 100%);
|
||||
--hm-sw-process-tab-ring: rgba(0, 0, 0, 0.251);
|
||||
--hm-sw-process-tab-active-base: linear-gradient(0deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.4) 26.41%, rgba(0, 0, 0, 0.451) 70%, rgba(0, 0, 0, 0.4) 100%);
|
||||
--hm-sw-process-tab-active-shade: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0) 80%);
|
||||
--hm-sw-process-tab-active-sheen: linear-gradient(180deg, rgba(122, 206, 179, 0) 60%, rgba(122, 206, 179, 0.102) 100%);
|
||||
--hm-sw-process-tab-inset: rgba(0, 0, 0, 0.659);
|
||||
--hm-sw-process-tab-disabled-shade: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
|
||||
--hm-sw-process-tab-disabled-sheen: linear-gradient(180deg, rgba(130, 181, 164, 0.502) 0%, rgba(122, 206, 179, 0) 40%);
|
||||
--hm-sw-process-tab-disabled-text: #64a491;
|
||||
|
||||
/* Header frame parts that differ from SWHeader. */
|
||||
--hm-sw-process-layer-border-gradient: linear-gradient(180deg, #08251c 0%, #0e3d2f 14.57%, #1e7c60 37.06%, #08241c 80.5%, #051612 100%);
|
||||
--hm-sw-process-pill-background: linear-gradient(180deg, rgba(0, 0, 0, 0.851) 0, rgba(0, 0, 0, 0.851) 2px, rgba(0, 0, 0, 0.8) 4px);
|
||||
--hm-sw-process-pill-border-gradient: linear-gradient(180deg, #092920 0%, #1c6f56 115%);
|
||||
--hm-sw-process-tool-border-gradient: linear-gradient(180deg, #08251c 0%, #208769 46.54%, #08241c 84.44%, #051612 100%);
|
||||
--hm-sw-process-tool-icon-color: #b6d0c9;
|
||||
--hm-sw-process-close-hover-background: #e40000;
|
||||
--hm-sw-process-close-icon-hover: #fff;
|
||||
|
||||
/* Viewport split tool (Strana 화면 분할, StrAna-SW 6758:12510): open button face, its 3px extension, the 233 x 36
|
||||
selector (face, shadow; the border is the SW hover gradient) and the cells (light frame, green panes, yellow when
|
||||
hovered or checked), the button icon panes. */
|
||||
--hm-sw-process-tool-open-background: linear-gradient(180deg, #081813 0%, #0b3227 25.43%, #0e3d2f 51.25%, #0b3227 80.03%, #081813 100%);
|
||||
--hm-sw-process-tool-open-edge: #081813;
|
||||
--hm-sw-process-split-panel-background: #082018;
|
||||
--hm-sw-process-split-panel-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
|
||||
--hm-sw-process-split-cell-frame: var(--hm-sw-process-tool-icon-color);
|
||||
--hm-sw-process-split-cell-pane: #1b6b53;
|
||||
--hm-sw-process-split-icon-pane: #003425;
|
||||
|
||||
/* File name surface without a document (Figma 873:16481 "프로젝트 생성 전"): 10px 700 at the top right. */
|
||||
--hm-sw-process-document-empty-color: #aedbcc;
|
||||
|
||||
/* appearance="multi" step tab (Figma 873:16481 / 873:17421, 25px, as wide as its label + 25). Base = a group not
|
||||
reached yet (70%); lit = the lit group and the done groups; selected = the checked step; disabled = 50% on the
|
||||
SW property surface. Text #F0F7F5 / selected #FFC600 are the SW title / selected tokens. */
|
||||
--hm-sw-process-multi-tab-background: linear-gradient(180deg, rgba(52, 143, 114, 0.8) 0%, rgba(2, 50, 35, 0.8) 100%);
|
||||
--hm-sw-process-multi-tab-border: #0c3125;
|
||||
--hm-sw-process-multi-tab-lit-background: linear-gradient(180deg, rgba(68, 178, 143, 0.8) 0%, rgba(0, 61, 41, 0.8) 100%);
|
||||
--hm-sw-process-multi-tab-lit-border: #0a5940;
|
||||
--hm-sw-process-multi-tab-shadow: 1px 1px 1px 0 rgba(31, 148, 113, 0.7), 1px 1px 1px 0 rgba(31, 148, 113, 0.7);
|
||||
--hm-sw-process-multi-tab-selected-background: linear-gradient(0deg, #113228 0%, #28735c 100%);
|
||||
--hm-sw-process-multi-tab-selected-border: #001e15;
|
||||
--hm-sw-process-multi-tab-selected-shadow: 1px 1px 0.5px 0 rgba(31, 148, 113, 0.7), inset 3px 2px 3px 0 rgba(9, 39, 30, 0.42), inset 3px 2px 3px 0 rgba(9, 39, 30, 0.42);
|
||||
--hm-sw-process-multi-tab-disabled-border: #021912;
|
||||
--hm-sw-process-multi-tab-disabled-shadow: 1px 1px 1px 0 rgba(31, 148, 113, 0.7), inset 3px 2px 3px 0 rgba(9, 39, 30, 0.42), inset 3px 2px 3px 0 rgba(9, 39, 30, 0.42);
|
||||
--hm-sw-process-multi-tab-disabled-text: #7d9f91;
|
||||
}
|
||||
|
||||
/* ---------- Header frame ---------- */
|
||||
|
||||
.hm-sw-process-header {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: var(--hm-sw-header-height);
|
||||
background: var(--hm-sw-header-background);
|
||||
overflow: clip;
|
||||
}
|
||||
.hm-sw-process-header__bar { display: flex; align-items: stretch; height: var(--hm-sw-header-bar-height); min-width: 0; }
|
||||
|
||||
/* 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. */
|
||||
.hm-sw-process-header__bar > .hm-sw-header__home { margin-right: -15px; }
|
||||
.hm-sw-process-header__wordmark {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 58px;
|
||||
color: var(--hm-sw-header-text-color);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: var(--hm-sw-header-logo-height);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
text-shadow: var(--hm-sw-header-text-outline);
|
||||
}
|
||||
/* Logo menu open (host menu): the hover look stays, like Strana IsSubmenuOpen. */
|
||||
.hm-sw-process-header .hm-sw-header__home[aria-expanded="true"] .hm-sw-header__home-bg-hover { opacity: 1; }
|
||||
|
||||
/* File name: SWHeaderDocumentTitle; only its width follows the Strana column (286*, 100 .. 286). */
|
||||
.hm-sw-process-header__bar > .hm-sw-header__document { flex: 286 1 0; min-width: 100px; max-width: 286px; }
|
||||
/* No document list here, so no dropdown arrow (the Strana file name has none). */
|
||||
.hm-sw-process-header__bar > .hm-sw-header__document .hm-sw-header__arrow { display: none; }
|
||||
|
||||
.hm-sw-process-header__center { display: flex; flex: 1260 1 0; min-width: 0; }
|
||||
.hm-sw-process-header__center > .hm-sw-process-bar { flex: 1003 1 0; }
|
||||
|
||||
/* Layer surface (Header.Border.LayerFace): SW object surface, 1px apart on both sides, 150 .. 255. */
|
||||
.hm-sw-process-header__layer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 257 1 0;
|
||||
min-width: 152px;
|
||||
max-width: 257px;
|
||||
position: relative;
|
||||
height: var(--hm-sw-header-bar-height);
|
||||
}
|
||||
/* Surface on a pseudo element 1px in from both sides: no padding, border or margin on the flex item, so the
|
||||
1003 : 257 column ratio and its 152 .. 257 limits are the Strana ones exactly (surface 150 .. 255). */
|
||||
.hm-sw-process-header__layer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 1px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--hm-sw-header-object-radius);
|
||||
background: var(--hm-sw-header-object-background) padding-box, var(--hm-sw-header-object-border-gradient) border-box;
|
||||
}
|
||||
/* Layer field (Header.ToggleButton.LayerField): 38px, at most 234, property surface + layer border gradient.
|
||||
The bulb and the field are sibling buttons; the field button covers the whole field under the bulb. */
|
||||
.hm-sw-process-header__layer-field {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 234px;
|
||||
height: var(--hm-sw-header-field-height);
|
||||
margin: 0 9px;
|
||||
padding: 0 10px 0 12px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--hm-sw-header-property-radius);
|
||||
background: var(--hm-sw-header-property-background) padding-box, var(--hm-sw-process-layer-border-gradient) border-box;
|
||||
box-shadow: var(--hm-sw-header-property-shadow);
|
||||
color: var(--hm-sw-header-text-color);
|
||||
}
|
||||
.hm-sw-process-header__layer-field::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -1px;
|
||||
padding: var(--hm-sw-header-hover-border-width);
|
||||
border-radius: inherit;
|
||||
background: var(--hm-sw-header-hover-border-gradient);
|
||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-process-header__layer-field:not([data-disabled]):hover::after,
|
||||
.hm-sw-process-header__layer-field[data-open]::after { opacity: 1; }
|
||||
.hm-sw-process-header__layer-field:not([data-disabled]):hover,
|
||||
.hm-sw-process-header__layer-field[data-open] { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-header__layer-field[data-disabled] { opacity: 0.4; }
|
||||
.hm-sw-process-header .hm-sw-process-header__layer-field button:disabled { opacity: 1; }
|
||||
.hm-sw-process-header__layer-button { position: absolute; inset: -1px; z-index: 0; border-radius: inherit; }
|
||||
.hm-sw-process-header__layer-field > .hm-sw-header__layer-toggle { z-index: 1; }
|
||||
.hm-sw-process-header__layer-text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
padding: 0 var(--hm-sw-header-text-outline-room);
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-process-header__layer-label,
|
||||
.hm-sw-process-header__layer-value { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.hm-sw-process-header__layer-label { margin-left: 1px; font-size: var(--hm-sw-header-label-size); line-height: 14px; text-shadow: var(--hm-sw-header-label-outline); }
|
||||
.hm-sw-process-header__layer-value { font-size: var(--hm-sw-header-font-size); line-height: 16px; text-shadow: var(--hm-sw-header-text-outline); }
|
||||
|
||||
/* Window area (Header.Border.Window): DocFace + DocBorder 1px behind; the buttons cover the full 48px on top. */
|
||||
.hm-sw-process-header__window { position: relative; display: flex; flex: 0 0 auto; height: var(--hm-sw-header-bar-height); }
|
||||
.hm-sw-process-header__window::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid transparent;
|
||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-header-document-border-gradient) border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-process-header__window > * { position: relative; }
|
||||
|
||||
/* Tool pill: the SWToolGroup pill (40 x 38 buttons, 1px apart). `default` puts the Strana pill well and gradient
|
||||
borders on it; `multi` keeps the SW pill as it is (Figma 873:16481 uses the SW pill: solid #092920, rgba(0,0,0,.8),
|
||||
inner shadow, buttons with a solid #08251C border) at its own size. The selected (pressed) look — yellow ring and
|
||||
icon — is added for both. */
|
||||
.hm-sw-process-header .hm-sw-process-header__window .hm-sw-process-header__tools { flex: 0 0 auto; height: 42px; margin: 3px 0 0 3px; }
|
||||
.hm-sw-process-header:not([data-appearance="multi"]) .hm-sw-process-header__window .hm-sw-process-header__tools {
|
||||
border-color: transparent;
|
||||
background: var(--hm-sw-process-pill-background) padding-box, var(--hm-sw-process-pill-border-gradient) border-box;
|
||||
box-shadow: none;
|
||||
}
|
||||
.hm-sw-process-header:not([data-appearance="multi"]) .hm-sw-process-header__tools .hm-sw-tool__pill-button {
|
||||
border-color: transparent;
|
||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-process-tool-border-gradient) border-box;
|
||||
}
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button { color: var(--hm-sw-process-tool-icon-color); }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button:hover:not(:disabled),
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"] { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"]::after { opacity: 1; }
|
||||
/* Pressed (not disabled) shows the hover picture too, like a hover that stays (SW pill hover icon = `hoverIcon`). */
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"]:not(:disabled) > .hm-sw-header__icon { visibility: hidden; }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"]:not(:disabled) > .hm-sw-header__icon-hover { visibility: visible; }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__icon { inset: 0; }
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__icon > :is(svg, img) { width: 24px; height: 24px; max-width: none; max-height: none; }
|
||||
|
||||
/* Caption buttons (Header.WindowCaption, 32 x 48): glyph #ADB0AF; hover dark + white; close hover red + white. */
|
||||
.hm-sw-process-header__caption { display: block; flex: 0 0 32px; width: 32px; height: var(--hm-sw-header-bar-height); color: var(--hm-sw-header-window-icon-color); }
|
||||
.hm-sw-process-header__caption > svg { display: block; }
|
||||
.hm-sw-process-header__caption:hover:not(:disabled) { background: var(--hm-sw-header-window-button-hover-background); color: var(--hm-sw-header-window-icon-hover-color); }
|
||||
.hm-sw-process-header__caption[data-kind="close"]:hover:not(:disabled) { background: var(--hm-sw-process-close-hover-background); color: var(--hm-sw-process-close-icon-hover); }
|
||||
|
||||
/* ---------- Process area (SWProcessBar) ---------- */
|
||||
|
||||
.hm-sw-process-bar {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
height: var(--hm-sw-header-bar-height);
|
||||
/* Strana ClipToBounds: surfaces and the arrow shade never reach past the area (no scroll width when narrow). */
|
||||
overflow: clip;
|
||||
color: var(--hm-sw-process-tab-text);
|
||||
}
|
||||
.hm-sw-process-bar::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px 6px 0 0;
|
||||
background: var(--hm-sw-process-base-background) padding-box, var(--hm-sw-process-base-border-gradient) border-box;
|
||||
}
|
||||
/* Rest of the width on both sides: same brightness as the outer group next to it. */
|
||||
.hm-sw-process-bar__rest { position: relative; flex: 1 1 0; min-width: 0; }
|
||||
.hm-sw-process-bar__rest[data-dim] { background: var(--hm-sw-process-dim); }
|
||||
/* The cover keeps the base's 1px side borders and round corners visible (Strana margin 1, radius 5). */
|
||||
.hm-sw-process-bar__rest[data-side="start"] { margin-left: 1px; border-top-left-radius: 5px; }
|
||||
.hm-sw-process-bar__rest[data-side="end"] { margin-right: 1px; border-top-right-radius: 5px; }
|
||||
|
||||
/* Group: [20][number 4 name][12][tabs][20]. Natural width at most; shrinks with the others in proportion. */
|
||||
.hm-sw-process-bar__group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.hm-sw-process-bar__group[data-dim]::before { content: ""; position: absolute; inset: 0; z-index: 0; background: var(--hm-sw-process-dim); }
|
||||
.hm-sw-process-bar__label,
|
||||
.hm-sw-process-bar__steps { position: relative; z-index: 3; }
|
||||
|
||||
.hm-sw-process-bar__label {
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
flex: 0 0 auto;
|
||||
margin: 10.67px 12px 0 0;
|
||||
font-size: var(--hm-sw-header-label-size);
|
||||
line-height: 14px;
|
||||
text-shadow: var(--hm-sw-header-label-outline);
|
||||
}
|
||||
.hm-sw-process-bar__label[data-lines="2"] { margin-top: 8.67px; }
|
||||
.hm-sw-process-bar__number { flex: 0 0 auto; margin-right: 4px; color: var(--hm-sw-process-number-color); font-weight: 500; }
|
||||
.hm-sw-process-bar__name { display: grid; max-width: 56px; }
|
||||
.hm-sw-process-bar__name > * { grid-area: 1 / 1; }
|
||||
.hm-sw-process-bar__name-reserve { font-weight: 700; visibility: hidden; }
|
||||
.hm-sw-process-bar__name-text { justify-self: start; color: var(--hm-sw-process-group-color); font-weight: 500; }
|
||||
.hm-sw-process-bar__group[data-active] .hm-sw-process-bar__name-text { color: var(--hm-sw-process-group-active-color); font-weight: 700; }
|
||||
|
||||
/* Tabs (both appearances): a flex row where the checked tab shrinks with the others in the same proportion (2026-11-03,
|
||||
it no longer keeps its whole name); a name without room ends with an ellipsis. default: all tabs share the row
|
||||
equally (Strana UniformGrid, 127 each at most); multi overrides below. The group minimum (name + every tab at its
|
||||
padding) is measured by SWProcessBar, so tabs never overlap. */
|
||||
.hm-sw-process-bar__steps {
|
||||
display: flex;
|
||||
column-gap: 1px;
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.hm-sw-process-bar__steps > .hm-sw-process-bar__step { flex: 1 1 0; }
|
||||
.hm-sw-process-bar__step {
|
||||
--hm-sw-process-step-radius: 4px;
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
min-width: 0;
|
||||
height: 32px;
|
||||
padding: 0 12px 2.67px;
|
||||
border-radius: var(--hm-sw-process-step-radius);
|
||||
color: var(--hm-sw-process-tab-text);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
text-shadow: var(--hm-sw-header-text-outline);
|
||||
}
|
||||
.hm-sw-process-bar__step[data-shape="single"] { --hm-sw-process-step-radius: 16px; }
|
||||
.hm-sw-process-bar__step[data-shape="start"] { --hm-sw-process-step-radius: 16px 4px 4px 16px; }
|
||||
.hm-sw-process-bar__step[data-shape="end"] { --hm-sw-process-step-radius: 4px 16px 16px 4px; }
|
||||
.hm-sw-process-bar__step-text,
|
||||
.hm-sw-process-bar__step-size { grid-area: 1 / 1; }
|
||||
/* The outline may paint into the room outside the clipped (ellipsis) text, like the SW file name. */
|
||||
.hm-sw-process-bar__step-text { position: relative; max-width: 100%; overflow: clip; overflow-clip-margin: var(--hm-sw-header-text-outline-room); white-space: nowrap; text-overflow: ellipsis; }
|
||||
/* Natural width 127 (Strana MinWidth) = 103 + 24 padding; never wider than the column. */
|
||||
.hm-sw-process-bar__step-size { display: block; width: 103px; max-width: 100%; height: 0; }
|
||||
|
||||
.hm-sw-process-bar__step-box { position: absolute; inset: 0; border-radius: inherit; opacity: 0.7; pointer-events: none; }
|
||||
.hm-sw-process-bar__step-box > i { position: absolute; inset: 0; border-radius: inherit; }
|
||||
.hm-sw-process-bar__step-box > [data-layer^="glow"] { inset: 1px -1px -1px 1px; border-right: 1px solid var(--hm-sw-process-tab-glow); border-bottom: 1px solid var(--hm-sw-process-tab-glow); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="depth"] { background: var(--hm-sw-process-tab-depth); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="shade"] { background: var(--hm-sw-process-tab-shade); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="sheen"] { background: var(--hm-sw-process-tab-sheen); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="frame"] {
|
||||
--hm-sw-process-frame: 1px;
|
||||
padding: var(--hm-sw-process-frame);
|
||||
background: var(--hm-sw-process-tab-border-gradient);
|
||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.hm-sw-process-bar__step-box > [data-layer^="inset"] { display: none; }
|
||||
.hm-sw-process-bar__step-box > [data-layer="inset-top"] { background: linear-gradient(180deg, var(--hm-sw-process-tab-inset) 0, transparent 5px); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="inset-left"] { background: linear-gradient(90deg, var(--hm-sw-process-tab-inset) 0, transparent 6px); }
|
||||
.hm-sw-process-bar__step-box > [data-layer="ring"] { border: 2px solid var(--hm-sw-process-tab-ring); }
|
||||
|
||||
.hm-sw-process-bar__step:hover:not(:disabled) { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] { color: var(--hm-sw-header-selected-color); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] > .hm-sw-process-bar__step-box { opacity: 1; }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="depth"] { background: var(--hm-sw-process-tab-active-base); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="shade"] { background: var(--hm-sw-process-tab-active-shade); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="sheen"] { background: var(--hm-sw-process-tab-active-sheen); }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer^="inset"] { display: block; opacity: 0.75; }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="frame"] { --hm-sw-process-frame: 2px; opacity: 0.25; }
|
||||
.hm-sw-process-bar__step[aria-checked="true"] [data-layer="glow2"] { display: none; }
|
||||
/* Disabled (after Checked, like the Strana trigger order). The SW button reset's 40% is replaced by the tab look. */
|
||||
.hm-sw-process-bar .hm-sw-process-bar__step:disabled { opacity: 1; color: var(--hm-sw-process-tab-disabled-text); cursor: default; }
|
||||
.hm-sw-process-bar__step:disabled > .hm-sw-process-bar__step-box { opacity: 0.5; }
|
||||
.hm-sw-process-bar__step:disabled [data-layer="depth"] { background: var(--hm-sw-process-tab-active-base); }
|
||||
.hm-sw-process-bar__step:disabled [data-layer="shade"] { background: var(--hm-sw-process-tab-disabled-shade); }
|
||||
.hm-sw-process-bar__step:disabled [data-layer="sheen"] { background: var(--hm-sw-process-tab-disabled-sheen); }
|
||||
.hm-sw-process-bar__step:disabled [data-layer^="inset"] { display: block; }
|
||||
.hm-sw-process-bar__step:disabled [data-layer="glow2"] { display: none; }
|
||||
|
||||
/* Group surfaces: 40px out on the left of the first group, mirrored and 44px out on the right of the last one.
|
||||
Clipped to their box like the Strana ClipToBounds. */
|
||||
.hm-sw-process-bar__shape { position: absolute; top: 0; left: calc(-1 * var(--hm-sw-process-shape-start, 40px)); right: 0; z-index: 1; height: var(--hm-sw-header-bar-height); overflow: hidden; pointer-events: none; }
|
||||
.hm-sw-process-bar__shape[data-mirrored] { left: 0; right: calc(-1 * var(--hm-sw-process-shape-end, 44px)); transform: scaleX(-1); }
|
||||
.hm-sw-process-bar__shape > * { position: absolute; }
|
||||
|
||||
/* Lit: halo around the face (filter on the parent so the clipped face still glows), face fading at the arrow end. */
|
||||
.hm-sw-process-bar__glow-halo { inset: 0; filter: drop-shadow(0 0 30px var(--hm-sw-process-glow-halo)); }
|
||||
.hm-sw-process-bar__glow-face {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -4.9526px;
|
||||
height: 49.1162px;
|
||||
clip-path: path("M38.99,8.97 C38.41,4.2025 42.13,0 46.94,0 H99999 V49.1162 H50.98 C46.94,49.1162 43.53,46.0995 43.04,42.0862 Z");
|
||||
-webkit-mask: linear-gradient(90deg, #000 calc(100% - 83.4px), transparent calc(100% - 7.4px));
|
||||
mask: linear-gradient(90deg, #000 calc(100% - 83.4px), transparent calc(100% - 7.4px));
|
||||
}
|
||||
.hm-sw-process-bar__glow-face > i { position: absolute; inset: 0; }
|
||||
.hm-sw-process-bar__glow-face > i:nth-child(1) { background: var(--hm-sw-process-glow-face-base); opacity: 0.85; }
|
||||
.hm-sw-process-bar__glow-face > i:nth-child(2) { background: var(--hm-sw-process-glow-face-rim); opacity: 0.8; }
|
||||
.hm-sw-process-bar__glow-face > i:nth-child(3) { background: var(--hm-sw-process-glow-face-sheen); opacity: 0.45; }
|
||||
.hm-sw-process-bar__glow-edge {
|
||||
left: 0;
|
||||
top: 0;
|
||||
fill: none;
|
||||
stroke: var(--hm-sw-process-glow-edge);
|
||||
stroke-width: 3;
|
||||
filter: blur(5px);
|
||||
-webkit-mask: linear-gradient(180deg, #000 64%, rgba(0, 0, 0, 0.302) 91%);
|
||||
mask: linear-gradient(180deg, #000 64%, rgba(0, 0, 0, 0.302) 91%);
|
||||
}
|
||||
|
||||
/* Unlit: blurred face with a dark halo and a rim light, fading from the outer end. */
|
||||
.hm-sw-process-bar__shape[data-kind="idle"] {
|
||||
-webkit-mask: linear-gradient(90deg, #000 79.8px, transparent 198.4px);
|
||||
mask: linear-gradient(90deg, #000 79.8px, transparent 198.4px);
|
||||
}
|
||||
.hm-sw-process-bar__shape[data-kind="idle"][data-mirrored] {
|
||||
-webkit-mask: linear-gradient(90deg, #000 236.7px, transparent 273.1px);
|
||||
mask: linear-gradient(90deg, #000 236.7px, transparent 273.1px);
|
||||
}
|
||||
.hm-sw-process-bar__idle-halo { inset: 0; filter: drop-shadow(0 0 15px var(--hm-sw-process-idle-halo)); }
|
||||
.hm-sw-process-bar__idle-blur { position: absolute; inset: 0; filter: blur(4px); }
|
||||
.hm-sw-process-bar__idle-face {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -4.95264px;
|
||||
height: 49.2497px;
|
||||
background: var(--hm-sw-process-idle-face);
|
||||
clip-path: path("M99999,0 H47.153 C42.095,0 38.306,4.63511 39.313,9.59189 L46.064,42.84164 C46.821,46.57024 50.099,49.24974 53.904,49.24974 H99999 Z");
|
||||
}
|
||||
.hm-sw-process-bar__idle-rim { left: 0; top: 0; fill: none; stroke: var(--hm-sw-process-idle-rim); stroke-width: 1.5; filter: blur(1px); }
|
||||
.hm-sw-process-bar__idle-rim > rect { fill: var(--hm-sw-process-idle-rim); stroke: none; }
|
||||
|
||||
/* Arrow cell (16px): dim cover under the surfaces, the arrow above them, its shade over the next group. */
|
||||
.hm-sw-process-bar__arrow { position: relative; flex: 0 0 16px; width: 16px; }
|
||||
.hm-sw-process-bar__arrow > svg { position: absolute; left: 0; top: 0; display: block; }
|
||||
.hm-sw-process-bar__arrow-dim { z-index: 0; height: 100%; fill: var(--hm-sw-process-dim); }
|
||||
.hm-sw-process-bar__arrow-art { z-index: 2; pointer-events: none; }
|
||||
.hm-sw-process-bar__arrow-shadow { stroke: var(--hm-sw-process-arrow-shadow); }
|
||||
|
||||
/* ---------- Shared additions (both appearances) ---------- */
|
||||
|
||||
/* A group name kept for assistive technology only (`labelHidden`). */
|
||||
.hm-sw-process-bar__label[data-label-hidden] { display: none; }
|
||||
|
||||
/* No document (`documentEmptyLabel`, Figma 873:16481): a small label at the top right of the file-name surface. */
|
||||
.hm-sw-process-header[data-document-empty] .hm-sw-header__document-button { align-items: flex-start; }
|
||||
.hm-sw-process-header[data-document-empty] .hm-sw-header__document-name {
|
||||
margin-top: 7px;
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
letter-spacing: -0.1px;
|
||||
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); }
|
||||
|
||||
/* ---------- 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.
|
||||
Text outline, hover, focus and the window-move motion are the shared SW rules (Figma texts have no stroke; the SW
|
||||
outline is applied on purpose, 2026-11-03 user request). */
|
||||
|
||||
/* Frame: 49px surfaces (no 1px gap). Logo and file-name text as the default (SWHeader). */
|
||||
.hm-sw-theme.hm-sw-process-header[data-appearance="multi"] { --hm-sw-header-bar-height: 49px; }
|
||||
/* SWHeader window move at the Figma place of this frame: stripes 82 x 39 at (3, 5), the icon (and the motion with its
|
||||
tooltip) 3px higher, at (28, 10). Only positions; the motion itself is the SWHeader one. */
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-header__motion { background-position: 3px 5px; background-size: 82px 39px; }
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-header__motion > * { translate: 0 -3px; }
|
||||
/* File name: 189 at most (Figma 873:16254), solid #0E4C3A border. */
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__bar > .hm-sw-header__document { flex: 189 1 0; max-width: 189px; }
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__bar > .hm-sw-header__document .hm-sw-header__document-button {
|
||||
background: var(--hm-sw-header-document-background) padding-box, linear-gradient(var(--hm-sw-header-document-border-color) 0 0) border-box;
|
||||
}
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__center { flex: 1278 1 0; }
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__window::before {
|
||||
background: var(--hm-sw-header-document-background) padding-box, linear-gradient(var(--hm-sw-header-document-border-color) 0 0) border-box;
|
||||
}
|
||||
/* SW pill at the Figma place: 41 high, 4 from the top, 5 from the window start, 2 before the window move. */
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__window .hm-sw-process-header__tools { height: 41px; margin: 4px 2px 0 5px; }
|
||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__tools .hm-sw-tool__pill-button { height: 37px; }
|
||||
|
||||
/* Process area: 49px, groups [10][title 14 / tabs 25][10] with the arrow between (content to content 36). */
|
||||
.hm-sw-process-bar[data-appearance="multi"] {
|
||||
--hm-sw-header-bar-height: 49px;
|
||||
--hm-sw-process-shape-start: 50px;
|
||||
--hm-sw-process-shape-end: 48px;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 3.8px 10px 0;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__label {
|
||||
align-self: stretch;
|
||||
height: 14px;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__label[data-label-hidden] { display: flex; visibility: hidden; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__number { margin-right: 2px; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__name { grid-template-columns: minmax(0, 1fr); flex: 0 1 auto; min-width: 0; max-width: none; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__name > * {
|
||||
overflow: clip;
|
||||
overflow-clip-margin: var(--hm-sw-header-text-outline-room);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group[data-progress="done"] .hm-sw-process-bar__name-text {
|
||||
color: var(--hm-sw-process-group-active-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Tabs as wide as their label (padding 13 / 12), no gap; ends round (50). All of them, the checked one included,
|
||||
shrink in proportion to that width with an ellipsis. */
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__steps {
|
||||
align-items: stretch;
|
||||
flex: 0 1 auto;
|
||||
max-width: 100%;
|
||||
margin-top: 1.9px;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__steps > .hm-sw-process-bar__step { flex: 0 1 auto; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step {
|
||||
--hm-sw-process-step-radius: 0;
|
||||
height: 25px;
|
||||
padding: 0 12px 0 13px;
|
||||
font-size: 14px;
|
||||
line-height: 23px;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step[data-shape="single"] { --hm-sw-process-step-radius: 50px; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step[data-shape="start"] { --hm-sw-process-step-radius: 50px 0 0 50px; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step[data-shape="end"] { --hm-sw-process-step-radius: 0 50px 50px 0; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step-size { display: none; }
|
||||
|
||||
/* One face instead of the default stacked layers. */
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step-box {
|
||||
border: 1px solid var(--hm-sw-process-multi-tab-border);
|
||||
background: var(--hm-sw-process-multi-tab-background);
|
||||
box-shadow: var(--hm-sw-process-multi-tab-shadow);
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step-box > i { display: none; }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group:is([data-active], [data-progress="done"]) .hm-sw-process-bar__step-box {
|
||||
border-color: var(--hm-sw-process-multi-tab-lit-border);
|
||||
background: var(--hm-sw-process-multi-tab-lit-background);
|
||||
opacity: 1;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group .hm-sw-process-bar__step[aria-checked="true"] > .hm-sw-process-bar__step-box {
|
||||
border-color: var(--hm-sw-process-multi-tab-selected-border);
|
||||
background: var(--hm-sw-process-multi-tab-selected-background);
|
||||
box-shadow: var(--hm-sw-process-multi-tab-selected-shadow);
|
||||
opacity: 1;
|
||||
}
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__step:disabled { color: var(--hm-sw-process-multi-tab-disabled-text); }
|
||||
.hm-sw-process-bar[data-appearance="multi"] .hm-sw-process-bar__group .hm-sw-process-bar__step:disabled > .hm-sw-process-bar__step-box {
|
||||
border-color: var(--hm-sw-process-multi-tab-disabled-border);
|
||||
background: var(--hm-sw-header-property-background);
|
||||
box-shadow: var(--hm-sw-process-multi-tab-disabled-shadow);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* ---------- Viewport split tool (SWProcessSplitButton, both appearances) ---------- */
|
||||
|
||||
.hm-sw-process-split { position: relative; display: flex; }
|
||||
.hm-sw-process-split__icon-pane { fill: var(--hm-sw-process-split-icon-pane); stroke: #000; stroke-width: 0.962; }
|
||||
.hm-sw-process-split__icon > rect:first-child { stroke: #000; stroke-width: 0.962; }
|
||||
|
||||
/* Open (Header.ToggleButton.ToolMenu checked): darker face, gold top and sides, open at the bottom, no hover ring,
|
||||
yellow icon. The bottom border is replaced by padding so the icon never moves. */
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-process-split[data-open] > .hm-sw-tool__pill-button {
|
||||
padding-bottom: 1px;
|
||||
border-color: transparent;
|
||||
border-bottom-width: 0;
|
||||
border-radius: 2px 2px 0 0;
|
||||
background: var(--hm-sw-process-tool-open-background) padding-box, var(--hm-sw-header-hover-border-gradient) border-box;
|
||||
color: var(--hm-sw-header-selected-color);
|
||||
}
|
||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-process-split[data-open] > .hm-sw-tool__pill-button::after { opacity: 0; }
|
||||
|
||||
/* Selector: fixed overlay 1px over the button bottom (no gap at any zoom), aligned to the button left (shifted only at
|
||||
the screen edge — --hm-sw-process-split-offset keeps the extension under the button). The extension reaches the
|
||||
header bottom (--hm-sw-process-split-tail, Strana 3px), so the panel never covers the window buttons. */
|
||||
.hm-sw-process-split__popup { position: fixed; z-index: 30; width: 233px; height: calc(var(--hm-sw-process-split-tail, 3px) + 36px); }
|
||||
.hm-sw-process-split__tail {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: var(--hm-sw-process-split-offset, 0px);
|
||||
width: 40px;
|
||||
height: var(--hm-sw-process-split-tail, 3px);
|
||||
border: solid transparent;
|
||||
border-width: 0 1px;
|
||||
background: linear-gradient(var(--hm-sw-process-tool-open-edge), var(--hm-sw-process-tool-open-edge)) padding-box, var(--hm-sw-header-hover-border-gradient) border-box;
|
||||
}
|
||||
.hm-sw-process-split__panel {
|
||||
position: absolute;
|
||||
top: var(--hm-sw-process-split-tail, 3px);
|
||||
left: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
width: 233px;
|
||||
height: 36px;
|
||||
padding: 0.5px 0;
|
||||
border: 1px solid transparent;
|
||||
border-top-width: 0;
|
||||
border-radius: 0 2px 2px 2px;
|
||||
background: linear-gradient(var(--hm-sw-process-split-panel-background), var(--hm-sw-process-split-panel-background)) padding-box, var(--hm-sw-header-hover-border-gradient) border-box;
|
||||
box-shadow: var(--hm-sw-process-split-panel-shadow);
|
||||
}
|
||||
/* Gold line on the panel top, from the button right edge to the panel end (Header.Border.ToolMenuStep). */
|
||||
.hm-sw-process-split__step {
|
||||
position: absolute;
|
||||
top: var(--hm-sw-process-split-tail, 3px);
|
||||
left: calc(var(--hm-sw-process-split-offset, 0px) + 39px);
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-top-right-radius: 2px;
|
||||
background: var(--hm-sw-header-hover-border-gradient) 0 0 / 100% 36px no-repeat;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-process-split__cell { display: grid; place-items: center; height: 34px; }
|
||||
/* Whole-pixel frame around the 29.6 x 19.73 picture (Strana 31.6 x 21.73): a fractional box is snapped to 21 or 22 rows
|
||||
depending on where the popup sits, and at 21 the light edge under the panes vanished (icons looked cut at 100%). */
|
||||
.hm-sw-process-split__frame {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 22px;
|
||||
border: 1px solid #000;
|
||||
border-radius: 2px;
|
||||
background: var(--hm-sw-process-split-cell-frame);
|
||||
}
|
||||
.hm-sw-process-split__frame > svg { display: block; overflow: visible; }
|
||||
.hm-sw-process-split__pane { fill: var(--hm-sw-process-split-cell-pane); stroke: rgba(0, 0, 0, 0.3); stroke-width: 1; }
|
||||
.hm-sw-process-split__cell:is(:hover, [aria-checked="true"]) .hm-sw-process-split__pane { fill: var(--hm-sw-header-selected-color); }
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="controls 1">
|
||||
<circle id="Ellipse 499" cx="7.71924" cy="7.79834" r="2.12158" fill="#003425"/>
|
||||
<circle id="Ellipse 500" cx="16.606" cy="16.5244" r="2.12158" fill="#003425"/>
|
||||
<g id="Union">
|
||||
<use xlink:href="#stroke0_0_190" fill="#FFC600"/>
|
||||
<mask id="stroke1_0_190" maskUnits="userSpaceOnUse" x="3.08594" y="3.68262" width="18" height="17" fill="black">
|
||||
<rect fill="white" x="3.08594" y="3.68262" width="18" height="17"/>
|
||||
<use xlink:href="#stroke0_0_190"/>
|
||||
</mask>
|
||||
<path d="M16.1807 13.041C17.9137 13.041 19.3191 14.4458 19.3193 16.1787C19.3192 17.9117 17.9137 19.3174 16.1807 19.3174C14.7063 19.3172 13.4701 18.2995 13.1338 16.9287H4.08594V15.4287H13.1338C13.4701 14.058 14.7063 13.0412 16.1807 13.041ZM16.1807 14.541C15.2763 14.5412 14.5432 15.2743 14.543 16.1787C14.5431 17.0832 15.2762 17.8172 16.1807 17.8174C17.0853 17.8174 17.8192 17.0833 17.8193 16.1787C17.8191 15.2742 17.0852 14.541 16.1807 14.541ZM7.82129 4.68262C9.29579 4.68267 10.5327 5.69951 10.8691 7.07031H19.915V8.57031H10.8691C10.5327 9.94121 9.29576 10.9589 7.82129 10.959C6.08843 10.9588 4.68373 9.55319 4.68359 7.82031C4.68382 6.08751 6.08849 4.68284 7.82129 4.68262ZM7.82129 6.18262C6.91691 6.18284 6.18382 6.91594 6.18359 7.82031C6.18373 8.72476 6.91686 9.45876 7.82129 9.45898C8.71966 9.45893 9.44889 8.73487 9.45898 7.83887V7.80078C9.44843 6.90519 8.71937 6.18268 7.82129 6.18262Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_190)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_190" d="M16.1807 13.041C17.9137 13.041 19.3191 14.4458 19.3193 16.1787C19.3192 17.9117 17.9137 19.3174 16.1807 19.3174C14.7063 19.3172 13.4701 18.2995 13.1338 16.9287H4.08594V15.4287H13.1338C13.4701 14.058 14.7063 13.0412 16.1807 13.041ZM16.1807 14.541C15.2763 14.5412 14.5432 15.2743 14.543 16.1787C14.5431 17.0832 15.2762 17.8172 16.1807 17.8174C17.0853 17.8174 17.8192 17.0833 17.8193 16.1787C17.8191 15.2742 17.0852 14.541 16.1807 14.541ZM7.82129 4.68262C9.29579 4.68267 10.5327 5.69951 10.8691 7.07031H19.915V8.57031H10.8691C10.5327 9.94121 9.29576 10.9589 7.82129 10.959C6.08843 10.9588 4.68373 9.55319 4.68359 7.82031C4.68382 6.08751 6.08849 4.68284 7.82129 4.68262ZM7.82129 6.18262C6.91691 6.18284 6.18382 6.91594 6.18359 7.82031C6.18373 8.72476 6.91686 9.45876 7.82129 9.45898C8.71966 9.45893 9.44889 8.73487 9.45898 7.83887V7.80078C9.44842 6.90519 8.71937 6.18268 7.82129 6.18262Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="controls 1">
|
||||
<circle id="Ellipse 499" cx="7.71924" cy="7.79834" r="2.12158" fill="#003425"/>
|
||||
<circle id="Ellipse 500" cx="16.606" cy="16.5244" r="2.12158" fill="#003425"/>
|
||||
<g id="Union">
|
||||
<use xlink:href="#stroke0_0_190" fill="#B6D0C9"/>
|
||||
<mask id="stroke1_0_190" maskUnits="userSpaceOnUse" x="3.08594" y="3.68262" width="18" height="17" fill="black">
|
||||
<rect fill="white" x="3.08594" y="3.68262" width="18" height="17"/>
|
||||
<use xlink:href="#stroke0_0_190"/>
|
||||
</mask>
|
||||
<path d="M16.1807 13.041C17.9137 13.041 19.3191 14.4458 19.3193 16.1787C19.3192 17.9117 17.9137 19.3174 16.1807 19.3174C14.7063 19.3172 13.4701 18.2995 13.1338 16.9287H4.08594V15.4287H13.1338C13.4701 14.058 14.7063 13.0412 16.1807 13.041ZM16.1807 14.541C15.2763 14.5412 14.5432 15.2743 14.543 16.1787C14.5431 17.0832 15.2762 17.8172 16.1807 17.8174C17.0853 17.8174 17.8192 17.0833 17.8193 16.1787C17.8191 15.2742 17.0852 14.541 16.1807 14.541ZM7.82129 4.68262C9.29579 4.68267 10.5327 5.69951 10.8691 7.07031H19.915V8.57031H10.8691C10.5327 9.94121 9.29576 10.9589 7.82129 10.959C6.08843 10.9588 4.68373 9.55319 4.68359 7.82031C4.68382 6.08751 6.08849 4.68284 7.82129 4.68262ZM7.82129 6.18262C6.91691 6.18284 6.18382 6.91594 6.18359 7.82031C6.18373 8.72476 6.91686 9.45876 7.82129 9.45898C8.71966 9.45893 9.44889 8.73487 9.45898 7.83887V7.80078C9.44843 6.90519 8.71937 6.18268 7.82129 6.18262Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_190)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_190" d="M16.1807 13.041C17.9137 13.041 19.3191 14.4458 19.3193 16.1787C19.3192 17.9117 17.9137 19.3174 16.1807 19.3174C14.7063 19.3172 13.4701 18.2995 13.1338 16.9287H4.08594V15.4287H13.1338C13.4701 14.058 14.7063 13.0412 16.1807 13.041ZM16.1807 14.541C15.2763 14.5412 14.5432 15.2743 14.543 16.1787C14.5431 17.0832 15.2762 17.8172 16.1807 17.8174C17.0853 17.8174 17.8192 17.0833 17.8193 16.1787C17.8191 15.2742 17.0852 14.541 16.1807 14.541ZM7.82129 4.68262C9.29579 4.68267 10.5327 5.69951 10.8691 7.07031H19.915V8.57031H10.8691C10.5327 9.94121 9.29576 10.9589 7.82129 10.959C6.08843 10.9588 4.68373 9.55319 4.68359 7.82031C4.68382 6.08751 6.08849 4.68284 7.82129 4.68262ZM7.82129 6.18262C6.91691 6.18284 6.18382 6.91594 6.18359 7.82031C6.18373 8.72476 6.91686 9.45876 7.82129 9.45898C8.71966 9.45893 9.44889 8.73487 9.45898 7.83887V7.80078C9.44842 6.90519 8.71937 6.18268 7.82129 6.18262Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="exit-arrow 1">
|
||||
<g id="Vector (Stroke)">
|
||||
<use xlink:href="#stroke0_0_68" fill="#FFC600"/>
|
||||
<mask id="stroke1_0_68" maskUnits="userSpaceOnUse" x="3" y="3.28747" width="19" height="18" fill="black">
|
||||
<rect fill="white" x="3" y="3.28747" width="19" height="18"/>
|
||||
<use xlink:href="#stroke0_0_68"/>
|
||||
</mask>
|
||||
<path d="M4 17.8383V6.16174C4 5.65257 4.12778 5.15234 4.49632 4.7838C4.86487 4.41525 5.3651 4.28747 5.87427 4.28747H10.514V5.57779H5.87427C5.56403 5.57779 5.44971 5.65487 5.40855 5.69602C5.3674 5.73718 5.29032 5.8515 5.29032 6.16174V17.8383C5.29032 18.1485 5.3674 18.2628 5.40855 18.304C5.44971 18.3451 5.56403 18.4222 5.87427 18.4222H10.514V19.7125H5.87427C5.3651 19.7125 4.86487 19.5847 4.49632 19.2162C4.12778 18.8477 4 18.3474 4 17.8383ZM20 12L15.3998 7.39985L14.487 8.31268L17.5298 11.3548H9.22371V12.6452H17.5298L14.487 15.6873L15.3998 16.6001L20 12Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_68)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_68" d="M4 17.8383V6.16174C4 5.65257 4.12778 5.15234 4.49632 4.7838C4.86487 4.41525 5.3651 4.28747 5.87427 4.28747H10.514V5.57779H5.87427C5.56403 5.57779 5.44971 5.65487 5.40855 5.69602C5.3674 5.73718 5.29032 5.8515 5.29032 6.16174V17.8383C5.29032 18.1485 5.3674 18.2628 5.40855 18.304C5.44971 18.3451 5.56403 18.4222 5.87427 18.4222H10.514V19.7125H5.87427C5.3651 19.7125 4.86487 19.5848 4.49632 19.2162C4.12778 18.8477 4 18.3474 4 17.8383ZM20 12L15.3998 16.6002L14.487 15.6873L17.5298 12.6452H9.22371V11.3548H17.5298L14.487 8.31268L15.3998 7.39985L20 12Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="exit-arrow 1">
|
||||
<g id="Vector (Stroke)">
|
||||
<use xlink:href="#stroke0_0_68" fill="#B6D0C9"/>
|
||||
<mask id="stroke1_0_68" maskUnits="userSpaceOnUse" x="3" y="3.28747" width="19" height="18" fill="black">
|
||||
<rect fill="white" x="3" y="3.28747" width="19" height="18"/>
|
||||
<use xlink:href="#stroke0_0_68"/>
|
||||
</mask>
|
||||
<path d="M4 17.8383V6.16174C4 5.65257 4.12778 5.15234 4.49632 4.7838C4.86487 4.41525 5.3651 4.28747 5.87427 4.28747H10.514V5.57779H5.87427C5.56403 5.57779 5.44971 5.65487 5.40855 5.69602C5.3674 5.73718 5.29032 5.8515 5.29032 6.16174V17.8383C5.29032 18.1485 5.3674 18.2628 5.40855 18.304C5.44971 18.3451 5.56403 18.4222 5.87427 18.4222H10.514V19.7125H5.87427C5.3651 19.7125 4.86487 19.5847 4.49632 19.2162C4.12778 18.8477 4 18.3474 4 17.8383ZM20 12L15.3998 7.39985L14.487 8.31268L17.5298 11.3548H9.22371V12.6452H17.5298L14.487 15.6873L15.3998 16.6001L20 12Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_68)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_68" d="M4 17.8383V6.16174C4 5.65257 4.12778 5.15234 4.49632 4.7838C4.86487 4.41525 5.3651 4.28747 5.87427 4.28747H10.514V5.57779H5.87427C5.56403 5.57779 5.44971 5.65487 5.40855 5.69602C5.3674 5.73718 5.29032 5.8515 5.29032 6.16174V17.8383C5.29032 18.1485 5.3674 18.2628 5.40855 18.304C5.44971 18.3451 5.56403 18.4222 5.87427 18.4222H10.514V19.7125H5.87427C5.3651 19.7125 4.86487 19.5848 4.49632 19.2162C4.12778 18.8477 4 18.3474 4 17.8383ZM20 12L15.3998 16.6002L14.487 15.6873L17.5298 12.6452H9.22371V11.3548H17.5298L14.487 8.31268L15.3998 7.39985L20 12Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="help 1">
|
||||
<circle id="Ellipse 499" cx="11.9995" cy="12" r="7.22998" fill="#003425"/>
|
||||
<g id="Union">
|
||||
<use xlink:href="#stroke0_0_58" fill="#FFC600"/>
|
||||
<mask id="stroke1_0_58" maskUnits="userSpaceOnUse" x="2.83984" y="2.84033" width="19" height="19" fill="black">
|
||||
<rect fill="white" x="2.83984" y="2.84033" width="19" height="19"/>
|
||||
<use xlink:href="#stroke0_0_58"/>
|
||||
</mask>
|
||||
<path d="M11.999 3.84033C16.5053 3.84033 20.159 7.4933 20.1592 11.9995C20.159 16.5057 16.5053 20.1597 11.999 20.1597C7.49289 20.1595 3.83998 16.5056 3.83984 11.9995C3.83998 7.49338 7.4929 3.84047 11.999 3.84033ZM11.999 5.46533C8.39036 5.46547 5.46498 8.39085 5.46484 11.9995C5.46498 15.6082 8.39035 18.5345 11.999 18.5347C15.6078 18.5347 18.534 15.6083 18.5342 11.9995C18.534 8.39076 15.6078 5.46533 11.999 5.46533ZM11.8047 14.938C12.0986 14.84 12.3929 14.9384 12.5889 15.1343C12.6868 15.2323 12.7852 15.4284 12.7852 15.7222C12.7851 15.918 12.6868 16.1142 12.5889 16.3101C12.3929 16.5059 12.0985 16.6033 11.8047 16.5054C11.7072 16.5051 11.707 16.4078 11.6094 16.4077C11.3156 16.2119 11.2178 16.0159 11.2178 15.7222C11.2178 15.4283 11.3156 15.2325 11.6094 15.0366C11.707 15.0365 11.7072 14.9383 11.8047 14.938ZM12.001 7.68896C12.7846 7.68896 13.2749 7.88474 13.7646 8.37451C14.2544 8.86431 14.4502 9.35481 14.4502 10.0405C14.4501 10.7261 14.0577 11.3139 13.3721 11.9995C13.1763 12.2932 13.0786 12.4892 12.8828 12.6851C12.7849 12.783 12.6868 13.0768 12.5889 13.3706C12.5889 13.6645 12.2946 13.8608 12.0986 13.8608H11.9033C11.5116 13.8608 11.2177 13.567 11.3154 13.1753C11.3154 13.0773 11.4131 12.8807 11.4131 12.7827C11.5111 12.4889 11.8051 12.097 12.001 11.8032C12.0989 11.7053 12.2946 11.4122 12.3926 11.314C12.9803 10.8242 13.1768 10.4317 13.1768 9.94189C13.1767 9.55043 13.0785 9.25689 12.8828 9.06104C12.6869 8.86512 12.2949 8.76709 12.001 8.76709C11.7071 8.76712 11.3153 8.96256 11.0215 9.25635C10.9236 9.35424 10.8254 9.54992 10.7275 9.64795C10.6296 9.94183 10.3359 10.1378 10.042 9.94189H9.94434C9.65046 9.94189 9.55243 9.55023 9.65039 9.25635C9.74835 8.96268 9.94387 8.669 10.1396 8.47314C10.6294 7.8854 11.2173 7.68897 12.001 7.68896Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_58)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_58" d="M11.999 3.84033C16.5053 3.84033 20.159 7.4933 20.1592 11.9995C20.159 16.5057 16.5053 20.1597 11.999 20.1597C7.49289 20.1595 3.83998 16.5056 3.83984 11.9995C3.83998 7.49338 7.4929 3.84047 11.999 3.84033ZM11.999 5.46533C8.39036 5.46547 5.46498 8.39085 5.46484 11.9995C5.46498 15.6082 8.39035 18.5345 11.999 18.5347C15.6078 18.5347 18.534 15.6083 18.5342 11.9995C18.534 8.39076 15.6078 5.46533 11.999 5.46533ZM11.8047 14.938C12.0986 14.84 12.3929 14.9384 12.5889 15.1343C12.6868 15.2323 12.7852 15.4284 12.7852 15.7222C12.7851 15.918 12.6868 16.1142 12.5889 16.3101C12.3929 16.5059 12.0985 16.6033 11.8047 16.5054C11.7072 16.5051 11.707 16.4078 11.6094 16.4077C11.3156 16.2119 11.2178 16.0159 11.2178 15.7222C11.2178 15.4283 11.3156 15.2325 11.6094 15.0366C11.707 15.0365 11.7072 14.9383 11.8047 14.938ZM12.001 7.68896C12.7846 7.68896 13.2749 7.88474 13.7646 8.37451C14.2544 8.86431 14.4502 9.35481 14.4502 10.0405C14.4501 10.7261 14.0577 11.3139 13.3721 11.9995C13.1763 12.2932 13.0786 12.4892 12.8828 12.6851C12.7849 12.783 12.6868 13.0768 12.5889 13.3706C12.5889 13.6645 12.2946 13.8608 12.0986 13.8608H11.9033C11.5116 13.8608 11.2177 13.567 11.3154 13.1753C11.3154 13.0773 11.4131 12.8807 11.4131 12.7827C11.5111 12.4889 11.8051 12.097 12.001 11.8032C12.0989 11.7053 12.2946 11.4122 12.3926 11.314C12.9803 10.8242 13.1768 10.4317 13.1768 9.94189C13.1767 9.55043 13.0785 9.25689 12.8828 9.06104C12.6869 8.86512 12.2949 8.76709 12.001 8.76709C11.7071 8.76712 11.3153 8.96256 11.0215 9.25635C10.9236 9.35424 10.8254 9.54992 10.7275 9.64795C10.6296 9.94183 10.3359 10.1378 10.042 9.94189H9.94434C9.65046 9.94189 9.55243 9.55023 9.65039 9.25635C9.74835 8.96268 9.94387 8.669 10.1396 8.47314C10.6294 7.8854 11.2173 7.68897 12.001 7.68896Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="help 1">
|
||||
<circle id="Ellipse 499" cx="11.9995" cy="12" r="7.22998" fill="#003425"/>
|
||||
<g id="Union">
|
||||
<use xlink:href="#stroke0_0_58" fill="#B6D0C9"/>
|
||||
<mask id="stroke1_0_58" maskUnits="userSpaceOnUse" x="2.83984" y="2.84033" width="19" height="19" fill="black">
|
||||
<rect fill="white" x="2.83984" y="2.84033" width="19" height="19"/>
|
||||
<use xlink:href="#stroke0_0_58"/>
|
||||
</mask>
|
||||
<path d="M11.999 3.84033C16.5053 3.84033 20.159 7.4933 20.1592 11.9995C20.159 16.5057 16.5053 20.1597 11.999 20.1597C7.49289 20.1595 3.83998 16.5056 3.83984 11.9995C3.83998 7.49338 7.4929 3.84047 11.999 3.84033ZM11.999 5.46533C8.39036 5.46547 5.46498 8.39085 5.46484 11.9995C5.46498 15.6082 8.39035 18.5345 11.999 18.5347C15.6078 18.5347 18.534 15.6083 18.5342 11.9995C18.534 8.39076 15.6078 5.46533 11.999 5.46533ZM11.8047 14.938C12.0986 14.84 12.3929 14.9384 12.5889 15.1343C12.6868 15.2323 12.7852 15.4284 12.7852 15.7222C12.7851 15.918 12.6868 16.1142 12.5889 16.3101C12.3929 16.5059 12.0985 16.6033 11.8047 16.5054C11.7072 16.5051 11.707 16.4078 11.6094 16.4077C11.3156 16.2119 11.2178 16.0159 11.2178 15.7222C11.2178 15.4283 11.3156 15.2325 11.6094 15.0366C11.707 15.0365 11.7072 14.9383 11.8047 14.938ZM12.001 7.68896C12.7846 7.68896 13.2749 7.88474 13.7646 8.37451C14.2544 8.86431 14.4502 9.35481 14.4502 10.0405C14.4501 10.7261 14.0577 11.3139 13.3721 11.9995C13.1763 12.2932 13.0786 12.4892 12.8828 12.6851C12.7849 12.783 12.6868 13.0768 12.5889 13.3706C12.5889 13.6645 12.2946 13.8608 12.0986 13.8608H11.9033C11.5116 13.8608 11.2177 13.567 11.3154 13.1753C11.3154 13.0773 11.4131 12.8807 11.4131 12.7827C11.5111 12.4889 11.8051 12.097 12.001 11.8032C12.0989 11.7053 12.2946 11.4122 12.3926 11.314C12.9803 10.8242 13.1768 10.4317 13.1768 9.94189C13.1767 9.55043 13.0785 9.25689 12.8828 9.06104C12.6869 8.86512 12.2949 8.76709 12.001 8.76709C11.7071 8.76712 11.3153 8.96256 11.0215 9.25635C10.9236 9.35424 10.8254 9.54992 10.7275 9.64795C10.6296 9.94183 10.3359 10.1378 10.042 9.94189H9.94434C9.65046 9.94189 9.55243 9.55023 9.65039 9.25635C9.74835 8.96268 9.94387 8.669 10.1396 8.47314C10.6294 7.8854 11.2173 7.68897 12.001 7.68896Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_58)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_58" d="M11.999 3.84033C16.5053 3.84033 20.159 7.4933 20.1592 11.9995C20.159 16.5057 16.5053 20.1597 11.999 20.1597C7.49289 20.1595 3.83998 16.5056 3.83984 11.9995C3.83998 7.49338 7.4929 3.84047 11.999 3.84033ZM11.999 5.46533C8.39036 5.46547 5.46498 8.39085 5.46484 11.9995C5.46498 15.6082 8.39035 18.5345 11.999 18.5347C15.6078 18.5347 18.534 15.6083 18.5342 11.9995C18.534 8.39076 15.6078 5.46533 11.999 5.46533ZM11.8047 14.938C12.0986 14.84 12.3929 14.9384 12.5889 15.1343C12.6868 15.2323 12.7852 15.4284 12.7852 15.7222C12.7851 15.918 12.6868 16.1142 12.5889 16.3101C12.3929 16.5059 12.0985 16.6033 11.8047 16.5054C11.7072 16.5051 11.707 16.4078 11.6094 16.4077C11.3156 16.2119 11.2178 16.0159 11.2178 15.7222C11.2178 15.4283 11.3156 15.2325 11.6094 15.0366C11.707 15.0365 11.7072 14.9383 11.8047 14.938ZM12.001 7.68896C12.7846 7.68896 13.2749 7.88474 13.7646 8.37451C14.2544 8.86431 14.4502 9.35481 14.4502 10.0405C14.4501 10.7261 14.0577 11.3139 13.3721 11.9995C13.1763 12.2932 13.0786 12.4892 12.8828 12.6851C12.7849 12.783 12.6868 13.0768 12.5889 13.3706C12.5889 13.6645 12.2946 13.8608 12.0986 13.8608H11.9033C11.5116 13.8608 11.2177 13.567 11.3154 13.1753C11.3154 13.0773 11.4131 12.8807 11.4131 12.7827C11.5111 12.4889 11.8051 12.097 12.001 11.8032C12.0989 11.7053 12.2946 11.4122 12.3926 11.314C12.9803 10.8242 13.1768 10.4317 13.1768 9.94189C13.1767 9.55043 13.0785 9.25689 12.8828 9.06104C12.6869 8.86512 12.2949 8.76709 12.001 8.76709C11.7071 8.76712 11.3153 8.96256 11.0215 9.25635C10.9236 9.35424 10.8254 9.54992 10.7275 9.64795C10.6296 9.94183 10.3359 10.1378 10.042 9.94189H9.94434C9.65046 9.94189 9.55243 9.55023 9.65039 9.25635C9.74835 8.96268 9.94387 8.669 10.1396 8.47314C10.6294 7.8854 11.2173 7.68897 12.001 7.68896Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="menu-list 1">
|
||||
<g id="Union">
|
||||
<use xlink:href="#stroke0_0_85" fill="#FFC600"/>
|
||||
<mask id="stroke1_0_85" maskUnits="userSpaceOnUse" x="2.89844" y="4.66113" width="18" height="15" fill="black">
|
||||
<rect fill="white" x="2.89844" y="4.66113" width="18" height="15"/>
|
||||
<use xlink:href="#stroke0_0_85"/>
|
||||
</mask>
|
||||
<path d="M5.44434 16.793V18.3389H3.89844V16.793H5.44434ZM19.8018 18.3164H7.78906V16.8164H19.8018V18.3164ZM5.44434 12.7734H3.89844V11.2266H5.44434V12.7734ZM19.8018 12.75H7.78906V11.25H19.8018V12.75ZM5.44434 7.20703H3.89844V5.66113H5.44434V7.20703ZM19.8018 7.18457H7.78906V5.68457H19.8018V7.18457Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_85)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_85" d="M5.44434 16.793V18.3389H3.89844V16.793H5.44434ZM19.8018 18.3164H7.78906V16.8164H19.8018V18.3164ZM5.44434 12.7734H3.89844V11.2266H5.44434V12.7734ZM19.8018 12.75H7.78906V11.25H19.8018V12.75ZM5.44434 7.20703H3.89844V5.66113H5.44434V7.20703ZM19.8018 7.18457H7.78906V5.68457H19.8018V7.18457Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="menu-list 1">
|
||||
<g id="Union">
|
||||
<use xlink:href="#stroke0_0_85" fill="#B6D0C9"/>
|
||||
<mask id="stroke1_0_85" maskUnits="userSpaceOnUse" x="2.89844" y="4.66113" width="18" height="15" fill="black">
|
||||
<rect fill="white" x="2.89844" y="4.66113" width="18" height="15"/>
|
||||
<use xlink:href="#stroke0_0_85"/>
|
||||
</mask>
|
||||
<path d="M5.44434 16.793V18.3389H3.89844V16.793H5.44434ZM19.8018 18.3164H7.78906V16.8164H19.8018V18.3164ZM5.44434 12.7734H3.89844V11.2266H5.44434V12.7734ZM19.8018 12.75H7.78906V11.25H19.8018V12.75ZM5.44434 7.20703H3.89844V5.66113H5.44434V7.20703ZM19.8018 7.18457H7.78906V5.68457H19.8018V7.18457Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_85)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="stroke0_0_85" d="M5.44434 16.793V18.3389H3.89844V16.793H5.44434ZM19.8018 18.3164H7.78906V16.8164H19.8018V18.3164ZM5.44434 12.7734H3.89844V11.2266H5.44434V12.7734ZM19.8018 12.75H7.78906V11.25H19.8018V12.75ZM5.44434 7.20703H3.89844V5.66113H5.44434V7.20703ZM19.8018 7.18457H7.78906V5.68457H19.8018V7.18457Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="view-3d 1">
|
||||
<g id="Rectangle 9122" filter="url(#filter0_i_0_49)">
|
||||
<path d="M20 4.36646H8.37521L4.88 7.74889L4 19.2053H16.2071L20 15.7102V4.36646Z" fill="#003425"/>
|
||||
</g>
|
||||
<g id="Vector">
|
||||
<use xlink:href="#stroke0_0_49" fill="#FFC600"/>
|
||||
<mask id="stroke1_0_49" maskUnits="userSpaceOnUse" x="3" y="3" width="18" height="18" fill="black">
|
||||
<rect fill="white" x="3" y="3" width="18" height="18"/>
|
||||
<use xlink:href="#stroke0_0_49"/>
|
||||
</mask>
|
||||
<path d="M19.4359 4C19.7462 4 20 4.25249 20 4.56094V15.6203C19.9999 15.7919 19.9193 15.9438 19.7961 16.0469L16.1195 19.7914C16.0159 19.917 15.8611 20 15.6859 20H4.56406C4.25395 20 4.00014 19.7474 4 19.439V8.37968C4 8.36816 4.00242 8.35672 4.00313 8.34528C4.00457 8.3212 4.00638 8.2972 4.01094 8.27344C4.01364 8.2596 4.01816 8.24632 4.02187 8.2328C4.02781 8.21096 4.03354 8.18912 4.04218 8.168C4.0471 8.15608 4.05368 8.14504 4.05938 8.1336C4.07013 8.11184 4.08075 8.08992 4.09453 8.06952C4.10043 8.06072 4.10769 8.05296 4.11406 8.04456C4.12754 8.02688 4.14036 8.0084 4.15625 7.99216L7.84528 4.17344C7.9616 4.05178 8.12072 3.9961 8.27736 4.00313C8.28952 4.00231 8.30168 4 8.31408 4H19.4359ZM5.12816 18.8782H15.1218V8.94064H5.12816V18.8782ZM16.25 8.5992V18.0578L18.8718 15.3883V5.9328L16.25 8.5992ZM5.88752 7.81872H15.4438L18.0914 5.12734H8.4828L5.88752 7.81872Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_49)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_0_49" x="4" y="4.36646" width="16" height="14.8389" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="1.68269"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_0_49"/>
|
||||
</filter>
|
||||
<path id="stroke0_0_49" d="M19.4359 4C19.7462 4 20 4.25249 20 4.56094V15.6203C19.9999 15.7919 19.9193 15.9438 19.7961 16.0469L16.1195 19.7914C16.0159 19.917 15.8611 20 15.6859 20H4.56406C4.25395 20 4.00014 19.7474 4 19.439V8.37968C4 8.36816 4.00242 8.35672 4.00313 8.34528C4.00457 8.3212 4.00638 8.2972 4.01094 8.27344C4.01364 8.2596 4.01816 8.24632 4.02187 8.2328C4.02781 8.21096 4.03354 8.18912 4.04218 8.168C4.0471 8.15608 4.05368 8.14504 4.05938 8.1336C4.07013 8.11184 4.08075 8.08992 4.09453 8.06952C4.10043 8.06072 4.10769 8.05296 4.11406 8.04456C4.12754 8.02688 4.14036 8.0084 4.15625 7.99216L7.84528 4.17344C7.9616 4.05178 8.12072 3.9961 8.27736 4.00313C8.28952 4.00231 8.30168 4 8.31408 4H19.4359ZM5.12816 18.8782H15.1218V8.94064H5.12816V18.8782ZM16.25 8.5992V18.0578L18.8718 15.3883V5.9328L16.25 8.5992ZM5.88752 7.81872H15.4438L18.0914 5.12734H8.4828L5.88752 7.81872Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="view-3d 1">
|
||||
<g id="Rectangle 9122" filter="url(#filter0_i_0_49)">
|
||||
<path d="M20 4.36646H8.37521L4.88 7.74889L4 19.2053H16.2071L20 15.7102V4.36646Z" fill="#003425"/>
|
||||
</g>
|
||||
<g id="Vector">
|
||||
<use xlink:href="#stroke0_0_49" fill="#B6D0C9"/>
|
||||
<mask id="stroke1_0_49" maskUnits="userSpaceOnUse" x="3" y="3" width="18" height="18" fill="black">
|
||||
<rect fill="white" x="3" y="3" width="18" height="18"/>
|
||||
<use xlink:href="#stroke0_0_49"/>
|
||||
</mask>
|
||||
<path d="M19.4359 4C19.7462 4 20 4.25249 20 4.56094V15.6203C19.9999 15.7919 19.9193 15.9438 19.7961 16.0469L16.1195 19.7914C16.0159 19.917 15.8611 20 15.6859 20H4.56406C4.25395 20 4.00014 19.7474 4 19.439V8.37968C4 8.36816 4.00242 8.35672 4.00313 8.34528C4.00457 8.3212 4.00638 8.2972 4.01094 8.27344C4.01364 8.2596 4.01816 8.24632 4.02187 8.2328C4.02781 8.21096 4.03354 8.18912 4.04218 8.168C4.0471 8.15608 4.05368 8.14504 4.05938 8.1336C4.07013 8.11184 4.08075 8.08992 4.09453 8.06952C4.10043 8.06072 4.10769 8.05296 4.11406 8.04456C4.12754 8.02688 4.14036 8.0084 4.15625 7.99216L7.84528 4.17344C7.9616 4.05178 8.12072 3.9961 8.27736 4.00313C8.28952 4.00231 8.30168 4 8.31408 4H19.4359ZM5.12816 18.8782H15.1218V8.94064H5.12816V18.8782ZM16.25 8.5992V18.0578L18.8718 15.3883V5.9328L16.25 8.5992ZM5.88752 7.81872H15.4438L18.0914 5.12734H8.4828L5.88752 7.81872Z" stroke-width="2" stroke="black" mask="url(#stroke1_0_49)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_0_49" x="4" y="4.36646" width="16" height="14.8389" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="1.68269"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_0_49"/>
|
||||
</filter>
|
||||
<path id="stroke0_0_49" d="M19.4359 4C19.7462 4 20 4.25249 20 4.56094V15.6203C19.9999 15.7919 19.9193 15.9438 19.7961 16.0469L16.1195 19.7914C16.0159 19.917 15.8611 20 15.6859 20H4.56406C4.25395 20 4.00014 19.7474 4 19.439V8.37968C4 8.36816 4.00242 8.35672 4.00313 8.34528C4.00457 8.3212 4.00638 8.2972 4.01094 8.27344C4.01364 8.2596 4.01816 8.24632 4.02187 8.2328C4.02781 8.21096 4.03354 8.18912 4.04218 8.168C4.0471 8.15608 4.05368 8.14504 4.05938 8.1336C4.07013 8.11184 4.08075 8.08992 4.09453 8.06952C4.10043 8.06072 4.10769 8.05296 4.11406 8.04456C4.12754 8.02688 4.14036 8.0084 4.15625 7.99216L7.84528 4.17344C7.9616 4.05178 8.12072 3.9961 8.27736 4.00313C8.28952 4.00231 8.30168 4 8.31408 4H19.4359ZM5.12816 18.8782H15.1218V8.94064H5.12816V18.8782ZM16.25 8.5992V18.0578L18.8718 15.3883V5.9328L16.25 8.5992ZM5.88752 7.81872H15.4438L18.0914 5.12734H8.4828L5.88752 7.81872Z"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,142 @@
|
||||
import { useState, type CSSProperties } from "react"
|
||||
import type { SWProcessGroup, SWProcessSplitType, SWProcessStep } from "@hm-design/sw"
|
||||
|
||||
// SW/Step Header demo data and tool icons (2026-11-03). Like a consuming program: the package ships no product
|
||||
// icons, so the Strana tool pictures (HeaderStyles.xaml Header.Icon.*) live here. Fills that change with the button
|
||||
// state use currentColor (the pill button color: #B6D0C9, hover / pressed #FFC600).
|
||||
|
||||
/** Strana Main Header process data (DesignPreviewData.cs). */
|
||||
export const PROCESS_GROUPS: SWProcessGroup[] = [
|
||||
{ id: "modeling", number: "01", label: "Modeling", steps: [{ id: "structure", label: "Structure" }, { id: "loads", label: "Loads" }] },
|
||||
{ id: "analysis", number: "02", label: "Analysis Result", steps: [{ id: "results", label: "Results" }] },
|
||||
]
|
||||
|
||||
/**
|
||||
* StrAna-SW multi step data (Figma 873:16481 / 873:16254 / 873:17421). Groups without a Figma title keep their name for
|
||||
* assistive technology only; the last two groups are not available yet (gray).
|
||||
*/
|
||||
export const MULTI_GROUPS: SWProcessGroup[] = [
|
||||
{ id: "data", label: "데이터 구축", steps: [{ id: "data-input", label: "데이터입력" }, { id: "data-check", label: "데이터확인" }] },
|
||||
{ id: "alignment", label: "선형 입력", labelHidden: true, steps: [{ id: "alignment-input", label: "선형입력" }] },
|
||||
{
|
||||
id: "structure", label: "구조물 상세 및 계획",
|
||||
steps: [{ id: "body-plan", label: "본체계획" }, { id: "body-detail", label: "본체상세" }, { id: "wing-plan", label: "날개벽계획" }, { id: "wing-detail", label: "날개벽상세" }],
|
||||
},
|
||||
{ id: "condition", label: "설계 및 해석조건", labelHidden: true, disabled: true, steps: [{ id: "condition", label: "설계 및 해석조건" }] },
|
||||
{
|
||||
id: "analysis", label: "해석 및 성과품", labelHidden: true, disabled: true,
|
||||
steps: [{ id: "analysis-model", label: "해석모델" }, { id: "design-review", label: "설계검토" }, { id: "deliverable", label: "성과품" }],
|
||||
},
|
||||
]
|
||||
|
||||
/** More groups than the Figma frame (8). */
|
||||
export const MULTI_MANY_GROUPS: SWProcessGroup[] = [
|
||||
...MULTI_GROUPS.slice(0, 3),
|
||||
{ id: "drawing", label: "도면 작성", steps: [{ id: "drawing-create", label: "도면생성" }, { id: "drawing-edit", label: "도면편집" }] },
|
||||
{ id: "quantity", label: "수량 산출", labelHidden: true, steps: [{ id: "quantity", label: "수량산출" }] },
|
||||
...MULTI_GROUPS.slice(3),
|
||||
{ id: "report", label: "보고서", disabled: true, steps: [{ id: "report-write", label: "보고서작성" }, { id: "report-print", label: "출력" }] },
|
||||
]
|
||||
|
||||
/** More steps in one group (8 in 구조물 상세 및 계획). */
|
||||
export const MULTI_MANY_STEPS: SWProcessGroup[] = MULTI_GROUPS.map(group => group.id !== "structure" ? group : {
|
||||
...group,
|
||||
steps: [
|
||||
...group.steps,
|
||||
{ id: "base-plan", label: "기초계획" }, { id: "base-detail", label: "기초상세" }, { id: "drain-plan", label: "배수계획" }, { id: "drain-detail", label: "배수상세" },
|
||||
],
|
||||
})
|
||||
|
||||
const OUTLINE = "#000"
|
||||
|
||||
export function ViewPropertyIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||||
<path d="M5,3.5 H19 C19.8284,3.5 20.5,4.1716 20.5,5 V19 C20.5,19.8285 19.8284,20.5 19,20.5 H5 C4.1716,20.5 3.5,19.8285 3.5,19 V5 C3.5,4.1716 4.1716,3.5 5,3.5 Z" fill="#0b3227" stroke={OUTLINE} />
|
||||
<path d="M6.0469,5.5481 H17.9507 C18.2268,5.5481 18.4507,5.772 18.4507,6.0481 V17.9519 C18.4507,18.2281 18.2268,18.4519 17.9507,18.4519 H6.0469 C5.7707,18.4519 5.5469,18.2281 5.5469,17.9519 V6.0481 C5.5469,5.772 5.7707,5.5481 6.0469,5.5481 Z" fill="#003022" stroke={OUTLINE} />
|
||||
<path fillRule="evenodd" fill="currentColor" d="M18.11,5.1359 C18.5265,5.1359 18.8642,5.4735 18.8642,5.8901 V18.11 C18.8642,18.5266 18.5265,18.8642 18.11,18.8642 H5.89 C5.4735,18.8642 5.1358,18.5266 5.1358,18.11 V5.8901 C5.1358,5.4735 5.4735,5.1359 5.89,5.1359 H18.11 Z M20,4.7542 C20,4.3377 19.6623,4 19.2458,4 H4.7542 C4.3377,4 4,4.3377 4,4.7542 V19.2459 C4,19.6624 4.3377,20 4.7542,20 H19.2458 C19.6623,20 20,19.6624 20,19.2459 V4.7542 Z" />
|
||||
<path fill="currentColor" opacity="0.85" d="M6.7578,6.7586 H14.5249 V12.0545 H6.7578 Z M14.9258,6.7586 H17.2425 V12.0545 H14.9258 Z M12.9164,15.258 H13.9841 V17.4899 H12.9164 Z M10.1078,13.2172 H11.1755 V15.334 H10.1078 Z" />
|
||||
<path fill="none" stroke={OUTLINE} strokeWidth="0.5" d="M6.7578,6.7586 H14.5249 V12.0545 H6.7578 Z M14.9258,6.7586 H17.2425 V12.0545 H14.9258 Z" />
|
||||
<path fill="none" stroke={OUTLINE} strokeWidth="0.3" d="M12.9164,15.258 H13.9841 V17.4899 H12.9164 Z M10.1078,13.2172 H11.1755 V15.334 H10.1078 Z" />
|
||||
<path fill="currentColor" stroke={OUTLINE} strokeWidth="0.3" opacity="0.5" d="M14.7445,15.7826 H17.1428 V16.8502 H14.7445 Z M11.8539,13.9657 H17.15 V15.0333 H11.8539 Z M6.8578,13.9657 H9.4288 V15.0333 H6.8578 Z M6.8578,15.7823 H12.1539 V16.85 H6.8578 Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
/** Sub window button picture: two monitors; the back one brightens on hover (story CSS, like Strana). */
|
||||
export function SubWindowIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" className="sw-process-story-sub-icon">
|
||||
<g transform="translate(0 4.81)">
|
||||
<path className="sw-process-story-sub-icon__back" fill="currentColor" d="M0.6836 1.4039C0.6836 1.0078 1.0047 0.6868 1.4007 0.6868H14.3084C14.7044 0.6868 15.0255 1.0078 15.0255 1.4039V10.0089C15.0255 10.4049 14.7044 10.726 14.3084 10.726H9.2888V12.4496H10.0057C10.4017 12.4496 10.7228 12.7059 10.7228 13.022C10.7228 13.3382 10.4017 13.5945 10.0057 13.5945H5.7031C5.3071 13.5945 4.986 13.3382 4.986 13.022C4.986 12.7059 5.3071 12.4496 5.7031 12.4496H6.4204V10.726H1.4007C1.0047 10.726 0.6836 10.4049 0.6836 10.0089V1.4039ZM1.8343 1.851V9.5785H7.8545H13.8715V1.851H1.8343Z" />
|
||||
<path fill={OUTLINE} opacity="0.5" fillRule="evenodd" d="M0 1.40417C0 0.628669 0.628669 0 1.40417 0H14.3119C15.0874 0 15.716 0.628669 15.716 1.40417V10.0093C15.716 10.7848 15.0874 11.4135 14.3119 11.4135H9.97936V11.7629H10.0092C10.6322 11.7629 11.4133 12.1905 11.4133 13.0225C11.4133 13.8544 10.6322 14.282 10.0092 14.282H5.7066C5.08357 14.282 4.30243 13.8544 4.30243 13.0225C4.30243 12.1905 5.08357 11.7629 5.7066 11.7629H5.73682V11.4135H1.40417C0.628669 11.4135 0 10.7848 0 10.0093V1.40417ZM6.4239 12.45H5.7066C5.31056 12.45 4.98951 12.7063 4.98951 13.0225C4.98951 13.3386 5.31056 13.5949 5.7066 13.5949H10.0092C10.4052 13.5949 10.7263 13.3386 10.7263 13.0225C10.7263 12.7063 10.4052 12.45 10.0092 12.45H9.29228V10.7264H14.3119C14.7079 10.7264 15.029 10.4053 15.029 10.0093V1.40417C15.029 1.00813 14.7079 0.687077 14.3119 0.687077H1.40417C1.00813 0.687077 0.687077 1.00813 0.687077 1.40417V10.0093C0.687077 10.4053 1.00813 10.7264 1.40417 10.7264H6.4239V12.45ZM2.52486 2.53837V8.8918H13.1879V2.53837H2.52486ZM1.83778 1.85129H13.875V9.57887H1.83778V1.85129Z" />
|
||||
<path fill="currentColor" d="M8.97266 1.40385C8.97266 1.00782 9.29371 0.686768 9.68975 0.686768H22.5975C22.9935 0.686768 23.3145 1.00782 23.3145 1.40385V10.0089C23.3145 10.4049 22.9935 10.726 22.5975 10.726H17.5779V12.4496H18.2948C18.6908 12.4496 19.0118 12.7059 19.0118 13.022C19.0118 13.3382 18.6908 13.5945 18.2948 13.5945H13.9922C13.5961 13.5945 13.2751 13.3382 13.2751 13.022C13.2751 12.7059 13.5961 12.4496 13.9922 12.4496H14.7095V10.726H9.68975C9.29371 10.726 8.97266 10.4049 8.97266 10.0089V1.40385ZM10.1234 1.85097V9.57847H16.1436H22.1606V1.85097H10.1234Z" />
|
||||
<path fill={OUTLINE} fillRule="evenodd" d="M8.28125 1.40417C8.28125 0.628669 8.90992 0 9.68542 0H22.5931C23.3686 0 23.9973 0.628669 23.9973 1.40417V10.0093C23.9973 10.7848 23.3686 11.4135 22.5931 11.4135H18.2606V11.7629H18.2904C18.9135 11.7629 19.6946 12.1905 19.6946 13.0225C19.6946 13.8544 18.9134 14.282 18.2904 14.282H13.9879C13.3648 14.282 12.5837 13.8544 12.5837 13.0225C12.5837 12.1905 13.3648 11.7629 13.9879 11.7629H14.0181V11.4135H9.68542C8.90992 11.4135 8.28125 10.7848 8.28125 10.0093V1.40417ZM14.7052 12.45H13.9879C13.5918 12.45 13.2708 12.7063 13.2708 13.0225C13.2708 13.3386 13.5918 13.5949 13.9879 13.5949H18.2904C18.6865 13.5949 19.0075 13.3386 19.0075 13.0225C19.0075 12.7063 18.6865 12.45 18.2904 12.45H17.5735V10.7264H22.5931C22.9892 10.7264 23.3102 10.4053 23.3102 10.0093V1.40417C23.3102 1.00813 22.9892 0.687077 22.5931 0.687077H9.68542C9.28938 0.687077 8.96833 1.00813 8.96833 1.40417V10.0093C8.96833 10.4053 9.28938 10.7264 9.68542 10.7264H14.7052V12.45ZM10.8061 2.53837V8.8918H21.4692V2.53837H10.8061ZM10.119 1.85129H22.1563V9.57887H10.119V1.85129Z" />
|
||||
<path fill="#0c372a" d="M10.7852 2.54395H21.476V8.97411H10.7852V2.54395Z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Demo program state: every header state is the program's; actions without state (tools, minimize, close, file name,
|
||||
* window move) have nothing to show and are no-ops.
|
||||
*/
|
||||
export function useProcessHeaderDemo(initialStep = "structure") {
|
||||
const [stepId, setStepId] = useState(initialStep)
|
||||
const [layerVisible, setLayerVisible] = useState(true)
|
||||
const [layerOpen, setLayerOpen] = useState(false)
|
||||
const [homeOpen, setHomeOpen] = useState(false)
|
||||
const [viewProperty, setViewProperty] = useState(false)
|
||||
const [maximized, setMaximized] = useState(true)
|
||||
const [splitType, setSplitType] = useState<SWProcessSplitType>("grid")
|
||||
const [splitOpen, setSplitOpen] = useState(false)
|
||||
const none = () => {}
|
||||
return {
|
||||
stepId, layerVisible, layerOpen, homeOpen, viewProperty, maximized, splitType, splitOpen,
|
||||
onSplitTypeChange: setSplitType,
|
||||
onSplitOpenChange: setSplitOpen,
|
||||
onStepSelect: (step: SWProcessStep) => setStepId(step.id),
|
||||
onLayerVisibilityChange: setLayerVisible,
|
||||
onLayerClick: () => setLayerOpen(open => !open),
|
||||
onHomeClick: () => setHomeOpen(open => !open),
|
||||
onViewProperty: () => setViewProperty(on => !on),
|
||||
onTool: (_name: string) => none,
|
||||
onMaximizeToggle: () => setMaximized(on => !on),
|
||||
onMinimize: none,
|
||||
onClose: none,
|
||||
onDocumentClick: none,
|
||||
onWindowMoveStart: none,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strana Dark theme through token overrides on the outermost SW scope (docs/sw/sw-process-header-design-spec.md §7).
|
||||
* Partial on purpose: shows the mechanism with the solid colors and the main surfaces; the rest keeps Hanmac.
|
||||
*/
|
||||
export const DARK_OVERRIDE = {
|
||||
"--hm-sw-header-background": "#0b0d11",
|
||||
"--hm-sw-header-text-color": "#f4f7fa",
|
||||
"--hm-sw-header-title-color": "#f4f7fa",
|
||||
"--hm-sw-header-selected-color": "#ffc833",
|
||||
"--hm-sw-header-stripe-color": "rgba(63, 72, 86, 0.322)",
|
||||
"--hm-sw-header-document-background": "linear-gradient(180deg, #12151a 0%, #181c23 8.69%, #1e232b 25.43%, #262c36 51.25%, #1e232b 80.03%, #1a1e25 89.76%, #12151a 100%)",
|
||||
"--hm-sw-header-document-border-gradient": "linear-gradient(180deg, #3a4250 0%, #0b0d11 13.73%, #0b0d11 89.51%, #3f4856 100%)",
|
||||
"--hm-sw-header-object-background": "linear-gradient(0deg, #1c2027 0%, #20252d 6%, #22272f 10%, #1a1e25 18%, #353d48 72%, #2a3039 83%, #1c2027 92%, #1e232b 100%)",
|
||||
"--hm-sw-header-object-border-gradient": "linear-gradient(180deg, #313843 0%, #1e232b 13.57%, #74c4b4 37.06%, #12151a 80.5%, #2e353f 100%)",
|
||||
"--hm-sw-process-base-background": "linear-gradient(180deg, #1c2027 0%, #4a5260 10.1%, #313843 18.27%, #1c2027 100%)",
|
||||
"--hm-sw-process-dim": "rgba(11, 13, 17, 0.902)",
|
||||
"--hm-sw-process-number-color": "#74c4b4",
|
||||
"--hm-sw-process-group-color": "#c9d3de",
|
||||
"--hm-sw-process-glow-halo": "rgba(116, 196, 180, 0.85)",
|
||||
"--hm-sw-process-arrow-fill": "#74c4b4",
|
||||
"--hm-sw-process-arrow-edge": "#1a1e25",
|
||||
"--hm-sw-process-arrow-mid": "#4a5260",
|
||||
"--hm-sw-process-arrow-light": "#5e8f87",
|
||||
"--hm-sw-process-tab-glow": "rgba(74, 82, 96, 0.702)",
|
||||
"--hm-sw-process-tab-border-gradient": "linear-gradient(180deg, #0b0d11 8.98%, #4a5260 22.9%, #262c36 100%)",
|
||||
"--hm-sw-process-tab-depth": "linear-gradient(180deg, rgba(26, 30, 37, 0) 0%, rgba(26, 30, 37, 0.8) 100%)",
|
||||
"--hm-sw-process-tab-sheen": "linear-gradient(180deg, rgba(116, 196, 180, 0.502) 0%, rgba(116, 196, 180, 0) 40%)",
|
||||
"--hm-sw-process-tab-disabled-text": "#6d7784",
|
||||
"--hm-sw-process-tool-icon-color": "#c9d3de",
|
||||
} as CSSProperties
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Story-owned layout for SW/Step Header. Like a consuming program it arranges public components and its own
|
||||
fixture icons; it never styles hm-sw-* classes. Rows, stacks and scrollers are the shared SW story classes. */
|
||||
.sw-process-story-screen { width: 100%; max-width: 1920px; background: #060606; }
|
||||
.sw-process-story-canvas { height: 120px; background: var(--sw-story-canvas, #1b1b1b); }
|
||||
.sw-process-story-surface { padding: 12px; border-radius: 4px; background: #060606; }
|
||||
/* Strana Header.Icon.ToolSubMonitor: the back monitor is 20% and 50% while its button is hovered or pressed. */
|
||||
.sw-process-story-sub-icon__back { opacity: 0.2; }
|
||||
button:is(:hover, [aria-pressed="true"]) .sw-process-story-sub-icon__back { opacity: 0.5; }
|
||||
@@ -0,0 +1,350 @@
|
||||
import { useState, type ReactNode } from "react"
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import {
|
||||
SWProcessBar, SWProcessHeader, SWProcessSplitButton, SWTheme, SWToolButton, type SWProcessGroup, type SWProcessHeaderProps, type SWProcessSplitType,
|
||||
} from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import {
|
||||
DARK_OVERRIDE, MULTI_GROUPS, MULTI_MANY_GROUPS, MULTI_MANY_STEPS, PROCESS_GROUPS, SubWindowIcon, useProcessHeaderDemo, ViewPropertyIcon,
|
||||
} from "./sw-process-header-fixture"
|
||||
import "./sw-stories.css"
|
||||
import "./sw-process-header-stories.css"
|
||||
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
|
||||
import toolHelp from "./assets/process-header/tool-help.svg"
|
||||
import toolView3d from "./assets/process-header/tool-view-3d.svg"
|
||||
import toolControls from "./assets/process-header/tool-controls.svg"
|
||||
import toolMenuList from "./assets/process-header/tool-menu-list.svg"
|
||||
import toolExit from "./assets/process-header/tool-exit.svg"
|
||||
import toolHelpHover from "./assets/process-header/tool-help-hover.svg"
|
||||
import toolView3dHover from "./assets/process-header/tool-view-3d-hover.svg"
|
||||
import toolControlsHover from "./assets/process-header/tool-controls-hover.svg"
|
||||
import toolMenuListHover from "./assets/process-header/tool-menu-list-hover.svg"
|
||||
import toolExitHover from "./assets/process-header/tool-exit-hover.svg"
|
||||
|
||||
// SW/Step Header (2026-11-03, shown as "Step Header"; the components keep the SWProcess* names): the Strana Main Header as an independent SW general-mode header. Public API only,
|
||||
// like a consuming program. The logo is the temporary SW placeholder through `logoSrc` (replace with the real logo).
|
||||
// Page layout as SW/Header and SW/Viewer: title, then sections; checks stay as verification stories (hidden).
|
||||
const KICKER = "HM DESIGN SYSTEM / SW"
|
||||
const LOGO = { logoSrc: placeholderLogo, logoAlt: "임시 로고" } satisfies Partial<SWProcessHeaderProps>
|
||||
|
||||
const meta = {
|
||||
title: "SW/Step Header",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
} satisfies Meta
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
interface SplitState { value: SWProcessSplitType; open?: boolean; defaultOpen?: boolean; onValueChange?: (value: SWProcessSplitType) => void; onOpenChange?: (open: boolean) => void }
|
||||
|
||||
/** The three Strana tools as host content of the pill: fixture icons (SWToolButton) and the package split tool. */
|
||||
function Tools({ viewProperty = false, onViewProperty, onTool, split = { value: "grid" } }: {
|
||||
viewProperty?: boolean; onViewProperty?: () => void; onTool?: (name: string) => () => void; split?: SplitState
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<SWToolButton appearance="pill" icon={<ViewPropertyIcon />} pressed={viewProperty} onClick={onViewProperty} title="View Property">View Property</SWToolButton>
|
||||
<SWProcessSplitButton {...split} />
|
||||
<SWToolButton appearance="pill" icon={<SubWindowIcon />} onClick={onTool?.("subWindow")} title="Sub 창">Sub 창</SWToolButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** A header with fixed demo state (no program behind it). */
|
||||
function StaticHeader({ step = "structure", ...props }: Partial<SWProcessHeaderProps> & { step?: string }) {
|
||||
return (
|
||||
<SWProcessHeader documentTitle="Example project" {...LOGO} process={{ groups: PROCESS_GROUPS, activeStepId: step }}
|
||||
layer={{ label: "Layer", value: "Layer 0" }} tools={<Tools />} maximized {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function LiveHeader() {
|
||||
const demo = useProcessHeaderDemo()
|
||||
return (
|
||||
<div className="sw-process-story-screen">
|
||||
<SWProcessHeader
|
||||
documentTitle="Example project" onDocumentClick={demo.onDocumentClick}
|
||||
{...LOGO} onHomeClick={demo.onHomeClick} homeMenuOpen={demo.homeOpen}
|
||||
process={{ groups: PROCESS_GROUPS, activeStepId: demo.stepId, onStepSelect: demo.onStepSelect }}
|
||||
layer={{ label: "Layer", value: "Layer 0", visible: demo.layerVisible, open: demo.layerOpen }}
|
||||
onLayerClick={demo.onLayerClick} onLayerVisibilityChange={demo.onLayerVisibilityChange}
|
||||
tools={<Tools viewProperty={demo.viewProperty} onViewProperty={demo.onViewProperty} onTool={demo.onTool}
|
||||
split={{ value: demo.splitType, open: demo.splitOpen, onValueChange: demo.onSplitTypeChange, onOpenChange: demo.onSplitOpenChange }} />}
|
||||
onWindowMoveStart={demo.onWindowMoveStart} maximized={demo.maximized}
|
||||
onMinimize={demo.onMinimize} onMaximizeToggle={demo.onMaximizeToggle} onClose={demo.onClose} />
|
||||
<div className="sw-process-story-canvas" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The five Figma tools (fixture icons from the Figma file; the package ships no product icons). Like the SW print
|
||||
* button, each has a hover picture with the #B6D0C9 fill in #FFC600 (`hoverIcon`).
|
||||
*/
|
||||
function MultiTools({ onTool, split }: { onTool?: (name: string) => () => void; split?: SplitState }) {
|
||||
const tool = (name: string, src: string, hover: string, label: string) => (
|
||||
<SWToolButton appearance="pill" icon={<img src={src} alt="" />} hoverIcon={<img src={hover} alt="" />} onClick={onTool?.(name)} title={label}>{label}</SWToolButton>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{tool("help", toolHelp, toolHelpHover, "도움말")}{tool("view3d", toolView3d, toolView3dHover, "3D 보기")}
|
||||
{split && <SWProcessSplitButton {...split} />}
|
||||
{tool("controls", toolControls, toolControlsHover, "설정")}{tool("menu", toolMenuList, toolMenuListHover, "메뉴")}
|
||||
{tool("exit", toolExit, toolExitHover, "나가기")}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** Figma multi step frame: no layer field, only minimize and close. */
|
||||
function MultiHeader({ step = "data-input", groups = MULTI_GROUPS, ...props }: Partial<SWProcessHeaderProps> & { step?: string; groups?: SWProcessGroup[] }) {
|
||||
return (
|
||||
<SWProcessHeader appearance="multi" documentTitle="" documentEmptyLabel="프로젝트 생성 전" {...LOGO}
|
||||
process={{ groups, activeStepId: step }} tools={<MultiTools />} hiddenControls={["maximize"]} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
/** A Figma multi step frame with its own program state (steps, logo, tools and window buttons respond). */
|
||||
function LiveMultiHeader({ step, documentTitle = "" }: { step: string; documentTitle?: string }) {
|
||||
const demo = useProcessHeaderDemo(step)
|
||||
return (
|
||||
<MultiHeader documentTitle={documentTitle} onDocumentClick={demo.onDocumentClick}
|
||||
onHomeClick={demo.onHomeClick} homeMenuOpen={demo.homeOpen}
|
||||
process={{ groups: MULTI_GROUPS, activeStepId: demo.stepId, onStepSelect: demo.onStepSelect }}
|
||||
tools={<MultiTools onTool={demo.onTool} />}
|
||||
onWindowMoveStart={demo.onWindowMoveStart} onMinimize={demo.onMinimize} onClose={demo.onClose} />
|
||||
)
|
||||
}
|
||||
|
||||
/** Split tool with a live layout (the selector opens on load; choosing a layout updates the icon and the checked cell). */
|
||||
function useSplitDemo(initial: SWProcessSplitType): SplitState {
|
||||
const [value, setValue] = useState(initial)
|
||||
return { value, onValueChange: setValue, defaultOpen: true }
|
||||
}
|
||||
|
||||
/** Row of a page: as wide as the Story column, at most `max` (like the SW/Header Logo and Responsive rows). */
|
||||
function Row({ max = 1920, children }: { max?: number; children: ReactNode }) {
|
||||
return <div style={{ width: "100%", maxWidth: max }}>{children}</div>
|
||||
}
|
||||
|
||||
/** A fixed program width in a horizontal scroller (2560 / 1920 are wider than most pages). Verification only. */
|
||||
function Fixed({ width, children }: { width: number; children: ReactNode }) {
|
||||
return <div className="sw-story-scroller"><div style={{ width }} data-width={width}>{children}</div></div>
|
||||
}
|
||||
|
||||
// ---------- Pages ----------
|
||||
|
||||
// Figma StrAna-SW multi step frames, in this order (the node ids stay as data only, never shown):
|
||||
// 873:16481 no project yet, 873:16254 project open, 873:17421 a later step.
|
||||
const FIGMA_FRAMES: { node: string; step: string; documentTitle?: string }[] = [
|
||||
{ node: "873:16481", step: "data-input" },
|
||||
{ node: "873:16254", step: "data-input", documentTitle: "Example project" },
|
||||
{ node: "873:17421", step: "body-plan" },
|
||||
]
|
||||
|
||||
// One page (2026-11-03): the Default header, then the three Multi Step frames; each header keeps its own state.
|
||||
export const ProcessHeader: Story = {
|
||||
name: "Step Header",
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Step Header" />
|
||||
<StorySection title="Step Header">
|
||||
<LiveHeader />
|
||||
</StorySection>
|
||||
<StorySection title="Multi Step">
|
||||
<div className="sw-story-stack">
|
||||
{FIGMA_FRAMES.map(frame => (
|
||||
<Row key={frame.node}><div data-figma-node={frame.node}><LiveMultiHeader step={frame.step} documentTitle={frame.documentTitle} /></div></Row>
|
||||
))}
|
||||
</div>
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
const WIDTHS = [1920, 1366, 1100]
|
||||
|
||||
export const Responsive: Story = {
|
||||
name: "Responsive",
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Step Header — 화면 너비" />
|
||||
<div className="sw-story-stack sw-story-responsive">
|
||||
{WIDTHS.map(width => (
|
||||
// The 1920 row shows only on a page with 1920px of room, like the SW Header / Viewer Responsive pages.
|
||||
<div key={width} className={width === 1920 ? "sw-story-responsive-hidden" : undefined}>
|
||||
<StorySection title={`${width}px`}>
|
||||
<Row max={width}>
|
||||
<div className="sw-story-stack" data-width={width}>
|
||||
<StaticHeader />
|
||||
<MultiHeader step="body-plan" documentTitle="Example project" />
|
||||
</div>
|
||||
</Row>
|
||||
</StorySection>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
/** Split selector open in both appearances (Default leftTall, Multi rows), over the drawing area. */
|
||||
function SplitOpenPage() {
|
||||
const defaultSplit = useSplitDemo("leftTall")
|
||||
const multiSplit = useSplitDemo("rows")
|
||||
return (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Step Header — 화면 분할" />
|
||||
<div className="sw-story-stack">
|
||||
<StorySection title="Default">
|
||||
<div className="sw-process-story-screen" data-appearance="default"><StaticHeader tools={<Tools split={defaultSplit} />} /><div className="sw-process-story-canvas" /></div>
|
||||
</StorySection>
|
||||
<StorySection title="Multi Step">
|
||||
<div className="sw-process-story-screen" data-appearance="multi"><MultiHeader documentTitle="Example project" tools={<MultiTools split={multiSplit} />} /><div className="sw-process-story-canvas" /></div>
|
||||
</StorySection>
|
||||
</div>
|
||||
</StoryPage>
|
||||
)
|
||||
}
|
||||
|
||||
// Hidden from the sidebar (2026-11-03): the split tool works in the Step Header page; this open-state check stays.
|
||||
export const SplitOpen: Story = {
|
||||
name: "화면 분할",
|
||||
tags: ["verification"],
|
||||
render: () => <SplitOpenPage />,
|
||||
}
|
||||
|
||||
// ---------- Verification (hidden from the sidebar; direct URLs kept for checks) ----------
|
||||
|
||||
/** The three Multi Step frames at the Figma 1920 width, marked with their node ids for comparisons. */
|
||||
export const MultiStep: Story = {
|
||||
name: "Multi Step",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="Multi Step" />
|
||||
<div className="sw-story-stack">
|
||||
{FIGMA_FRAMES.map(frame => (
|
||||
<div key={frame.node} data-figma-node={frame.node}><Fixed width={1920}><MultiHeader step={frame.step} documentTitle={frame.documentTitle ?? ""} /></Fixed></div>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const FixedWidths: Story = {
|
||||
name: "Fixed Widths",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="Fixed Widths" />
|
||||
<div className="sw-story-stack">
|
||||
{[1100, 1366, 1920, 2560].map(width => (
|
||||
<StorySection key={width} title={`${width}px`}>
|
||||
<div className="sw-story-stack">
|
||||
<Fixed width={width}><StaticHeader /></Fixed>
|
||||
<Fixed width={width}><MultiHeader step="body-plan" documentTitle="Example project" /></Fixed>
|
||||
</div>
|
||||
</StorySection>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
const DISABLED_LOADS: SWProcessGroup[] = [
|
||||
{ ...PROCESS_GROUPS[0], steps: [PROCESS_GROUPS[0].steps[0], { ...PROCESS_GROUPS[0].steps[1], disabled: true }] },
|
||||
PROCESS_GROUPS[1],
|
||||
]
|
||||
|
||||
export const ProcessBar: Story = {
|
||||
name: "Process Bar",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="SWProcessBar" />
|
||||
<SWTheme className="sw-process-story-surface">
|
||||
<div className="sw-story-stack">
|
||||
<div style={{ width: 1003 }}><SWProcessBar groups={PROCESS_GROUPS} activeStepId="structure" /></div>
|
||||
<div style={{ width: 1003 }}><SWProcessBar groups={PROCESS_GROUPS} activeStepId="results" /></div>
|
||||
<div style={{ width: 520 }}><SWProcessBar groups={PROCESS_GROUPS} activeStepId="loads" /></div>
|
||||
<div style={{ width: 1003 }}><SWProcessBar groups={DISABLED_LOADS} activeStepId="structure" /></div>
|
||||
<div style={{ width: 1003 }}><SWProcessBar groups={PROCESS_GROUPS} activeStepId="structure" disabled /></div>
|
||||
<div style={{ width: 1278 }}><SWProcessBar appearance="multi" groups={MULTI_GROUPS} activeStepId="data-input" /></div>
|
||||
<div style={{ width: 1278 }}><SWProcessBar appearance="multi" groups={MULTI_GROUPS} activeStepId="body-plan" /></div>
|
||||
</div>
|
||||
</SWTheme>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const MultiStepManyGroups: Story = {
|
||||
name: "Multi Step · Many Groups",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="Multi Step · Many Groups" />
|
||||
<div className="sw-story-stack">
|
||||
<Fixed width={1920}><MultiHeader groups={MULTI_MANY_GROUPS} step="drawing-edit" documentTitle="Example project" /></Fixed>
|
||||
<Fixed width={1920}><MultiHeader groups={MULTI_MANY_GROUPS} step="body-plan" documentTitle="Example project" /></Fixed>
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const MultiStepManySteps: Story = {
|
||||
name: "Multi Step · Many Steps",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="Multi Step · Many Steps" />
|
||||
<div className="sw-story-stack">
|
||||
<Fixed width={1920}><MultiHeader groups={MULTI_MANY_STEPS} step="drain-detail" documentTitle="Example project" /></Fixed>
|
||||
<Fixed width={1920}><MultiHeader groups={MULTI_MANY_STEPS} step="data-check" documentTitle="Example project" /></Fixed>
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
// Three groups: renders, design not confirmed (trapezoids only on the first and last group). Verification only.
|
||||
const THREE_GROUPS: SWProcessGroup[] = [
|
||||
PROCESS_GROUPS[0],
|
||||
{ id: "design", number: "02", label: "Design", steps: [{ id: "member", label: "Member" }, { id: "joint", label: "Joint", disabled: true }] },
|
||||
{ ...PROCESS_GROUPS[1], number: "03" },
|
||||
]
|
||||
|
||||
const STATES: [string, Partial<SWProcessHeaderProps>][] = [
|
||||
["default", {}],
|
||||
["results", { process: { groups: PROCESS_GROUPS, activeStepId: "results" } }],
|
||||
["layer-open", { layer: { label: "Layer", value: "Layer 0", open: true, visible: false }, tools: <Tools viewProperty /> }],
|
||||
["home-open", { homeMenuOpen: true, maximized: false }],
|
||||
["disabled", { disabled: true }],
|
||||
["hidden-controls", { hiddenControls: ["tools", "windowMove", "maximize"] }],
|
||||
["long-names", { documentTitle: "A very long project file name for the ellipsis check.strn", layer: { label: "Layer", value: "Foundation_Pile_Cap_Layer_Long_Name" } }],
|
||||
["three-groups", { process: { groups: THREE_GROUPS, activeStepId: "member" } }],
|
||||
]
|
||||
|
||||
export const States: Story = {
|
||||
name: "States",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="States" />
|
||||
<div className="sw-story-stack">
|
||||
{STATES.map(([state, props]) => (
|
||||
<div key={state} data-state={state}><Fixed width={1920}><StaticHeader {...props} /></Fixed></div>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
export const ThemeOverride: Story = {
|
||||
name: "Theme Override",
|
||||
tags: ["verification"],
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader kicker={KICKER} title="Theme Override" />
|
||||
<Fixed width={1920}><StaticHeader style={DARK_OVERRIDE} /></Fixed>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||