From 9394050c72ea83a35e41b8b93c0ee7a264d05933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 19:23:17 +0900 Subject: [PATCH 1/5] feat(storybook): add shared SW notice and refine SW previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add StoryNotice and a withSWNotice decorator so every visible SW Story shows the common notice above its title; non-SW Stories are unaffected. - Align the SW Header overview with the common SW Story page layout, let the preview Stage fill the available width and keep inner scroll on narrow screens. - Rename SW/Footer to SW/하단 명령어바 (sidebar title only, Story IDs fixed via id: "sw-footer"), drop the top SWHeader from its preview and lower the workspace height bounds by the removed 49px. --- src/components/story-page.css | 26 ++++++++++++++++++++++++++ src/components/story-page.tsx | 20 ++++++++++++++++++++ src/sw/sw-footer-stories.css | 5 +++-- src/sw/sw-footer.stories.tsx | 20 +++++++++++--------- src/sw/sw-header-dropdown.stories.tsx | 2 ++ src/sw/sw-header-home-menu.stories.tsx | 2 ++ src/sw/sw-header-open.stories.tsx | 2 ++ src/sw/sw-header-overview.tsx | 8 +++++--- src/sw/sw-header.stories.tsx | 2 ++ src/sw/sw-popup.stories.tsx | 2 ++ src/sw/sw-process-header.stories.tsx | 2 ++ src/sw/sw-property-panel.stories.tsx | 2 ++ src/sw/sw-stories.css | 16 ++++++++-------- src/sw/sw-story-notice.tsx | 22 ++++++++++++++++++++++ src/sw/sw-theme.stories.tsx | 2 ++ src/sw/sw-viewer.stories.tsx | 2 ++ 16 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 src/sw/sw-story-notice.tsx diff --git a/src/components/story-page.css b/src/components/story-page.css index 4088575..6c879db 100644 --- a/src/components/story-page.css +++ b/src/components/story-page.css @@ -61,3 +61,29 @@ font-weight: var(--hm-input-font-weight); } .sb-page .sb-page-section-body { min-width: 0; } + +/* Notice above a page title (StoryNotice). Reuses .sb-page / .sb-page-inner (width and side margin are not redefined). + The frame drops its bottom gutter and pulls the next page up so the notice sits 24px above the title block's own top gutter. */ +.sb-page.sb-page-notice { + padding-bottom: 0; + margin-bottom: calc(24px - var(--sb-page-gutter)); +} +.sb-page .sb-notice { + display: flex; + align-items: flex-start; + gap: 8px; + margin: 0; + font-size: var(--hm-label-font-size); + line-height: var(--hm-label-line-height); + font-weight: var(--hm-input-font-weight); + color: var(--hm-sem-fg-primary); + overflow-wrap: anywhere; +} +.sb-page .sb-notice-icon { + flex: none; + width: 20px; + height: 20px; + /* centers the icon on the first text line even when the line height differs from 20px */ + margin-block: calc((var(--hm-label-line-height) - 20px) / 2); +} +.sb-page .sb-notice-text { min-width: 0; } diff --git a/src/components/story-page.tsx b/src/components/story-page.tsx index 0ca67bb..a52b61c 100644 --- a/src/components/story-page.tsx +++ b/src/components/story-page.tsx @@ -25,6 +25,26 @@ export function StoryPageHeader({ kicker, title, description }: { kicker?: strin ) } +// Notice row (icon + text) shown above a Story page title. Reusable by any Story group through a decorator +// (e.g. src/sw/sw-story-notice.tsx); nothing renders it unless a group opts in. It owns a page frame whose Inner is the +// same as StoryPage's, and cancels its own bottom gutter so the page title below stays where it was. +// Icon: Group 1171290812.svg (circle + "!"), colors via currentColor. +export function StoryNotice({ children }: { children: ReactNode }) { + return ( +
+
+

+ + {children} +

