Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a09386b11 | ||
|
|
2690a160e9 | ||
|
|
d159ad58af | ||
|
|
b6b7c656e3 |
No files matched your search
@@ -90,7 +90,7 @@ Collapsible의 trigger/content는 `asChild`로 자체 `button`/`div`를 써서 C
|
|||||||
| 연결선 | 3뎁스만, 1px, 세로선 x 68, 가로 12, 글자까지 8, 행 세로 중앙, 마지막 행 중앙에서 끝 | 행마다 background 2겹(세로 `1px × 100%`, 마지막 행 `1px × 50%`, 가로 `12 × 1` at `calc(50% - 0.5px)`). DOM·tab stop·aria 없음 | MATCHED / IMPLEMENTATION_CHANGE(명세의 "배경 레이어" 방식) |
|
| 연결선 | 3뎁스만, 1px, 세로선 x 68, 가로 12, 글자까지 8, 행 세로 중앙, 마지막 행 중앙에서 끝 | 행마다 background 2겹(세로 `1px × 100%`, 마지막 행 `1px × 50%`, 가로 `12 × 1` at `calc(50% - 0.5px)`). DOM·tab stop·aria 없음 | MATCHED / IMPLEMENTATION_CHANGE(명세의 "배경 레이어" 방식) |
|
||||||
| 연결선 색 | 기본·hover `--hm-green-tint` 40%, 선택 `--hm-green-light-2` 40% | 같음. 선택 색은 선택된 행의 선에만 적용(나머지 행은 기본색) | MATCHED |
|
| 연결선 색 | 기본·hover `--hm-green-tint` 40%, 선택 `--hm-green-light-2` 40% | 같음. 선택 색은 선택된 행의 선에만 적용(나머지 행은 기본색) | MATCHED |
|
||||||
| 접힘 | 하위 메뉴·연결선 함께 숨김 | Radix가 내용을 unmount → 선도 같이 사라짐 | MATCHED |
|
| 접힘 | 하위 메뉴·연결선 함께 숨김 | Radix가 내용을 unmount → 선도 같이 사라짐 | MATCHED |
|
||||||
| 중첩 그룹 | 배경·테두리·padding 중복, 폭 감소, 오른쪽 틈 금지 | 안쪽 그룹은 면·선·padding-bottom 없음, 폭 100%(바깥 그룹과 같은 252), 선택 항목 오른쪽 273(1px 겹침) | MATCHED |
|
| 중첩 그룹 | 배경·테두리·padding 중복, 폭 감소, 오른쪽 틈 금지 | 안쪽 그룹은 면·선·padding-bottom 없음, 폭 100%(바깥 그룹과 같은 220), 선택 항목 오른쪽 241(1px 겹침). 2026-11-10 240px 열 실측(Third Level, 1440); 이전 252/273은 272px 열 기준 | MATCHED |
|
||||||
| 안쪽 그룹 이름 위치 | 명세 수치 없음, 이미지상 2뎁스 글자와 같은 시작선 | trigger padding-left = 68 − 화살표 18 − gap 12 = 38 → 글자 x 68 | INTENTIONAL_CHANGE(이미지 기준) |
|
| 안쪽 그룹 이름 위치 | 명세 수치 없음, 이미지상 2뎁스 글자와 같은 시작선 | trigger padding-left = 68 − 화살표 18 − gap 12 = 38 → 글자 x 68 | INTENTIONAL_CHANGE(이미지 기준) |
|
||||||
|
|
||||||
모바일(<1024): LNB ONLY는 Desktop 열을 숨기고 같은 메뉴를 독립 `LNBMobileHeader / LNBMobileMenu`로 표시한다(§3b). GNB ONLY 내부 Sidebar는 [GNB §15](gnb.md#15-gnb-only-sidebar-composition-2026-10-08)의 GNB 단일 Sheet를 사용하며 독립 LNB Mobile Header/Menu를 추가하지 않는다. DASHBOARD ONLY 운영 화면에는 Sidebar가 없다([Dashboard 현재 계약](dashboard-layout.md#1-현재-계약)).
|
모바일(<1024): LNB ONLY는 Desktop 열을 숨기고 같은 메뉴를 독립 `LNBMobileHeader / LNBMobileMenu`로 표시한다(§3b). GNB ONLY 내부 Sidebar는 [GNB §15](gnb.md#15-gnb-only-sidebar-composition-2026-10-08)의 GNB 단일 Sheet를 사용하며 독립 LNB Mobile Header/Menu를 추가하지 않는다. DASHBOARD ONLY 운영 화면에는 Sidebar가 없다([Dashboard 현재 계약](dashboard-layout.md#1-현재-계약)).
|
||||||
|
|||||||
@@ -90,9 +90,17 @@ Strana의 2그룹(01 Modeling: Structure · Loads / 02 Analysis Result: Results)
|
|||||||
|
|
||||||
전체 목록과 Strana 원본 키는 §7 표의 왼쪽 두 열을 봅니다.
|
전체 목록과 Strana 원본 키는 §7 표의 왼쪽 두 열을 봅니다.
|
||||||
|
|
||||||
|
**Step Header 전용 alias (2026-11-10).** Step Header 규칙이 칠하는 SWHeader 토큰 18개는 같은 이름의 `--hm-sw-process-*`(가장 바깥 SW 범위, 기본값 `var(--hm-sw-header-…)`)를 거쳐 읽습니다: `background`, `height`, `text-color`, `selected-color`, `hover-border-gradient`, `hover-border-width`, `document-background`, `document-border-gradient`, `document-border-color`, `object-background`, `object-border-gradient`, `object-radius`, `property-background`, `property-radius`, `property-shadow`, `window-icon-color`, `window-icon-hover-color`, `window-button-hover-background`. `--hm-sw-process-base-border-gradient`는 `--hm-sw-process-document-border-gradient`를 거쳐 같은 기본값을 씁니다.
|
||||||
|
|
||||||
|
- 기본(상속): `--hm-sw-process-*`가 없으면 가장 바깥 범위의 SWHeader 토큰 값이 그대로 적용됩니다. 계산값은 변경 전과 같습니다.
|
||||||
|
- Step Header만 변경: 가장 바깥 SW 범위에 `--hm-sw-process-*`를 지정합니다(SWHeader 토큰보다 우선, SWHeader·Viewer 영향 없음).
|
||||||
|
- 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 18개에 전달되지 않습니다.
|
||||||
|
- 공유 유지: 바 높이·필드·로고 높이, 글자 크기, 글자 외곽선, 재사용 부품(홈·로고, 파일명 버튼, 창 이동, 도구 pill)의 `sw.css` 규칙.
|
||||||
|
- `--hm-sw-process-text-color`는 Step Header 자체 글자 규칙(글자 로고, 레이어 필드)에만 적용됩니다. 루트의 기본 글자색은 SW 공통 규칙(`sw.css` `.hm-sw-header, .hm-sw-theme`)이 `--hm-sw-header-text-color`로 정하므로, 이를 상속하는 영역과 재사용 부품은 Header 토큰을 따릅니다.
|
||||||
|
|
||||||
## 7. Strana 테마 → 토큰 Override 대응표
|
## 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`)에서 생성했습니다.
|
Hanmac 열이 기본값입니다. 다른 테마는 Header의 `style`(가장 바깥 SW 범위)에 같은 토큰을 지정합니다. 예: `style={{ "--hm-sw-header-selected-color": "#FFC833" }}`. Strana ARGB `#AARRGGBB`는 CSS `rgba()`로 바꿉니다. 아래 표는 Strana `Styles/Theme/*/Colors.xaml`(HEAD `edda3cc`)에서 생성했습니다. 표의 `--hm-sw-header-*` 중 §6의 18개는 Step Header만 바꿀 때 같은 이름의 `--hm-sw-process-*`(예: `--hm-sw-process-selected-color`)에 지정해도 됩니다(2026-11-10).
|
||||||
|
|
||||||
<!-- theme-table:start -->
|
<!-- theme-table:start -->
|
||||||
| 토큰 | Strana `Color.Header.*` | Hanmac(기본값) | Dark | Light | HighContrast |
|
| 토큰 | Strana `Color.Header.*` | Hanmac(기본값) | Dark | Light | HighContrast |
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
|
|||||||
|
|
||||||
### 3.1 테마(토큰) 설정 — 공식 방법 (2026-10-20)
|
### 3.1 테마(토큰) 설정 — 공식 방법 (2026-10-20)
|
||||||
|
|
||||||
SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-sw-viewer-*`, `--hm-sw-footer-*`)의 기본값은 **가장 바깥 SW 범위**(`<SWTheme>`, 또는 단독으로 쓴 `SWHeader`·`SWViewerHeader`·`SWViewerPanel`·`SWViewerMenu`·`SWFooter`의 루트)에서 한 번만 선언됩니다. 그 안의 SW 요소 — Header의 Dropdown·More, `SWViewerMenu`, `SWViewerPanel`과 안쪽 컨트롤, `SWFooterCommandLine`, Tooltip·안내 — 는 자기 루트에 `hm-sw-theme`이 있어도 값을 다시 정하지 않고 **상속**합니다.
|
SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-sw-viewer-*`, `--hm-sw-footer-*`, `--hm-sw-process-*`, `--hm-sw-switch-*`, `--hm-sw-slider-*`, `--hm-sw-select-*`)의 기본값은 **가장 바깥 SW 범위**(`<SWTheme>`, 또는 단독으로 쓴 `SWHeader`·`SWProcessHeader`·`SWViewerHeader`·`SWViewerPanel`·`SWViewerMenu`·`SWFooter`의 루트)에서 한 번만 선언됩니다. 예외: 홈 메뉴 토큰(`--hm-sw-home-menu-*`, `--hm-sw-recent-files-*`)은 `SWHomeMenu` 루트(`.hm-sw-home-menu`)에 선언되어 `<SWTheme style>`로는 바뀌지 않습니다. `SWHomeMenu`의 `style`(SWHeader에서는 `homeMenu.style`)로 정합니다([DESIGN_RULES.md](./DESIGN_RULES.md) §12). 그 안의 SW 요소 — Header의 Dropdown·More, `SWViewerMenu`, `SWViewerPanel`과 안쪽 컨트롤, `SWFooterCommandLine`, Tooltip·안내 — 는 자기 루트에 `hm-sw-theme`이 있어도 값을 다시 정하지 않고 **상속**합니다.
|
||||||
|
|
||||||
1. 화면 전체(Header·Panel·Menu가 같은 값)를 바꾸려면 화면을 `<SWTheme style={theme}>` 하나로 감싸고 거기서만 토큰을 정합니다. 프로그램 class(`<SWTheme className="app-sw-theme">` + `.app-sw-theme { --hm-sw-header-font-family: … }`)도 됩니다.
|
1. 화면 전체(Header·Panel·Menu가 같은 값)를 바꾸려면 화면을 `<SWTheme style={theme}>` 하나로 감싸고 거기서만 토큰을 정합니다. 프로그램 class(`<SWTheme className="app-sw-theme">` + `.app-sw-theme { --hm-sw-header-font-family: … }`)도 됩니다.
|
||||||
```tsx
|
```tsx
|
||||||
@@ -136,6 +136,7 @@ SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-sw-
|
|||||||
4. 영역마다 다른 값이 필요하면 영역별 `<SWTheme>`을 둡니다. 옆 영역끼리 섞이지 않고, 안쪽 `<SWTheme style>`은 그 안에서만 적용됩니다.
|
4. 영역마다 다른 값이 필요하면 영역별 `<SWTheme>`을 둡니다. 옆 영역끼리 섞이지 않고, 안쪽 `<SWTheme style>`은 그 안에서만 적용됩니다.
|
||||||
5. **하지 않음:** `hm-sw-*` class를 선택자로 쓴 CSS(`.hm-sw-header, .hm-sw-theme { --… }` 등), `!important`, 안쪽 컴포넌트마다 같은 값을 반복 지정, SW 범위가 아닌 조상(`body`, 앱 루트 `div`)에만 토큰 지정(SW 범위가 기본값을 다시 선언하므로 적용되지 않음).
|
5. **하지 않음:** `hm-sw-*` class를 선택자로 쓴 CSS(`.hm-sw-header, .hm-sw-theme { --… }` 등), `!important`, 안쪽 컴포넌트마다 같은 값을 반복 지정, SW 범위가 아닌 조상(`body`, 앱 루트 `div`)에만 토큰 지정(SW 범위가 기본값을 다시 선언하므로 적용되지 않음).
|
||||||
- 다른 토큰을 참조하는 토큰(예: `--hm-sw-viewer-panel-background` = `var(--hm-sw-property-surface)`)은 가장 바깥 범위에서 계산됩니다. 참조되는 토큰을 바꿀 때도 가장 바깥 범위에서 바꿉니다.
|
- 다른 토큰을 참조하는 토큰(예: `--hm-sw-viewer-panel-background` = `var(--hm-sw-property-surface)`)은 가장 바깥 범위에서 계산됩니다. 참조되는 토큰을 바꿀 때도 가장 바깥 범위에서 바꿉니다.
|
||||||
|
- Step Header(`SWProcessHeader`, 2026-11-10): 칠하는 SWHeader 토큰 18개를 `--hm-sw-process-*` alias(기본값 `var(--hm-sw-header-…)`)로 읽습니다. 아무것도 정하지 않으면 SWHeader 테마를 따르고, `--hm-sw-process-selected-color` 등을 가장 바깥 범위에 정하면 Step Header만 바뀝니다. 위 규칙대로 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 alias에 전달되지 않습니다. 목록·공유 유지 토큰은 [DESIGN_RULES.md](./DESIGN_RULES.md) §14.
|
||||||
|
|
||||||
## 4. 판단 기록 남기기
|
## 4. 판단 기록 남기기
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
|
|
||||||
- Home On(Figma 662:67267, 2026-10-19 사용자 확정): 메뉴가 열려 있는 동안 SWHeader가 자동으로 로고에 노란 Hover 테두리를 유지하고, Header 전폭 상·하단에 `#5F4B0F` 2px 선(`--hm-sw-header-home-on-line-color` / `-width`)을 그립니다. 닫으면 기본 상태로 돌아갑니다. 크기·위치 변화가 없고 SW 일반 모드 전용입니다(뷰어 모드에는 없음). 소비 코드에서 별도 class나 상태를 추가하지 않습니다.
|
- Home On(Figma 662:67267, 2026-10-19 사용자 확정): 메뉴가 열려 있는 동안 SWHeader가 자동으로 로고에 노란 Hover 테두리를 유지하고, Header 전폭 상·하단에 `#5F4B0F` 2px 선(`--hm-sw-header-home-on-line-color` / `-width`)을 그립니다. 닫으면 기본 상태로 돌아갑니다. 크기·위치 변화가 없고 SW 일반 모드 전용입니다(뷰어 모드에는 없음). 소비 코드에서 별도 class나 상태를 추가하지 않습니다.
|
||||||
- 새 색·크기는 위 토큰만 `style`로 덮어씁니다. `hm-sw-home-menu*` class를 소비 코드에 쓰지 않습니다.
|
- 새 색·크기는 위 토큰만 `style`로 덮어씁니다. `hm-sw-home-menu*` class를 소비 코드에 쓰지 않습니다.
|
||||||
|
- 덮어쓰는 위치(2026-11-10 구현 확인): 홈 메뉴 토큰 39개(`--hm-sw-home-menu-*` 28, `--hm-sw-recent-files-*` 11)는 가장 바깥 SW 범위가 아니라 `SWHomeMenu` 루트(`.hm-sw-home-menu`, `sw-home-menu.css`)에 선언됩니다. 루트가 값을 다시 정하므로 `<SWTheme style>`이나 `SWHeader`의 `style`로 지정한 `--hm-sw-home-menu-*`·`--hm-sw-recent-files-*`는 적용되지 않습니다. `SWHomeMenu`의 `style`, 또는 `SWHeader`에서는 `homeMenu.style`(그대로 `SWHomeMenu`에 전달)로 지정합니다. 단, 홈 메뉴 토큰 중 `--hm-sw-header-layer-*`·`--hm-sw-header-text-color`를 참조하는 값(더보기 Dropdown·최근파일 면·행 Hover 등)은 홈 메뉴 요소에서 계산되므로 바깥 SW 범위에서 그 Header 토큰을 바꾸면 따라 바뀝니다. 선언 위치는 현재 구조를 유지합니다.
|
||||||
|
|
||||||
## 13. Footer (`SWFooter*`, Figma 26:10097) — SW 일반 모드 전용
|
## 13. Footer (`SWFooter*`, Figma 26:10097) — SW 일반 모드 전용
|
||||||
|
|
||||||
@@ -226,6 +227,13 @@
|
|||||||
|
|
||||||
원본 `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).
|
원본 `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).
|
||||||
|
|
||||||
|
Step Header 전용 토큰(2026-11-10): Step Header 규칙이 칠하는 SWHeader 토큰 18개는 같은 이름의 `--hm-sw-process-*`를 거쳐 읽습니다 — `background`·`height`·`text-color`·`selected-color`·`hover-border-gradient`·`hover-border-width`·`document-background`·`document-border-gradient`·`document-border-color`·`object-background`·`object-border-gradient`·`object-radius`·`property-background`·`property-radius`·`property-shadow`·`window-icon-color`·`window-icon-hover-color`·`window-button-hover-background`(예: `--hm-sw-process-selected-color`). 기본값은 각각 `var(--hm-sw-header-…)`라 계산값은 그대로입니다. `--hm-sw-process-base-border-gradient`도 `--hm-sw-process-document-border-gradient`를 거쳐 같은 기본값을 씁니다.
|
||||||
|
- 기본 동작(상속): `--hm-sw-process-*`를 정하지 않으면 가장 바깥 SW 범위의 `--hm-sw-header-*` 값이 Step Header에도 적용됩니다.
|
||||||
|
- Step Header만 바꾸기: 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWProcessHeader`의 `style`)에 `--hm-sw-process-*`를 지정합니다. SWHeader·Viewer에는 영향이 없습니다.
|
||||||
|
- 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소(예: `<SWTheme>` 안 `SWProcessHeader`의 `style`)에서 `--hm-sw-header-*`만 바꾸면 이 18개에는 전달되지 않습니다. 이 경우 같은 위치에 `--hm-sw-process-*`를 지정하거나 바깥 범위에서 바꿉니다.
|
||||||
|
- 공유 유지: 바 높이(`--hm-sw-header-bar-height`)·필드·로고 높이, 글자 크기, 글자 외곽선(`text-outline`·`label-outline`·`text-outline-room`)과 재사용 부품(홈·로고, 파일명 버튼, 창 이동, 도구 pill)의 `sw.css` 규칙은 SWHeader 토큰을 그대로 읽습니다.
|
||||||
|
- `--hm-sw-process-text-color` 범위: Step Header 자체 글자 규칙(글자 로고, 레이어 필드)에만 적용됩니다. Step Header 루트의 기본 글자색은 SW 공통 규칙(`sw.css`의 `.hm-sw-header, .hm-sw-theme { color: var(--hm-sw-header-text-color) }`)이 정하므로, 그 색을 상속하는 영역과 재사용 부품의 글자는 `--hm-sw-header-text-color`를 따릅니다.
|
||||||
|
|
||||||
| 항목 | 값 | 토큰 |
|
| 항목 | 값 | 토큰 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 배치 | 홈·파일명 글자 = SWHeader 그대로(두 타입 공통, 2026-11-03): 홈 12 + 로고(32 × 17 → 80) + 36, 파일명은 홈과 15 겹친 뒤 2 간격(x 65), padding 5 / 12, 14px 700·자간 0.06em·SW 외곽선, 화살표 없음(글자 로고 18px 700, 최소 58px 칸 → 홈 최소 106, 더 긴 글자는 글자 폭만큼 넓어짐) → 프로세스 → Layer 면(객체 면, 양옆 1) → 창 영역(파일명 면 + 테두리 1px) | SW 토큰 |
|
| 배치 | 홈·파일명 글자 = SWHeader 그대로(두 타입 공통, 2026-11-03): 홈 12 + 로고(32 × 17 → 80) + 36, 파일명은 홈과 15 겹친 뒤 2 간격(x 65), padding 5 / 12, 14px 700·자간 0.06em·SW 외곽선, 화살표 없음(글자 로고 18px 700, 최소 58px 칸 → 홈 최소 106, 더 긴 글자는 글자 폭만큼 넓어짐) → 프로세스 → Layer 면(객체 면, 양옆 1) → 창 영역(파일명 면 + 테두리 1px) | SW 토큰 |
|
||||||
|
|||||||
@@ -109,11 +109,16 @@ export function useSWHeaderDropdown({ open: openProp, onOpenChange, disabled = f
|
|||||||
/** Focusable entries of the panel in DOM order (rows, inputs, footer). */
|
/** Focusable entries of the panel in DOM order (rows, inputs, footer). */
|
||||||
const entries = () => Array.from(panelRef.current?.querySelectorAll<HTMLElement>("button:not(:disabled), input:not(:disabled)") ?? [])
|
const entries = () => Array.from(panelRef.current?.querySelectorAll<HTMLElement>("button:not(:disabled), input:not(:disabled)") ?? [])
|
||||||
|
|
||||||
|
// ArrowDown on the closed trigger asks for the entry focus. A fixed overlay (property window, SW popup) stays
|
||||||
|
// visibility: hidden until its position is measured, and focus() on it is a no-op: the request stays pending and is
|
||||||
|
// retried on the render that shows the list (the position update), then cleared once focus moved; closing drops it.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && focusEntryRef.current) {
|
if (!open) { focusEntryRef.current = false; return }
|
||||||
focusEntryRef.current = false
|
if (focusEntryRef.current) {
|
||||||
const current = panelRef.current?.querySelector<HTMLElement>('[aria-current="true"]:not(:disabled), [aria-pressed="true"]:not(:disabled)')
|
const current = panelRef.current?.querySelector<HTMLElement>('[aria-current="true"]:not(:disabled), [aria-pressed="true"]:not(:disabled)')
|
||||||
;(current ?? entries()[0])?.focus()
|
const target = current ?? entries()[0]
|
||||||
|
target?.focus()
|
||||||
|
if (!target || document.activeElement === target) focusEntryRef.current = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
:is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *) {
|
: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. */
|
/* 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-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);
|
--hm-sw-process-base-border-gradient: var(--hm-sw-process-document-border-gradient);
|
||||||
/* Cover over an unlit group, its arrow side and its outer rest (ProcessDim #E6004430). */
|
/* Cover over an unlit group, its arrow side and its outer rest (ProcessDim #E6004430). */
|
||||||
--hm-sw-process-dim: rgba(0, 68, 48, 0.902);
|
--hm-sw-process-dim: rgba(0, 68, 48, 0.902);
|
||||||
|
|
||||||
@@ -93,6 +93,30 @@
|
|||||||
--hm-sw-process-multi-tab-disabled-border: #021912;
|
--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-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;
|
--hm-sw-process-multi-tab-disabled-text: #7d9f91;
|
||||||
|
|
||||||
|
/* Step Header's own copies of the SWHeader tokens it paints with (2026-11-10). Each defaults to the SWHeader token, so a
|
||||||
|
theme that sets --hm-sw-header-* on the outermost SW scope still reaches the Step Header; set --hm-sw-process-* there to
|
||||||
|
change the Step Header alone. Resolved on the outermost scope like every alias here: a --hm-sw-header-* value set on an
|
||||||
|
inner element no longer reaches these. The reused SW parts (home / logo, file-name button, window move) keep reading
|
||||||
|
--hm-sw-header-* in sw.css. */
|
||||||
|
--hm-sw-process-background: var(--hm-sw-header-background);
|
||||||
|
--hm-sw-process-height: var(--hm-sw-header-height);
|
||||||
|
--hm-sw-process-text-color: var(--hm-sw-header-text-color);
|
||||||
|
--hm-sw-process-selected-color: var(--hm-sw-header-selected-color);
|
||||||
|
--hm-sw-process-hover-border-gradient: var(--hm-sw-header-hover-border-gradient);
|
||||||
|
--hm-sw-process-hover-border-width: var(--hm-sw-header-hover-border-width);
|
||||||
|
--hm-sw-process-document-background: var(--hm-sw-header-document-background);
|
||||||
|
--hm-sw-process-document-border-gradient: var(--hm-sw-header-document-border-gradient);
|
||||||
|
--hm-sw-process-document-border-color: var(--hm-sw-header-document-border-color);
|
||||||
|
--hm-sw-process-object-background: var(--hm-sw-header-object-background);
|
||||||
|
--hm-sw-process-object-border-gradient: var(--hm-sw-header-object-border-gradient);
|
||||||
|
--hm-sw-process-object-radius: var(--hm-sw-header-object-radius);
|
||||||
|
--hm-sw-process-property-background: var(--hm-sw-header-property-background);
|
||||||
|
--hm-sw-process-property-radius: var(--hm-sw-header-property-radius);
|
||||||
|
--hm-sw-process-property-shadow: var(--hm-sw-header-property-shadow);
|
||||||
|
--hm-sw-process-window-icon-color: var(--hm-sw-header-window-icon-color);
|
||||||
|
--hm-sw-process-window-icon-hover-color: var(--hm-sw-header-window-icon-hover-color);
|
||||||
|
--hm-sw-process-window-button-hover-background: var(--hm-sw-header-window-button-hover-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Header frame ---------- */
|
/* ---------- Header frame ---------- */
|
||||||
@@ -102,8 +126,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: var(--hm-sw-header-height);
|
height: var(--hm-sw-process-height);
|
||||||
background: var(--hm-sw-header-background);
|
background: var(--hm-sw-process-background);
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
}
|
}
|
||||||
.hm-sw-process-header__bar { display: flex; align-items: stretch; height: var(--hm-sw-header-bar-height); min-width: 0; }
|
.hm-sw-process-header__bar { display: flex; align-items: stretch; height: var(--hm-sw-header-bar-height); min-width: 0; }
|
||||||
@@ -120,7 +144,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
min-width: 58px;
|
min-width: 58px;
|
||||||
color: var(--hm-sw-header-text-color);
|
color: var(--hm-sw-process-text-color);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: var(--hm-sw-header-logo-height);
|
line-height: var(--hm-sw-header-logo-height);
|
||||||
@@ -157,8 +181,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0 1px;
|
inset: 0 1px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: var(--hm-sw-header-object-radius);
|
border-radius: var(--hm-sw-process-object-radius);
|
||||||
background: var(--hm-sw-header-object-background) padding-box, var(--hm-sw-header-object-border-gradient) border-box;
|
background: var(--hm-sw-process-object-background) padding-box, var(--hm-sw-process-object-border-gradient) border-box;
|
||||||
}
|
}
|
||||||
/* Layer field (Header.ToggleButton.LayerField): 38px, at most 234, property surface + layer border gradient.
|
/* 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. */
|
The bulb and the field are sibling buttons; the field button covers the whole field under the bulb. */
|
||||||
@@ -174,18 +198,18 @@
|
|||||||
margin: 0 9px;
|
margin: 0 9px;
|
||||||
padding: 0 10px 0 12px;
|
padding: 0 10px 0 12px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: var(--hm-sw-header-property-radius);
|
border-radius: var(--hm-sw-process-property-radius);
|
||||||
background: var(--hm-sw-header-property-background) padding-box, var(--hm-sw-process-layer-border-gradient) border-box;
|
background: var(--hm-sw-process-property-background) padding-box, var(--hm-sw-process-layer-border-gradient) border-box;
|
||||||
box-shadow: var(--hm-sw-header-property-shadow);
|
box-shadow: var(--hm-sw-process-property-shadow);
|
||||||
color: var(--hm-sw-header-text-color);
|
color: var(--hm-sw-process-text-color);
|
||||||
}
|
}
|
||||||
.hm-sw-process-header__layer-field::after {
|
.hm-sw-process-header__layer-field::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -1px;
|
inset: -1px;
|
||||||
padding: var(--hm-sw-header-hover-border-width);
|
padding: var(--hm-sw-process-hover-border-width);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
background: var(--hm-sw-header-hover-border-gradient);
|
background: var(--hm-sw-process-hover-border-gradient);
|
||||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||||
-webkit-mask-composite: xor;
|
-webkit-mask-composite: xor;
|
||||||
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
||||||
@@ -195,7 +219,7 @@
|
|||||||
.hm-sw-process-header__layer-field:not([data-disabled]):hover::after,
|
.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[data-open]::after { opacity: 1; }
|
||||||
.hm-sw-process-header__layer-field:not([data-disabled]):hover,
|
.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-open] { color: var(--hm-sw-process-selected-color); }
|
||||||
.hm-sw-process-header__layer-field[data-disabled] { opacity: 0.4; }
|
.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 .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-button { position: absolute; inset: -1px; z-index: 0; border-radius: inherit; }
|
||||||
@@ -221,7 +245,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-header-document-border-gradient) border-box;
|
background: var(--hm-sw-process-document-background) padding-box, var(--hm-sw-process-document-border-gradient) border-box;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.hm-sw-process-header__window > * { position: relative; }
|
.hm-sw-process-header__window > * { position: relative; }
|
||||||
@@ -238,11 +262,11 @@
|
|||||||
}
|
}
|
||||||
.hm-sw-process-header:not([data-appearance="multi"]) .hm-sw-process-header__tools .hm-sw-tool__pill-button {
|
.hm-sw-process-header:not([data-appearance="multi"]) .hm-sw-process-header__tools .hm-sw-tool__pill-button {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background: var(--hm-sw-header-document-background) padding-box, var(--hm-sw-process-tool-border-gradient) border-box;
|
background: var(--hm-sw-process-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 { 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: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"] { color: var(--hm-sw-process-selected-color); }
|
||||||
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-tool__pill-button[aria-pressed="true"]::after { opacity: 1; }
|
.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`). */
|
/* 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 { visibility: hidden; }
|
||||||
@@ -251,9 +275,9 @@
|
|||||||
.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; }
|
.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. */
|
/* 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 { display: block; flex: 0 0 32px; width: 32px; height: var(--hm-sw-header-bar-height); color: var(--hm-sw-process-window-icon-color); }
|
||||||
.hm-sw-process-header__caption > svg { display: block; }
|
.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:hover:not(:disabled) { background: var(--hm-sw-process-window-button-hover-background); color: var(--hm-sw-process-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); }
|
.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) ---------- */
|
/* ---------- Process area (SWProcessBar) ---------- */
|
||||||
@@ -374,8 +398,8 @@
|
|||||||
.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="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-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:hover:not(:disabled) { color: var(--hm-sw-process-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"] { color: var(--hm-sw-process-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"] > .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="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="shade"] { background: var(--hm-sw-process-tab-active-shade); }
|
||||||
@@ -490,11 +514,11 @@
|
|||||||
/* File name: 189 at most (Figma 873:16254), solid #0E4C3A border. */
|
/* 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 { 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 {
|
.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;
|
background: var(--hm-sw-process-document-background) padding-box, linear-gradient(var(--hm-sw-process-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__center { flex: 1278 1 0; }
|
||||||
.hm-sw-process-header[data-appearance="multi"] .hm-sw-process-header__window::before {
|
.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;
|
background: var(--hm-sw-process-document-background) padding-box, linear-gradient(var(--hm-sw-process-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. */
|
/* 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__window .hm-sw-process-header__tools { height: 41px; margin: 4px 2px 0 5px; }
|
||||||
@@ -609,7 +633,7 @@
|
|||||||
.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__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 {
|
.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);
|
border-color: var(--hm-sw-process-multi-tab-disabled-border);
|
||||||
background: var(--hm-sw-header-property-background);
|
background: var(--hm-sw-process-property-background);
|
||||||
box-shadow: var(--hm-sw-process-multi-tab-disabled-shadow);
|
box-shadow: var(--hm-sw-process-multi-tab-disabled-shadow);
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -627,8 +651,8 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
border-bottom-width: 0;
|
border-bottom-width: 0;
|
||||||
border-radius: 2px 2px 0 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;
|
background: var(--hm-sw-process-tool-open-background) padding-box, var(--hm-sw-process-hover-border-gradient) border-box;
|
||||||
color: var(--hm-sw-header-selected-color);
|
color: var(--hm-sw-process-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; }
|
.hm-sw-process-header .hm-sw-process-header__tools .hm-sw-process-split[data-open] > .hm-sw-tool__pill-button::after { opacity: 0; }
|
||||||
|
|
||||||
@@ -644,7 +668,7 @@
|
|||||||
height: var(--hm-sw-process-split-tail, 3px);
|
height: var(--hm-sw-process-split-tail, 3px);
|
||||||
border: solid transparent;
|
border: solid transparent;
|
||||||
border-width: 0 1px;
|
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;
|
background: linear-gradient(var(--hm-sw-process-tool-open-edge), var(--hm-sw-process-tool-open-edge)) padding-box, var(--hm-sw-process-hover-border-gradient) border-box;
|
||||||
}
|
}
|
||||||
.hm-sw-process-split__panel {
|
.hm-sw-process-split__panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -658,7 +682,7 @@
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-top-width: 0;
|
border-top-width: 0;
|
||||||
border-radius: 0 2px 2px 2px;
|
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;
|
background: linear-gradient(var(--hm-sw-process-split-panel-background), var(--hm-sw-process-split-panel-background)) padding-box, var(--hm-sw-process-hover-border-gradient) border-box;
|
||||||
box-shadow: var(--hm-sw-process-split-panel-shadow);
|
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). */
|
/* Gold line on the panel top, from the button right edge to the panel end (Header.Border.ToolMenuStep). */
|
||||||
@@ -669,7 +693,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
border-top-right-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
background: var(--hm-sw-header-hover-border-gradient) 0 0 / 100% 36px no-repeat;
|
background: var(--hm-sw-process-hover-border-gradient) 0 0 / 100% 36px no-repeat;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.hm-sw-process-split__cell { display: grid; place-items: center; height: 34px; }
|
.hm-sw-process-split__cell { display: grid; place-items: center; height: 34px; }
|
||||||
@@ -685,4 +709,4 @@
|
|||||||
}
|
}
|
||||||
.hm-sw-process-split__frame > svg { display: block; overflow: visible; }
|
.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__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); }
|
.hm-sw-process-split__cell:is(:hover, [aria-checked="true"]) .hm-sw-process-split__pane { fill: var(--hm-sw-process-selected-color); }
|
||||||
@@ -196,10 +196,45 @@ async function dropdownColors(page, def, themed) {
|
|||||||
await page.close()
|
await page.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 1b. Step Header (2026-11-10): --hm-sw-process-* copies of the SWHeader tokens it paints with ----------
|
||||||
|
// Without a Step Header value an SWHeader theme on the outermost SW scope still reaches the Step Header; a
|
||||||
|
// --hm-sw-process-* value there changes the Step Header alone (it wins over the SWHeader token) and leaves SWHeader as is.
|
||||||
|
{
|
||||||
|
const page = await newPage()
|
||||||
|
await open(page, 'sw-step-header--process-header')
|
||||||
|
const outerOf = selector => page.locator(selector).first().evaluateHandle(element => { let outer = element; for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e; return outer })
|
||||||
|
const scope = await outerOf('.hm-sw-process-header')
|
||||||
|
const set = async (values) => scope.evaluate((outer, values) => Object.entries(values).forEach(([token, value]) => value === null ? outer.style.removeProperty(token) : outer.style.setProperty(token, value)), values)
|
||||||
|
const read = () => page.evaluate(() => {
|
||||||
|
const header = getComputedStyle(document.querySelector('.hm-sw-process-header'))
|
||||||
|
const step = document.querySelector('.hm-sw-process-bar__step[aria-checked="true"]')
|
||||||
|
return { background: `${header.backgroundColor} ${header.backgroundImage}`, selected: step ? getComputedStyle(step).color : null }
|
||||||
|
})
|
||||||
|
const base = await read()
|
||||||
|
await set({ '--hm-sw-header-background': 'rgb(44, 55, 66)', '--hm-sw-header-selected-color': 'rgb(11, 22, 33)' })
|
||||||
|
const inherited = await read()
|
||||||
|
check('Step Header: 바깥 범위의 SWHeader 토큰(배경·선택 색)이 그대로 전달', base.selected !== null && inherited.background.includes('rgb(44, 55, 66)') && inherited.selected === 'rgb(11, 22, 33)', JSON.stringify({ base, inherited }))
|
||||||
|
await set({ '--hm-sw-process-background': 'rgb(1, 2, 3)', '--hm-sw-process-selected-color': 'rgb(77, 88, 99)' })
|
||||||
|
const own = await read()
|
||||||
|
check('Step Header: --hm-sw-process-* 지정이 SWHeader 토큰보다 우선', own.background.includes('rgb(1, 2, 3)') && own.selected === 'rgb(77, 88, 99)', JSON.stringify(own))
|
||||||
|
await set({ '--hm-sw-header-background': null, '--hm-sw-header-selected-color': null, '--hm-sw-process-background': null, '--hm-sw-process-selected-color': null })
|
||||||
|
check('Step Header: 지정을 지우면 기본값으로 복귀', JSON.stringify(await read()) === JSON.stringify(base), JSON.stringify(base))
|
||||||
|
await page.close()
|
||||||
|
// The same Step Header tokens on an SWHeader page change nothing there.
|
||||||
|
const header = await newPage()
|
||||||
|
await open(header, 'sw-header--overview')
|
||||||
|
const paint = () => header.evaluate(() => [...document.querySelectorAll('.hm-sw-header, .hm-sw-header *')].map(element => { const s = getComputedStyle(element); return `${s.color}|${s.backgroundColor}|${s.backgroundImage}|${s.boxShadow}|${s.borderTopColor}` }).join('\n'))
|
||||||
|
const before = await paint()
|
||||||
|
await header.locator('.hm-sw-header').first().evaluate(element => { let outer = element; for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e; ['background', 'height', 'text-color', 'selected-color', 'hover-border-gradient', 'document-background', 'object-background', 'property-background', 'window-icon-color'].forEach(name => outer.style.setProperty(`--hm-sw-process-${name}`, 'rgb(1, 2, 3)')) })
|
||||||
|
check('Step Header 토큰은 SWHeader 화면에 영향 없음', before === await paint(), `${before.split('\n').length} elements`)
|
||||||
|
await header.close()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 2. Regression: new cascade vs legacy cascade on every existing SW Story ----------
|
// ---------- 2. Regression: new cascade vs legacy cascade on every existing SW Story ----------
|
||||||
const here = path.dirname(fileURLToPath(import.meta.url))
|
const here = path.dirname(fileURLToPath(import.meta.url))
|
||||||
const outermost = ':is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *)'
|
const outermost = ':is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *)'
|
||||||
const tokenBlocks = (await Promise.all(['sw.css', 'sw-viewer.css', 'sw-footer.css'].map(file => readFile(path.join(here, '../src/theme', file), 'utf8'))))
|
// 2026-11-10: sw-process-header.css (Step Header aliases) and the Step Header Stories joined the comparison.
|
||||||
|
const tokenBlocks = (await Promise.all(['sw.css', 'sw-viewer.css', 'sw-footer.css', 'sw-process-header.css'].map(file => readFile(path.join(here, '../src/theme', file), 'utf8'))))
|
||||||
.map(text => {
|
.map(text => {
|
||||||
const start = text.indexOf(`${outermost} {`)
|
const start = text.indexOf(`${outermost} {`)
|
||||||
const end = text.indexOf('\n}', start)
|
const end = text.indexOf('\n}', start)
|
||||||
@@ -212,7 +247,7 @@ const legacy = tokenBlocks.map(block => `.hm-sw-header,\n.hm-sw-theme ${block}`)
|
|||||||
// command row) even though no value changes, so the legacy shot is also compared with this control shot.
|
// command row) even though no value changes, so the legacy shot is also compared with this control shot.
|
||||||
const control = tokenBlocks.map(block => `${outermost} ${block}`).join('\n')
|
const control = tokenBlocks.map(block => `${outermost} ${block}`).join('\n')
|
||||||
const index = await (await fetch(`${storybook}/index.json`)).json()
|
const index = await (await fetch(`${storybook}/index.json`)).json()
|
||||||
const ids = Object.values(index.entries).filter(entry => entry.type === 'story' && /^sw-(header|popup|viewer|footer|home-menu)/.test(entry.id)).map(entry => entry.id)
|
const ids = Object.values(index.entries).filter(entry => entry.type === 'story' && /^sw-(header|popup|viewer|footer|home-menu|step-header)/.test(entry.id)).map(entry => entry.id)
|
||||||
const snapshot = page => page.evaluate(() => [...document.querySelectorAll('.hm-sw-header, .hm-sw-theme')].flatMap(root => [root, ...root.querySelectorAll('*')]).map(element => {
|
const snapshot = page => page.evaluate(() => [...document.querySelectorAll('.hm-sw-header, .hm-sw-theme')].flatMap(root => [root, ...root.querySelectorAll('*')]).map(element => {
|
||||||
const style = getComputedStyle(element), rect = element.getBoundingClientRect()
|
const style = getComputedStyle(element), rect = element.getBoundingClientRect()
|
||||||
return `${style.fontFamily}|${style.fontSize}|${style.color}|${style.backgroundImage.slice(0, 80)}|${Math.round(rect.x * 10)},${Math.round(rect.y * 10)},${Math.round(rect.width * 10)},${Math.round(rect.height * 10)}`
|
return `${style.fontFamily}|${style.fontSize}|${style.color}|${style.backgroundImage.slice(0, 80)}|${Math.round(rect.x * 10)},${Math.round(rect.y * 10)},${Math.round(rect.width * 10)},${Math.round(rect.height * 10)}`
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Brown·Orange 계열 팔레트(`--hm-sem-palette-brown-*`, `--hm-sem-palette-ora
|
|||||||
| 페이지·콘텐츠 배경 | `--hm-sem-bg-content` | Beige |
|
| 페이지·콘텐츠 배경 | `--hm-sem-bg-content` | Beige |
|
||||||
| 카드·팝업 표면 | `--hm-sem-bg-surface` | White |
|
| 카드·팝업 표면 | `--hm-sem-bg-surface` | White |
|
||||||
| 보조 면 | `--hm-sem-bg-subtle` | warm-neutral-muted |
|
| 보조 면 | `--hm-sem-bg-subtle` | warm-neutral-muted |
|
||||||
| 브랜드 배경(gradient) | `--hm-sem-bg-brand`, `--hm-sem-bg-brown`, `--hm-sem-bg-orange` | 세로 gradient(GNB·Brand 버튼 면) |
|
| 브랜드 배경(gradient) | `--hm-sem-bg-brand`, `--hm-sem-bg-brown`, `--hm-sem-bg-orange` | 공통 세로 gradient 배경(소비 화면 배경용). 패키지 컴포넌트(GNB·Button·Dashboard)에는 자동 연결되지 않음 — GNB 면은 `--hm-gnb-background`(저장소 `docs/guidelines/runtime-theme-contract.md` "Brown·Orange 공통 Background") |
|
||||||
| 기본 글자 | `--hm-sem-fg-primary` | green-dark-1 |
|
| 기본 글자 | `--hm-sem-fg-primary` | green-dark-1 |
|
||||||
| 보조 글자 | `--hm-sem-fg-secondary` | warm-neutral-dark |
|
| 보조 글자 | `--hm-sem-fg-secondary` | warm-neutral-dark |
|
||||||
| placeholder | `--hm-sem-fg-placeholder` | gray-500 |
|
| placeholder | `--hm-sem-fg-placeholder` | gray-500 |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* LNB pattern (layout visual reference: D:\컴포넌트\ui-viz Patterns/LNB, implementation: HM Collapsible + plain elements).
|
/* LNB pattern (layout visual reference: D:\컴포넌트\ui-viz Patterns/LNB, implementation: HM Collapsible + plain elements).
|
||||||
A 272px green column: horizontal 5-stop surface, brand on top, menu in the middle, text-only profile at the bottom. The current
|
A 240px green column (--hm-lnb-width; 272px before 2026-11-09): horizontal 5-stop surface, brand on top, menu in the middle, text-only profile at the bottom. The current
|
||||||
item is a Beige face (left corners rounded) that runs to the content edge and joins it through two concave corners; hover is
|
item is a Beige face (left corners rounded) that runs to the content edge and joins it through two concave corners; hover is
|
||||||
Black 10% to the same edge; an open group is a Black 8% face with a 1px green line. Spacing values are pattern-local. */
|
Black 10% to the same edge; an open group is a Black 8% face with a 1px green line. Spacing values are pattern-local. */
|
||||||
:root, [data-hm-theme] {
|
:root, [data-hm-theme] {
|
||||||
|
|||||||
Reference in new issue
Block a user