108 lines
17 KiB
Markdown
108 lines
17 KiB
Markdown
# Public Runtime Theme Contract
|
|
|
|
계약 Source of Truth: `packages/ui/src/theme/contract.ts`. 모든 입력은 선택적이며 외부 override 가능. Component/derived 변수는 공개 입력이 아니다.
|
|
|
|
| Runtime JSON Token | CSS Token | Default | 의미 | 외부 override | 주요 영향 |
|
|
|---|---|---|---|---|---|
|
|
| `color.bg.content` | `--hm-sem-bg-content` | `#f5f0e5` | 콘텐츠 면 | Yes | gnb, lnb |
|
|
| `color.bg.surface` | `--hm-sem-bg-surface` | `#ffffff` | 기본/카드/입력 면 | Yes | checkbox, radio, switch, slider, badge, table, input, select, button, gnb |
|
|
| `color.fg.primary` | `--hm-sem-fg-primary` | `#0a221d` | 주요 텍스트 | Yes | calendar, pagination, badge, table, input, select, gnb |
|
|
| `color.fg.secondary` | `--hm-sem-fg-secondary` | `#6e6759` | 보조 텍스트 | Yes | calendar, pagination, badge, table, gnb |
|
|
| `color.fg.placeholder` | `--hm-sem-fg-placeholder` | `#999999` | Placeholder | Yes | input, select |
|
|
| `color.action.primary` | `--hm-sem-action-primary` | `#123328` | Strong / Primary ink | Yes | checkbox/radio hover, slider Hover/Drag halo, pagination current, badge default, button outline, navigation current ink |
|
|
| `color.action.cancel` | `--hm-sem-action-cancel` | `#6e6759` | 공개 취소 역할 | Yes | Button `variant="cancel"` + `appearance="outline"` |
|
|
| `color.action.destructive` | `--hm-sem-action-destructive` | `#d32f2f` | 오류/위험 역할 | Yes | checkbox, radio, badge, input, button |
|
|
| `color.stroke.default` | `--hm-sem-stroke-default` | `#9a9284` | 기본 border | Yes | checkbox, radio, switch, badge |
|
|
| `color.stroke.input.default` | `--hm-sem-stroke-input-default` | `#dedbd2` | 입력/패널 divider border | Yes | table, input, select, GNB 콘텐츠 LNB divider |
|
|
| `color.stroke.input.focus` | `--hm-sem-stroke-input-focus` | `#266651` | 공통 Focus border/ring | Yes | input, select, button, checkbox, calendar, slider Keyboard Focus halo 등; GNB/LNB는 contextual 예외 |
|
|
| `color.bg.brand.stop.0` | `--hm-sem-brand-bg-stop-0` | `#0a221d` | 기존 gradient stop: color.bg.brand.stop.0 | Yes | Theme palette / adapter |
|
|
| `color.bg.brand.stop.12` | `--hm-sem-brand-bg-stop-12` | `#071e18` | 기존 gradient stop: color.bg.brand.stop.12 | Yes | Theme palette / adapter |
|
|
| `color.bg.brand.stop.42` | `--hm-sem-brand-bg-stop-42` | `#123328` | 기존 gradient stop: color.bg.brand.stop.42 | Yes | Theme palette / adapter |
|
|
| `color.bg.brand.stop.78` | `--hm-sem-brand-bg-stop-78` | `#266651` | 기존 gradient stop: color.bg.brand.stop.78 | Yes | Theme palette / adapter |
|
|
| `color.bg.brand.stop.100` | `--hm-sem-brand-bg-stop-100` | `#205241` | 기존 gradient stop: color.bg.brand.stop.100 | Yes | Theme palette / adapter |
|
|
| `surface.action.primary.stop.0` | `--hm-sem-action-surface-stop-0` | `#1f8265` | 기존 gradient stop: surface.action.primary.stop.0 | Yes | button, gnb |
|
|
| `surface.action.primary.stop.30` | `--hm-sem-action-surface-stop-30` | `#165c47` | 기존 gradient stop: surface.action.primary.stop.30 | Yes | button, gnb |
|
|
| `surface.action.primary.stop.65` | `--hm-sem-action-surface-stop-65` | `#114838` | 기존 gradient stop: surface.action.primary.stop.65 | Yes | button, gnb |
|
|
| `surface.action.primary.stop.100` | `--hm-sem-action-surface-stop-100` | `#08241c` | 기존 gradient stop: surface.action.primary.stop.100 | Yes | button, gnb |
|
|
| `surface.action.primary.border.stop.0` | `--hm-sem-action-border-stop-0` | `#0a221d` | 기존 gradient stop: surface.action.primary.border.stop.0 | Yes | button |
|
|
| `surface.action.primary.border.stop.50` | `--hm-sem-action-border-stop-50` | `#34daaa` | 기존 gradient stop: surface.action.primary.border.stop.50 | Yes | button |
|
|
| `surface.action.primary.border.stop.100` | `--hm-sem-action-border-stop-100` | `#08241c` | 기존 gradient stop: surface.action.primary.border.stop.100 | Yes | button |
|
|
| `radius.input.default` | `--hm-radius-input` | `4px` | Input/Select radius | Yes | input, select |
|
|
| `radius.container.default` | `--hm-radius-container` | `4px` | Container radius | Yes | Theme palette / adapter |
|
|
| `color.state.active` | `--hm-sem-state-active` | `#266651` | Checked / Selected / ON fill | Yes | checkbox, radio, switch, slider, progress, calendar |
|
|
| `color.on.state.active` | `--hm-sem-on-state-active` | `#ffffff` | Active 위 텍스트/indicator | Yes | checkbox, calendar |
|
|
| `color.fg.inverse` | `--hm-sem-on-action-primary` | `#ffffff` | 반전 텍스트 | Yes | pagination, badge, button, gnb, lnb |
|
|
| `color.bg.subtle` | `--hm-sem-bg-subtle` | `#f4f1ea` | 은은한 면 | Yes | calendar, badge, table |
|
|
| `color.accent` | `--hm-sem-accent` | `#bddfd4` | Soft Highlight 45%, Table 행 20% / Selected·Open surface 100% 원색 | Yes | select, combobox, menu item, table, calendar, pagination hover / toggle, menubar, navigation-menu trigger |
|
|
| `color.on.accent` | `--hm-sem-on-accent` | `#123328` | Accent 위 텍스트; Primary와 독립 sibling | Yes | select, combobox, menu item, calendar, pagination hover, toggle, menubar, navigation-menu |
|
|
| `color.palette.green-light-4` | `--hm-sem-palette-green-light-4` | `#34daaa` | 선택적 Theme 팔레트: green-light-4 | Yes | button |
|
|
| `color.palette.green-light-3` | `--hm-sem-palette-green-light-3` | `#249473` | 선택적 Theme 팔레트: green-light-3 | Yes | data-table Sort Direction Accent, tabs 장식, lnb gradient |
|
|
| `color.palette.green-light-2` | `--hm-sem-palette-green-light-2` | `#266651` | 선택적 Theme 팔레트: green-light-2 | Yes | checkbox, radio, switch, slider, progress, calendar, table, input, select, gnb, lnb |
|
|
| `color.palette.green-light-1` | `--hm-sem-palette-green-light-1` | `#205241` | 선택적 Theme 팔레트: green-light-1 | Yes | table, button, lnb |
|
|
| `color.palette.green-default` | `--hm-sem-palette-green-default` | `#123328` | 선택적 Theme 팔레트: green-default | Yes | checkbox, radio, slider, calendar, pagination, badge, select, button, gnb, lnb |
|
|
| `color.palette.green-dark-1` | `--hm-sem-palette-green-dark-1` | `#0a221d` | 선택적 Theme 팔레트: green-dark-1 | Yes | calendar, pagination, badge, table, input, select, button, gnb |
|
|
| `color.palette.green-dark-2` | `--hm-sem-palette-green-dark-2` | `#071e18` | 선택적 Theme 팔레트: green-dark-2 | Yes | Theme palette / adapter |
|
|
| `color.palette.brown-light-2` | `--hm-sem-palette-brown-light-2` | `#897445` | 선택적 Theme 팔레트: brown-light-2 | Yes | button |
|
|
| `color.palette.brown-light-1` | `--hm-sem-palette-brown-light-1` | `#604f32` | 선택적 Theme 팔레트: brown-light-1 | Yes | button |
|
|
| `color.palette.brown-default` | `--hm-sem-palette-brown-default` | `#3e3523` | 선택적 Theme 팔레트: brown-default | Yes | button |
|
|
| `color.palette.brown-dark-1` | `--hm-sem-palette-brown-dark-1` | `#352d1d` | 선택적 Theme 팔레트: brown-dark-1 | Yes | button |
|
|
| `color.palette.brown-light-4` | `--hm-sem-palette-brown-light-4` | `#ecd4a2` | 선택적 Theme 팔레트: brown-light-4 | Yes | Theme palette / adapter |
|
|
| `color.palette.orange-light-4` | `--hm-sem-palette-orange-light-4` | `#feba88` | 선택적 Theme 팔레트: orange-light-4 | Yes | button |
|
|
| `color.palette.orange-default` | `--hm-sem-palette-orange-default` | `#cc5200` | 선택적 Theme 팔레트: orange-default | Yes | button |
|
|
| `color.palette.warm-neutral-light` | `--hm-sem-palette-warm-neutral-light` | `#e0dace` | 선택적 Theme 팔레트: warm-neutral-light | Yes | slider, progress |
|
|
| `color.palette.warm-neutral-mid` | `--hm-sem-palette-warm-neutral-mid` | `#9a9284` | 선택적 Theme 팔레트: warm-neutral-mid | Yes | checkbox, radio, switch, badge |
|
|
| `color.palette.warm-neutral-dark` | `--hm-sem-palette-warm-neutral-dark` | `#6e6759` | 선택적 Theme 팔레트: warm-neutral-dark | Yes | calendar, pagination, badge, table, gnb |
|
|
| `color.palette.beige-light` | `--hm-sem-palette-beige-light` | `#f5f0e5` | 선택적 Theme 팔레트: beige-light | Yes | gnb, lnb |
|
|
| `color.palette.red-danger` | `--hm-sem-palette-red-danger` | `#d32f2f` | 선택적 Theme 팔레트: red-danger | Yes | checkbox, radio, badge, input, button |
|
|
| `color.palette.white` | `--hm-sem-palette-white` | `#ffffff` | 선택적 Theme 팔레트: white | Yes | checkbox, radio, switch, slider, calendar, pagination, badge, table, input, select, button, gnb, lnb |
|
|
| `color.palette.black` | `--hm-sem-palette-black` | `#000000` | 선택적 Theme 팔레트: black | Yes | checkbox, switch, select, button, gnb, lnb |
|
|
| `color.palette.neutral-gray-500` | `--hm-sem-palette-neutral-gray-500` | `#999999` | 선택적 Theme 팔레트: neutral-gray-500 | Yes | input, select |
|
|
| `color.palette.green-tone-1` | `--hm-sem-palette-green-tone-1` | `#1f8265` | 선택적 Theme 팔레트: green-tone-1 | Yes | button, gnb |
|
|
| `color.palette.green-tone-2` | `--hm-sem-palette-green-tone-2` | `#165c47` | 선택적 Theme 팔레트: green-tone-2 | Yes | button, gnb |
|
|
| `color.palette.green-tone-3` | `--hm-sem-palette-green-tone-3` | `#114838` | 선택적 Theme 팔레트: green-tone-3 | Yes | button, gnb |
|
|
| `color.palette.green-tone-4` | `--hm-sem-palette-green-tone-4` | `#08241c` | 선택적 Theme 팔레트: green-tone-4 | Yes | button, gnb |
|
|
| `color.palette.warm-neutral-pale` | `--hm-sem-palette-warm-neutral-pale` | `#dedbd2` | 선택적 Theme 팔레트: warm-neutral-pale | Yes | table, input, select |
|
|
| `color.palette.warm-neutral-secondary` | `--hm-sem-palette-warm-neutral-secondary` | `#ede8db` | 선택적 Theme 팔레트: warm-neutral-secondary | Yes | Theme palette / adapter |
|
|
| `color.palette.warm-neutral-muted` | `--hm-sem-palette-warm-neutral-muted` | `#f4f1ea` | 선택적 Theme 팔레트: warm-neutral-muted | Yes | calendar, badge, table |
|
|
| `color.palette.green-tint` | `--hm-sem-palette-green-tint` | `#bddfd4` | 선택적 Theme 팔레트: green-tint | Yes | calendar, pagination, table, select, gnb, lnb |
|
|
| `color.palette.yellow-accent` | `--hm-sem-palette-yellow-accent` | `#ffc600` | 선택적 Theme 팔레트: yellow-accent | Yes | lnb |
|
|
| `color.palette.brown-deep` | `--hm-sem-palette-brown-deep` | `#221d13` | 선택적 Theme 팔레트: brown-deep | Yes | button |
|
|
| `color.palette.brown-highlight` | `--hm-sem-palette-brown-highlight` | `#a98d53` | 선택적 Theme 팔레트: brown-highlight | Yes | button |
|
|
| `color.palette.orange-light` | `--hm-sem-palette-orange-light` | `#d35a00` | 선택적 Theme 팔레트: orange-light | Yes | button |
|
|
| `color.palette.orange-mid` | `--hm-sem-palette-orange-mid` | `#ce5600` | 선택적 Theme 팔레트: orange-mid | Yes | button |
|
|
| `color.palette.orange-dark` | `--hm-sem-palette-orange-dark` | `#b44b00` | 선택적 Theme 팔레트: orange-dark | Yes | button |
|
|
| `color.palette.orange-deep` | `--hm-sem-palette-orange-deep` | `#783402` | 선택적 Theme 팔레트: orange-deep | Yes | button |
|
|
| `color.palette.orange-border` | `--hm-sem-palette-orange-border` | `#bc4c00` | 선택적 Theme 팔레트: orange-border | Yes | button |
|
|
| `color.palette.orange-border-deep` | `--hm-sem-palette-orange-border-deep` | `#5e2902` | 선택적 Theme 팔레트: orange-border-deep | Yes | button |
|
|
| `color.palette.gray` | `--hm-sem-palette-gray` | `#767676` | 선택적 Theme 팔레트: gray | Yes | button |
|
|
| `color.palette.gray-dark` | `--hm-sem-palette-gray-dark` | `#5a5a5a` | 선택적 Theme 팔레트: gray-dark | Yes | button |
|
|
| `color.palette.indicator` | `--hm-sem-palette-indicator` | `#2d2d2d` | 선택적 Theme 팔레트: indicator | Yes | select |
|
|
|
|
| `color.palette.green-dark-3` | `--hm-sem-palette-green-dark-3` | `#020a07` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.brown-light-3` | `--hm-sem-palette-brown-light-3` | `#c1a770` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.brown-dark-2` | `--hm-sem-palette-brown-dark-2` | `#2f281c` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.brown-dark-3` | `--hm-sem-palette-brown-dark-3` | `#1d1810` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.orange-light-3` | `--hm-sem-palette-orange-light-3` | `#ec8f53` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.orange-light-2` | `--hm-sem-palette-orange-light-2` | `#f87725` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.orange-light-1` | `--hm-sem-palette-orange-light-1` | `#eb5f00` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
| `color.palette.orange-dark-3` | `--hm-sem-palette-orange-dark-3` | `#4f2000` | ui_viz 확정 브랜드 단계 | Yes | Color Reference / 선택적 Theme palette |
|
|
|
|
총 81개. 기존 25개 + 핵심 역할 6개 + 선택적 팔레트 50개(2026-10-11 ui_viz 확정 단계 8개 추가). 팔레트는 컴포넌트별 계약이 아니며 Primary / Active / Focus를 개별 지정하면 팔레트 alias보다 우선한다. Focus와 Active, Primary와 On Accent는 각각 sibling이며 서로 alias하지 않는다.
|
|
|
|
`color.action.cancel`의 공개 key·기본값은 보존한다. 2026-10-10 Gray 통일 요청에 따라 기본 Outline 취소 ink는 기존 Gray palette `#767676`을 사용한다. 기본값과 다른 명시적 취소 컬러 override는 내부 Button mapping에 전달하고 Reset 시 복원한다. 전체 Default preset을 주입해도 기본 Gray와 동일하다. Solid 취소는 기존 Gray mapping을 유지한다. [Runtime Theme](runtime-theme.md).
|
|
# 디자인팀 Runtime 운영 결정 — 2026-10-06, 팔레트 보강 2026-10-11
|
|
|
|
기존 v1 입력 73개의 Key·CSS property·type·Default를 유지하고 확정 팔레트 8개만 추가한다. Orange Dark 1·2는 Needs Review라 공개 입력에 추가하지 않는다. 공개 API `isRuntimeTheme(unknown): value is RuntimeTheme`를 제공한다. 이 사전 검증은 DOM을 변경하지 않는다. 기존 `applyRuntimeTheme`의 invalid 입력 시 reset 정책은 유지한다. 편집기는 사전 검증으로 invalid Import를 차단해 마지막 유효 Theme를 보존한다.
|
|
|
|
Default 컬러의 운영 원본은 `themes/default.json`(79개 컬러)이다. `scripts/theme-defaults.mjs`가 JSON과 컬러 literal 없는 alias/geometry template에서 package `primitives.css`, `default.css`, `default-values.generated.ts`를 생성한다. `contract.ts`의 defaultValue와 `defaultRuntimeTheme`는 생성 데이터를 읽는다. 2개 radius/geometry는 template에서 유지한다. CSS fallback/현재 색을 유지하고 세 가지 최종 명령의 pre hook에서 stale 생성 데이터를 검사한다. Theme-only 산출은 `npm run build:themes`로 분리한다.
|
|
|
|
Storybook `Theme/Designer Playground/Editor`는 17개 상위 컬러 역할과 JSON URL/파일 Preview를 제공하는 소비자다. Global decorator에서 `resolveRuntimeTheme(theme, defaultJson)` 후 Production `applyRuntimeTheme(document.documentElement, theme)` → CSS Custom Properties → 기존 Mapping으로 적용한다. JSON Export/Import는 동일 v1 계약이다. 컬러 artifact/편집기만 radius를 거부한다. Production API의 기존 radius/gradient 계약은 유지한다.
|
|
|
|
추가 Production API는 `fetchRuntimeTheme(url, request?)`, `resolveRuntimeTheme(theme, defaults?)`, `loadRuntimeTheme(root,url,options?)`다. fetch는 DOM과 분리되며 validation은 기존 `isRuntimeTheme`을 재사용한다. Loader 실패는 HM package fallback, partial은 같은 계약으로 교체/병합하며 superseded 요청은 새 Theme를 덮거나 reset하지 않는다. 회사 CDN URL은 하드코딩하지 않는다.
|
|
|
|
사용법: [디자인팀 Theme 가이드](designer-theme-guide.md), [Theme 운영/배포](theme-delivery.md). 적용·fallback·scope 정책: [Runtime Theme](runtime-theme.md).
|
|
|
|
## Dashboard 상태 색상 범위
|
|
|
|
Dashboard의 `active / hold / done / issue` 업무 상태 계약은 `packages/ui/src/patterns/dashboard-status/contract.ts`에서 ID·label·tone을 제공한다. 상태 변수 원본은 `packages/ui/src/patterns/dashboard-status/dashboard-status.css`이며 `@hm-design/ui/dashboard-status.css`로 배포한다. `src/patterns/dashboard-status-contract.css`는 기존 Story/HTML 경로의 호환 import다. Theme CSS 다음에 상태 CSS를 로드하고 `--hm-dashboard-status-active|hold|done|issue`를 화면 요소의 색상에 별도로 연결한다. 상태 CSS 자체는 Badge나 `data-status`를 자동으로 꾸미지 않는다. active/hold/issue는 기존 Story 색상을 보존하는 compatibility mapping이고 done은 `:root` 및 `[data-hm-theme]`에서 해당 scope의 기존 HM white token을 참조한다. 기존 HM Theme 및 Public Runtime Token/JSON에 상태 입력은 추가하지 않으며, 실제 Production 소비처가 반복되어 공통 색상 의미가 확정될 때만 Semantic Token 승격을 검토한다.
|