docs(storybook): add admin text color swatches to typography
This commit is contained in:
1 parent
0486cecd5c
commit
a71ecbe0b2
2 files changed
+75
No files matched your search
@@ -15,6 +15,66 @@ const textLabels = ["Page Header · title", "Card · title", "Page Header · des
|
||||
const textRows = 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.
|
||||
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 기본값)" },
|
||||
{ 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 기본값)" },
|
||||
{ role: "Placeholder", name: "입력 안내", token: "--hm-sem-fg-placeholder", surface: "--hm-sem-bg-surface", surfaceLabel: "흰 면", sample: "검색어를 입력하세요", usage: "Input·Textarea·Select·Command·Combobox·Date Picker의 흰 입력 면 전용" },
|
||||
{ role: "대비 예외", name: "베이지 면 Placeholder", token: "--hm-sem-fg-secondary", surface: "--hm-sem-bg-content", surfaceLabel: "베이지 면", sample: "읽기 전용 · 값 없음", usage: "투명 면 readonly Input의 Placeholder. 베이지 면에서는 Placeholder 색이 4.5:1 미만이라 Secondary를 사용" },
|
||||
] as const
|
||||
// Palette steps: only some are wired to a text role. Strong Secondary / Tertiary Semantic tokens do not exist.
|
||||
const grayPalette = [
|
||||
{ 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) {
|
||||
if (!value) return ""
|
||||
if (/^#[\da-f]{6}$/i.test(value)) return value.toUpperCase()
|
||||
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
|
||||
}
|
||||
// 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> }) {
|
||||
return <table className="hm-typo-table hm-text-color-table">
|
||||
<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>
|
||||
<tbody>{textColorRoles.map(item => {
|
||||
const hex = toHex(values[item.token])
|
||||
return <tr key={item.role} data-text-color-role={item.role}>
|
||||
<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 }) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [metrics, setMetrics] = useState({ main: "", spacing: "" })
|
||||
@@ -43,6 +103,7 @@ function SpecimenTable({ rows }: { rows: readonly TypographyReference[] }) {
|
||||
|
||||
function TypographyReferencePage() {
|
||||
const values = useResolvedProperties(typographyProperties)
|
||||
const colorValues = useResolvedProperties(textColorProperties)
|
||||
return <ReferencePage title="폰트">
|
||||
<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>
|
||||
@@ -57,6 +118,12 @@ function TypographyReferencePage() {
|
||||
<ReferenceSection title="Typography Specimens">
|
||||
<div className="hm-typo-tables"><SpecimenTable rows={textRows} /><SpecimenTable rows={controlRows} /></div>
|
||||
</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>
|
||||
}
|
||||
// The sidebar name is "폰트"; `id` keeps the existing Story ID (foundation-typography--all) stable.
|
||||
|
||||
Reference in new issue
Block a user