Merge pull request 'feat(sw): add remaining component theme tokens' (#36) from feat/sw-remaining-theme-tokens into main
Deploy Storybook / deploy (push) Successful in 1m21s

Reviewed-on: #36
This commit was merged in pull request #36.
This commit is contained in:
shkim4 committed 2026-11-10 18:22:46 +09:00
commit 4218b7e1f5
8 files changed
+174 -23

No files matched your search

+3 -2
View File
@@ -557,7 +557,8 @@ Composed Controls의 기존 레이어·색상·상세보기 예제는 보존하
| `--hm-sw-header-focus-color` | `#F0F7F5` | Focus | | `--hm-sw-header-focus-color` | `#F0F7F5` | Focus |
| `--hm-sw-header-arrow-color` | `#47AF91` | 파일명 화살표 | | `--hm-sw-header-arrow-color` | `#47AF91` | 파일명 화살표 |
| `--hm-sw-header-hover` | `rgba(255,255,255,.08)` | 더보기 메뉴 항목 Hover 배경(`--hm-sw-header-more-item-hover-background` 기본값) | | `--hm-sw-header-hover` | `rgba(255,255,255,.08)` | 더보기 메뉴 항목 Hover 배경(`--hm-sw-header-more-item-hover-background` 기본값) |
| `--hm-sw-header-more-menu-background` / `-more-menu-shadow` / `-more-menu-border-color` / `-more-item-hover-background` | `#0E3D2F` / `0 4px 12px rgba(0,0,0,.5)` / `var(--hm-sw-header-object-border-color)` / `var(--hm-sw-header-hover)` | 더보기(`...`) 메뉴 면·그림자·테두리·항목 Hover(2026-11-10 개별 토큰, 값 변경 없음). 뷰어 Header Dropdown·로고 메뉴도 사용 | | `--hm-sw-header-more-menu-background` / `-more-menu-shadow` / `-more-menu-border-color` / `-more-item-hover-background` | `#0E3D2F` / `0 4px 12px rgba(0,0,0,.5)` / `var(--hm-sw-header-object-border-color)` / `var(--hm-sw-header-hover)` | 더보기(`...`) 메뉴 면·그림자·테두리·항목 Hover(2026-11-10 개별 토큰, 값 변경 없음). 뷰어 Header Dropdown·로고 메뉴는 이 값을 기본값으로 하는 `--hm-sw-viewer-header-menu-*`를 읽음 |
| `--hm-sw-header-k-guide-border-color` / `-k-guide-background` | `var(--hm-sw-property-border)`(`#A67D03`) / `#0E3C2E` | K 가이드 패널 테두리·면(2026-11-10 개별 토큰, 값 변경 없음). 속성창 테두리와 독립 |
| `--hm-sw-header-hover-border-gradient` / `-hover-border-width` | Figma 홈 hover gradient / 1px | Hover 테두리(§11.1) | | `--hm-sw-header-hover-border-gradient` / `-hover-border-width` | Figma 홈 hover gradient / 1px | Hover 테두리(§11.1) |
| `--hm-sw-header-home-on-line-color` / `-home-on-line-width` | `#5F4B0F` / 2px | Home On(로고 메뉴 열림, Figma 662:67267): Header 전폭 상·하단 선. 로고는 Hover 테두리 유지 | | `--hm-sw-header-home-on-line-color` / `-home-on-line-width` | `#5F4B0F` / 2px | Home On(로고 메뉴 열림, Figma 662:67267): Header 전폭 상·하단 선. 로고는 Hover 테두리 유지 |
| `--hm-sw-header-document-menu-width` / `-document-menu-background` / `-document-menu-border-color` / `-document-menu-border-width` | 310px / `#103127` / `#041611` / 1px | 문서 Dropdown(§5.1) | | `--hm-sw-header-document-menu-width` / `-document-menu-background` / `-document-menu-border-color` / `-document-menu-border-width` | 310px / `#103127` / `#041611` / 1px | 문서 Dropdown(§5.1) |
@@ -589,7 +590,7 @@ Composed Controls의 기존 레이어·색상·상세보기 예제는 보존하
| `--hm-sw-header-menu-list-max-height` / `-color-palette-max-height` | 240px / 118px | 긴 목록·사용자 색상 내부 스크롤 시작(구현값) | | `--hm-sw-header-menu-list-max-height` / `-color-palette-max-height` | 240px / 118px | 긴 목록·사용자 색상 내부 스크롤 시작(구현값) |
| `--hm-sw-header-color-mode-color` / `-color-mode-background` / `-color-mode-on-background` | `#D4CBBD` / `rgba(98,83,61,.3)` / `#62533D` | ByLayer / ByBlock | | `--hm-sw-header-color-mode-color` / `-color-mode-background` / `-color-mode-on-background` | `#D4CBBD` / `rgba(98,83,61,.3)` / `#62533D` | ByLayer / ByBlock |
| `--hm-sw-header-color-pick-background` / `-color-chip-border-color` / `-color-input-border-color` | `#C5B19E → #5F554C` / `#C9C9C9` / `rgba(197,177,158,.2)` | 색상 추출, 22px 칩, 색상 입력 Hover·구분선 | | `--hm-sw-header-color-pick-background` / `-color-chip-border-color` / `-color-input-border-color` | `#C5B19E → #5F554C` / `#C9C9C9` / `rgba(197,177,158,.2)` | 색상 추출, 22px 칩, 색상 입력 Hover·구분선 |
| `--hm-sw-header-input-color` / `-input-hover-border-color` / `-input-editing-background` / `-input-editing-border-color` | `#E60403` / `rgba(212,203,189,.25)` / `#0F0D0A` / `rgba(212,203,189,.3)` | `SWHeaderInput`(§5.5), caret 색 포함 | | `--hm-sw-header-input-color` / `-input-hover-border-color` / `-input-editing-background` / `-input-editing-border-color` | `#E60403` / `rgba(212,203,189,.25)` / `#0F0D0A` / `rgba(212,203,189,.3)` | `SWHeaderInput`(§5.5), caret 색 포함. Popup(속성창·`SWPopupRow`·뷰어 패널) 값 글자색은 이 값을 기본값으로 하는 `--hm-sw-popup-value-color`(2026-11-10)를 읽음 |
## 15. Figma 비교 결과와 의도적 차이 ## 15. Figma 비교 결과와 의도적 차이
+7 -1
View File
@@ -107,7 +107,7 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
- SW 화면은 `SWTheme`(또는 SW 컴포넌트 루트)와 SW 토큰의 글꼴을 그대로 쓴다. Web UI도 Noto Sans KR이지만 Web 토큰(`--hm-font-family-base`)이나 Web `theme.css`의 글꼴 설정을 SW 화면에 복사하지 않고, SW 컴포넌트에 `font-family`를 개별 지정하지 않는다. 프로그램(사용자)이 다른 글꼴을 정한 경우에만 §3.1 방법으로 `--hm-sw-header-font-family`를 바꾸고 결과 보고에 "글꼴 변경"으로 적는다. - SW 화면은 `SWTheme`(또는 SW 컴포넌트 루트)와 SW 토큰의 글꼴을 그대로 쓴다. Web UI도 Noto Sans KR이지만 Web 토큰(`--hm-font-family-base`)이나 Web `theme.css`의 글꼴 설정을 SW 화면에 복사하지 않고, SW 컴포넌트에 `font-family`를 개별 지정하지 않는다. 프로그램(사용자)이 다른 글꼴을 정한 경우에만 §3.1 방법으로 `--hm-sw-header-font-family`를 바꾸고 결과 보고에 "글꼴 변경"으로 적는다.
- Weight 범위(2026-10-20 사용자 확정): SWHeader 안의 필드 값은 400, Composed Controls(SWHeader 밖에 단독 배치한 `SWHeaderLayerMenu` 등)의 필드 값은 700. 파일명·Footer 보조기능/명령어 제목·Footer 분류 탭·Viewer Header 파일명/버튼 라벨/Model·Layout·최근파일 이름/경로/미리보기 제목, Footer Model/Layout 탭 라벨(모든 상태, 2026-10-22 사용자 확정, 이름 편집 입력은 400)은 700, Footer `전체보기`·명령·보조기능 라벨은 400. 임의로 일괄 변경하지 않는다. Figma 고정 폭 칸(예: 가중치·스케일 38px)에 그보다 긴 값(예: `ByLayer`)이 들어가면 말줄임 + `title`이 된다. Figma 레이어 이름의 `Dotum/bold/14`를 보고 700으로 바꾸거나 겹침·외곽선을 따로 만들지 않는다. - Weight 범위(2026-10-20 사용자 확정): SWHeader 안의 필드 값은 400, Composed Controls(SWHeader 밖에 단독 배치한 `SWHeaderLayerMenu` 등)의 필드 값은 700. 파일명·Footer 보조기능/명령어 제목·Footer 분류 탭·Viewer Header 파일명/버튼 라벨/Model·Layout·최근파일 이름/경로/미리보기 제목, Footer Model/Layout 탭 라벨(모든 상태, 2026-10-22 사용자 확정, 이름 편집 입력은 400)은 700, Footer `전체보기`·명령·보조기능 라벨은 400. 임의로 일괄 변경하지 않는다. Figma 고정 폭 칸(예: 가중치·스케일 38px)에 그보다 긴 값(예: `ByLayer`)이 들어가면 말줄임 + `title`이 된다. Figma 레이어 이름의 `Dotum/bold/14`를 보고 700으로 바꾸거나 겹침·외곽선을 따로 만들지 않는다.
- CSS는 전역(`html`, `body`, `*`)을 건드리지 않는다. `body` margin 등 페이지 기본값은 소비 앱이 정한다. - CSS는 전역(`html`, `body`, `*`)을 건드리지 않는다. `body` margin 등 페이지 기본값은 소비 앱이 정한다.
- 스크롤바: SW 전용 스크롤바 디자인은 없다. 기존 SW 컴포넌트는 브라우저 기본 스크롤바를 쓰며(속성Bar만 `scrollbar-width: thin`), Popup 본문 스크롤은 `SWPopup`이 처리한다. 새 스크롤 영역에 임의 스크롤바 색·두께를 만들지 않는다. - 스크롤바: 일반 스크롤바 디자인은 없다. 패키지가 정한 곳만 `scrollbar-width: thin`이다 — 속성Bar, Popup 본문(`SWPopup`·`SWViewerPanel`·속성창, `--hm-sw-popup-scrollbar-thumb`·`-track`), Footer 명령창 기록(`--hm-sw-footer-command-scrollbar-thumb`·`-track`, 2026-11-10). 그 밖의 SW 컴포넌트는 브라우저 기본 스크롤바를 쓴다. 새 스크롤 영역에 임의 스크롤바 색·두께를 만들지 않는다.
- TypeScript에서 CSS side-effect import 오류가 나면 Vite의 `vite/client` 타입을 포함한다. - TypeScript에서 CSS side-effect import 오류가 나면 Vite의 `vite/client` 타입을 포함한다.
## 3. 스타일 규칙 ## 3. 스타일 규칙
@@ -138,6 +138,12 @@ SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-popup-*`, `--hm-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. - 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.
- Viewer Header(`SWViewerHeader`, 2026-11-10): 파일명·객체 수정·속성 면의 SWHeader 토큰 10개를 `--hm-sw-viewer-document-*`·`--hm-sw-viewer-object-*`·`--hm-sw-viewer-property-*` alias(기본값 `var(--hm-sw-header-…)`)로 읽습니다. 기본은 SWHeader 테마 공유 그대로이고, 가장 바깥 범위에 `--hm-sw-viewer-*`를 정하면 Viewer Header만 바뀝니다. 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 alias에 전달되지 않습니다. 목록·적용 범위는 [DESIGN_RULES.md](./DESIGN_RULES.md) §11. - Viewer Header(`SWViewerHeader`, 2026-11-10): 파일명·객체 수정·속성 면의 SWHeader 토큰 10개를 `--hm-sw-viewer-document-*`·`--hm-sw-viewer-object-*`·`--hm-sw-viewer-property-*` alias(기본값 `var(--hm-sw-header-…)`)로 읽습니다. 기본은 SWHeader 테마 공유 그대로이고, 가장 바깥 범위에 `--hm-sw-viewer-*`를 정하면 Viewer Header만 바뀝니다. 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 alias에 전달되지 않습니다. 목록·적용 범위는 [DESIGN_RULES.md](./DESIGN_RULES.md) §11.
- 컴포넌트 전용 색 토큰(2026-11-10, 값 변경 없음): 아래 9개는 같은 방식(가장 바깥 범위에서 정함, 기본값은 기존 토큰 또는 기존 값)이며, 정하지 않으면 지금 모양 그대로이고 정하면 해당 부분만 바뀝니다.
- Viewer Header 메뉴(로고 메뉴·Header 안 Dropdown·More): `--hm-sw-viewer-header-menu-background`·`-border-color`·`-shadow`·`-item-hover-background`(기본 SWHeader `--hm-sw-header-more-*`). SWHeader `...` 메뉴는 그대로입니다. [DESIGN_RULES.md](./DESIGN_RULES.md) §11.
- SWHeader K 가이드 패널: `--hm-sw-header-k-guide-border-color`(기본 `var(--hm-sw-property-border)`)·`--hm-sw-header-k-guide-background`(`#0e3c2e`). 속성창 테두리는 그대로입니다. §5.
- Popup 값 글자색: `--hm-sw-popup-value-color`(기본 `var(--hm-sw-header-input-color)`). 속성Bar 값은 그대로입니다. §8.
- Footer 명령창 기록 스크롤바: `--hm-sw-footer-command-scrollbar-thumb`(`#574b30`)·`-track`(`transparent`). §13.
- 공유 유지(전용 토큰 없음): Focus 링(`--hm-sw-header-focus-color`, Footer 포함), 글자색·글자 외곽선, Viewer 메뉴 구분선.
## 4. 판단 기록 남기기 ## 4. 판단 기록 남기기
+7 -4
View File
@@ -71,7 +71,8 @@
- 목록 최대 높이 240px(8행), 넘치면 내부 스크롤. 색상 팔레트 118px. - 목록 최대 높이 240px(8행), 넘치면 내부 스크롤. 색상 팔레트 118px.
- 속성창·K 가이드는 `position: fixed` Overlay(뷰포트 안 보정, Portal·Dim 없음). - 속성창·K 가이드는 `position: fixed` Overlay(뷰포트 안 보정, Portal·Dim 없음).
- 한 번에 하나의 Dropdown(SWHeader). 단독 사용 시 각 컴포넌트가 바깥 클릭으로 스스로 닫힙니다. - 한 번에 하나의 Dropdown(SWHeader). 단독 사용 시 각 컴포넌트가 바깥 클릭으로 스스로 닫힙니다.
- More(`...`) 메뉴 색(2026-11-10 개별 토큰, 값 변경 없음): 면 `--hm-sw-header-more-menu-background`(`#0E3D2F`), 그림자 `--hm-sw-header-more-menu-shadow`(`0 4px 12px rgba(0,0,0,.5)`), 테두리 `--hm-sw-header-more-menu-border-color`(기본 `var(--hm-sw-header-object-border-color)`), 항목 Hover `--hm-sw-header-more-item-hover-background`(기본 `var(--hm-sw-header-hover)`). 뷰어 Header의 Dropdown·로고 메뉴도 같은 토큰을 읽습니다([VIEWER.md](./VIEWER.md)). - More(`...`) 메뉴 색(2026-11-10 개별 토큰, 값 변경 없음): 면 `--hm-sw-header-more-menu-background`(`#0E3D2F`), 그림자 `--hm-sw-header-more-menu-shadow`(`0 4px 12px rgba(0,0,0,.5)`), 테두리 `--hm-sw-header-more-menu-border-color`(기본 `var(--hm-sw-header-object-border-color)`), 항목 Hover `--hm-sw-header-more-item-hover-background`(기본 `var(--hm-sw-header-hover)`). 뷰어 Header의 Dropdown·로고 메뉴는 기본값이 이 4개인 `--hm-sw-viewer-header-menu-*` alias를 읽습니다(§11, [VIEWER.md](./VIEWER.md)). 정하지 않으면 같은 색이고, alias를 정하면 뷰어 메뉴만 바뀝니다.
- K 가이드 패널 색(2026-11-10 개별 토큰, 값 변경 없음): 테두리 `--hm-sw-header-k-guide-border-color`(기본 `var(--hm-sw-property-border)`, `#A67D03`), 면 `--hm-sw-header-k-guide-background`(`#0E3C2E`). 정하면 K 가이드만 바뀌고 속성창 테두리(`--hm-sw-property-border`)는 그대로입니다. 문구 글자·외곽선은 `--hm-sw-header-text-color`·`--hm-sw-header-label-outline` 그대로입니다.
## 6. Motion ## 6. Motion
@@ -111,7 +112,8 @@
| 구분선 | 개수 목록 아래선 검정 50%, 위치 표 선 검정 40% | `--hm-sw-popup-divider-color`, `--hm-sw-popup-table-line-color` | | 구분선 | 개수 목록 아래선 검정 50%, 위치 표 선 검정 40% | `--hm-sw-popup-divider-color`, `--hm-sw-popup-table-line-color` |
| Switch 그림자 | Off·On 안쪽 그림자 + 테두리 링 + 1px `#FCF0D8` 하이라이트, Thumb `0 .5px 1px rgba(0,0,0,.6)` | `--hm-sw-switch-off-shadow`, `--hm-sw-switch-on-shadow`, `--hm-sw-switch-thumb-shadow` | | Switch 그림자 | Off·On 안쪽 그림자 + 테두리 링 + 1px `#FCF0D8` 하이라이트, Thumb `0 .5px 1px rgba(0,0,0,.6)` | `--hm-sw-switch-off-shadow`, `--hm-sw-switch-on-shadow`, `--hm-sw-switch-thumb-shadow` |
| 속성 행(`SWPopupPropertyRow`) | 속성창 행 그대로(라벨 72px 700, 값 상자 28px, `picker` 필드). 데이터 = `SWHeaderProperty` + `menus` | `--hm-sw-property-*`, `--hm-sw-property-field-*` | | 속성 행(`SWPopupPropertyRow`) | 속성창 행 그대로(라벨 72px 700, 값 상자 28px, `picker` 필드). 데이터 = `SWHeaderProperty` + `menus` | `--hm-sw-property-*`, `--hm-sw-property-field-*` |
| 행(`SWPopupRow`) | 최소 32px, 라벨 72px 700 검정, 값 글자 `#E60403` 500, 단위 12px | `--hm-sw-property-label`, `--hm-sw-header-input-color` | | 행(`SWPopupRow`) | 최소 32px, 라벨 72px 700 검정, 값 글자 `#E60403` 500, 단위 12px | `--hm-sw-property-label`, `--hm-sw-popup-value-color` |
| 값 글자색(2026-11-10 개별 토큰, 값 변경 없음) | 편집 가능한 값 `#E60403`: 속성창·`SWPopupRow`의 값 Input(값·게이지 값·색상 값 칸), 값 `select`, 속성창 안 `SWSelect`(사용 가능), Dropdown Trigger, `SWViewerPanel`의 `SWViewerInput`(검색 제외). 읽기 전용 값은 라벨색 그대로. 기본 `var(--hm-sw-header-input-color)`라 Header 테마를 따르고, 정하면 Popup 값만 바뀜(SWHeader 속성Bar 값은 `--hm-sw-header-input-color`) | `--hm-sw-popup-value-color` |
| 값 상자 | 28px, Hover 1px `rgba(33,29,19,.4)`, 숫자 편집 On `#0F0D0A`(단위 흰색), Dropdown On `#211D13` | `--hm-sw-property-field-*` | | 값 상자 | 28px, Hover 1px `rgba(33,29,19,.4)`, 숫자 편집 On `#0F0D0A`(단위 흰색), Dropdown On `#211D13` | `--hm-sw-property-field-*` |
| 동작 | Non-modal(Dim·Portal·Focus trap 없음). Escape는 Popup 안이면 그 Popup, 밖이면 최근 사용 Popup 하나. `autoFocus`면 열릴 때 Focus, 안에서 닫히면 원래 위치로 | — | | 동작 | Non-modal(Dim·Portal·Focus trap 없음). Escape는 Popup 안이면 그 Popup, 밖이면 최근 사용 Popup 하나. `autoFocus`면 열릴 때 Focus, 안에서 닫히면 원래 위치로 | — |
@@ -160,7 +162,8 @@
- **Model / Layout 탭 토큰:** 위 `--hm-sw-viewer-tabs-*`·`--hm-sw-viewer-tab-*`은 탭 컨테이너·버튼에만 연결되며 다른 Header 부품·SWHeader·Footer·패널에 쓰이지 않습니다. 탭 색만 바꾸려면 가장 바깥 SW 범위의 `style`로 덮습니다: 화면을 `<SWTheme style={...}>`로 감싸거나 단독 `SWViewerHeader`의 루트에 지정합니다(토큰이 그 범위에 선언되므로 일반 `div` 래퍼의 값은 가려짐). 예: `style={{ "--hm-sw-viewer-tab-active-border": "linear-gradient(#e07b00, #e07b00)", "--hm-sw-viewer-tab-inactive-border": "linear-gradient(#3d6bb3, #3d6bb3)" }}`. `var(...)` 기본값(`active-text`·`hover-*`·`focus-color`)은 SW 범위에서 계산되므로 공통 토큰(`--hm-sw-viewer-accent` 등)을 바꾸면 같은 범위에서 함께 따라가고, 탭 토큰을 직접 지정하면 공통 토큰과 독립합니다. 글자 외곽선(`text-shadow`)은 탭에서도 기존 그대로(이름 `--hm-sw-header-text-outline` 1.3px, 개수 `--hm-sw-header-label-outline` 1px)이며 탭 토큰이 아닙니다. 탭 class로 색을 덮지 않습니다. - **Model / Layout 탭 토큰:** 위 `--hm-sw-viewer-tabs-*`·`--hm-sw-viewer-tab-*`은 탭 컨테이너·버튼에만 연결되며 다른 Header 부품·SWHeader·Footer·패널에 쓰이지 않습니다. 탭 색만 바꾸려면 가장 바깥 SW 범위의 `style`로 덮습니다: 화면을 `<SWTheme style={...}>`로 감싸거나 단독 `SWViewerHeader`의 루트에 지정합니다(토큰이 그 범위에 선언되므로 일반 `div` 래퍼의 값은 가려짐). 예: `style={{ "--hm-sw-viewer-tab-active-border": "linear-gradient(#e07b00, #e07b00)", "--hm-sw-viewer-tab-inactive-border": "linear-gradient(#3d6bb3, #3d6bb3)" }}`. `var(...)` 기본값(`active-text`·`hover-*`·`focus-color`)은 SW 범위에서 계산되므로 공통 토큰(`--hm-sw-viewer-accent` 등)을 바꾸면 같은 범위에서 함께 따라가고, 탭 토큰을 직접 지정하면 공통 토큰과 독립합니다. 글자 외곽선(`text-shadow`)은 탭에서도 기존 그대로(이름 `--hm-sw-header-text-outline` 1.3px, 개수 `--hm-sw-header-label-outline` 1px)이며 탭 토큰이 아닙니다. 탭 class로 색을 덮지 않습니다.
- **공유 토큰(모드 혼합 아님):** 뷰어 Header의 파일명·객체 수정·속성 면은 `--hm-sw-header-document-*`, `--hm-sw-header-object-*`, `--hm-sw-header-property-*`를, 글자 외곽선은 `--hm-sw-header-text-outline`·`--hm-sw-header-label-outline`을 그대로 읽습니다(§2·§3). 패널 제목 막대 추가 버튼(`ghost`)의 Hover·Focus 색은 `--hm-sw-header-selected-color`입니다. - **공유 토큰(모드 혼합 아님):** 뷰어 Header의 파일명·객체 수정·속성 면은 `--hm-sw-header-document-*`, `--hm-sw-header-object-*`, `--hm-sw-header-property-*`를, 글자 외곽선은 `--hm-sw-header-text-outline`·`--hm-sw-header-label-outline`을 그대로 읽습니다(§2·§3). 패널 제목 막대 추가 버튼(`ghost`)의 Hover·Focus 색은 `--hm-sw-header-selected-color`입니다.
- **Viewer Header 전용 덮어쓰기(2026-11-10, 공유 유지):** 위 면 토큰 10개는 같은 이름의 `--hm-sw-viewer-*` alias(가장 바깥 SW 범위, 기본값 `var(--hm-sw-header-…)`)를 거쳐 읽습니다 — `--hm-sw-viewer-document-background`·`-document-border-gradient`·`-document-border-color`, `--hm-sw-viewer-object-background`·`-object-border-gradient`·`-object-radius`, `--hm-sw-viewer-property-background`·`-property-border-color`·`-property-radius`·`-property-shadow`. 적용 범위: 파일명 면, 객체 수정 면, 속성 입력 그룹(`data-surface="field"`), 오른쪽 상태·버튼 띠와 그 시작 조각. 기본 동작은 그대로 공유입니다(정하지 않으면 SWHeader 테마를 따름). Viewer Header만 바꾸려면 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWViewerHeader`의 `style`)에 `--hm-sw-viewer-*`를 지정합니다(SWHeader·Step Header 영향 없음). 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소에서만 바꾼 `--hm-sw-header-*`는 이 10개에 전달되지 않습니다. 파일명 버튼 등 재사용 SWHeader 부품의 `sw.css` 규칙(로고가 있을 때 로고 칸 바탕 — SWHeader 홈과 같은 공용 규칙 `.hm-sw-header__home::before, .hm-sw-viewer-header__brand[data-logo]::before` — 포함), Dropdown·More·파일 목록, 패널(`selected-color`·`input-color`), 글자 외곽선은 계속 `--hm-sw-header-*`를 읽습니다. - **Viewer Header 전용 덮어쓰기(2026-11-10, 공유 유지):** 위 면 토큰 10개는 같은 이름의 `--hm-sw-viewer-*` alias(가장 바깥 SW 범위, 기본값 `var(--hm-sw-header-…)`)를 거쳐 읽습니다 — `--hm-sw-viewer-document-background`·`-document-border-gradient`·`-document-border-color`, `--hm-sw-viewer-object-background`·`-object-border-gradient`·`-object-radius`, `--hm-sw-viewer-property-background`·`-property-border-color`·`-property-radius`·`-property-shadow`. 적용 범위: 파일명 면, 객체 수정 면, 속성 입력 그룹(`data-surface="field"`), 오른쪽 상태·버튼 띠와 그 시작 조각. 기본 동작은 그대로 공유입니다(정하지 않으면 SWHeader 테마를 따름). Viewer Header만 바꾸려면 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWViewerHeader`의 `style`)에 `--hm-sw-viewer-*`를 지정합니다(SWHeader·Step Header 영향 없음). 제약: alias는 가장 바깥 범위에서 계산되므로 안쪽 요소에서만 바꾼 `--hm-sw-header-*`는 이 10개에 전달되지 않습니다. 파일명 버튼 등 재사용 SWHeader 부품의 `sw.css` 규칙(로고가 있을 때 로고 칸 바탕 — SWHeader 홈과 같은 공용 규칙 `.hm-sw-header__home::before, .hm-sw-viewer-header__brand[data-logo]::before` — 포함), 파일 목록, 패널 `selected-color`, 글자 외곽선은 계속 `--hm-sw-header-*`를 읽습니다. Header 안 Dropdown·More·로고 메뉴는 아래 메뉴 alias, 패널 값 글자색은 `--hm-sw-popup-value-color`(§8)를 읽습니다.
- **Viewer Header 메뉴 전용 덮어쓰기(2026-11-10, 공유 유지):** 로고 메뉴와 Header 안 Dropdown(메뉴 버튼·More·하위 메뉴)은 `--hm-sw-viewer-header-menu-background`·`-border-color`·`-shadow`·`-item-hover-background`(가장 바깥 SW 범위, 기본값 각각 `var(--hm-sw-header-more-menu-background)`·`var(--hm-sw-header-more-menu-border-color)`·`var(--hm-sw-header-more-menu-shadow)`·`var(--hm-sw-header-more-item-hover-background)`)를 읽습니다. Hover 토큰은 Focus·서브메뉴 열림에도 같습니다. 정하지 않으면 SWHeader `...` 메뉴와 같은 색이고, 정하면 Viewer Header 메뉴만 바뀝니다(SWHeader More 영향 없음). 글자 `--hm-sw-header-text-color`(보조 글자 80%)·구분선 `--hm-sw-header-document-menu-divider-color`·Disabled 0.5는 공유 그대로이고, 단독 `SWViewerMenu`는 위 `--hm-sw-viewer-menu-*`·`--hm-sw-viewer-more-*`를 씁니다. 제약은 면 alias와 같습니다(안쪽 요소에서만 바꾼 `--hm-sw-header-more-*`는 전달되지 않음).
| 상태 | Header 항목(button·toggle·menu·tabs·switch) | 패널 콘텐츠 | | 상태 | Header 항목(button·toggle·menu·tabs·switch) | 패널 콘텐츠 |
|---|---|---| |---|---|---|
@@ -216,7 +219,7 @@
| Layout 탭 | 높이 22, 12px 자간 -5%(Model 포함, 2026-10-22; 이전 2%), 라벨 700(모든 상태, 2026-10-22; 이름 편집 입력은 400). Default 면 `#C3B79E` 글자 `#161410`. On: `linear-gradient(0deg, #7C6C4C, #7C6C4C 85%, #A6946F)` 흰 글자. 이름 편집: `#0F0D0A` 빨강 `#E60403` 주황 테두리. Hover·On·편집 글자 아래 70% 검정 1.3px 외곽선. 둥근 탭: 1px `rgba(0,0,0,.05)`(Hover·편집 `#DA6813`), 위 radius 4, 안쪽 그림자 `inset 0 -3px 4px rgba(56,48,31,.25)`, ×(16) Hover 노랑. 사선 탭(마지막): 97 × 22 자산(9-slice, 2026-10-22: 97px·라벨 57px가 최소, 라벨이 길면 사선 21px·모서리 4px는 고정하고 가운데만 늘어남, 라벨 최대 12em), On hover = 베이지 면 + 주황 글자, ×(28, 빨강 사선). Hover(선택 아님, 두 모양 모두, 2026-10-20) = Model과 같은 `gra-border-orange` 면(사선 탭은 자산 모양 안)만 바뀌고 글자는 Default 그대로(`#161410` 12px 700, 외곽선 없음, 위치·크기 불변) | `--hm-sw-footer-tab-*`, `--hm-sw-footer-gra-border-orange` | | Layout 탭 | 높이 22, 12px 자간 -5%(Model 포함, 2026-10-22; 이전 2%), 라벨 700(모든 상태, 2026-10-22; 이름 편집 입력은 400). Default 면 `#C3B79E` 글자 `#161410`. On: `linear-gradient(0deg, #7C6C4C, #7C6C4C 85%, #A6946F)` 흰 글자. 이름 편집: `#0F0D0A` 빨강 `#E60403` 주황 테두리. Hover·On·편집 글자 아래 70% 검정 1.3px 외곽선. 둥근 탭: 1px `rgba(0,0,0,.05)`(Hover·편집 `#DA6813`), 위 radius 4, 안쪽 그림자 `inset 0 -3px 4px rgba(56,48,31,.25)`, ×(16) Hover 노랑. 사선 탭(마지막): 97 × 22 자산(9-slice, 2026-10-22: 97px·라벨 57px가 최소, 라벨이 길면 사선 21px·모서리 4px는 고정하고 가운데만 늘어남, 라벨 최대 12em), On hover = 베이지 면 + 주황 글자, ×(28, 빨강 사선). Hover(선택 아님, 두 모양 모두, 2026-10-20) = Model과 같은 `gra-border-orange` 면(사선 탭은 자산 모양 안)만 바뀌고 글자는 Default 그대로(`#161410` 12px 700, 외곽선 없음, 위치·크기 불변) | `--hm-sw-footer-tab-*`, `--hm-sw-footer-gra-border-orange` |
| `+` 버튼 | Layout 옆 `+`(24 × 22) Hover = `gra-border-orange` 면 + `+`(분류 행 `+` 자산). 분류 행 `+`(28 × 12) Hover = `+` 채움 `#FF5C00`(검정 외곽선 그대로) | `--hm-sw-footer-gra-border-orange`, `--hm-sw-footer-accent` | | `+` 버튼 | Layout 옆 `+`(24 × 22) Hover = `gra-border-orange` 면 + `+`(분류 행 `+` 자산). 분류 행 `+`(28 × 12) Hover = `+` 채움 `#FF5C00`(검정 외곽선 그대로) | `--hm-sw-footer-gra-border-orange`, `--hm-sw-footer-accent` |
| Model 탭 | 73 × 22 자산(왼쪽 사선, 9-slice: 73px 최소, 긴 라벨이면 사선 21px 고정·가운데만 늘어남, 라벨 최대 12em, 2026-10-22), 글자 왼쪽 22. Hover·On = 자산 모양 안 `gra-border-orange` 면 + 검정 `#161410` 글자(12px, 외곽선 없음)(2026-10-20 사용자 확정, 이전 Figma On 갈색·흰 글자 대체). 라벨은 모든 상태 700(2026-10-22). 앞 사선 탭과 8px 겹침 | `--hm-sw-footer-gra-border-orange` | | Model 탭 | 73 × 22 자산(왼쪽 사선, 9-slice: 73px 최소, 긴 라벨이면 사선 21px 고정·가운데만 늘어남, 라벨 최대 12em, 2026-10-22), 글자 왼쪽 22. Hover·On = 자산 모양 안 `gra-border-orange` 면 + 검정 `#161410` 글자(12px, 외곽선 없음)(2026-10-20 사용자 확정, 이전 Figma On 갈색·흰 글자 대체). 라벨은 모든 상태 700(2026-10-22). 앞 사선 탭과 8px 겹침 | `--hm-sw-footer-gra-border-orange` |
| 명령창 | 566 × 71 / 270, 위 radius 4, 위 71px `linear-gradient(180deg, #635537, #262218 5%, #281F16 10%, #282216)` + 아래 `#282216`. 12px 자간 10% 줄 14, 1px 외곽선. 입력 칸 23(좌우 4) `#16130D` + `0 -1px 2px rgba(255,255,255,.25)`, `inset 0 1px 4px #000`. 화살표 18 × 18 검정 radius 2(4, 4 오른쪽 위) | `--hm-sw-footer-command-*` | | 명령창 | 566 × 71 / 270, 위 radius 4, 위 71px `linear-gradient(180deg, #635537, #262218 5%, #281F16 10%, #282216)` + 아래 `#282216`. 12px 자간 10% 줄 14, 1px 외곽선. 입력 칸 23(좌우 4) `#16130D` + `0 -1px 2px rgba(255,255,255,.25)`, `inset 0 1px 4px #000`. 화살표 18 × 18 검정 radius 2(4, 4 오른쪽 위). 기록 스크롤바 `thin`, Thumb `#574B30` / Track `transparent`(2026-11-10 개별 토큰, 값 변경 없음) | `--hm-sw-footer-command-*`(스크롤바 `--hm-sw-footer-command-scrollbar-thumb`·`-track`) |
| 바 안 명령창(실제 화면) | 보조기능 뒤 4px, 바 아래에 붙어 펼치면 위로 270. 남는 폭(바 − 보조기능 영역 − 명령어 전체보기)을 칸 영역 0.57 : 명령창 나머지(최소 280)로 나눔 — 칸 수는 바 폭으로만 계산, 분류의 명령 수와 무관(1920 → 12칸) | `--hm-sw-footer-command-min-width`, `--hm-sw-footer-slot-share` | | 바 안 명령창(실제 화면) | 보조기능 뒤 4px, 바 아래에 붙어 펼치면 위로 270. 남는 폭(바 − 보조기능 영역 − 명령어 전체보기)을 칸 영역 0.57 : 명령창 나머지(최소 280)로 나눔 — 칸 수는 바 폭으로만 계산, 분류의 명령 수와 무관(1920 → 12칸) | `--hm-sw-footer-command-min-width`, `--hm-sw-footer-slot-share` |
| 좌표(실제 화면) | 바 위 왼쪽, 12px `#F6F4F2` 1px 외곽선, 아래 3px | — | | 좌표(실제 화면) | 바 위 왼쪽, 12px `#F6F4F2` 1px 외곽선, 아래 3px | — |
| 좁은 폭(실제 화면) | 빈 칸이 먼저 줄고, 넘치는 명령은 마지막 칸 ▲(ico_arrow 18 × 18, 오른쪽 위 4) → 명령 칸 위 패널(같은 칸, 오른쪽 맞춤, 테두리 1px `#886D35`·아래 없음·위 radius 4, 명령 칸 면). 분류 탭 넘침 = 탭 영역 잘림 + ◀ ▶(13 × 16, 6 × 8 삼각형 `#F6F4F2`, Hover `#FF5C00`) + +. 바 1279px 이하 보조기능 아이콘만 45 × 33 | `--hm-sw-footer-aux-compact-width` | | 좁은 폭(실제 화면) | 빈 칸이 먼저 줄고, 넘치는 명령은 마지막 칸 ▲(ico_arrow 18 × 18, 오른쪽 위 4) → 명령 칸 위 패널(같은 칸, 오른쪽 맞춤, 테두리 1px `#886D35`·아래 없음·위 radius 4, 명령 칸 면). 분류 탭 넘침 = 탭 영역 잘림 + ◀ ▶(13 × 16, 6 × 8 삼각형 `#F6F4F2`, Hover `#FF5C00`) + +. 바 1279px 이하 보조기능 아이콘만 45 × 33 | `--hm-sw-footer-aux-compact-width` |
+1 -1
View File
@@ -45,7 +45,7 @@
- **반응형(2026-10-21, 실제 가용 폭 기준 3단계):** 고정 Breakpoint를 쓰지 않고 Header 행의 실제 남는 폭(로고·파일명·메뉴·Gap·Padding·More 버튼 포함)을 측정합니다. 파일명 면은 글자 수가 아니라 Header 폭으로 정한 기본 폭으로 계산하므로, 같은 폭에서는 짧은 파일명과 긴 파일명의 단계·파일 열기 버튼·파일명 면 너비가 같고 긴 이름은 `…`입니다(2026-10-21). ① 모두 들어가면 기본 형태(아이콘+텍스트), ② 부족하면 `icon`과 `iconOnlyBelow`가 있는 항목을 모두 아이콘만(라벨은 Tooltip·접근성 이름), ③ 그래도 부족하면 `collapseBelow` 항목을 하나씩 More(⋯, 베이지 260px Dropdown)로 옮깁니다(값이 큰 것부터, 같으면 오른쪽 먼저). ③의 판단(2026-10-26)은 tabs를 최소 폭(`--hm-sw-viewer-tabs-min-width`)으로 세므로, tabs가 `…`로 좁아져서 들어가면 More로 옮기지 않습니다. 파일명 면 기본 폭은 `clamp(80px, min(45cqw - 256px, 100cqw - 780px), 360px)`(1280px 이상은 이전과 같음), Header 560px 미만에서는 56px입니다. 넓어지면 More → 아이콘 → 기본 순서로 복귀하고, 상단과 More에 같은 항목이 동시에 나오지 않습니다. `collapseBelow`·`iconOnlyBelow`의 숫자는 px Breakpoint가 아니며 순서만 정합니다(`iconOnlyBelow`는 지정 여부만, `collapseBelow`는 값이 큰 것부터; Props 형식 동일). 로고 옆 파일 열기 버튼(`onDocumentOpen`)은 ② 단계에서 아이콘만으로 줄고 More로 옮기지 않으며, 샘플은 `documents`. 레이어·설정·속성은 `collapseBelow` 없이 아이콘까지만. 1180px 미만 간격 2px, 560px 미만 상태 글자 숨김(CSS container)은 그대로이고, 560px 미만에서는 tabs 개수 숨김·switch `shortLabel` 캡션·switch 좌우 여백 1px도 적용됩니다(2026-10-26). 가로 스크롤은 만들지 않습니다. - **반응형(2026-10-21, 실제 가용 폭 기준 3단계):** 고정 Breakpoint를 쓰지 않고 Header 행의 실제 남는 폭(로고·파일명·메뉴·Gap·Padding·More 버튼 포함)을 측정합니다. 파일명 면은 글자 수가 아니라 Header 폭으로 정한 기본 폭으로 계산하므로, 같은 폭에서는 짧은 파일명과 긴 파일명의 단계·파일 열기 버튼·파일명 면 너비가 같고 긴 이름은 `…`입니다(2026-10-21). ① 모두 들어가면 기본 형태(아이콘+텍스트), ② 부족하면 `icon`과 `iconOnlyBelow`가 있는 항목을 모두 아이콘만(라벨은 Tooltip·접근성 이름), ③ 그래도 부족하면 `collapseBelow` 항목을 하나씩 More(⋯, 베이지 260px Dropdown)로 옮깁니다(값이 큰 것부터, 같으면 오른쪽 먼저). ③의 판단(2026-10-26)은 tabs를 최소 폭(`--hm-sw-viewer-tabs-min-width`)으로 세므로, tabs가 `…`로 좁아져서 들어가면 More로 옮기지 않습니다. 파일명 면 기본 폭은 `clamp(80px, min(45cqw - 256px, 100cqw - 780px), 360px)`(1280px 이상은 이전과 같음), Header 560px 미만에서는 56px입니다. 넓어지면 More → 아이콘 → 기본 순서로 복귀하고, 상단과 More에 같은 항목이 동시에 나오지 않습니다. `collapseBelow`·`iconOnlyBelow`의 숫자는 px Breakpoint가 아니며 순서만 정합니다(`iconOnlyBelow`는 지정 여부만, `collapseBelow`는 값이 큰 것부터; Props 형식 동일). 로고 옆 파일 열기 버튼(`onDocumentOpen`)은 ② 단계에서 아이콘만으로 줄고 More로 옮기지 않으며, 샘플은 `documents`. 레이어·설정·속성은 `collapseBelow` 없이 아이콘까지만. 1180px 미만 간격 2px, 560px 미만 상태 글자 숨김(CSS container)은 그대로이고, 560px 미만에서는 tabs 개수 숨김·switch `shortLabel` 캡션·switch 좌우 여백 1px도 적용됩니다(2026-10-26). 가로 스크롤은 만들지 않습니다.
- More는 그룹/항목 단위 구획으로 자동 구성됩니다: menu·tabs는 자기 구획(`moreLabel`), button·toggle·switch는 그룹 구획(`group.moreLabel ?? group.label`). toggle·switch는 체크 항목, tabs는 라디오 항목이 됩니다. - More는 그룹/항목 단위 구획으로 자동 구성됩니다: menu·tabs는 자기 구획(`moreLabel`), button·toggle·switch는 그룹 구획(`group.moreLabel ?? group.label`). toggle·switch는 체크 항목, tabs는 라디오 항목이 됩니다.
- **Dropdown 색(2026-10-21):** Header 안의 Dropdown — 메뉴 버튼(`type: "menu"`), More(⋯), 로고 메뉴 — 은 크기·간격·글자는 `SWViewerMenu` 그대로, 색은 일반 `SWHeader`의 `...` 메뉴와 같은 More 토큰을 읽습니다(2026-11-10: 면 `--hm-sw-header-more-menu-background` #0e3d2f, Border `--hm-sw-header-more-menu-border-color` = `var(--hm-sw-header-object-border-color)`, 그림자 `--hm-sw-header-more-menu-shadow` 0 4px 12px rgba(0,0,0,.5), 글자 `--hm-sw-header-text-color`, 보조 글자는 같은 색 80%, Hover `--hm-sw-header-more-item-hover-background` = `var(--hm-sw-header-hover)`, 구분선 `--hm-sw-header-document-menu-divider-color`). 파일명 목록(`documents`)은 `SWHeader` 파일명 목록 모양을 유지하고, 단독 `SWViewerMenu`(우클릭 메뉴 등)는 기존 색 그대로입니다. - **Dropdown 색(2026-10-21):** Header 안의 Dropdown — 메뉴 버튼(`type: "menu"`), More(⋯), 로고 메뉴 — 은 크기·간격·글자는 `SWViewerMenu` 그대로, 색은 기본적으로 일반 `SWHeader`의 `...` 메뉴와 같습니다. 2026-11-10부터 면·Border·그림자·Hover는 Viewer 전용 alias를 거쳐 읽습니다 — 면 `--hm-sw-viewer-header-menu-background`(기본 `var(--hm-sw-header-more-menu-background)`, #0e3d2f), Border `--hm-sw-viewer-header-menu-border-color`(기본 `var(--hm-sw-header-more-menu-border-color)` = `var(--hm-sw-header-object-border-color)`), 그림자 `--hm-sw-viewer-header-menu-shadow`(기본 `var(--hm-sw-header-more-menu-shadow)`, 0 4px 12px rgba(0,0,0,.5)), Hover·Focus·서브메뉴 열림 `--hm-sw-viewer-header-menu-item-hover-background`(기본 `var(--hm-sw-header-more-item-hover-background)` = `var(--hm-sw-header-hover)`). 이 4개를 가장 바깥 SW 범위에 정하면 Viewer Header 메뉴만 바뀌고 SWHeader `...` 메뉴는 그대로입니다. 글자 `--hm-sw-header-text-color`, 보조 글자는 같은 색 80%, 구분선 `--hm-sw-header-document-menu-divider-color`는 공유 그대로입니다. 파일명 목록(`documents`)은 `SWHeader` 파일명 목록 모양을 유지하고, 단독 `SWViewerMenu`(우클릭 메뉴 등)는 기존 색 그대로입니다.
- **상태 표현:** Hover·Active·Pressed·열림·선택 탭 = 글자·아이콘 #FFC600(면·선택 박스 없음, Hover 전후 크기·위치 동일 — 2026-10-21 Hover 면 #266651 제거), Disabled = 60% 불투명, Focus = 2px #FFC600 외곽선. - **상태 표현:** Hover·Active·Pressed·열림·선택 탭 = 글자·아이콘 #FFC600(면·선택 박스 없음, Hover 전후 크기·위치 동일 — 2026-10-21 Hover 면 #266651 제거), Disabled = 60% 불투명, Focus = 2px #FFC600 외곽선.
- **글자·아이콘 외곽선(SWHeader와 동일):** 14px 글자 `--hm-sw-header-text-outline`(검정 1.3px), 11px 캡션·개수·상태 `--hm-sw-header-label-outline`(검정 1px), 아이콘 `SWViewerIcon`(SWHeader 아이콘 자산의 1px 검정 바깥 Stroke를 **SVG stroke**로 재현: 아이콘 뒤 복사본의 모든 도형을 검정, 원래 선 두께 + 화면 기준 양쪽 1px(`--hm-sw-viewer-icon-outline-width`)로 그림. 위 아이콘은 그대로라 색·모양·선 두께 유지. CSS `filter` 미사용, glow는 생략). Header 항목·`SWViewerHeaderButton`의 `icon`은 자동 적용, `custom` / `trailing`의 직접 그린 아이콘은 `<SWViewerIcon>`으로 감쌉니다. 색·크기·굵기·간격은 그대로이고 Hover/Active는 채움 색만 바뀝니다. Dropdown·패널 안에는 외곽선이 없습니다(SWHeader More 메뉴와 같음). 프로그램 아이콘은 `currentColor`로 그리면 상태 색과 외곽선이 자동 적용됩니다. - **글자·아이콘 외곽선(SWHeader와 동일):** 14px 글자 `--hm-sw-header-text-outline`(검정 1.3px), 11px 캡션·개수·상태 `--hm-sw-header-label-outline`(검정 1px), 아이콘 `SWViewerIcon`(SWHeader 아이콘 자산의 1px 검정 바깥 Stroke를 **SVG stroke**로 재현: 아이콘 뒤 복사본의 모든 도형을 검정, 원래 선 두께 + 화면 기준 양쪽 1px(`--hm-sw-viewer-icon-outline-width`)로 그림. 위 아이콘은 그대로라 색·모양·선 두께 유지. CSS `filter` 미사용, glow는 생략). Header 항목·`SWViewerHeaderButton`의 `icon`은 자동 적용, `custom` / `trailing`의 직접 그린 아이콘은 `<SWViewerIcon>`으로 감쌉니다. 색·크기·굵기·간격은 그대로이고 Hover/Active는 채움 색만 바뀝니다. Dropdown·패널 안에는 외곽선이 없습니다(SWHeader More 메뉴와 같음). 프로그램 아이콘은 `currentColor`로 그리면 상태 색과 외곽선이 자동 적용됩니다.
- **면 색(SWHeader와 공유, 2026-11-10 Viewer 전용 덮어쓰기 추가):** 파일명·객체 수정·속성 입력 면과 오른쪽 상태·버튼 띠는 기본으로 SWHeader 토큰(`--hm-sw-header-document-*`·`-object-*`·`-property-*`)을 따릅니다. Viewer Header만 바꾸려면 같은 이름의 `--hm-sw-viewer-document-*`·`--hm-sw-viewer-object-*`·`--hm-sw-viewer-property-*`(10개)를 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWViewerHeader`의 `style`)에 지정합니다. 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 값에 전달되지 않습니다. 목록은 [DESIGN_RULES.md](./DESIGN_RULES.md) §11. - **면 색(SWHeader와 공유, 2026-11-10 Viewer 전용 덮어쓰기 추가):** 파일명·객체 수정·속성 입력 면과 오른쪽 상태·버튼 띠는 기본으로 SWHeader 토큰(`--hm-sw-header-document-*`·`-object-*`·`-property-*`)을 따릅니다. Viewer Header만 바꾸려면 같은 이름의 `--hm-sw-viewer-document-*`·`--hm-sw-viewer-object-*`·`--hm-sw-viewer-property-*`(10개)를 가장 바깥 SW 범위(`<SWTheme style>` 또는 단독 `SWViewerHeader`의 `style`)에 지정합니다. 안쪽 요소에서 바꾼 `--hm-sw-header-*`는 이 값에 전달되지 않습니다. 목록은 [DESIGN_RULES.md](./DESIGN_RULES.md) §11.
+4 -1
View File
@@ -94,6 +94,9 @@
--hm-sw-footer-command-input-shadow: 0 -1px 2px rgba(255, 255, 255, 0.25), inset 0 1px 4px #000; --hm-sw-footer-command-input-shadow: 0 -1px 2px rgba(255, 255, 255, 0.25), inset 0 1px 4px #000;
--hm-sw-footer-command-font-size: 12px; --hm-sw-footer-command-font-size: 12px;
--hm-sw-footer-command-line-height: 14px; --hm-sw-footer-command-line-height: 14px;
/* History scrollbar (thin; 2026-11-10 individual tokens, values as before: the former scrollbar-color literals). */
--hm-sw-footer-command-scrollbar-thumb: #574b30;
--hm-sw-footer-command-scrollbar-track: transparent;
/* Not in Figma (reference screens 2026-10-20): the command line inside the bar keeps at least this width. */ /* Not in Figma (reference screens 2026-10-20): the command line inside the bar keeps at least this width. */
--hm-sw-footer-command-min-width: 280px; --hm-sw-footer-command-min-width: 280px;
/* Not in Figma: with the command line inside the bar, the slot area takes this share of the free width (bar minus /* Not in Figma: with the command line inside the bar, the slot area takes this share of the free width (bar minus
@@ -613,7 +616,7 @@
overflow-y: auto; overflow-y: auto;
list-style: none; list-style: none;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: #574b30 transparent; scrollbar-color: var(--hm-sw-footer-command-scrollbar-thumb) var(--hm-sw-footer-command-scrollbar-track);
text-shadow: var(--hm-sw-header-label-outline); text-shadow: var(--hm-sw-header-label-outline);
} }
.hm-sw-footer-command__history > li { white-space: pre; } .hm-sw-footer-command__history > li { white-space: pre; }
+21 -7
View File
@@ -127,6 +127,17 @@
--hm-sw-viewer-property-border-color: var(--hm-sw-header-property-border-color); --hm-sw-viewer-property-border-color: var(--hm-sw-header-property-border-color);
--hm-sw-viewer-property-radius: var(--hm-sw-header-property-radius); --hm-sw-viewer-property-radius: var(--hm-sw-header-property-radius);
--hm-sw-viewer-property-shadow: var(--hm-sw-header-property-shadow); --hm-sw-viewer-property-shadow: var(--hm-sw-header-property-shadow);
/* Viewer Header menus (2026-11-10): the logo menu and the header dropdowns (menu buttons, More, their submenus) read
these copies of the SWHeader `...` More tokens. Each defaults to the More token, so the shared look stays; set
--hm-sw-viewer-header-menu-* on the outermost SW scope to change the Viewer Header menus alone (SWHeader More
unchanged). Resolved on the outermost scope like the face aliases above. Text color and the divider stay shared
(--hm-sw-header-text-color, --hm-sw-header-document-menu-divider-color); a standalone SWViewerMenu keeps
--hm-sw-viewer-menu-* / -more-*. */
--hm-sw-viewer-header-menu-background: var(--hm-sw-header-more-menu-background);
--hm-sw-viewer-header-menu-border-color: var(--hm-sw-header-more-menu-border-color);
--hm-sw-viewer-header-menu-shadow: var(--hm-sw-header-more-menu-shadow);
--hm-sw-viewer-header-menu-item-hover-background: var(--hm-sw-header-more-item-hover-background);
} }
.hm-sw-viewer-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } .hm-sw-viewer-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
@@ -225,7 +236,10 @@ button.hm-sw-viewer-header__brand { text-align: left; }
min-width: 160px; min-width: 160px;
padding: 4px; padding: 4px;
border-width: 1px; border-width: 1px;
border-color: var(--hm-sw-viewer-header-menu-border-color);
border-radius: var(--hm-sw-viewer-radius); border-radius: var(--hm-sw-viewer-radius);
background: var(--hm-sw-viewer-header-menu-background);
box-shadow: var(--hm-sw-viewer-header-menu-shadow);
color: var(--hm-sw-header-text-color); color: var(--hm-sw-header-text-color);
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
@@ -237,7 +251,7 @@ button.hm-sw-viewer-header__brand { text-align: left; }
padding: 0 12px; padding: 0 12px;
color: inherit; color: inherit;
} }
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:is(:hover, :focus):not(:disabled) { background: var(--hm-sw-header-more-item-hover-background); outline: none; } .hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:is(:hover, :focus):not(:disabled) { background: var(--hm-sw-viewer-header-menu-item-hover-background); outline: none; }
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:disabled { opacity: 0.5; } .hm-sw-viewer-header__logo-menu .hm-sw-header__more-item:disabled { opacity: 0.5; }
/* Title = SWViewerMenu label, directly above its items (one group, no divider); icons = SWViewerMenu icon slot. */ /* Title = SWViewerMenu label, directly above its items (one group, no divider); icons = SWViewerMenu icon slot. */
.hm-sw-viewer-header__logo-menu .hm-sw-header__more-title { min-height: 0; padding: 6px 12px; font-size: 12px; font-weight: 500; line-height: 18px; } .hm-sw-viewer-header__logo-menu .hm-sw-header__more-title { min-height: 0; padding: 6px 12px; font-size: 12px; font-weight: 500; line-height: 18px; }
@@ -318,13 +332,13 @@ button.hm-sw-viewer-header__brand { text-align: left; }
unchanged on hover. Size, spacing and type stay SWViewerMenu. A standalone SWViewerMenu (context menu) and the unchanged on hover. Size, spacing and type stay SWViewerMenu. A standalone SWViewerMenu (context menu) and the
document list keep their own colors. */ document list keep their own colors. */
.hm-sw-viewer-header .hm-sw-viewer-menu[data-surface] { .hm-sw-viewer-header .hm-sw-viewer-menu[data-surface] {
border-color: var(--hm-sw-header-more-menu-border-color); border-color: var(--hm-sw-viewer-header-menu-border-color);
background: var(--hm-sw-header-more-menu-background); background: var(--hm-sw-viewer-header-menu-background);
box-shadow: var(--hm-sw-header-more-menu-shadow); box-shadow: var(--hm-sw-viewer-header-menu-shadow);
color: var(--hm-sw-header-text-color); color: var(--hm-sw-header-text-color);
} }
.hm-sw-viewer-header .hm-sw-viewer-menu .hm-sw-viewer-menu__item:is(:focus, :hover, [data-state="open"]):not([aria-disabled]) { .hm-sw-viewer-header .hm-sw-viewer-menu .hm-sw-viewer-menu__item:is(:focus, :hover, [data-state="open"]):not([aria-disabled]) {
background: var(--hm-sw-header-more-item-hover-background); background: var(--hm-sw-viewer-header-menu-item-hover-background);
color: inherit; color: inherit;
} }
.hm-sw-viewer-header .hm-sw-viewer-menu :is(.hm-sw-viewer-menu__label, .hm-sw-viewer-menu__shortcut, .hm-sw-viewer-menu__chevron) { color: inherit; opacity: 0.8; } .hm-sw-viewer-header .hm-sw-viewer-menu :is(.hm-sw-viewer-menu__label, .hm-sw-viewer-menu__shortcut, .hm-sw-viewer-menu__chevron) { color: inherit; opacity: 0.8; }
@@ -880,8 +894,8 @@ button.hm-sw-viewer-header__brand { text-align: left; }
border-color: transparent; border-color: transparent;
border-radius: 2px; border-radius: 2px;
background: transparent; background: transparent;
color: var(--hm-sw-header-input-color); color: var(--hm-sw-popup-value-color);
caret-color: var(--hm-sw-header-input-color); caret-color: var(--hm-sw-popup-value-color);
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: 16px; line-height: 16px;
+19 -7
View File
@@ -112,12 +112,21 @@
--hm-sw-popup-group-shadow: 0 2px 2px rgba(255, 255, 255, .6), inset 0 2px 4px #e1cfab, inset 0 -2px 2px rgba(104, 95, 76, .8); --hm-sw-popup-group-shadow: 0 2px 2px rgba(255, 255, 255, .6), inset 0 2px 4px #e1cfab, inset 0 -2px 2px rgba(104, 95, 76, .8);
--hm-sw-popup-divider-color: rgba(0, 0, 0, .5); --hm-sw-popup-divider-color: rgba(0, 0, 0, .5);
--hm-sw-popup-table-line-color: rgba(0, 0, 0, .4); --hm-sw-popup-table-line-color: rgba(0, 0, 0, .4);
/* Popup value (2026-11-10): the red editable value of the property window / SWPopupRow (input, select, SWSelect,
dropdown trigger) and of SWViewerPanel inputs. Defaults to the header input color it used, so a header theme still
reaches it; set it to change popup values alone (the SWHeader 속성Bar keeps --hm-sw-header-input-color). */
--hm-sw-popup-value-color: var(--hm-sw-header-input-color);
/* SWHeader `...` More menu (2026-11-10 individual tokens, values as before). The viewer header dropdowns and the /* SWHeader `...` More menu (2026-11-10 individual tokens, values as before). The viewer header dropdowns and the
viewer logo menu take the same face and shadow. Border and row hover default to the header tokens they used. */ viewer logo menu default to the same face and shadow (--hm-sw-viewer-header-menu-*, sw-viewer.css). Border and row
hover default to the header tokens they used. */
--hm-sw-header-more-menu-background: #0e3d2f; --hm-sw-header-more-menu-background: #0e3d2f;
--hm-sw-header-more-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); --hm-sw-header-more-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
--hm-sw-header-more-menu-border-color: var(--hm-sw-header-object-border-color); --hm-sw-header-more-menu-border-color: var(--hm-sw-header-object-border-color);
--hm-sw-header-more-item-hover-background: var(--hm-sw-header-hover); --hm-sw-header-more-item-hover-background: var(--hm-sw-header-hover);
/* SWHeader K guide preview panel (2026-11-10 individual tokens, values as before): its border defaulted to the
property window border, its face was a literal. Text and outline stay the header tokens. */
--hm-sw-header-k-guide-border-color: var(--hm-sw-property-border);
--hm-sw-header-k-guide-background: #0e3c2e;
/* Popup scrollbar (every SW popup body: SWPopup, SWViewerPanel, property window). Same as the DWG/DXF viewer /* Popup scrollbar (every SW popup body: SWPopup, SWViewerPanel, property window). Same as the DWG/DXF viewer
panels (thin, HM warm-neutral-mid thumb on warm-neutral-light track). */ panels (thin, HM warm-neutral-mid thumb on warm-neutral-light track). */
--hm-sw-popup-scrollbar-thumb: #9a9284; --hm-sw-popup-scrollbar-thumb: #9a9284;
@@ -1696,16 +1705,19 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
.hm-sw-property__row > label { flex: 0 0 72px; min-width: 0; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .hm-sw-property__row > label { flex: 0 0 72px; min-width: 0; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hm-sw-property__value { display: flex; align-items: center; gap: 4px; flex: 1; min-width: 0; } .hm-sw-property__value { display: flex; align-items: center; gap: 4px; flex: 1; min-width: 0; }
.hm-sw-property__value > .hm-sw-header__input { width: auto; max-width: 100%; min-width: 0; flex: 1; text-shadow: none; letter-spacing: .02em; font-weight: 500; } .hm-sw-property__value > .hm-sw-header__input { width: auto; max-width: 100%; min-width: 0; flex: 1; text-shadow: none; letter-spacing: .02em; font-weight: 500; }
.hm-sw-property__value > .hm-sw-header__input:focus { color: var(--hm-sw-header-input-color); } /* Popup value color (2026-11-10, --hm-sw-popup-value-color): the inputs of the value, gauge value and color value cells
.hm-sw-property__value > select:not(.hm-sw-select) { flex: 1; min-width: 0; width: 0; height: 28px; padding: 0; appearance: none; border: 1px solid transparent; border-radius: 2px; background: transparent; color: var(--hm-sw-header-input-color); font: inherit; } (before: the shared .hm-sw-header__input color); read-only values keep the label color (rule below, higher specificity). */
.hm-sw-property__value > select:not(.hm-sw-select):disabled { opacity: 1; appearance: none; color: var(--hm-sw-header-input-color); } :is(.hm-sw-property__value, .hm-sw-property__slider-value, .hm-sw-property__color-value) > .hm-sw-header__input,
.hm-sw-property__value > .hm-sw-header__input:focus { color: var(--hm-sw-popup-value-color); caret-color: var(--hm-sw-popup-value-color); }
.hm-sw-property__value > select:not(.hm-sw-select) { flex: 1; min-width: 0; width: 0; height: 28px; padding: 0; appearance: none; border: 1px solid transparent; border-radius: 2px; background: transparent; color: var(--hm-sw-popup-value-color); font: inherit; }
.hm-sw-property__value > select:not(.hm-sw-select):disabled { opacity: 1; appearance: none; color: var(--hm-sw-popup-value-color); }
.hm-sw-property__value > select:not(.hm-sw-select):focus { background: var(--hm-sw-property-content); border-color: var(--hm-sw-property-inner-border); } .hm-sw-property__value > select:not(.hm-sw-select):focus { background: var(--hm-sw-property-content); border-color: var(--hm-sw-property-inner-border); }
/* SWSelect: native select, panel field look; focus = the SW popup focus ring. */ /* SWSelect: native select, panel field look; focus = the SW popup focus ring. */
.hm-sw-select { flex: 0 0 auto; height: 28px; padding: 0 8px; border: 1px solid var(--hm-sw-select-border-color); border-radius: 4px; background: var(--hm-sw-select-background); color: var(--hm-sw-select-text); font: inherit; font-size: 12px; } .hm-sw-select { flex: 0 0 auto; height: 28px; padding: 0 8px; border: 1px solid var(--hm-sw-select-border-color); border-radius: 4px; background: var(--hm-sw-select-background); color: var(--hm-sw-select-text); font: inherit; font-size: 12px; }
.hm-sw-select:disabled { opacity: 0.4; cursor: default; } .hm-sw-select:disabled { opacity: 0.4; cursor: default; }
/* 2026-10-21: in an SW popup the chosen value of an enabled select is editable, so it is red like every editable value; /* 2026-10-21: in an SW popup the chosen value of an enabled select is editable, so it is red like every editable value;
a disabled select and the option list keep --hm-sw-select-text. The viewer panels keep their own text color (sw-viewer.css). */ a disabled select and the option list keep --hm-sw-select-text. The viewer panels keep their own text color (sw-viewer.css). */
.hm-sw-property .hm-sw-select:not(:disabled) { color: var(--hm-sw-header-input-color); } .hm-sw-property .hm-sw-select:not(:disabled) { color: var(--hm-sw-popup-value-color); }
.hm-sw-property .hm-sw-select option { color: var(--hm-sw-select-text); } .hm-sw-property .hm-sw-select option { color: var(--hm-sw-select-text); }
.hm-sw-property__unit { flex: 0 0 auto; font-size: 12px; white-space: nowrap; } .hm-sw-property__unit { flex: 0 0 auto; font-size: 12px; white-space: nowrap; }
/* Color preview (26:12175): 70 x 16 black tile (1px padding, radius 2), 68 x 14 swatch with a white 1px border. */ /* Color preview (26:12175): 70 x 16 black tile (1px padding, radius 2), 68 x 14 swatch with a white 1px border. */
@@ -1915,7 +1927,7 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
align-self: stretch; align-self: stretch;
min-width: 0; min-width: 0;
padding: 0 7px; padding: 0 7px;
color: var(--hm-sw-header-input-color); color: var(--hm-sw-popup-value-color);
text-align: left; text-align: left;
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
@@ -1948,7 +1960,7 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
/* K guide 26:11698 / 26:11669: original image, fixed mask viewport and continuous pan/zoom. */ /* K guide 26:11698 / 26:11669: original image, fixed mask viewport and continuous pan/zoom. */
.hm-sw-k-guide { position: relative; flex: 0 0 40px; height: 38px; } .hm-sw-k-guide { position: relative; flex: 0 0 40px; height: 38px; }
.hm-sw-k-guide__panel { z-index: 45; height: 149px; min-height: 149px; overflow: hidden; border: 1px solid var(--hm-sw-property-border); border-radius: 2px; background: #0e3c2e; animation: hm-sw-k-guide-enter 511ms ease-out; } .hm-sw-k-guide__panel { z-index: 45; height: 149px; min-height: 149px; overflow: hidden; border: 1px solid var(--hm-sw-header-k-guide-border-color); border-radius: 2px; background: var(--hm-sw-header-k-guide-background); animation: hm-sw-k-guide-enter 511ms ease-out; }
.hm-sw-k-guide__viewport { position: absolute; top: 6px; left: 6px; width: 192px; height: 109px; mask-size: 192px 109px; mask-repeat: no-repeat; outline: 1px solid #000; } .hm-sw-k-guide__viewport { position: absolute; top: 6px; left: 6px; width: 192px; height: 109px; mask-size: 192px 109px; mask-repeat: no-repeat; outline: 1px solid #000; }
.hm-sw-k-guide__viewport > img { display: block; max-width: none; width: 456px; height: 256.5px; transform-origin: 0 0; transform: translate(0, 0) scale(1); transition-property: transform; transition-timing-function: ease-in-out; } .hm-sw-k-guide__viewport > img { display: block; max-width: none; width: 456px; height: 256.5px; transform-origin: 0 0; transform: translate(0, 0) scale(1); transition-property: transform; transition-timing-function: ease-in-out; }
.hm-sw-k-guide__panel[data-frame="2"] .hm-sw-k-guide__viewport > img { transform: translate(0, -40.738px) scale(1); } .hm-sw-k-guide__panel[data-frame="2"] .hm-sw-k-guide__viewport > img { transform: translate(0, -40.738px) scale(1); }
@@ -274,6 +274,118 @@ async function dropdownColors(page, def, themed) {
check('Viewer Header 토큰은 SWHeader·Step Header 화면에 영향 없음', untouched.every(item => item.includes(' same ')), untouched.join(', ')) check('Viewer Header 토큰은 SWHeader·Step Header 화면에 영향 없음', untouched.every(item => item.includes(' same ')), untouched.join(', '))
} }
// ---------- 1d. Component tokens (2026-11-10): viewer header menus, K guide, popup value, footer command scrollbar ----------
// Each defaults to the token / literal it replaced (same value), follows a shared token set on the outermost SW scope,
// and when set itself changes its component alone.
{
const setOn = (page, root, values) => page.locator(root).first().evaluate((element, values) => {
let outer = element
for (let e = element; e; e = e.parentElement) if (e.matches?.('.hm-sw-header, .hm-sw-theme')) outer = e
Object.entries(values).forEach(([token, value]) => value === null ? outer.style.removeProperty(token) : outer.style.setProperty(token, value))
}, values)
const C1 = 'rgb(44, 55, 66)', C2 = 'rgb(1, 2, 3)'
const flow = async (label, page, root, read, shared, own, expect) => {
const base = await read()
await setOn(page, root, Object.fromEntries(shared.map(token => [token, C1])))
const inherited = await read()
await setOn(page, root, Object.fromEntries(own.map(token => [token, C2])))
const mine = await read()
await setOn(page, root, Object.fromEntries([...shared, ...own].map(token => [token, null])))
const back = await read()
check(`${label}: 기본값 = 기존 값`, JSON.stringify(base) === JSON.stringify(expect), JSON.stringify(base))
check(`${label}: 바깥 범위의 공유 토큰을 따름`, Object.values(inherited).every(value => value.includes(C1)), JSON.stringify(inherited))
check(`${label}: 전용 토큰 지정이 우선, 지우면 복귀`, Object.values(mine).every(value => value.includes(C2)) && JSON.stringify(back) === JSON.stringify(base), JSON.stringify(mine))
}
// Viewer Header menus: a header dropdown (도면) and the logo menu (no Story renders `logoMenu`: the same class
// structure is appended to the header, so the real rules apply).
const viewer = await newPage()
await open(viewer, 'sw-viewer--dropdowns')
await viewer.locator('.hm-sw-viewer-header').first().evaluate(header => {
const wrap = document.createElement('div'); wrap.className = 'hm-sw-viewer-header__logo-menu hm-sw-header__more'
wrap.innerHTML = '<div class="hm-sw-header__more-menu" data-probe=""><button type="button" class="hm-sw-header__more-item" data-visible="true">probe</button></div>'
header.append(wrap)
})
await viewer.getByRole('button', { name: '도면' }).first().click()
const menu = viewer.locator('.hm-sw-viewer-header .hm-sw-viewer-menu[data-surface]').first()
await menu.locator('.hm-sw-viewer-menu__item:not([aria-disabled])').first().hover()
const readMenu = () => viewer.evaluate(() => {
const m = getComputedStyle(document.querySelector('.hm-sw-viewer-header .hm-sw-viewer-menu[data-surface]'))
const hovered = [...document.querySelectorAll('.hm-sw-viewer-header .hm-sw-viewer-menu .hm-sw-viewer-menu__item')].find(item => item.matches(':hover, :focus'))
const logo = getComputedStyle(document.querySelector('[data-probe]'))
return { face: `${m.backgroundColor} | ${m.borderTopColor} | ${m.boxShadow}`, hover: hovered ? getComputedStyle(hovered).backgroundColor : 'none', logo: `${logo.backgroundColor} | ${logo.borderTopColor} | ${logo.boxShadow}` }
})
const header = await newPage()
await open(header, 'sw-header--overview')
// Expected defaults: the SWHeader More menu face (same tokens) — computed on a probe More menu inside SWHeader.
const moreFace = await header.locator('.hm-sw-header').first().evaluate(root => {
const wrap = document.createElement('div'); wrap.className = 'hm-sw-header__more'
wrap.innerHTML = '<div class="hm-sw-header__more-menu"></div>'; root.append(wrap)
const s = getComputedStyle(wrap.firstChild); const value = `${s.backgroundColor} | ${s.borderTopColor} | ${s.boxShadow}`; wrap.remove(); return value
})
const hoverDefault = 'rgba(255, 255, 255, 0.08)'
await flow('Viewer Header 메뉴', viewer, '.hm-sw-viewer-header', readMenu,
['--hm-sw-header-more-menu-background', '--hm-sw-header-more-menu-border-color', '--hm-sw-header-more-menu-shadow', '--hm-sw-header-more-item-hover-background'],
['--hm-sw-viewer-header-menu-background', '--hm-sw-viewer-header-menu-border-color', '--hm-sw-viewer-header-menu-shadow', '--hm-sw-viewer-header-menu-item-hover-background'],
{ face: moreFace, hover: hoverDefault, logo: moreFace })
// The shadow token is a full shadow: the C1 / C2 color strings above are not valid shadows, so the shadow keeps its
// value there; check it once with a real shadow.
await setOn(viewer, '.hm-sw-viewer-header', { '--hm-sw-viewer-header-menu-shadow': `0 0 0 3px ${C2}` })
const shadow = await readMenu()
await setOn(viewer, '.hm-sw-viewer-header', { '--hm-sw-viewer-header-menu-shadow': null })
check('Viewer Header 메뉴: 그림자 전용 토큰', shadow.face.endsWith(`${C2} 0px 0px 0px 3px`) && shadow.logo.endsWith(`${C2} 0px 0px 0px 3px`), JSON.stringify(shadow))
await viewer.close()
// K guide panel (hover opens it).
const guide = await newPage()
await open(guide, 'sw-header--k-guide')
await guide.locator('.hm-sw-k-guide button').first().hover()
await guide.locator('.hm-sw-k-guide__panel').first().waitFor()
const readGuide = () => guide.evaluate(() => { const s = getComputedStyle(document.querySelector('.hm-sw-k-guide__panel')); return { border: s.borderTopColor, face: s.backgroundColor } })
// Shared token: the border follows --hm-sw-property-border; the face was a literal (own token only).
const guideBase = await readGuide()
await setOn(guide, '.hm-sw-header', { '--hm-sw-property-border': C1 })
const guideShared = await readGuide()
await setOn(guide, '.hm-sw-header', { '--hm-sw-header-k-guide-border-color': C2, '--hm-sw-header-k-guide-background': C2 })
const guideOwn = await readGuide()
await setOn(guide, '.hm-sw-header', { '--hm-sw-property-border': null, '--hm-sw-header-k-guide-border-color': null, '--hm-sw-header-k-guide-background': null })
check('K 가이드: 기본값 = 기존 값(#A67D03 / #0E3C2E)', guideBase.border === 'rgb(166, 125, 3)' && guideBase.face === 'rgb(14, 60, 46)', JSON.stringify(guideBase))
check('K 가이드: 바깥 범위의 --hm-sw-property-border를 따름', guideShared.border === C1 && guideShared.face === guideBase.face, JSON.stringify(guideShared))
check('K 가이드: 전용 토큰 지정이 우선, 지우면 복귀', guideOwn.border === C2 && guideOwn.face === C2 && JSON.stringify(await readGuide()) === JSON.stringify(guideBase), JSON.stringify(guideOwn))
await guide.close()
// Popup value color: property window inputs (value, gauge value), dropdown triggers, and the SWHeader 속성Bar untouched.
const popup = await newPage()
await open(popup, 'sw-header--property-panel-compare')
const readValue = () => popup.evaluate(() => {
const color = selector => [...new Set([...document.querySelectorAll(selector)].map(e => getComputedStyle(e).color))].join(',')
return {
input: color('.hm-sw-property__value:not([data-readonly]) > .hm-sw-header__input:not(:read-only)'),
gauge: color('.hm-sw-property__slider-value:not([data-readonly]) > .hm-sw-header__input:not(:read-only)'),
trigger: color('.hm-sw-property__value:not([data-readonly]) .hm-sw-property__trigger'),
}
})
await flow('Popup 값 글자색', popup, '.hm-sw-property', readValue, ['--hm-sw-header-input-color'], ['--hm-sw-popup-value-color'],
{ input: 'rgb(230, 4, 3)', gauge: 'rgb(230, 4, 3)', trigger: 'rgb(230, 4, 3)' })
await popup.close()
const bar = await newPage()
await open(bar, 'sw-header--overview')
const barColor = () => bar.evaluate(() => [...document.querySelectorAll('.hm-sw-property-bar .hm-sw-header__input, .hm-sw-property-bar__value')].map(e => getComputedStyle(e).color).join(','))
const barBefore = await barColor()
await setOn(bar, '.hm-sw-header', { '--hm-sw-popup-value-color': C2 })
check('Popup 값 토큰은 SWHeader 속성Bar 값에 영향 없음', barBefore.length > 0 && barBefore === await barColor(), barBefore)
await bar.close()
// Footer command history scrollbar.
const footer = await newPage()
await open(footer, 'sw-footer--full-footer')
const readBar = () => footer.evaluate(() => ({ scrollbar: getComputedStyle(document.querySelector('.hm-sw-footer-command__history')).scrollbarColor }))
const footerBase = await readBar()
await setOn(footer, '.hm-sw-footer-command', { '--hm-sw-footer-command-scrollbar-thumb': C2, '--hm-sw-footer-command-scrollbar-track': C1 })
const footerOwn = await readBar()
await setOn(footer, '.hm-sw-footer-command', { '--hm-sw-footer-command-scrollbar-thumb': null, '--hm-sw-footer-command-scrollbar-track': null })
check('Footer 명령창 스크롤바: 기본값 = 기존 값(#574B30 / transparent)', footerBase.scrollbar === 'rgb(87, 75, 48) rgba(0, 0, 0, 0)', JSON.stringify(footerBase))
check('Footer 명령창 스크롤바: 전용 토큰 지정이 우선, 지우면 복귀', footerOwn.scrollbar === `${C2} ${C1}` && JSON.stringify(await readBar()) === JSON.stringify(footerBase), JSON.stringify(footerOwn))
await footer.close()
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) *)'