+
+
+ ) +} + // Headings and specimens share StoryPage's single Inner. Wide specimens own their local scroll frame. export function StorySection({ title, diff --git a/src/sw/sw-footer-stories.css b/src/sw/sw-footer-stories.css index c8e3d13..f1a31c9 100644 --- a/src/sw/sw-footer-stories.css +++ b/src/sw/sw-footer-stories.css @@ -3,8 +3,9 @@ .sw-footer-story-screen { position: relative; display: flex; flex-direction: column; background: #060606; } .sw-footer-story-canvas { position: relative; background: var(--sw-story-canvas); } /* 2026-10-22: the workspace fits the viewport under the page title (720px at most), so the page itself does not - scroll where there is room; a short window keeps 480px and the page scrolls instead of clipping. */ -.sw-footer-story-workspace { display: flex; flex-direction: column; height: clamp(480px, calc(100dvh - 210px), 720px); background: #060606; } + scroll where there is room; a short window keeps 480px and the page scrolls instead of clipping. + 2026-11-09: the 49px SWHeader above the drawing area was removed, so every bound is 49px lower (drawing area and footer unchanged). */ +.sw-footer-story-workspace { display: flex; flex-direction: column; height: clamp(431px, calc(100dvh - 259px), 671px); background: #060606; } .sw-footer-story-canvas--fill { flex: 1 1 auto; min-height: 0; } .sw-footer-story-nodes { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 16px; } .sw-footer-story-node { margin: 0; padding: 12px; border: 1px solid #dedbd2; border-radius: 6px; background: #fff; } diff --git a/src/sw/sw-footer.stories.tsx b/src/sw/sw-footer.stories.tsx index 3becd20..50901cb 100644 --- a/src/sw/sw-footer.stories.tsx +++ b/src/sw/sw-footer.stories.tsx @@ -1,8 +1,9 @@ import { useState, type ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { SWFooter, SWFooterAddSlot, SWFooterAuxButton, SWFooterAuxGroup, SWFooterCommandLine, SWFooterGuide, SWFooterLayoutTabs, SWFooterMenu, - SWFooterToolButton, SWHeader, SWTheme, SW_FOOTER_MODEL, swFooterToolIcons, + SWFooterToolButton, SWTheme, SW_FOOTER_MODEL, swFooterToolIcons, type SWFooterAuxItem, type SWFooterCategory, type SWFooterLayout, type SWFooterTool, } from "@hm-design/sw" import "@hm-design/sw/styles.css" @@ -14,7 +15,6 @@ import { } from "./sw-footer-fixture" import "./sw-stories.css" import "./sw-footer-stories.css" -import placeholderLogo from "./assets/sw-header-logo-placeholder.svg" // SW/Footer (2026-10-20): Figma 이지빔-SW 26:10097 bottom bar and its 34 component sets as @hm-design/sw components. // Stories use the public API only, like a consuming program. No CAD engine: commands are logged. @@ -22,8 +22,11 @@ const KICKER = "HM DESIGN SYSTEM / SW" const ref = (id: string) => new URL(`./assets/figma-reference/footer/${id}.png`, import.meta.url).href const meta = { - title: "SW/Footer", + title: "SW/하단 명령어바", + // Fixed so the Story IDs (sw-footer--*) and direct URLs stay unchanged when the sidebar title changes. + id: "sw-footer", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj @@ -51,7 +54,7 @@ function FigmaFooter() { const LAYOUTS_2: SWFooterLayout[] = [{ id: "layout2", label: "Layout 02" }, { id: "layout1", label: "Layout 01" }] -/** The reference screen (2026-10-20): SWHeader, drawing area, footer with the command line inside the bar. */ +/** The reference screen (2026-10-20): drawing area, footer with the command line inside the bar. 2026-11-09: no SWHeader above the drawing area. */ function Workspace({ width = "100%" }: { width?: number | string }) { const demo = useFooterDemo({ aux: REFERENCE_AUX, layouts: LAYOUTS_2, history: REFERENCE_HISTORY }) const [commandOpen, setCommandOpen] = useState(false) @@ -60,7 +63,6 @@ function Workspace({ width = "100%" }: { width?: number | string }) { {/* 2026-10-22: no inner scroller; `width` is a maximum and the header / footer fit the available width. */}
-
( - + {/* 2026-10-22 (user request): no section heading; the live composition follows the page title. Behaviour: command click = select + history, category tabs, empty slot guide, aux toggles, ▲ = command window, Layout tabs, Enter runs. */}
@@ -379,10 +381,10 @@ function ReferenceFooter() { } export const Responsive: Story = { - name: "Responsive More", + name: "화면 너비", render: () => ( - + {/* Same 1920px row rule as SW Header / SW Viewer Responsive More (sw-stories.css .sw-story-responsive). */}
{[1920, 1440, 1280, 950, 860, 760].map(width => ( diff --git a/src/sw/sw-header-dropdown.stories.tsx b/src/sw/sw-header-dropdown.stories.tsx index 5951717..586af3e 100644 --- a/src/sw/sw-header-dropdown.stories.tsx +++ b/src/sw/sw-header-dropdown.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { ComposedDemo } from "./sw-header.stories" import "./sw-composed-controls.css" @@ -7,6 +8,7 @@ const meta = { // Keep existing bookmarks; Dropdown is a Story directly under Header. id: "sw-header", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta diff --git a/src/sw/sw-header-home-menu.stories.tsx b/src/sw/sw-header-home-menu.stories.tsx index 725d39a..fd69699 100644 --- a/src/sw/sw-header-home-menu.stories.tsx +++ b/src/sw/sw-header-home-menu.stories.tsx @@ -1,5 +1,6 @@ import { useLayoutEffect, useRef, useState, type ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" // Public API only, like a consuming project. import { SWHeader, SWHeaderPropertyBar, SWHomeMenu, SWHomeMenuItem, SWHomeMenuSection, SWPopup, SWPopupRow, SWPopupSection, @@ -25,6 +26,7 @@ const KICKER = "HM DESIGN SYSTEM / SW" const meta = { title: "SW/Header", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj diff --git a/src/sw/sw-header-open.stories.tsx b/src/sw/sw-header-open.stories.tsx index 3c8e78a..45b605f 100644 --- a/src/sw/sw-header-open.stories.tsx +++ b/src/sw/sw-header-open.stories.tsx @@ -1,5 +1,6 @@ import { useState, type ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { SWHeader, SWHeaderDocumentTitle, SWHeaderHome, SWHeaderLayerMenu, SWTheme, SWToolButton, type SWHeaderDocument, type SWHeaderLayer, type SWHeaderProps, @@ -54,6 +55,7 @@ const meta = { title: "SW/Header", id: "sw-header", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj diff --git a/src/sw/sw-header-overview.tsx b/src/sw/sw-header-overview.tsx index 3961963..809199e 100644 --- a/src/sw/sw-header-overview.tsx +++ b/src/sw/sw-header-overview.tsx @@ -69,8 +69,10 @@ export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFi if (!slot) return const measure = () => { const bounds = slot.getBoundingClientRect() - const width = Math.max(0, Math.min(bounds.width, bounds.height * 16 / 9)) - setSize(previous => previous.width === width ? previous : { width, height: width * 9 / 16 }) + // 2026-11-09: the Stage height is still the 16:9 height of the fitted area (unchanged); the width now fills the slot. + const height = Math.max(0, Math.min(bounds.width, bounds.height * 16 / 9)) * 9 / 16 + const width = Math.max(0, bounds.width) + setSize(previous => previous.width === width && previous.height === height ? previous : { width, height }) } measure() const observer = new ResizeObserver(measure) @@ -150,7 +152,7 @@ export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFi
}
-
({ id: "layer", name: const meta = { title: "SW/Popup", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj diff --git a/src/sw/sw-process-header.stories.tsx b/src/sw/sw-process-header.stories.tsx index e49f993..90f883b 100644 --- a/src/sw/sw-process-header.stories.tsx +++ b/src/sw/sw-process-header.stories.tsx @@ -1,5 +1,6 @@ import { useState, type ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { SWProcessBar, SWProcessHeader, SWProcessSplitButton, SWTheme, SWToolButton, type SWProcessGroup, type SWProcessHeaderProps, type SWProcessSplitType, } from "@hm-design/sw" @@ -31,6 +32,7 @@ const LOGO = { logoSrc: placeholderLogo, logoAlt: "임시 로고" } satisfies Pa const meta = { title: "SW/Step Header", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj diff --git a/src/sw/sw-property-panel.stories.tsx b/src/sw/sw-property-panel.stories.tsx index cb6f4bb..bac20af 100644 --- a/src/sw/sw-property-panel.stories.tsx +++ b/src/sw/sw-property-panel.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { PropertyPanelComparePage } from "./sw-header.stories" const meta = { @@ -8,6 +9,7 @@ const meta = { // Custom Content and Multiple Popups. id: "sw-header", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta diff --git a/src/sw/sw-stories.css b/src/sw/sw-stories.css index ecf37ee..36bc2b8 100644 --- a/src/sw/sw-stories.css +++ b/src/sw/sw-stories.css @@ -68,20 +68,20 @@ .sw-story-detail-row > div { display: flex; align-items: center; gap: 8px; } /* Overview host layout only. Native controls below are Storybook data controls, outside the product. */ -.sw-story-overview.sb-page { height: 100dvh; min-height: 0; padding-block: 16px; } -.sw-story-overview > .sb-page-inner { height: 100%; display: flex; flex-direction: column; gap: 12px; } -.sw-story-overview .sb-page-header { flex: 0 0 auto; padding-bottom: 8px; } -.sw-story-overview .sb-page-header .sb-page-kicker { margin-bottom: 4px; } -.sw-story-overview .sb-page-header .sb-page-title { margin-block: 0 4px; } -.sw-overview-toolbar { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px; } +/* 2026-11-09: the page frame (gutter, title block, Inner) is the common SW Story page; only the Stage keeps a viewport-based height. + The Stage fits the slot (16:9 inside it), so the slot gets a definite height instead of the page being locked to 100dvh. */ +.sw-story-overview > .sb-page-inner { display: flex; flex-direction: column; } +.sw-overview-toolbar { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; } .sw-overview-toolbar select, .sw-overview-data button { font: inherit; padding: 3px 8px; color: inherit; background: var(--hm-sem-bg-surface); border: 1px solid var(--hm-sem-stroke-input-default); border-radius: 4px; } .sw-overview-toolbar details { flex-basis: 100%; } .sw-overview-toolbar summary { width: max-content; cursor: pointer; } .sw-overview-data { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-block: 8px; } .sw-overview-data label { display: inline-flex; align-items: center; gap: 4px; } -.sw-overview-slot { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; } +.sw-overview-slot { flex: none; height: max(360px, calc(100dvh - 260px)); min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; } .sw-overview-stage { flex: 0 0 auto; overflow: auto; overscroll-behavior: contain; background: var(--sw-story-canvas); outline: 1px solid var(--hm-sem-stroke-input-default); } -.sw-overview-world { min-width: 390px; min-height: 100%; position: relative; } +/* 2026-11-09: floor 390 -> 358, the measured minimum width of SWHeader (content width 358px; narrower overflows). 390 forced a 32px + Stage scroll at a 390px viewport (Inner 358px). Below 358px the Stage still scrolls instead of clipping. */ +.sw-overview-world { min-width: 358px; min-height: 100%; position: relative; } /* Placement-only Storybook exception: the panel sits in the Stage. 2026-10-26 (user request): no Stage scrollbar when it opens — the panel is clamped to the Stage height (as the product clamps it to the viewport) and its body scrolls. The Stage height comes from the Story (--sw-preview-stage-height): the panel's containing block is the Header. diff --git a/src/sw/sw-story-notice.tsx b/src/sw/sw-story-notice.tsx new file mode 100644 index 0000000..aa6e622 --- /dev/null +++ b/src/sw/sw-story-notice.tsx @@ -0,0 +1,22 @@ +import type { Decorator } from "@storybook/react-vite" +import { StoryNotice } from "@/components/story-page" + +// Storybook presentation only. Common notice shown above the page title of every SW Story (SW/* groups only): +// each SW stories file opts in with `decorators: [withSWNotice]`, so other menus never render it. +// Hidden verification / internal Stories are skipped; `parameters.swNotice: false` opts a Story out +// (use it where the page already carries the same notice, so it is never shown twice). +export const SW_NOTICE_TEXT = + "SW UI는 공통 컴포넌트의 기능과 동작을 유지하면서, 제품별 특성에 따라 시각적 스타일을 다르게 적용할 수 있습니다." + +const HIDDEN_TAGS = ["verification", "internal"] + +export const withSWNotice: Decorator = (Story, context) => { + const hidden = (context.tags ?? []).some(tag => HIDDEN_TAGS.includes(tag)) + if (hidden || context.parameters.swNotice === false) return + return ( + <> + {SW_NOTICE_TEXT} + + + ) +} diff --git a/src/sw/sw-theme.stories.tsx b/src/sw/sw-theme.stories.tsx index 9833ebe..e3087c4 100644 --- a/src/sw/sw-theme.stories.tsx +++ b/src/sw/sw-theme.stories.tsx @@ -1,6 +1,7 @@ import { useState, type CSSProperties, type ReactNode } from "react" import { createPortal } from "react-dom" import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { SWFooter, SWFooterCommandLine, SWHeader, SWTheme, SWViewerButton, SWViewerHeader, SWViewerHint, SWViewerSection, } from "@hm-design/sw" @@ -23,6 +24,7 @@ const NESTED_THEME = { "--hm-sw-header-font-family": '"Malgun Gothic", sans-seri const meta = { title: "SW/Theme", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj diff --git a/src/sw/sw-viewer.stories.tsx b/src/sw/sw-viewer.stories.tsx index 85af3b2..c220369 100644 --- a/src/sw/sw-viewer.stories.tsx +++ b/src/sw/sw-viewer.stories.tsx @@ -1,5 +1,6 @@ import { useRef, useState, type ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" +import { withSWNotice } from "./sw-story-notice" import { SWHeader, SWViewerButton, SWViewerHeader, SWViewerHint, SWViewerMenu, SWViewerPanel, SWViewerSection, type SWViewerHeaderGroup, type SWViewerMenuEntry, @@ -20,6 +21,7 @@ const KICKER = "HM DESIGN SYSTEM / SW" const meta = { title: "SW/Viewer", parameters: { layout: "fullscreen", controls: { disable: true } }, + decorators: [withSWNotice], } satisfies Meta export default meta type Story = StoryObj From ea984ab3a689db8ce8b7c0190f7b18b9f0712eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 19:23:23 +0900 Subject: [PATCH 2/5] chore(storybook): refine navigation and foundation labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename Foundation/Typography to 폰트 and SW/Footer to SW/하단 명령어바 in the sidebar order; Story IDs are kept (foundation-typography, sw-footer). - Hanmac Color: label Light 4 as "보더 하이라이트용" and add a divider between Light 4 and Light 3. - Hide Patterns/Layout/Page Header from the sidebar with the verification tag (Story IDs and direct URLs are unchanged). - Remove the Variants section from the representative LNB page; With Logo and Long Labels remain as verification Stories. --- .storybook/preview.tsx | 4 ++-- src/foundation/color.stories.tsx | 2 +- src/foundation/foundation.css | 3 +++ src/foundation/typography.stories.tsx | 7 ++++--- src/patterns/lnb.stories.tsx | 9 --------- src/patterns/page-header.stories.tsx | 1 + 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 741e0f3..4ad220b 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -25,7 +25,7 @@ const preview: Preview = { includeNames: true, order: [ "Foundation", - ["한맥 컬러", "가족사 컬러", "Typography"], + ["한맥 컬러", "가족사 컬러", "폰트"], "Components", [ "Form", @@ -46,7 +46,7 @@ const preview: Preview = { "Patterns", ["Data", ["Data Table", "Filter Bar"], "Layout", ["GNB", "LNB", "Dashboard Layout", "Loading Screen"]], "SW", - ["Header", ["SW Header", "*"], "Footer", ["SW Footer", "*"], "Popup", "Viewer", "Theme"], + ["Header", ["SW Header", "*"], "하단 명령어바", ["SW 하단 명령어바", "*"], "Popup", "Viewer", "Theme"], "조직도", ["개요", "부서별 색상", "전체 구성", "확장 조직도"], "노선도", diff --git a/src/foundation/color.stories.tsx b/src/foundation/color.stories.tsx index 1157979..3658a39 100644 --- a/src/foundation/color.stories.tsx +++ b/src/foundation/color.stories.tsx @@ -11,7 +11,7 @@ const palettes = [ ] as const type ColorStage = typeof stages[number] type PalettePrefix = typeof palettes[number]["prefix"] -const stageLabel = (stage: ColorStage) => stage === "default" ? "Default · 기본색" : stage.replaceAll("-", " ").replace(/^./, letter => letter.toUpperCase()) +const stageLabel = (stage: ColorStage) => stage === "default" ? "Default · 기본색" : stage === "light-4" ? "Light 4 · 보더 하이라이트용" : stage.replaceAll("-", " ").replace(/^./, letter => letter.toUpperCase()) const paletteToken = (prefix: PalettePrefix, stage: ColorStage) => `color.palette.${prefix}-${stage}` as RuntimeThemeToken const colorProperties = palettes.flatMap(palette => stages.map(stage => runtimeThemeContract[paletteToken(palette.prefix, stage)].property)) diff --git a/src/foundation/foundation.css b/src/foundation/foundation.css index 5a95199..ef29866 100644 --- a/src/foundation/foundation.css +++ b/src/foundation/foundation.css @@ -27,6 +27,9 @@ .hm-brand-color-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 8px; } .hm-brand-color-chip { flex: 1 0 110px; min-width: 0; padding: 8px; border: 1px solid var(--hm-sem-stroke-default); } .hm-brand-color-chip[data-base] { border: 2px solid var(--hm-sem-fg-primary); font-weight: var(--hm-font-weight-bold); } +/* Hanmac Color only (data-color-stage): wider gap (8px + 16px extra = 24px) between Light 4 and Light 3 with a divider centered in it. */ +.hm-brand-color-chip[data-color-stage="light-3"] { --hm-color-divider-extra: 16px; position: relative; margin-left: var(--hm-color-divider-extra); } +.hm-brand-color-chip[data-color-stage="light-3"]::before { content: ""; position: absolute; top: 0; bottom: 0; left: calc(-1px - 0.5px - (8px + var(--hm-color-divider-extra)) / 2); width: 1px; background: var(--hm-sem-stroke-default); pointer-events: none; } .hm-brand-color-chip h3, .hm-brand-color-chip p { margin: 8px 0 0; font-size: 13px; } /* Color reference additions only; preserve the existing palette row and Default emphasis. */ diff --git a/src/foundation/typography.stories.tsx b/src/foundation/typography.stories.tsx index 050ebc6..fab208d 100644 --- a/src/foundation/typography.stories.tsx +++ b/src/foundation/typography.stories.tsx @@ -43,7 +43,7 @@ function SpecimenTable({ rows }: { rows: readonly TypographyReference[] }) { function TypographyReferencePage() { const values = useResolvedProperties(typographyProperties) - return + return

Noto Sans KR → 맑은 고딕 → 시스템 sans-serif--hm-font-family-base: {values["--hm-font-family-base"]}

@@ -59,6 +59,7 @@ function TypographyReferencePage() {
} -const meta = { title: "Foundation/Typography", parameters: { layout: "fullscreen" } } satisfies Meta +// The sidebar name is "폰트"; `id` keeps the existing Story ID (foundation-typography--all) stable. +const meta = { title: "Foundation/폰트", id: "foundation-typography", parameters: { layout: "fullscreen" } } satisfies Meta export default meta -export const All: StoryObj = { name: "Typography", render: () => } +export const All: StoryObj = { name: "폰트", render: () => } diff --git a/src/patterns/lnb.stories.tsx b/src/patterns/lnb.stories.tsx index 85a5f49..7f4eb56 100644 --- a/src/patterns/lnb.stories.tsx +++ b/src/patterns/lnb.stories.tsx @@ -641,15 +641,6 @@ export const All: Story = { ]} /> - - }, - { id: "long", label: "Long Labels", children: }, - ]} - /> - ), } diff --git a/src/patterns/page-header.stories.tsx b/src/patterns/page-header.stories.tsx index 796ebcf..bb4035e 100644 --- a/src/patterns/page-header.stories.tsx +++ b/src/patterns/page-header.stories.tsx @@ -20,6 +20,7 @@ import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-pag const meta = { title: "Patterns/Layout/Page Header", + tags: ["verification"], // 임시 숨김: 사이드바에서만 제외. 다시 표시하려면 이 줄 삭제 component: PageHeader, parameters: { layout: "fullscreen", controls: { disable: true } }, } satisfies Meta From be0661062f22bc6adf5a9069c7496b85e78b6b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 19:23:29 +0900 Subject: [PATCH 3/5] fix(storybook): load local Noto Sans KR and this checkout's UI theme - Dev server: rewrite the @hm-design/ui dist font URL to the /fonts static directory so Preview no longer gets a 403 from the Vite fs allow list. - When node_modules/@hm-design/ui is linked to another checkout, resolve the five @hm-design/ui entry points to this checkout's packages/ui source so the current tokens are shown instead of a stale palette. Checkouts whose links already point at their own packages/ui keep the dist-based resolution. --- .storybook/main.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 0b2c1b2..73605cf 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,5 +1,57 @@ import type { StorybookConfig } from "@storybook/react-vite" -import { readFileSync } from "node:fs" +import { readFileSync, realpathSync } from "node:fs" +import { fileURLToPath } from "node:url" +import path from "node:path" + +// Checkouts that share another checkout's node_modules (a linked git worktree whose node_modules is a junction) also +// inherit that checkout's node_modules/@hm-design/* links. Those links point at the other checkout's packages/ui and its +// compiled dist, so Storybook would show the other checkout's tokens (a stale palette) instead of this checkout's. +// Only in that case, resolve the five @hm-design/ui entry points to this checkout's own source. Nothing is built, the +// other checkout's node_modules and dist are only read, and a checkout whose links already point at its own +// packages/ui keeps the unchanged dist-based resolution. +const checkoutRoot = fileURLToPath(new URL("../", import.meta.url)) +const localUiRoot = path.join(checkoutRoot, "packages/ui") +function uiLinkedElsewhere() { + try { + return realpathSync(path.join(checkoutRoot, "node_modules/@hm-design/ui")) !== realpathSync(localUiRoot) + } catch { + return false + } +} +const localUiAliases = [ + [/^@hm-design\/ui$/, "src/index.ts"], + [/^@hm-design\/ui\/theme$/, "src/theme/index.ts"], + [/^@hm-design\/ui\/theme\.css$/, "src/theme.css"], + [/^@hm-design\/ui\/tailwind\.css$/, "src/tailwind.css"], + [/^@hm-design\/ui\/dashboard-status\.css$/, "src/patterns/dashboard-status/dashboard-status.css"], +].map(([find, file]) => ({ find: find as RegExp, replacement: path.join(localUiRoot, file as string) })) + +// @hm-design/ui's dist/theme.css declares the bundled font with a relative URL. When node_modules/@hm-design/ui is +// symlinked to a checkout outside this Vite root, Vite rewrites that URL to /@fs/ and the dev preview +// gets a 403 ("outside of Vite serving allow list"). Point only that font URL at the /fonts static directory the +// Manager already uses, instead of widening server.fs.allow. theme.css is inlined through @import, so the rewrite +// runs on the already-resolved CSS and is limited to the exact ".../ui/dist/fonts/NotoSansKR-variable.ttf" URL. +// Dev server only: the static build emits the font as a bundled asset. +const UI_DIST_FONT_URL = /[^"'()\\\s]*\/ui\/dist\/fonts\/NotoSansKR-variable\.ttf/g +const FONT_STATIC_URL = "/fonts/NotoSansKR-variable.ttf" + +const hmUiThemeFontPath = { + name: "hm-ui-theme-font-path", + apply: "serve" as const, + enforce: "post" as const, + transform(code: string, id: string) { + const file = id.split("?")[0] + if (!file.endsWith(".css") || !code.includes("NotoSansKR-variable.ttf")) return null + const next = code.replace(UI_DIST_FONT_URL, FONT_STATIC_URL) + if (next === code) { + if (!code.includes(FONT_STATIC_URL)) { + this.warn(`[hm-ui-theme-font-path] ${file}: found NotoSansKR-variable.ttf but not at ".../ui/dist/fonts/"; the URL was left unchanged and Preview may fail to load Noto Sans KR.`) + } + return null + } + return { code: next, map: null } + }, +} const config: StorybookConfig = { stories: ["../src/**/*.stories.@(ts|tsx)"], @@ -21,6 +73,13 @@ const config: StorybookConfig = { viteFinal: (config) => { config.server ??= {} config.server.watch = { ...config.server.watch, ignored: ["**/_local/**", "**/storybook-static/**"] } + config.plugins = [...(config.plugins ?? []), hmUiThemeFontPath] + if (uiLinkedElsewhere()) { + config.resolve ??= {} + const existing = config.resolve.alias + const current = Array.isArray(existing) ? existing : Object.entries(existing ?? {}).map(([find, replacement]) => ({ find, replacement })) + config.resolve.alias = [...localUiAliases, ...current] + } return config }, framework: "@storybook/react-vite", From 73ef042c5536ee28ef9d5be4aae6aceec62acc9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 19:23:43 +0900 Subject: [PATCH 4/5] feat(theme): update Hanmac Green and Brown palette steps Seven shared Hanmac color tokens change: - Green Default #123328 -> #164434, Dark 1 #0a221d -> #11372d, Dark 2 #071e18 -> #0b2b23, Dark 3 #020a07 -> #030e0a - Brown Default #3e3523 -> #4e422d, Dark 1 #352d1d -> #403726, Dark 3 #1d1810 -> #0d0b07 Updates primitives.css, the generated default values, themes/default.json, the verification baseline and the hard-coded Default expectations in the theme Stories and the quality-reference script. --hm-sem-on-accent is intentionally unchanged. check:theme-defaults reports generated outputs match. --- .../ui/src/theme/default-values.generated.ts | 30 +++++++++---------- packages/ui/src/theme/primitives.css | 14 ++++----- scripts/verify-quality-reference.mjs | 6 ++-- src/theme/designer-theme.stories.tsx | 2 +- src/theme/runtime-loader.stories.tsx | 2 +- src/theme/runtime-theme.stories.tsx | 2 +- src/theme/verification-baseline.json | 10 +++---- themes/default.json | 30 +++++++++---------- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/packages/ui/src/theme/default-values.generated.ts b/packages/ui/src/theme/default-values.generated.ts index 14003bd..512203e 100644 --- a/packages/ui/src/theme/default-values.generated.ts +++ b/packages/ui/src/theme/default-values.generated.ts @@ -3,24 +3,24 @@ export const runtimeThemeDefaults = { "--hm-sem-bg-content": "#f5f0e5", "--hm-sem-bg-surface": "#ffffff", - "--hm-sem-fg-primary": "#0a221d", + "--hm-sem-fg-primary": "#11372d", "--hm-sem-fg-secondary": "#6e6759", "--hm-sem-fg-placeholder": "#999999", - "--hm-sem-action-primary": "#123328", + "--hm-sem-action-primary": "#164434", "--hm-sem-action-cancel": "#6e6759", "--hm-sem-action-destructive": "#d32f2f", "--hm-sem-status-error": "#b3261e", "--hm-sem-stroke-default": "#9a9284", "--hm-sem-stroke-input-default": "#dedbd2", "--hm-sem-stroke-input-focus": "#266651", - "--hm-sem-brand-bg-stop-0": "#0a221d", - "--hm-sem-brand-bg-stop-12": "#071e18", - "--hm-sem-brand-bg-stop-42": "#123328", + "--hm-sem-brand-bg-stop-0": "#11372d", + "--hm-sem-brand-bg-stop-12": "#0b2b23", + "--hm-sem-brand-bg-stop-42": "#164434", "--hm-sem-brand-bg-stop-78": "#266651", "--hm-sem-brand-bg-stop-100": "#205241", - "--hm-sem-brown-bg-stop-0": "#352d1d", + "--hm-sem-brown-bg-stop-0": "#403726", "--hm-sem-brown-bg-stop-12": "#2f281c", - "--hm-sem-brown-bg-stop-42": "#3e3523", + "--hm-sem-brown-bg-stop-42": "#4e422d", "--hm-sem-brown-bg-stop-78": "#897445", "--hm-sem-brown-bg-stop-100": "#604f32", "--hm-sem-orange-bg-stop-0": "#bc4c00", @@ -32,7 +32,7 @@ export const runtimeThemeDefaults = { "--hm-sem-action-surface-stop-30": "#165c47", "--hm-sem-action-surface-stop-65": "#114838", "--hm-sem-action-surface-stop-100": "#08241c", - "--hm-sem-action-border-stop-0": "#0a221d", + "--hm-sem-action-border-stop-0": "#11372d", "--hm-sem-action-border-stop-50": "#34daaa", "--hm-sem-action-border-stop-100": "#08241c", "--hm-radius-input": "4px", @@ -47,13 +47,13 @@ export const runtimeThemeDefaults = { "--hm-sem-palette-green-light-3": "#249473", "--hm-sem-palette-green-light-2": "#266651", "--hm-sem-palette-green-light-1": "#205241", - "--hm-sem-palette-green-default": "#123328", - "--hm-sem-palette-green-dark-1": "#0a221d", - "--hm-sem-palette-green-dark-2": "#071e18", + "--hm-sem-palette-green-default": "#164434", + "--hm-sem-palette-green-dark-1": "#11372d", + "--hm-sem-palette-green-dark-2": "#0b2b23", "--hm-sem-palette-brown-light-2": "#897445", "--hm-sem-palette-brown-light-1": "#604f32", - "--hm-sem-palette-brown-default": "#3e3523", - "--hm-sem-palette-brown-dark-1": "#352d1d", + "--hm-sem-palette-brown-default": "#4e422d", + "--hm-sem-palette-brown-dark-1": "#403726", "--hm-sem-palette-brown-light-4": "#ecd4a2", "--hm-sem-palette-orange-light-4": "#feba88", "--hm-sem-palette-orange-default": "#cc5200", @@ -85,10 +85,10 @@ export const runtimeThemeDefaults = { "--hm-sem-palette-gray": "#767676", "--hm-sem-palette-gray-dark": "#5a5a5a", "--hm-sem-palette-indicator": "#2d2d2d", - "--hm-sem-palette-green-dark-3": "#020a07", + "--hm-sem-palette-green-dark-3": "#030e0a", "--hm-sem-palette-brown-light-3": "#c1a770", "--hm-sem-palette-brown-dark-2": "#2f281c", - "--hm-sem-palette-brown-dark-3": "#1d1810", + "--hm-sem-palette-brown-dark-3": "#0d0b07", "--hm-sem-palette-orange-light-3": "#ec8f53", "--hm-sem-palette-orange-light-2": "#f87725", "--hm-sem-palette-orange-light-1": "#eb5f00", diff --git a/packages/ui/src/theme/primitives.css b/packages/ui/src/theme/primitives.css index baeca6b..f679180 100644 --- a/packages/ui/src/theme/primitives.css +++ b/packages/ui/src/theme/primitives.css @@ -5,14 +5,14 @@ --hm-green-light-3: #249473; --hm-green-light-2: #266651; --hm-green-light-1: #205241; - --hm-green-default: #123328; - --hm-green-dark-1: #0a221d; - --hm-green-dark-2: #071e18; + --hm-green-default: #164434; + --hm-green-dark-1: #11372d; + --hm-green-dark-2: #0b2b23; --hm-brown-light-2: #897445; --hm-brown-light-1: #604f32; - --hm-brown-default: #3e3523; - --hm-brown-dark-1: #352d1d; + --hm-brown-default: #4e422d; + --hm-brown-dark-1: #403726; --hm-brown-light-4: #ecd4a2; @@ -47,10 +47,10 @@ /* Confirmed brand palette additions (ui_viz Color 4.1; Orange Dark 1/2 confirmed by user 2026-10-11). */ :root { - --hm-green-dark-3: #020a07; + --hm-green-dark-3: #030e0a; --hm-brown-light-3: #c1a770; --hm-brown-dark-2: #2f281c; - --hm-brown-dark-3: #1d1810; + --hm-brown-dark-3: #0d0b07; --hm-orange-light-3: #ec8f53; --hm-orange-light-2: #f87725; --hm-orange-light-1: #eb5f00; diff --git a/scripts/verify-quality-reference.mjs b/scripts/verify-quality-reference.mjs index c342cc3..168d45a 100644 --- a/scripts/verify-quality-reference.mjs +++ b/scripts/verify-quality-reference.mjs @@ -62,11 +62,11 @@ try { const page = await browser.newPage(); await page.goto(`${base}/storybook/iframe.html?id=theme-designer-playground--editor&viewMode=story`, { waitUntil: 'networkidle' }); await page.getByRole('button', { name: 'Load Service Example JSON', exact: true }).click(); - await page.waitForFunction(() => document.querySelector('[aria-label="Primary / Brand HEX"]')?.value !== '#123328'); + await page.waitForFunction(() => document.querySelector('[aria-label="Primary / Brand HEX"]')?.value !== '#164434'); const changed = await page.getByRole('textbox', { name: 'Primary / Brand HEX', exact: true }).inputValue(); await page.getByRole('button', { name: 'Load Default JSON', exact: true }).click(); - await page.waitForFunction(() => document.querySelector('[aria-label="Primary / Brand HEX"]')?.value === '#123328'); - await page.close(); return { servicePrimary: changed, defaultPrimary: '#123328' }; + await page.waitForFunction(() => document.querySelector('[aria-label="Primary / Brand HEX"]')?.value === '#164434'); + await page.close(); return { servicePrimary: changed, defaultPrimary: '#164434' }; }); await task('RUNTIME_THEME_KEYBOARD', async () => { const page = await browser.newPage(); diff --git a/src/theme/designer-theme.stories.tsx b/src/theme/designer-theme.stories.tsx index 045fe2a..a737854 100644 --- a/src/theme/designer-theme.stories.tsx +++ b/src/theme/designer-theme.stories.tsx @@ -240,7 +240,7 @@ export const LiveVerification: Story = { } fillJson('{"schemaVersion":1,"tokens":{"color.state.active":"#345678"}}'); click("Import JSON") await waitForThemeState(() => variable("--hm-sem-state-active") === "#345678", "Designer partial import") - assert(variable("--hm-sem-state-active") === "#345678" && variable("--hm-sem-action-primary") === "#123328" && variable("--hm-sem-stroke-input-focus") === "#266651", "Partial import replaces previous keys; unspecified roles use Default") + assert(variable("--hm-sem-state-active") === "#345678" && variable("--hm-sem-action-primary") === "#164434" && variable("--hm-sem-stroke-input-focus") === "#266651", "Partial import replaces previous keys; unspecified roles use Default") for (const [index, [token, label]] of roles.entries()) { const value = `#3456${(120 + index).toString(16)}` setInput(`${label} HEX`, value) diff --git a/src/theme/runtime-loader.stories.tsx b/src/theme/runtime-loader.stories.tsx index cc017f4..c9a150a 100644 --- a/src/theme/runtime-loader.stories.tsx +++ b/src/theme/runtime-loader.stories.tsx @@ -50,7 +50,7 @@ async function verifyLoader(container: HTMLElement) { assert(checked.dataset.state === "checked" && checked.getAttribute("aria-checked") === "true" && getComputedStyle(checked).backgroundColor === "rgb(0, 127, 115)", "HTTP JSON reaches rendered Checkbox") assert(JSON.stringify(geometry()) === initial, "External JSON preserves component geometry") const resolved = resolveRuntimeTheme({ schemaVersion: 1, tokens: { "color.state.active": "#345678" } }, defaults) - assert(resolved.tokens["color.action.primary"] === "#123328" && resolved.tokens["color.state.active"] === "#345678", "Default JSON + partial service override merge") + assert(resolved.tokens["color.action.primary"] === "#164434" && resolved.tokens["color.state.active"] === "#345678", "Default JSON + partial service override merge") assert(await loadRuntimeTheme(root, "/themes/service-example.json", { defaults }), "Loader accepts separately fetched Default JSON") assert(variable("--hm-sem-action-primary") === "#6837a8", "Service override wins supplied Default") resetRuntimeTheme(root) diff --git a/src/theme/runtime-theme.stories.tsx b/src/theme/runtime-theme.stories.tsx index 79f0207..39fd39a 100644 --- a/src/theme/runtime-theme.stories.tsx +++ b/src/theme/runtime-theme.stories.tsx @@ -190,7 +190,7 @@ async function verifyRuntime(canvasElement: HTMLElement, keyboardFocus = false) applyRuntimeTheme(after, {schemaVersion:1,tokens:{"color.state.active":"#345678"}}) assert(variable("--hm-checkbox-checked-background") === "#345678", "Scoped B overrides global A") assert(getComputedStyle(globalRoot).getPropertyValue("--hm-checkbox-checked-background").trim() === "#007f73", "Global A retains its own mapping") - assert(variable("--hm-badge-default-background") === "#123328", "Explicit isolated scope falls back to HM preset") + assert(variable("--hm-badge-default-background") === "#164434", "Explicit isolated scope falls back to HM preset") } finally { resetRuntimeTheme(globalRoot); resetRuntimeTheme(after) } after.style.setProperty("--hm-sem-state-active", "#112233", "important") applyRuntimeTheme(after, external); resetRuntimeTheme(after) diff --git a/src/theme/verification-baseline.json b/src/theme/verification-baseline.json index c9cf137..600eb16 100644 --- a/src/theme/verification-baseline.json +++ b/src/theme/verification-baseline.json @@ -779,13 +779,13 @@ "--hm-green-light-3": "#249473", "--hm-green-light-2": "#266651", "--hm-green-light-1": "#205241", - "--hm-green-default": "#123328", - "--hm-green-dark-1": "#0a221d", - "--hm-green-dark-2": "#071e18", + "--hm-green-default": "#164434", + "--hm-green-dark-1": "#11372d", + "--hm-green-dark-2": "#0b2b23", "--hm-brown-light-2": "#897445", "--hm-brown-light-1": "#604f32", - "--hm-brown-default": "#3e3523", - "--hm-brown-dark-1": "#352d1d", + "--hm-brown-default": "#4e422d", + "--hm-brown-dark-1": "#403726", "--hm-brown-light-4": "#ecd4a2", "--hm-orange-light-4": "#feba88", "--hm-orange-default": "#cc5200", diff --git a/themes/default.json b/themes/default.json index b97dbf9..fca225e 100644 --- a/themes/default.json +++ b/themes/default.json @@ -3,24 +3,24 @@ "tokens": { "color.bg.content": "#f5f0e5", "color.bg.surface": "#ffffff", - "color.fg.primary": "#0a221d", + "color.fg.primary": "#11372d", "color.fg.secondary": "#6e6759", "color.fg.placeholder": "#999999", - "color.action.primary": "#123328", + "color.action.primary": "#164434", "color.action.cancel": "#6e6759", "color.action.destructive": "#d32f2f", "color.status.error": "#b3261e", "color.stroke.default": "#9a9284", "color.stroke.input.default": "#dedbd2", "color.stroke.input.focus": "#266651", - "color.bg.brand.stop.0": "#0a221d", - "color.bg.brand.stop.12": "#071e18", - "color.bg.brand.stop.42": "#123328", + "color.bg.brand.stop.0": "#11372d", + "color.bg.brand.stop.12": "#0b2b23", + "color.bg.brand.stop.42": "#164434", "color.bg.brand.stop.78": "#266651", "color.bg.brand.stop.100": "#205241", - "color.bg.brown.stop.0": "#352d1d", + "color.bg.brown.stop.0": "#403726", "color.bg.brown.stop.12": "#2f281c", - "color.bg.brown.stop.42": "#3e3523", + "color.bg.brown.stop.42": "#4e422d", "color.bg.brown.stop.78": "#897445", "color.bg.brown.stop.100": "#604f32", "color.bg.orange.stop.0": "#bc4c00", @@ -32,7 +32,7 @@ "surface.action.primary.stop.30": "#165c47", "surface.action.primary.stop.65": "#114838", "surface.action.primary.stop.100": "#08241c", - "surface.action.primary.border.stop.0": "#0a221d", + "surface.action.primary.border.stop.0": "#11372d", "surface.action.primary.border.stop.50": "#34daaa", "surface.action.primary.border.stop.100": "#08241c", "color.state.active": "#266651", @@ -45,13 +45,13 @@ "color.palette.green-light-3": "#249473", "color.palette.green-light-2": "#266651", "color.palette.green-light-1": "#205241", - "color.palette.green-default": "#123328", - "color.palette.green-dark-1": "#0a221d", - "color.palette.green-dark-2": "#071e18", + "color.palette.green-default": "#164434", + "color.palette.green-dark-1": "#11372d", + "color.palette.green-dark-2": "#0b2b23", "color.palette.brown-light-2": "#897445", "color.palette.brown-light-1": "#604f32", - "color.palette.brown-default": "#3e3523", - "color.palette.brown-dark-1": "#352d1d", + "color.palette.brown-default": "#4e422d", + "color.palette.brown-dark-1": "#403726", "color.palette.brown-light-4": "#ecd4a2", "color.palette.orange-light-4": "#feba88", "color.palette.orange-default": "#cc5200", @@ -83,10 +83,10 @@ "color.palette.gray": "#767676", "color.palette.gray-dark": "#5a5a5a", "color.palette.indicator": "#2d2d2d", - "color.palette.green-dark-3": "#020a07", + "color.palette.green-dark-3": "#030e0a", "color.palette.brown-light-3": "#c1a770", "color.palette.brown-dark-2": "#2f281c", - "color.palette.brown-dark-3": "#1d1810", + "color.palette.brown-dark-3": "#0d0b07", "color.palette.orange-light-3": "#ec8f53", "color.palette.orange-light-2": "#f87725", "color.palette.orange-light-1": "#eb5f00", From 074ed8706b372ae1b44ca4f4b510503376f6b152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 19:23:54 +0900 Subject: [PATCH 5/5] docs(theme): align palette references with the updated Green and Brown steps Replace the old Green Default #123328 and Dark steps in the component docs, brand color steps, runtime theme contract, token guide, loading screen doc and the @hm-design/ui AI design rules. check:ai-docs passes. Remaining references elsewhere are left for the final documentation consistency pass. --- docs/components/badge.md | 2 +- docs/components/pagination.md | 2 +- docs/components/tabs.md | 4 +-- docs/guidelines/brand-color-steps.md | 8 +++--- docs/guidelines/runtime-theme-contract.md | 30 +++++++++++------------ docs/guidelines/테마-토큰-가이드.md | 2 +- docs/patterns/loading-screen.md | 2 +- packages/ui/ai/DESIGN_RULES.md | 4 +-- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/components/badge.md b/docs/components/badge.md index 30e16db..b9646ea 100644 --- a/docs/components/badge.md +++ b/docs/components/badge.md @@ -32,7 +32,7 @@ | variant | 면 | 글자 | 테두리 | |---|---|---|---| -| `default` | `--hm-sem-action-primary` (Primary Green `#123328`) | `--hm-sem-on-action-primary` (White) | 면과 동일 | +| `default` | `--hm-sem-action-primary` (Primary Green `#164434`) | `--hm-sem-on-action-primary` (White) | 면과 동일 | | `secondary` | `--hm-sem-bg-subtle` | `--hm-sem-fg-secondary` | 면과 동일 | | `destructive` | `--hm-sem-action-destructive` | White | 면과 동일 | | `outline` | `--hm-sem-bg-surface` | `--hm-sem-fg-primary` | `--hm-sem-stroke-default` | diff --git a/docs/components/pagination.md b/docs/components/pagination.md index e301713..8fdd862 100644 --- a/docs/components/pagination.md +++ b/docs/components/pagination.md @@ -27,7 +27,7 @@ | Page link | 34 × 34px(min-width 34), radius 4px(`--hm-radius-control-md`), 14 / 20 / 500, `--hm-sem-fg-primary` | | Previous / Next | 높이 34px, padding `0 12px`, 아이콘 16px, 라벨 `hidden sm:block`("Previous"/"Next", 모바일은 아이콘만) | | 간격 | 목록 gap 4px | -| Current Page | 배경 `--hm-sem-action-primary`(Primary Green `#123328`), 글자 `--hm-sem-on-action-primary`(흰색), weight 700 | +| Current Page | 배경 `--hm-sem-action-primary`(Primary Green `#164434`), 글자 `--hm-sem-on-action-primary`(흰색), weight 700 | | Ellipsis | 34 × 34 영역, 아이콘 16px, `--hm-sem-fg-secondary` | ## 4. 상태 규격 diff --git a/docs/components/tabs.md b/docs/components/tabs.md index 408881d..63d12b3 100644 --- a/docs/components/tabs.md +++ b/docs/components/tabs.md @@ -67,8 +67,8 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@hm-design/ui" | 상태 | 면 | 테두리 | 전경 | |---|---|---|---| | Default (`data-state="inactive"`) | `rgba(189,223,212,.45)` | top/right `rgba(38,102,81,.35)`, bottom `#DEDBD2` | `#266651` | -| Selected (`data-state="active"`) | `#FFFFFF` (`--hm-sem-bg-surface`) | top/right/left `#DEDBD2`, bottom 1px 표면색(기준선을 덮어 콘텐츠와 연결), `z-index: 2` | `#123328` | -| Hover (비선택만, `:hover`) | 기본 면 + Black 20% overlay (`linear-gradient`) | 변화 없음 | `#123328` | +| Selected (`data-state="active"`) | `#FFFFFF` (`--hm-sem-bg-surface`) | top/right/left `#DEDBD2`, bottom 1px 표면색(기준선을 덮어 콘텐츠와 연결), `z-index: 2` | `#164434` | +| Hover (비선택만, `:hover`) | 기본 면 + Black 20% overlay (`linear-gradient`) | 변화 없음 | `#164434` | | Hover (선택) | 적용 안 함 | — | — | | Focus (`:focus-visible`) | shadcn ring 유지 | — | — | | Disabled | shadcn 기본 `opacity-50`, 클릭 불가 | — | — | diff --git a/docs/guidelines/brand-color-steps.md b/docs/guidelines/brand-color-steps.md index 9d010cf..b7e25fe 100644 --- a/docs/guidelines/brand-color-steps.md +++ b/docs/guidelines/brand-color-steps.md @@ -8,10 +8,10 @@ | Light 3 | #249473 | #C1A770 | #EC8F53 | | Light 2 | #266651 | #897445 | #F87725 | | Light 1 | #205241 | #604F32 | #EB5F00 | -| Default | #123328 | #3E3523 | #CC5200 | -| Dark 1 | #0A221D | #352D1D | #BC4C00 | -| Dark 2 | #071E18 | #2F281C | #883700 | -| Dark 3 | #020A07 | #1D1810 | #4F2000 | +| Default | #164434 | #4E422D | #CC5200 | +| Dark 1 | #11372D | #403726 | #BC4C00 | +| Dark 2 | #0B2B23 | #2F281C | #883700 | +| Dark 3 | #030E0A | #0D0B07 | #4F2000 | 모든 칸의 Key는 `color.palette.{green|brown|orange}-{light-4|light-3|light-2|light-1|default|dark-1|dark-2|dark-3}`로 선언되어 있다. CSS는 동일 suffix의 `--hm-sem-palette-*` → `--hm-*` Primitive를 사용한다. Orange Dark 1·2 확정 당시에는 83개(색상 81개 + radius 2개)였으며 이후 Generic Status Error 1개와 Brown/Orange Background Stop 10개가 추가됐다. 현재 공개 입력은 94개(색상 92개 + radius 2개)다. Dark 3와 Light 4~Default 값은 유지한다. diff --git a/docs/guidelines/runtime-theme-contract.md b/docs/guidelines/runtime-theme-contract.md index ca739fc..a21ee5c 100644 --- a/docs/guidelines/runtime-theme-contract.md +++ b/docs/guidelines/runtime-theme-contract.md @@ -6,24 +6,24 @@ |---|---|---|---|---|---| | `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.primary` | `--hm-sem-fg-primary` | `#11372d` | 주요 텍스트 | 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.primary` | `--hm-sem-action-primary` | `#164434` | 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.status.error` | `--hm-sem-status-error` | `#b3261e` | Generic Status Error · 일반 상태 오류 · Provisional | Yes | Foundation Color / 선택적 Theme 입력; 기존 Component 자동 연결 없음 | | `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.0` | `--hm-sem-brand-bg-stop-0` | `#11372d` | 기존 gradient stop: color.bg.brand.stop.0 | Yes | Theme palette / adapter | +| `color.bg.brand.stop.12` | `--hm-sem-brand-bg-stop-12` | `#0b2b23` | 기존 gradient stop: color.bg.brand.stop.12 | Yes | Theme palette / adapter | +| `color.bg.brand.stop.42` | `--hm-sem-brand-bg-stop-42` | `#164434` | 기존 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 | -| `color.bg.brown.stop.0` | `--hm-sem-brown-bg-stop-0` | `#352d1d` | Brown Background · Dark 1 | Yes · 5개 세트 | 공통 Background / Foundation Color | +| `color.bg.brown.stop.0` | `--hm-sem-brown-bg-stop-0` | `#403726` | Brown Background · Dark 1 | Yes · 5개 세트 | 공통 Background / Foundation Color | | `color.bg.brown.stop.12` | `--hm-sem-brown-bg-stop-12` | `#2f281c` | Brown Background · Dark 2 | Yes · 5개 세트 | 공통 Background / Foundation Color | -| `color.bg.brown.stop.42` | `--hm-sem-brown-bg-stop-42` | `#3e3523` | Brown Background · Default | Yes · 5개 세트 | 공통 Background / Foundation Color | +| `color.bg.brown.stop.42` | `--hm-sem-brown-bg-stop-42` | `#4e422d` | Brown Background · Default | Yes · 5개 세트 | 공통 Background / Foundation Color | | `color.bg.brown.stop.78` | `--hm-sem-brown-bg-stop-78` | `#897445` | Brown Background · Light 2 | Yes · 5개 세트 | 공통 Background / Foundation Color | | `color.bg.brown.stop.100` | `--hm-sem-brown-bg-stop-100` | `#604f32` | Brown Background · Light 1 | Yes · 5개 세트 | 공통 Background / Foundation Color | | `color.bg.orange.stop.0` | `--hm-sem-orange-bg-stop-0` | `#bc4c00` | Orange Background · Dark 1 | Yes · 5개 세트 | 공통 Background / Foundation Color | @@ -35,7 +35,7 @@ | `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.0` | `--hm-sem-action-border-stop-0` | `#11372d` | 기존 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 | @@ -50,13 +50,13 @@ | `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.green-default` | `--hm-sem-palette-green-default` | `#164434` | 선택적 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` | `#11372d` | 선택적 Theme 팔레트: green-dark-1 | Yes | calendar, pagination, badge, table, input, select, button, gnb | +| `color.palette.green-dark-2` | `--hm-sem-palette-green-dark-2` | `#0b2b23` | 선택적 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-default` | `--hm-sem-palette-brown-default` | `#4e422d` | 선택적 Theme 팔레트: brown-default | Yes | button | +| `color.palette.brown-dark-1` | `--hm-sem-palette-brown-dark-1` | `#403726` | 선택적 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 | @@ -89,10 +89,10 @@ | `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.green-dark-3` | `--hm-sem-palette-green-dark-3` | `#030e0a` | 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.brown-dark-3` | `--hm-sem-palette-brown-dark-3` | `#0d0b07` | 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 | diff --git a/docs/guidelines/테마-토큰-가이드.md b/docs/guidelines/테마-토큰-가이드.md index 6f7c045..bad1996 100644 --- a/docs/guidelines/테마-토큰-가이드.md +++ b/docs/guidelines/테마-토큰-가이드.md @@ -281,7 +281,7 @@ HMDesignSystem의 핵심 원칙은 다음과 같다. - Progress는 현재 Active와 색 정책이 같아 같은 Semantic을 쓴다. 의미가 달라지면 별도 Semantic으로 분리한다. - 대상 아님: Toggle / Toggle Group(on = `--accent` 면, 별도 정책), GNB·LNB·Navigation Menu current(기존 Strong Brand 언어), Error/Destructive, Button. - **Persistent Selected Surface**: Toggle / Toggle Group의 Selected 및 Selected + Hover = `--accent` 100% / `--accent-foreground`(Git baseline `d101530` 매핑, 2026-10-06 원복). 아이콘은 전경을 따르고 border는 기존 default 투명 / outline input stroke를 유지한다. Active override에 영향받지 않고 Accent override를 따른다. Focus override는 별도 ring만 바꾼다. - - Color 역할 정리: Hover / Soft surface = `--accent` 45% (Select·Command·Combobox·Menu Item highlighted, Calendar hover·range middle, Pagination hover, Table row hover·selected) / Persistent Selected Surface = `--accent` 100%(Toggle·Toggle Group) / Menubar Top Trigger Highlighted·Open 및 Navigation Menu Trigger·Open = `--accent` 100% + On Accent / Active Control fill = `--hm-sem-state-active`(위 적용 대상) / Focus = Focus chain / Strong Brand·Navigation·Badge default·Pagination current = Primary Green(`--hm-sem-action-primary`, `#123328`) / Error = Destructive. + - Color 역할 정리: Hover / Soft surface = `--accent` 45% (Select·Command·Combobox·Menu Item highlighted, Calendar hover·range middle, Pagination hover, Table row hover·selected) / Persistent Selected Surface = `--accent` 100%(Toggle·Toggle Group) / Menubar Top Trigger Highlighted·Open 및 Navigation Menu Trigger·Open = `--accent` 100% + On Accent / Active Control fill = `--hm-sem-state-active`(위 적용 대상) / Focus = Focus chain / Strong Brand·Navigation·Badge default·Pagination current = Primary Green(`--hm-sem-action-primary`, `#164434`) / Error = Destructive. - Runtime 공개 계약(`packages/ui/src/theme/contract.ts`)에 Active/on Active를 포함했다. `src/theme/preview.ts`는 패키지 Runtime API의 호환 어댑터다. - **Floating Surface Semantic**(`default.css`): `--hm-sem-floating-background|border-width|border-color|radius|shadow`. Dropdown Menu·Context Menu·Popover·Hover Card·Menubar content·Navigation Menu panel의 `--hm--*`가 이를 참조한다. Toast(Sonner)는 notification surface라 이를 쓰지 않고 `--hm-toast-*`를 따로 둔다(Component Token은 유지하며 컴포넌트별 override 가능). Date Picker·Combobox는 Popover를 조합하므로 별도 mapping이 없다. Tooltip(어두운 면)·Dialog/Alert Dialog/Sheet/Drawer(modal)·Select·Card·Command는 대상이 아니다. diff --git a/docs/patterns/loading-screen.md b/docs/patterns/loading-screen.md index a243722..5b43de1 100644 --- a/docs/patterns/loading-screen.md +++ b/docs/patterns/loading-screen.md @@ -51,7 +51,7 @@ Progress는 Indeterminate 표현을 여전히 갖지 않는다(`progress.md` §1 | 텍스트 | 14/500/-0.02em, white 60%, 끝에 정적 "..." | 동일, 폰트는 `--hm-font-family-base` | MATCHED | | Inline | padding 36/24, radius 6, inset shadow 24px .45, gap 12, 가운데 정렬 | 동일 (실측 높이 149, ui_viz smoke 148은 폰트 line box 차이) | MATCHED | | Fullscreen | fixed inset 0, assembly는 block·gap 0, 스피너는 묶음 leading edge(텍스트가 44px보다 넓으면 두 중심축이 어긋남), z-index 9999 | fixed inset 0, inline과 같은 column·gap 12로 묶음 전체를 viewport 중앙(스피너·텍스트 중심축 일치) | INTENTIONAL_CHANGE (2026-10-06 사용자 지시: 스피너가 왼쪽으로 치우쳐 보임) | -| 면 색 | `#0a3322` (Dashboard-local) | `--hm-sem-action-primary` (Default `#123328`) | INTENTIONAL_CHANGE (HM Primitive 재사용, 새 색 만들지 않음) | +| 면 색 | `#0a3322` (Dashboard-local) | `--hm-sem-action-primary` (Default `#164434`) | INTENTIONAL_CHANGE (HM Primitive 재사용, 새 색 만들지 않음) | | 폰트 | Inter → Noto Sans KR | HM 기본 폰트 | INTENTIONAL_CHANGE | | 막대 끝 | radius 1.5 → 렌더에서 pill | `9999px` | MATCHED | | 숨김/표시 fade 0.3s | 있음 | 없음 | FOLLOW_UP (소비자 lifecycle) | diff --git a/packages/ui/ai/DESIGN_RULES.md b/packages/ui/ai/DESIGN_RULES.md index 4d600b8..ad3f326 100644 --- a/packages/ui/ai/DESIGN_RULES.md +++ b/packages/ui/ai/DESIGN_RULES.md @@ -11,8 +11,8 @@ | 역할 | 값 | 변수 | |---|---|---| -| Green(주색) | `#123328` | `--hm-sem-palette-green-default` | -| Green 진한 글자 | `#0A221D` | `--hm-sem-palette-green-dark-1` | +| Green(주색) | `#164434` | `--hm-sem-palette-green-default` | +| Green 진한 글자 | `#11372D` | `--hm-sem-palette-green-dark-1` | | Green Focus·Active | `#266651` | `--hm-sem-palette-green-light-2` | | Green Tint(강조 면) | `#BDDFD4` | `--hm-sem-palette-green-tint` | | Beige(콘텐츠 면) | `#F5F0E5` | `--hm-sem-palette-beige-light` |