diff --git a/README.md b/README.md index 06a943a..648c5fb 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ import { OrgChart } from "@hm-design/org-chart-react" // Core: @hm-design/org-ch 별도 React 앱은 [Tailwind v4 검색 범위 가이드](docs/guidelines/runtime-portability.md#21-tailwind-v4-소비자-검색-범위)를 확인합니다. `source("./")`는 CSS 위치에 따라 범위가 달라지며 HM `tailwind.css`의 명시 source를 유지해야 합니다. 공개 package Theme는 동봉된 `dist/fonts/`를 상대 URL로 참조합니다. 개발 저장소·포터블 ZIP·React 소비자·단일 HTML의 차이는 [폰트·Asset 경로](docs/guidelines/runtime-portability.md#41-noto-sans-kr-경로-실행-환경별-확인)를 따릅니다. -SW는 Tailwind 없이 앱 진입점에서 `import "@hm-design/sw/styles.css"`를 한 번 추가합니다. 한 화면에서는 SW 일반 모드와 뷰어 모드 중 하나를 고르고, 글꼴 등 토큰은 가장 바깥 ``에서 바꿉니다([SW 사용 안내](packages/sw/ai/AI_USAGE.md)). SW 기본 글꼴은 Noto Sans KR이며 Figma의 Dotum을 기본값으로 쓸지는 디자인 승인 대기입니다. OrgChart는 Core CSS 두 개(`@hm-design/org-chart/engine.css`, `/style.css`)의 URL을 `stylesheetUrls`로 전달합니다(Shadow DOM, [OrgChart React README](packages/org-chart-react/README.md)). +SW는 Tailwind 없이 앱 진입점에서 `import "@hm-design/sw/styles.css"`를 한 번 추가합니다. 한 화면에서는 SW 일반 모드와 뷰어 모드 중 하나를 고르고, 글꼴 등 토큰은 가장 바깥 ``에서 바꿉니다([SW 사용 안내](packages/sw/ai/AI_USAGE.md)). SW 기본 글꼴은 돋움(Dotum, 설치된 시스템 글꼴)이고 Web UI 기본 글꼴은 Noto Sans KR입니다. 서로의 글꼴 설정을 복사하지 않습니다. OrgChart는 Core CSS 두 개(`@hm-design/org-chart/engine.css`, `/style.css`)의 URL을 `stylesheetUrls`로 전달합니다(Shadow DOM, [OrgChart React README](packages/org-chart-react/README.md)). 화면 생성 Reference: [Navigation 선택 근거](docs/guidelines/navigation-single-mode.md#선택-근거), [GNB Desktop·Mobile 구성](docs/patterns/gnb.md#8-desktop--mobile)과 [내부 Sidebar](docs/patterns/gnb.md#15-gnb-only-sidebar-composition-2026-10-08), [FilterBar 조회·초기화 소비 예제](docs/patterns/filter-bar.md#61-조회-버튼으로-적용하는-소비-예제). 일반 업무 목록의 모드를 임의 확정하거나 FilterBar에 조회 API가 있다고 가정하지 않습니다. diff --git a/docs/ai/AI_GUIDE.md b/docs/ai/AI_GUIDE.md index 8ac07f4..5fd7e42 100644 --- a/docs/ai/AI_GUIDE.md +++ b/docs/ai/AI_GUIDE.md @@ -138,7 +138,7 @@ 혼합 규칙: -1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. +1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. 글꼴도 같습니다: SW 기본 글꼴은 돋움(Dotum, `--hm-sw-header-font-family`), Web 기본 글꼴은 Noto Sans KR(`--hm-font-family-base`), OrgChart는 자기 설정을 유지합니다. 한 패키지의 글꼴 값을 다른 영역에 복사하지 않습니다. 2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다. - SW 일반 모드 `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`) - SW 뷰어 모드 `SWViewerPanel`의 `children` 안 Web 컨트롤 — `SWViewer*` 컨트롤로 안 될 때만(SW `AI_USAGE.md` §0.4) diff --git a/packages/org-chart-react/ai/AI_GUIDE.md b/packages/org-chart-react/ai/AI_GUIDE.md index 8ac07f4..5fd7e42 100644 --- a/packages/org-chart-react/ai/AI_GUIDE.md +++ b/packages/org-chart-react/ai/AI_GUIDE.md @@ -138,7 +138,7 @@ 혼합 규칙: -1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. +1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. 글꼴도 같습니다: SW 기본 글꼴은 돋움(Dotum, `--hm-sw-header-font-family`), Web 기본 글꼴은 Noto Sans KR(`--hm-font-family-base`), OrgChart는 자기 설정을 유지합니다. 한 패키지의 글꼴 값을 다른 영역에 복사하지 않습니다. 2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다. - SW 일반 모드 `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`) - SW 뷰어 모드 `SWViewerPanel`의 `children` 안 Web 컨트롤 — `SWViewer*` 컨트롤로 안 될 때만(SW `AI_USAGE.md` §0.4) diff --git a/packages/org-chart/ai/AI_GUIDE.md b/packages/org-chart/ai/AI_GUIDE.md index 8ac07f4..5fd7e42 100644 --- a/packages/org-chart/ai/AI_GUIDE.md +++ b/packages/org-chart/ai/AI_GUIDE.md @@ -138,7 +138,7 @@ 혼합 규칙: -1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. +1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. 글꼴도 같습니다: SW 기본 글꼴은 돋움(Dotum, `--hm-sw-header-font-family`), Web 기본 글꼴은 Noto Sans KR(`--hm-font-family-base`), OrgChart는 자기 설정을 유지합니다. 한 패키지의 글꼴 값을 다른 영역에 복사하지 않습니다. 2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다. - SW 일반 모드 `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`) - SW 뷰어 모드 `SWViewerPanel`의 `children` 안 Web 컨트롤 — `SWViewer*` 컨트롤로 안 될 때만(SW `AI_USAGE.md` §0.4) diff --git a/packages/sw/ai/AI_GUIDE.md b/packages/sw/ai/AI_GUIDE.md index 8ac07f4..5fd7e42 100644 --- a/packages/sw/ai/AI_GUIDE.md +++ b/packages/sw/ai/AI_GUIDE.md @@ -138,7 +138,7 @@ 혼합 규칙: -1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. +1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. 글꼴도 같습니다: SW 기본 글꼴은 돋움(Dotum, `--hm-sw-header-font-family`), Web 기본 글꼴은 Noto Sans KR(`--hm-font-family-base`), OrgChart는 자기 설정을 유지합니다. 한 패키지의 글꼴 값을 다른 영역에 복사하지 않습니다. 2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다. - SW 일반 모드 `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`) - SW 뷰어 모드 `SWViewerPanel`의 `children` 안 Web 컨트롤 — `SWViewer*` 컨트롤로 안 될 때만(SW `AI_USAGE.md` §0.4) diff --git a/packages/sw/ai/AI_USAGE.md b/packages/sw/ai/AI_USAGE.md index cb50552..b09a057 100644 --- a/packages/sw/ai/AI_USAGE.md +++ b/packages/sw/ai/AI_USAGE.md @@ -102,8 +102,9 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번 - **금지:** `@hm-design/sw/src/...`, `@hm-design/sw/dist/components/...` 같은 내부 경로 Import. `dist`에 내부 모듈의 `.d.ts`가 보여도 공개 API가 아니다. - Peer: `react`, `react-dom` `^19.0.0`. 런타임 dependency 없음. `@hm-design/ui`·조직도 패키지와 무관하다. - SVG·PNG 자산은 JS에 data URI로 포함되어 별도 설정이 없다. -- 글꼴 파일은 포함하지 않는다. 앱에서 Noto Sans KR(400/500/700)을 로드한다(없으면 Malgun Gothic → Apple SD Gothic Neo). -- SW 기본 글꼴은 Noto Sans KR이다(Figma는 Dotum). **Dotum을 기본으로 쓸지는 디자인 승인 대기**이므로 AI가 임의로 Dotum을 적용하지 않는다. 프로그램(사용자)이 글꼴을 정한 경우에만 §3.1 방법으로 `--hm-sw-header-font-family`를 바꾸고, 결과 보고에 "글꼴 변경 — 디자인 승인 대기 항목"으로 적는다. +- SW 기본 글꼴은 돋움(Dotum)이다(`--hm-sw-header-font-family: Dotum, "돋움", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif`, Figma와 같음). 설치된 시스템 글꼴(Windows)을 쓰며 패키지는 글꼴 파일을 포함하지 않는다. 돋움이 없는 환경은 Malgun Gothic → Apple SD Gothic Neo로 대체된다. 앱이 별도 글꼴 파일을 로드할 필요는 없다. +- SW 화면은 `SWTheme`(또는 SW 컴포넌트 루트)와 SW 토큰의 글꼴을 그대로 쓴다. Web UI의 기본 글꼴(Noto Sans KR, `--hm-font-family-base`)이나 Web `theme.css`의 글꼴 설정을 SW 화면에 복사하지 않고, SW 컴포넌트에 `font-family`를 개별 지정하지 않는다. 프로그램(사용자)이 다른 글꼴을 정한 경우에만 §3.1 방법으로 `--hm-sw-header-font-family`를 바꾸고 결과 보고에 "글꼴 변경"으로 적는다. +- 돋움에는 Bold 글꼴이 없어 700은 브라우저가 합성 Bold로 그리며 글자당 약 1px 넓어진다. Figma 고정 폭 칸(예: 가중치·스케일 38px, 레이어 값, 문자/치수스타일)에 긴 값이 들어가면 말줄임이 된다. - CSS는 전역(`html`, `body`, `*`)을 건드리지 않는다. `body` margin 등 페이지 기본값은 소비 앱이 정한다. - 스크롤바: SW 전용 스크롤바 디자인은 없다. 기존 SW 컴포넌트는 브라우저 기본 스크롤바를 쓰며(속성Bar만 `scrollbar-width: thin`), Popup 본문 스크롤은 `SWPopup`이 처리한다. 새 스크롤 영역에 임의 스크롤바 색·두께를 만들지 않는다. - TypeScript에서 CSS side-effect import 오류가 나면 Vite의 `vite/client` 타입을 포함한다. @@ -122,7 +123,7 @@ SW 토큰(`--hm-sw-header-*`, `--hm-sw-property-*`, `--hm-sw-viewer-*`, `--hm-sw 1. 화면 전체(Header·Panel·Menu가 같은 값)를 바꾸려면 화면을 `` 하나로 감싸고 거기서만 토큰을 정합니다. 프로그램 class(`` + `.app-sw-theme { --hm-sw-header-font-family: … }`)도 됩니다. ```tsx - const appSWTheme = { "--hm-sw-header-font-family": '"Noto Sans KR", sans-serif' } as CSSProperties + const appSWTheme = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-serif' } as CSSProperties // 프로그램이 정한 값만 {/* Dropdown·More 포함 */} … diff --git a/packages/sw/ai/COMPOSITION_PATTERNS.md b/packages/sw/ai/COMPOSITION_PATTERNS.md index 891e8f9..ad08596 100644 --- a/packages/sw/ai/COMPOSITION_PATTERNS.md +++ b/packages/sw/ai/COMPOSITION_PATTERNS.md @@ -235,7 +235,7 @@ const sections: SWHomeMenuSectionData[] = [ 일반·뷰어 모드 공통입니다. 토큰 기본값은 가장 바깥 SW 범위에서만 선언되고 안쪽 SW 요소는 상속합니다([AI_USAGE.md](./AI_USAGE.md) §3.1). ```tsx -const appSWTheme = { "--hm-sw-header-font-family": '"Noto Sans KR", sans-serif' } as CSSProperties // 프로그램이 정한 값만 +const appSWTheme = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-serif' } as CSSProperties // 프로그램이 정한 값만 {/* 가장 바깥 범위: 여기서만 지정 */} {/* Dropdown·More까지 상속 */} @@ -246,7 +246,7 @@ const appSWTheme = { "--hm-sw-header-font-family": '"Noto Sans KR", sans-serif' ``` - 일반 모드도 같습니다: `` 안의 `SWHeader`(Dropdown·창 이동 Tooltip)·`SWFooter`(`SWFooterCommandLine`·명령 등록 안내)가 같은 값을 씁니다. -- 기본 글꼴은 Noto Sans KR입니다. Dotum 기본 적용은 디자인 승인 대기이므로 AI가 임의로 Dotum을 넣지 않습니다. +- 기본 글꼴은 돋움(Dotum)입니다. 기본값을 쓸 때는 글꼴 토큰을 지정하지 않습니다. Web UI 글꼴(Noto Sans KR)을 SW 화면에 넣지 않습니다. - `hm-sw-*` class 선택자로 토큰을 덮거나 `!important`, 컴포넌트마다 같은 값 반복 지정을 하지 않습니다. ## 9. 하지 말 것 diff --git a/packages/sw/ai/DESIGN_RULES.md b/packages/sw/ai/DESIGN_RULES.md index 85ac0be..437eda1 100644 --- a/packages/sw/ai/DESIGN_RULES.md +++ b/packages/sw/ai/DESIGN_RULES.md @@ -44,7 +44,7 @@ | 항목 | 값 | |---|---| -| 글꼴 | Noto Sans KR → Malgun Gothic → Apple SD Gothic Neo (`--hm-sw-header-font-family`, Figma Dotum 대체 — 현재 패키지 기본값). 폰트 파일은 앱이 로드. **Dotum 기본 적용 여부는 디자인 승인 대기**: 기본값을 바꾸지 않으며, 프로그램이 정한 경우에만 [AI_USAGE.md](./AI_USAGE.md) §3.1 방법으로 변경 | +| 글꼴 | 돋움(Dotum) → Malgun Gothic → Apple SD Gothic Neo (`--hm-sw-header-font-family: Dotum, "돋움", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif`, Figma와 같음, 2026-10-20). 설치된 시스템 글꼴을 쓰며 글꼴 파일은 포함하지 않음. Web UI 기본 글꼴(Noto Sans KR)과 별개. 다른 글꼴은 프로그램이 정한 경우에만 [AI_USAGE.md](./AI_USAGE.md) §3.1 방법으로 변경. 돋움 700은 합성 Bold(글자당 약 +1px) | | 기본 / 라벨 | 14px / 12px (`--hm-sw-header-font-size` / `--hm-sw-header-label-size`) | | Weight | 400, 500, 700만 사용(600 없음). 값·제목·토글 700 | | 자간 | 값 0.04em, 라벨 0.02em, 파일명 0.06em | @@ -180,7 +180,7 @@ Container `hm-sw-header` 기준 Breakpoint: **< 1800** 선/문자/치수스타 ## 13. Footer (`SWFooter*`, Figma 26:10097) — SW 일반 모드 전용 -원본 `src/theme/sw-footer.css`. 토큰은 `--hm-sw-footer-*`(`.hm-sw-header`, `.hm-sw-theme` 범위). 값은 Figma Plugin API로 읽은 값입니다(2026-10-20). 글꼴은 Header와 같이 Dotum 대신 Noto Sans KR, 글자 외곽선은 Header 토큰(`--hm-sw-header-text-outline` 1.3px / `--hm-sw-header-label-outline` 1px)을 공유하고, Figma의 자기 색 0.3 / 0.4px stroke는 Header 2026-10-16 수정과 같이 그리지 않습니다. +원본 `src/theme/sw-footer.css`. 토큰은 `--hm-sw-footer-*`(`.hm-sw-header`, `.hm-sw-theme` 범위). 값은 Figma Plugin API로 읽은 값입니다(2026-10-20). 글꼴은 Header와 같은 SW 기본 글꼴(돋움), 글자 외곽선은 Header 토큰(`--hm-sw-header-text-outline` 1.3px / `--hm-sw-header-label-outline` 1px)을 공유하고, Figma의 자기 색 0.3 / 0.4px stroke는 Header 2026-10-16 수정과 같이 그리지 않습니다. | 항목 | 값 | 토큰 | |---|---|---| diff --git a/packages/sw/ai/VIEWER.md b/packages/sw/ai/VIEWER.md index 6c76a21..4fdae42 100644 --- a/packages/sw/ai/VIEWER.md +++ b/packages/sw/ai/VIEWER.md @@ -101,7 +101,7 @@ const groups: SWViewerHeaderGroup[] = [ | 뷰어 Header를 `div` + `button`으로 다시 만들기, `hm-sw-viewer-*` class를 소비 코드에 붙이기 | 데이터(`groups`)와 `custom` / `trailing`으로 구성 | | Header 버튼 글자에 `-webkit-text-stroke`·`border`로 외곽선 만들기 | 외곽선은 패키지 토큰이 이미 적용합니다 | | 메뉴 색·간격을 `#123456`, `margin` 등 임의 값으로 덮기 | 토큰 `--hm-sw-viewer-*` / `--hm-sw-header-*`만 가장 바깥 SW 범위(`` 또는 단독 컴포넌트 루트)의 `style`로 조정(기본값 유지 권장, [AI_USAGE.md](./AI_USAGE.md) §3.1) | -| Header·Dropdown·More·Panel의 글꼴을 각각 따로 지정, `.hm-sw-viewer-menu` 등 class로 덮기 | 화면을 `` 하나로 감싸면 Header·Dropdown·More·Panel이 같은 값을 상속합니다. 글꼴 기본값은 Noto Sans KR이며 Dotum 기본 적용은 디자인 승인 대기 | +| Header·Dropdown·More·Panel의 글꼴을 각각 따로 지정, `.hm-sw-viewer-menu` 등 class로 덮기 | 화면을 `` 하나로 감싸면 Header·Dropdown·More·Panel이 같은 값을 상속합니다. 글꼴 기본값은 돋움(Dotum)이며 Web UI 글꼴(Noto Sans KR)을 복사하지 않습니다 | | 존재하지 않는 Props 추측(`items`, `content`, `renderBody` …) | 위 표와 `dist/index.d.ts`가 전부 | | 일반 모드 화면에서 `SWViewerHeader` 사용, 뷰어에서 `SWHeader` 재스타일 | 모드별 Header를 바꿔 끼웁니다 | | 기능 없는 메뉴를 가짜 동작으로 연결 | 항목 제외 또는 `disabled` + 미연결 보고 | diff --git a/packages/sw/ai/examples/ExampleJ-Theme.tsx b/packages/sw/ai/examples/ExampleJ-Theme.tsx index 3b1d240..91bc5e9 100644 --- a/packages/sw/ai/examples/ExampleJ-Theme.tsx +++ b/packages/sw/ai/examples/ExampleJ-Theme.tsx @@ -1,8 +1,8 @@ // Example J — SW theme tokens (both modes). The only official way to change SW tokens: `style` (or the program's own // class) on the OUTERMOST SW scope. Here one wraps the viewer screen, so the header, its dropdowns, More, // and every panel read the same value. Portals leave the DOM tree: wrap their content in with the same style. -// The font below is a placeholder for a program decision: the SW default stays Noto Sans KR (a Dotum default is -// pending design approval), so only set it when the program has decided to. +// The font below is a placeholder for a program decision: the SW default is Dotum (2026-10-20) and needs no token, +// so only set it when the program has decided on another font. Never copy the Web UI font (Noto Sans KR) here. import { useState, type CSSProperties } from "react" import { createPortal } from "react-dom" import { @@ -12,7 +12,7 @@ import { /** One theme object for the screen and for every portal. Token names only (`--hm-sw-*`), never `hm-sw-*` classes. */ export const appSWTheme = { - "--hm-sw-header-font-family": '"Noto Sans KR", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif', + "--hm-sw-header-font-family": '"Malgun Gothic", "Apple SD Gothic Neo", sans-serif', } as CSSProperties export function ExampleJTheme({ onEvent }: { onEvent?: (name: string) => void }) { diff --git a/packages/sw/src/theme/sw-footer.css b/packages/sw/src/theme/sw-footer.css index 41f13b1..9e63565 100644 --- a/packages/sw/src/theme/sw-footer.css +++ b/packages/sw/src/theme/sw-footer.css @@ -1,6 +1,6 @@ /* @hm-design/sw — SW Footer (Figma "레이아웃 분리" 26:10097 bottom bar, components 26:9459 … 26:10097, read through the Figma Plugin API 2026-10-20). Footer-only tokens. Text keeps the SW typography of sw.css - (Noto Sans KR instead of Dotum, black OUTSIDE outline as 16 text-shadows, own-color 0.3 / 0.4px strokes removed + (Dotum like Figma since 2026-10-20, black OUTSIDE outline as 16 text-shadows, own-color 0.3 / 0.4px strokes removed like the 2026-10-16 header fix). Never touches global selectors, SWHeader or SWPopup classes. Declared on the outermost SW scope only, like sw.css: the command line inside the bar inherits the tokens. */ diff --git a/packages/sw/src/theme/sw.css b/packages/sw/src/theme/sw.css index ad511f5..27441bb 100644 --- a/packages/sw/src/theme/sw.css +++ b/packages/sw/src/theme/sw.css @@ -17,8 +17,10 @@ --hm-sw-header-logo-width: 32px; --hm-sw-header-logo-height: 17px; - /* Typography (Figma Dotum is replaced by Noto Sans KR on purpose) */ - --hm-sw-header-font-family: "Noto Sans KR", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif; + /* Typography: SW default is Dotum like Figma (2026-10-20). Installed system font only (Windows gulim.ttc); the + package ships no font file. Malgun Gothic / Apple SD Gothic Neo cover systems without Dotum. Web UI keeps + Noto Sans KR (its own token); this token never leaves the SW scope. */ + --hm-sw-header-font-family: Dotum, "돋움", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif; --hm-sw-header-font-size: 14px; --hm-sw-header-label-size: 12px; @@ -539,7 +541,9 @@ flex: 0 1 90px; min-width: 0; margin-left: -1px; - padding: 0 8px; + /* 4px (was 8px): Dotum has no bold face, so 700 is synthesized ~1px wider per glyph ("그리기모드" 77.8px); + the centered label fits the Figma 90px cell again. */ + padding: 0 4px; overflow: hidden; text-align: center; white-space: nowrap; diff --git a/packages/sw/tests/ai-template-consumer.browser.mjs b/packages/sw/tests/ai-template-consumer.browser.mjs index b4b1abf..ad917cc 100644 --- a/packages/sw/tests/ai-template-consumer.browser.mjs +++ b/packages/sw/tests/ai-template-consumer.browser.mjs @@ -41,7 +41,7 @@ await page.evaluate(() => document.fonts.ready) const assets = await page.evaluate(() => [...document.images].map(image => ({ src: image.currentSrc.slice(0, 30), ok: image.complete && image.naturalWidth > 0 }))) check("1 all SVG/PNG assets decode (data URI, no external path)", assets.length > 0 && assets.every(item => item.ok && item.src.startsWith("data:")), { count: assets.length, broken: assets.filter(item => !item.ok) }) const tokens = await page.evaluate(() => { const s = getComputedStyle(document.querySelector('[data-example="b"] .hm-sw-theme')); return { height: s.getPropertyValue("--hm-sw-header-height").trim(), font: s.fontFamily } }) -check("1 styles.css loaded; SWTheme provides tokens", tokens.height === "49px" && tokens.font.includes("Noto Sans KR"), tokens) +check("1 styles.css loaded; SWTheme provides tokens", tokens.height === "49px" && tokens.font.startsWith("Dotum"), tokens) // 2. Example A: default header unchanged (Figma 1920 coordinates) const a = section("a") diff --git a/packages/sw/tests/theme-inheritance.browser.mjs b/packages/sw/tests/theme-inheritance.browser.mjs index 007855e..1e6ebab 100644 --- a/packages/sw/tests/theme-inheritance.browser.mjs +++ b/packages/sw/tests/theme-inheritance.browser.mjs @@ -1,7 +1,7 @@ // SW theme token inheritance (2026-10-20, A-1). // 1) SW/Theme › Theme Inheritance: a token set through `style` on the outermost SW scope reaches the header, dropdowns, // More, panels, footer command line and tooltips; portals get it only when wrapped in a themed ; areas do -// not mix; unthemed components keep Noto Sans KR. Dotum is a verification value only (not the SW default). +// not mix; unthemed components keep the SW default Dotum (2026-10-20). Noto Sans KR is a verification value only. // 2) Regression: every existing SW Story renders identically with the new cascade and with the legacy cascade // (token blocks re-declared on every `.hm-sw-header, .hm-sw-theme`, i.e. the code before this change): computed // font / color / geometry of every SW element and an element screenshot comparison. No Story sets an override, @@ -22,9 +22,9 @@ const [storybook = 'http://127.0.0.1:6024', outArg] = process.argv.slice(2) const out = path.resolve(outArg ?? 'docs/sw/theme-inheritance-qa') await mkdir(out, { recursive: true }) -const VERIFY = 'Dotum, 돋움, sans-serif' +const VERIFY = '"Noto Sans KR", sans-serif' const NESTED = '"Malgun Gothic", sans-serif' -const DEFAULT = '"Noto Sans KR", "Malgun Gothic", "Apple SD Gothic Neo", sans-serif' +const DEFAULT = 'Dotum, 돋움, "Malgun Gothic", "Apple SD Gothic Neo", sans-serif' const results = [] const check = (name, pass, detail = '') => { results.push({ name, result: pass ? 'PASS' : 'FAIL', detail }); console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`) } const errors = [] @@ -67,52 +67,52 @@ const fmt = map => Object.entries(map).map(([family, count]) => `${count}× ${fa // Default (no theme): header, then its sample dropdown and More. const def = scope('default') - check('기본: 단독 SWViewerHeader = Noto Sans KR', only(await fonts(page, `${def} .hm-sw-viewer-header`), DEFAULT), fmt(await fonts(page, `${def} .hm-sw-viewer-header`))) + check('기본: 단독 SWViewerHeader = 기본 Dotum', only(await fonts(page, `${def} .hm-sw-viewer-header`), DEFAULT), fmt(await fonts(page, `${def} .hm-sw-viewer-header`))) await page.locator(`${def} .hm-sw-viewer-header [aria-haspopup="menu"]`).first().click() await page.waitForSelector(`${def} .hm-sw-viewer-menu`) - check('기본: Dropdown = Noto Sans KR', only(await fonts(page, `${def} .hm-sw-viewer-menu`), DEFAULT), fmt(await fonts(page, `${def} .hm-sw-viewer-menu`))) + check('기본: Dropdown = 기본 Dotum', only(await fonts(page, `${def} .hm-sw-viewer-menu`), DEFAULT), fmt(await fonts(page, `${def} .hm-sw-viewer-menu`))) await page.keyboard.press('Escape') await page.locator(`${def} .hm-sw-viewer-header button[aria-label="추가 도구"]`).click() await page.waitForSelector(`${def} .hm-sw-viewer-menu[data-surface="beige"]`) - check('기본: More = Noto Sans KR', only(await fonts(page, `${def} .hm-sw-viewer-menu`), DEFAULT), fmt(await fonts(page, `${def} .hm-sw-viewer-menu`))) + check('기본: More = 기본 Dotum', only(await fonts(page, `${def} .hm-sw-viewer-menu`), DEFAULT), fmt(await fonts(page, `${def} .hm-sw-viewer-menu`))) await page.keyboard.press('Escape') // Viewer, themed through . const viewer = scope('viewer-themed') - check('뷰어 테마: SWViewerHeader 전체 = Dotum', only(await fonts(page, `${viewer} .hm-sw-viewer-header`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-header`))) + check('뷰어 테마: SWViewerHeader 전체 = 검증 글꼴', only(await fonts(page, `${viewer} .hm-sw-viewer-header`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-header`))) await page.locator(`${viewer} .hm-sw-viewer-header [aria-haspopup="menu"]`).first().click() await page.waitForSelector(`${viewer} .hm-sw-viewer-menu`) - check('뷰어 테마: Dropdown = Dotum', only(await fonts(page, `${viewer} .hm-sw-viewer-menu`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-menu`))) + check('뷰어 테마: Dropdown = 검증 글꼴', only(await fonts(page, `${viewer} .hm-sw-viewer-menu`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-menu`))) await page.keyboard.press('Escape') await page.locator(`${viewer} .hm-sw-viewer-header button[aria-label="추가 도구"]`).click() await page.waitForSelector(`${viewer} .hm-sw-viewer-menu[data-surface="beige"]`) - check('뷰어 테마: More = Dotum', only(await fonts(page, `${viewer} .hm-sw-viewer-menu`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-menu`))) + check('뷰어 테마: More = 검증 글꼴', only(await fonts(page, `${viewer} .hm-sw-viewer-menu`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-menu`))) // A submenu of More, if any, is a nested menu inside a nested menu. const sub = page.locator(`${viewer} .hm-sw-viewer-menu[data-surface="beige"] [aria-haspopup="menu"]`).first() if (await sub.count()) { await sub.hover() await page.waitForTimeout(150) - check('뷰어 테마: More 서브메뉴 = Dotum', only(await fonts(page, `${viewer} .hm-sw-viewer-menu`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-menu`))) + check('뷰어 테마: More 서브메뉴 = 검증 글꼴', only(await fonts(page, `${viewer} .hm-sw-viewer-menu`), VERIFY), fmt(await fonts(page, `${viewer} .hm-sw-viewer-menu`))) } await page.keyboard.press('Escape') const panels = await page.locator(`${viewer} .hm-sw-viewer-panel`).count() - check('뷰어 테마: SWViewerPanel(제목·입력·버튼·스위치·힌트) = Dotum', panels === 2 && only(await fonts(page, `${viewer} .hm-sw-viewer-panel`), VERIFY), `${panels} panels · ${fmt(await fonts(page, `${viewer} .hm-sw-viewer-panel`))}`) + check('뷰어 테마: SWViewerPanel(제목·입력·버튼·스위치·힌트) = 검증 글꼴', panels === 2 && only(await fonts(page, `${viewer} .hm-sw-viewer-panel`), VERIFY), `${panels} panels · ${fmt(await fonts(page, `${viewer} .hm-sw-viewer-panel`))}`) await page.screenshot({ path: path.join(out, 'viewer-themed-more.png'), clip: await page.locator(viewer).boundingBox() }) // General mode: SWHeader (+ open layer dropdown, window-move tooltip), SWFooter (+ command line, guide tooltip). const general = scope('general-themed') const headerFonts = await fonts(page, `${general} .hm-sw-header`, '.hm-sw-header__motion-tooltip') - check('일반 테마: SWHeader + 열린 레이어 Dropdown = Dotum (창 이동 Tooltip 별도)', only(headerFonts, VERIFY), fmt(headerFonts)) + check('일반 테마: SWHeader + 열린 레이어 Dropdown = 검증 글꼴 (창 이동 Tooltip 별도)', only(headerFonts, VERIFY), fmt(headerFonts)) const tooltip = await page.locator(`${general} .hm-sw-header__motion-tooltip`).evaluate(element => getComputedStyle(element).fontFamily) check('일반 테마: 창 이동 Tooltip = Inter → 테마 글꼴', tooltip === `Inter, ${VERIFY}`, tooltip) const layerMenu = await page.locator(`${general} .hm-sw-header`).getByText('A-WALL').count() check('일반 테마: 레이어 Dropdown이 열린 상태(목록 행 A-WALL 표시)', layerMenu > 0, `${layerMenu} row`) - check('일반 테마: SWFooter = Dotum', only(await fonts(page, `${general} .hm-sw-footer`), VERIFY), fmt(await fonts(page, `${general} .hm-sw-footer`))) - check('일반 테마: 명령창(SWFooterCommandLine, 중첩 hm-sw-theme) = Dotum', only(await fonts(page, `${general} .hm-sw-footer-command`), VERIFY), fmt(await fonts(page, `${general} .hm-sw-footer-command`))) + check('일반 테마: SWFooter = 검증 글꼴', only(await fonts(page, `${general} .hm-sw-footer`), VERIFY), fmt(await fonts(page, `${general} .hm-sw-footer`))) + check('일반 테마: 명령창(SWFooterCommandLine, 중첩 hm-sw-theme) = 검증 글꼴', only(await fonts(page, `${general} .hm-sw-footer-command`), VERIFY), fmt(await fonts(page, `${general} .hm-sw-footer-command`))) await page.locator(`${general} .hm-sw-footer-add`).first().focus() await page.waitForTimeout(150) const guide = page.locator(`${general} .hm-sw-footer [role="tooltip"]`) - check('일반 테마: 명령 등록 안내(Tooltip) = Dotum', (await guide.count()) > 0 && only(await fonts(page, `${general} .hm-sw-footer [role="tooltip"]`), VERIFY), fmt(await fonts(page, `${general} .hm-sw-footer [role="tooltip"]`))) + check('일반 테마: 명령 등록 안내(Tooltip) = 검증 글꼴', (await guide.count()) > 0 && only(await fonts(page, `${general} .hm-sw-footer [role="tooltip"]`), VERIFY), fmt(await fonts(page, `${general} .hm-sw-footer [role="tooltip"]`))) // Portals. const [plain, themed, insideSource] = await page.evaluate(() => { @@ -122,15 +122,15 @@ const fmt = map => Object.entries(map).map(([family, count]) => `${count}× ${fa !!document.querySelector('[data-theme-case="portal-source"] [data-portal]')] }) check('Portal: DOM이 테마 영역 밖에 렌더링됨', !insideSource) - check('Portal: SWTheme만 감싸면 기본 글꼴(테마 미전달)', plain.startsWith('"Noto Sans KR"'), plain) + check('Portal: SWTheme만 감싸면 기본 글꼴(테마 미전달)', plain === DEFAULT, plain) check('Portal: 같은 theme style의 SWTheme으로 감싸면 테마 적용', themed === VERIFY, themed) // Areas. const area = name => fonts(page, `${scope(name)} .hm-sw-viewer-hint`) - check('영역 A(Dotum)', only(await area('area-a'), VERIFY), fmt(await area('area-a'))) + check('영역 A(검증 글꼴)', only(await area('area-a'), VERIFY), fmt(await area('area-a'))) check('영역 B(기본) — 옆 영역 값이 섞이지 않음', only(await area('area-b'), DEFAULT), fmt(await area('area-b'))) const outer = await page.locator(`${scope('area-c')} > .hm-sw-viewer-hint`).evaluate(element => getComputedStyle(element).fontFamily) - check('영역 C 바깥(Dotum)', outer === VERIFY, outer) + check('영역 C 바깥(검증 글꼴)', outer === VERIFY, outer) check('영역 C 안쪽 SWTheme style(Malgun Gothic)만 안쪽에 적용', only(await area('area-c-nested'), NESTED), fmt(await area('area-c-nested'))) await page.screenshot({ path: path.join(out, 'theme-inheritance-full.png'), fullPage: true }) await page.close() @@ -213,7 +213,7 @@ for (const width of [1920, 1440, 1180, 900, 560]) for (const id of ids) { await page.waitForSelector(`${viewer} .hm-sw-viewer-menu[data-surface="beige"]`) const more = await fonts(page, `${viewer} .hm-sw-viewer-menu`) const command = await fonts(page, '[data-theme-case="general-themed"] .hm-sw-footer-command') - check('대조: 기존 구조 주입 시 More·명령창이 Noto로 되돌아감(결함 재현)', only(more, DEFAULT) && only(command, DEFAULT), `More ${fmt(more)} · 명령창 ${fmt(command)}`) + check('대조: 기존 구조 주입 시 More·명령창이 기본 글꼴로 되돌아감(결함 재현)', only(more, DEFAULT) && only(command, DEFAULT), `More ${fmt(more)} · 명령창 ${fmt(command)}`) await page.close() } const failedRegression = regression.filter(item => item.result !== 'PASS') diff --git a/packages/ui/ai/AI_GUIDE.md b/packages/ui/ai/AI_GUIDE.md index 8ac07f4..5fd7e42 100644 --- a/packages/ui/ai/AI_GUIDE.md +++ b/packages/ui/ai/AI_GUIDE.md @@ -138,7 +138,7 @@ 혼합 규칙: -1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. +1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다. 글꼴도 같습니다: SW 기본 글꼴은 돋움(Dotum, `--hm-sw-header-font-family`), Web 기본 글꼴은 Noto Sans KR(`--hm-font-family-base`), OrgChart는 자기 설정을 유지합니다. 한 패키지의 글꼴 값을 다른 영역에 복사하지 않습니다. 2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다. - SW 일반 모드 `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`) - SW 뷰어 모드 `SWViewerPanel`의 `children` 안 Web 컨트롤 — `SWViewer*` 컨트롤로 안 될 때만(SW `AI_USAGE.md` §0.4) diff --git a/src/sw/sw-theme.stories.tsx b/src/sw/sw-theme.stories.tsx index a389af1..bc4c2d8 100644 --- a/src/sw/sw-theme.stories.tsx +++ b/src/sw/sw-theme.stories.tsx @@ -14,9 +14,9 @@ import "./sw-viewer-stories.css" // SW theme token inheritance (2026-10-20). The official way to change SW tokens: set them through `style` (or the // consumer's own class) on the OUTERMOST SW scope — around the screen, or the component root when it is // used alone. Nested SW roots (viewer menus, More, panels, footer command line) inherit the value. -// Dotum is used here ONLY to make the inheritance visible: it is NOT the SW default font (design approval pending). +// The SW default font is Dotum (2026-10-20). Noto Sans KR is used here ONLY to make the inheritance visible. const KICKER = "HM DESIGN SYSTEM / SW" -const VERIFY_FONT = 'Dotum, "돋움", sans-serif' +const VERIFY_FONT = '"Noto Sans KR", sans-serif' const VERIFY_THEME = { "--hm-sw-header-font-family": VERIFY_FONT } as CSSProperties const NESTED_THEME = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-serif' } as CSSProperties @@ -77,8 +77,8 @@ export const ThemeInheritance: Story = { render: () => ( SW 토큰은 가장 바깥 SW 범위(<SWTheme> 또는 단독 컴포넌트 루트)에서 한 번 선언되고, 안쪽 Dropdown·More·패널·명령창은 그 값을 상속합니다. 토큰은 가장 바깥 범위의 style로만 바꿉니다. 아래 Dotum은 상속 확인용 검증 값이며 SW 기본 글꼴(Noto Sans KR)은 바뀌지 않습니다(Dotum 기본 적용은 디자인 승인 대기).} /> - + description={<>SW 토큰은 가장 바깥 SW 범위(<SWTheme> 또는 단독 컴포넌트 루트)에서 한 번 선언되고, 안쪽 Dropdown·More·패널·명령창은 그 값을 상속합니다. 토큰은 가장 바깥 범위의 style로만 바꿉니다. SW 기본 글꼴은 Dotum이며, 아래 Noto Sans KR은 상속 확인용 검증 값입니다.} /> + @@ -105,10 +105,10 @@ export const ThemeInheritance: Story = {
- 영역 A — 검증 글꼴(Dotum) - 영역 B — 기본(Noto Sans KR) + 영역 A — 검증 글꼴(Noto Sans KR) + 영역 B — 기본(Dotum) - 영역 C 바깥 — Dotum + 영역 C 바깥 — Noto Sans KR 영역 C 안쪽 — Malgun Gothic