style(storybook): refine typography text color swatches
Show the four admin text colors (Primary, Secondary, Placeholder, Readonly Placeholder) as Hanmac color chips with Korean usage names at the top of Foundation/폰트, replacing the comparison and gray palette tables. Font Family, Font Weight and Typography Specimens are unchanged.
This commit is contained in:
1 parent
a71ecbe0b2
commit
f22428548a
2 files changed
+20
-63
No files matched your search
Vendored
+3
-8
@@ -65,14 +65,9 @@
|
|||||||
.hm-foundation .hm-typo-metrics span, .hm-foundation .hm-typo-metrics small { display: block; }
|
.hm-foundation .hm-typo-metrics span, .hm-foundation .hm-typo-metrics small { display: block; }
|
||||||
.hm-foundation .hm-typo-metrics small { font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
.hm-foundation .hm-typo-metrics small { font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
||||||
@media (min-width: 1100px) { .hm-foundation .hm-typo-tables { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
@media (min-width: 1100px) { .hm-foundation .hm-typo-tables { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
/* Typography Text Colors / Foundation Gray: same comparison table, a 28px swatch in the first column. Colors come from the row's token. */
|
/* Typography Text Colors reuse the Hanmac color chips (.hm-brand-color-chip). Four equal tracks keep "name · 설명" on one line on wide screens;
|
||||||
.hm-foundation .hm-text-color-table th, .hm-foundation .hm-text-color-table td { vertical-align: middle; }
|
narrow screens fall back to 150px tracks (2 columns at 390px) and wrap the label inside the chip instead of scrolling. */
|
||||||
.hm-foundation .hm-text-color-table tbody th { width: 20%; }
|
.hm-foundation .hm-text-color-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(max(150px, (100% - 24px) / 4), 1fr)); overflow: visible; padding-bottom: 0; }
|
||||||
.hm-foundation .hm-text-color-table td:last-child { width: 30%; }
|
|
||||||
.hm-foundation .hm-text-color-table small { display: block; font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); font-weight: var(--hm-input-font-weight); }
|
|
||||||
.hm-foundation .hm-text-color-role { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
|
||||||
.hm-foundation .hm-text-color-chip { flex: none; width: 28px; height: 28px; border: 1px solid var(--hm-sem-stroke-input-default); border-radius: var(--hm-radius-control-md); }
|
|
||||||
.hm-foundation .hm-text-color-sample { display: inline-block; max-width: 100%; padding: 2px 8px; border-radius: var(--hm-radius-control-md); }
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.hm-foundation .hm-typo-table thead { display: none; }
|
.hm-foundation .hm-typo-table thead { display: none; }
|
||||||
.hm-foundation .hm-typo-table, .hm-foundation .hm-typo-table tbody, .hm-foundation .hm-typo-table tr, .hm-foundation .hm-typo-table th, .hm-foundation .hm-typo-table td { display: block; width: auto !important; }
|
.hm-foundation .hm-typo-table, .hm-foundation .hm-typo-table tbody, .hm-foundation .hm-typo-table tr, .hm-foundation .hm-typo-table th, .hm-foundation .hm-typo-table td { display: block; width: auto !important; }
|
||||||
|
|||||||
@@ -15,23 +15,15 @@ const textLabels = ["Page Header · title", "Card · title", "Page Header · des
|
|||||||
const textRows = typographyReferences.filter(item => textLabels.includes(item.label))
|
const textRows = typographyReferences.filter(item => textLabels.includes(item.label))
|
||||||
const controlRows = typographyReferences.filter(item => !textLabels.includes(item.label))
|
const controlRows = typographyReferences.filter(item => !textLabels.includes(item.label))
|
||||||
|
|
||||||
// Admin text colors (Web / Chart / OrgChart; not SW). Swatches, samples and HEX read the current tokens; no color is hardcoded here.
|
// Admin text colors (Web / Chart / OrgChart; not SW). The chip color and HEX read the current token; no color is hardcoded here.
|
||||||
const textColorRoles = [
|
const textColorRoles = [
|
||||||
{ role: "Primary", name: "기본 텍스트", token: "--hm-sem-fg-primary", alias: "--foreground", surface: "--hm-sem-bg-surface", surfaceLabel: "흰 면", sample: "제목과 본문 한맥 Hanmac 0123", usage: "페이지·카드·Dialog 제목, 본문, 입력값, 테이블 셀, 차트 값 · OrgChart 기본 글자(--hm-orgchart-fg-primary 기본값)" },
|
{ label: "Primary", name: "기본 텍스트", token: "--hm-sem-fg-primary" },
|
||||||
{ role: "Secondary", name: "보조 텍스트", token: "--hm-sem-fg-secondary", alias: "--muted-foreground", surface: "--hm-sem-bg-surface", surfaceLabel: "흰 면", sample: "설명과 캡션 한맥 Hanmac 0123", usage: "설명, Label, 캡션·요약·빈 상태, 차트 축·범례 · OrgChart 직위·보조 정보(--hm-orgchart-fg-secondary 기본값)" },
|
{ label: "Secondary", name: "보조 텍스트", token: "--hm-sem-fg-secondary" },
|
||||||
{ role: "Placeholder", name: "입력 안내", token: "--hm-sem-fg-placeholder", surface: "--hm-sem-bg-surface", surfaceLabel: "흰 면", sample: "검색어를 입력하세요", usage: "Input·Textarea·Select·Command·Combobox·Date Picker의 흰 입력 면 전용" },
|
{ label: "Placeholder", name: "입력 안내", token: "--hm-sem-fg-placeholder" },
|
||||||
{ role: "대비 예외", name: "베이지 면 Placeholder", token: "--hm-sem-fg-secondary", surface: "--hm-sem-bg-content", surfaceLabel: "베이지 면", sample: "읽기 전용 · 값 없음", usage: "투명 면 readonly Input의 Placeholder. 베이지 면에서는 Placeholder 색이 4.5:1 미만이라 Secondary를 사용" },
|
// Transparent readonly Input on the beige surface uses Secondary (Placeholder is below 4.5:1 there).
|
||||||
] as const
|
{ label: "Readonly Placeholder", name: "읽기 전용 입력 안내", token: "--hm-sem-fg-secondary" },
|
||||||
// Palette steps: only some are wired to a text role. Strong Secondary / Tertiary Semantic tokens do not exist.
|
]
|
||||||
const grayPalette = [
|
const textColorProperties = [...new Set(textColorRoles.map(item => item.token))]
|
||||||
{ step: "900", link: "Primary", linked: true },
|
|
||||||
{ step: "800", link: "팔레트만 · Semantic 없음(Strong Secondary 미정의)", linked: false },
|
|
||||||
{ step: "700", link: "Secondary · 대비 예외", linked: true },
|
|
||||||
{ step: "600", link: "Placeholder", linked: true },
|
|
||||||
{ step: "500", link: "팔레트만 · Disabled 텍스트 색 아님(Disabled는 opacity 방식)", linked: false },
|
|
||||||
] as const
|
|
||||||
const grayToken = (step: string) => `--hm-sem-palette-gray-${step}`
|
|
||||||
const textColorProperties = [...new Set([...textColorRoles.flatMap(item => [item.token, item.surface]), ...grayPalette.map(item => grayToken(item.step))])]
|
|
||||||
|
|
||||||
function toHex(value: string | undefined) {
|
function toHex(value: string | undefined) {
|
||||||
if (!value) return ""
|
if (!value) return ""
|
||||||
@@ -39,40 +31,13 @@ function toHex(value: string | undefined) {
|
|||||||
const rgb = value.match(/^rgb\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)\s*\)$/)
|
const rgb = value.match(/^rgb\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)\s*\)$/)
|
||||||
return rgb ? `#${rgb.slice(1).map(channel => Number(channel).toString(16).padStart(2, "0")).join("").toUpperCase()}` : value
|
return rgb ? `#${rgb.slice(1).map(channel => Number(channel).toString(16).padStart(2, "0")).join("").toUpperCase()}` : value
|
||||||
}
|
}
|
||||||
// WCAG 2 contrast ratio of two #RRGGBB values; empty while a value is not resolved yet.
|
|
||||||
function contrast(foreground: string, background: string) {
|
|
||||||
if (!/^#[\da-f]{6}$/i.test(foreground) || !/^#[\da-f]{6}$/i.test(background)) return ""
|
|
||||||
const luminance = (hex: string) => [1, 3, 5].map(index => parseInt(hex.slice(index, index + 2), 16) / 255).map(channel => channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4).reduce((sum, channel, index) => sum + channel * [0.2126, 0.7152, 0.0722][index], 0)
|
|
||||||
const [light, dark] = [luminance(foreground), luminance(background)].sort((a, b) => b - a)
|
|
||||||
return `${((light + 0.05) / (dark + 0.05)).toFixed(2)}:1`
|
|
||||||
}
|
|
||||||
|
|
||||||
function TextColorTable({ values }: { values: Record<string, string> }) {
|
// Same chip markup and classes as Foundation/한맥 컬러 and 가족사 컬러 (foundation.css): swatch, name, HEX.
|
||||||
return <table className="hm-typo-table hm-text-color-table">
|
function TextColorChips({ values }: { values: Record<string, string> }) {
|
||||||
<thead><tr><th scope="col">Role</th><th scope="col">Sample</th><th scope="col">HEX · Contrast</th><th scope="col">Token</th><th scope="col">Usage</th></tr></thead>
|
return <div className="hm-brand-color-row hm-text-color-row">{textColorRoles.map(item => <article className="hm-brand-color-chip" key={item.label} data-text-color={item.label} data-color-token={item.token}>
|
||||||
<tbody>{textColorRoles.map(item => {
|
<div className="hm-foundation-swatch" style={{ background: `var(${item.token})` }} />
|
||||||
const hex = toHex(values[item.token])
|
<h3>{item.label} · {item.name}</h3><p data-color-value>{toHex(values[item.token])}</p>
|
||||||
return <tr key={item.role} data-text-color-role={item.role}>
|
</article>)}</div>
|
||||||
<th scope="row"><span className="hm-text-color-role"><span className="hm-text-color-chip" style={{ background: `var(${item.token})` }} aria-hidden="true" /><span><span>{item.role}</span><small className="hm-foundation-note">{item.name}</small></span></span></th>
|
|
||||||
<td><span className="hm-text-color-sample" lang="ko" style={{ color: `var(${item.token})`, background: `var(${item.surface})` }}>{item.sample}</span></td>
|
|
||||||
<td><span data-text-color-hex>{hex}</span><small className="hm-foundation-note">{item.surfaceLabel} {contrast(hex, toHex(values[item.surface]))}</small></td>
|
|
||||||
<td><code>{item.token}</code>{"alias" in item ? <small className="hm-foundation-note"><code>{item.alias}</code></small> : null}</td>
|
|
||||||
<td className="hm-foundation-note">{item.usage}</td>
|
|
||||||
</tr>
|
|
||||||
})}</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
function GrayPaletteTable({ values }: { values: Record<string, string> }) {
|
|
||||||
return <table className="hm-typo-table hm-text-color-table">
|
|
||||||
<thead><tr><th scope="col">Step</th><th scope="col">HEX</th><th scope="col">Token</th><th scope="col">Text Role</th></tr></thead>
|
|
||||||
<tbody>{grayPalette.map(item => <tr key={item.step} data-gray-step={item.step} data-linked={item.linked || undefined}>
|
|
||||||
<th scope="row"><span className="hm-text-color-role"><span className="hm-text-color-chip" style={{ background: `var(${grayToken(item.step)})` }} aria-hidden="true" /><span>Gray {item.step}</span></span></th>
|
|
||||||
<td><span data-text-color-hex>{toHex(values[grayToken(item.step)])}</span></td>
|
|
||||||
<td><code>{grayToken(item.step)}</code></td>
|
|
||||||
<td className={item.linked ? undefined : "hm-foundation-note"}>{item.link}</td>
|
|
||||||
</tr>)}</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SpecimenRow({ label, size, line, weight }: TypographyReference & { weight?: string }) {
|
function SpecimenRow({ label, size, line, weight }: TypographyReference & { weight?: string }) {
|
||||||
@@ -105,6 +70,9 @@ function TypographyReferencePage() {
|
|||||||
const values = useResolvedProperties(typographyProperties)
|
const values = useResolvedProperties(typographyProperties)
|
||||||
const colorValues = useResolvedProperties(textColorProperties)
|
const colorValues = useResolvedProperties(textColorProperties)
|
||||||
return <ReferencePage title="폰트">
|
return <ReferencePage title="폰트">
|
||||||
|
<ReferenceSection title="Text Colors" note="">
|
||||||
|
<TextColorChips values={colorValues} />
|
||||||
|
</ReferenceSection>
|
||||||
<ReferenceSection title="Font Family">
|
<ReferenceSection title="Font Family">
|
||||||
<p className="hm-typo-family"><span>Noto Sans KR → 맑은 고딕 → 시스템 sans-serif</span><code className="hm-foundation-note">--hm-font-family-base: {values["--hm-font-family-base"]}</code></p>
|
<p className="hm-typo-family"><span>Noto Sans KR → 맑은 고딕 → 시스템 sans-serif</span><code className="hm-foundation-note">--hm-font-family-base: {values["--hm-font-family-base"]}</code></p>
|
||||||
</ReferenceSection>
|
</ReferenceSection>
|
||||||
@@ -118,12 +86,6 @@ function TypographyReferencePage() {
|
|||||||
<ReferenceSection title="Typography Specimens">
|
<ReferenceSection title="Typography Specimens">
|
||||||
<div className="hm-typo-tables"><SpecimenTable rows={textRows} /><SpecimenTable rows={controlRows} /></div>
|
<div className="hm-typo-tables"><SpecimenTable rows={textRows} /><SpecimenTable rows={controlRows} /></div>
|
||||||
</ReferenceSection>
|
</ReferenceSection>
|
||||||
<ReferenceSection title="Text Colors" note="Web · Chart · OrgChart 공통 텍스트 색입니다(SW 제외). 칩·샘플·HEX는 현재 토큰 값을 읽습니다.">
|
|
||||||
<TextColorTable values={colorValues} />
|
|
||||||
</ReferenceSection>
|
|
||||||
<ReferenceSection title="Foundation Gray" note="텍스트 역할에 연결된 단계와 팔레트에만 정의된 단계를 구분합니다.">
|
|
||||||
<GrayPaletteTable values={colorValues} />
|
|
||||||
</ReferenceSection>
|
|
||||||
</ReferencePage>
|
</ReferencePage>
|
||||||
}
|
}
|
||||||
// The sidebar name is "폰트"; `id` keeps the existing Story ID (foundation-typography--all) stable.
|
// The sidebar name is "폰트"; `id` keeps the existing Story ID (foundation-typography--all) stable.
|
||||||
|
|||||||
Reference in new issue
Block a user