Merge pull request 'style(storybook): refine foundation color chips and typography layout' (#44) from style/storybook-ui-refinements into main
Deploy Storybook / deploy (push) Successful in 1m25s
Deploy Storybook / deploy (push) Successful in 1m25s
Reviewed-on: #44
This commit was merged in pull request #44.
This commit is contained in:
commit
3eedd898df
7 files changed
+223
-111
No files matched your search
@@ -1,5 +1,17 @@
|
||||
# Working Context
|
||||
|
||||
## 2026-11-12 — Foundation 한맥·가족사 컬러칩, 폰트 미니멀 타이포그래피 레이아웃 (브랜치 `style/storybook-ui-refinements`)
|
||||
|
||||
- 범위: `src/foundation/`(`color.stories.tsx`·`company-colors.stories.tsx`·`typography.stories.tsx`·`reference.tsx`·`foundation.css`)과 `docs/guidelines/brand-color-steps.md`. 토큰·HEX·`themes/*.json`·컴포넌트·패키지 공개 API·`reference-data.ts`·Story ID(`foundation-color--all`·`foundation-company-colors--all`·`foundation-typography--all`) 불변. 폰트 섹션 순서(Text Colors → Font Family → Font Weight → Typography Specimens) 불변.
|
||||
- 공통 로직(`reference.tsx`): `colorHex()`(CSS 값 → HEX)와 `textTone()`(견본 글자색: `--hm-sem-fg-primary` → 흰색 `--hm-color-text-inverse` → 검정 `--hm-sem-palette-black` 중 배경 대비 4.5:1을 처음 충족하는 토큰, `data-text-tone`)을 한맥·가족사·Text Colors가 공유.
|
||||
- 한맥 컬러: 3그룹 24칩을 연속형 컬러 띠로 표시(팔레트 섹션 폭 기준 container query 1040px 이상 8열, 520px 이상 4열, 미만 2열, 칩 간격 0, 줄 양 끝만 `--hm-radius-control-md`). 견본 120px·Padding 12 안에 단계명(14/700)·용도(Default `기본색` 12/500, Light 4 `보더 하이라이트용` 12/400) 왼쪽 위, HEX 12 오른쪽 아래. 어두운 글자 칩은 용도·HEX 500. 이전 Default 강조 테두리(`data-base`)·칩 외곽 박스·Light 4 구분선 제거. 24색 모두 4.5:1 이상(최저 브라운 Light 2 4.52), 검정 글자는 그린 Light 3·오렌지 Default만.
|
||||
- 가족사 컬러: 회사 배치(3 → 2 → 1열, 2/2/3 줄)·`companyColorPresets` 데이터 유지. 회사마다 3칸 Grid(간격 12px)에 독립 칩(120px·Padding 12·Radius 4), 이름(메인·서브 N) 왼쪽 위·HEX 오른쪽 아래, 글자색은 `textTone()`. 메인 강조 테두리 없음(이름으로만 구분). 최저 대비 4.89.
|
||||
- Text Colors: 가족사 컬러와 같은 개별 칩으로 변경. 행에 `data-text-colors`를 붙여 한맥·가족사 공통 견본 규칙(`:is([data-palette], [data-company-id], [data-text-colors])`: 120px·Padding 12·Radius 4, 이름 + 용도 12/400 왼쪽 위, HEX 12 오른쪽 아래)을 재사용하고(이름은 Text Colors만 14/500: 어두운 회색 위 흰 Bold가 퍼져 보여 사용자 결정, 한맥·가족사는 700 유지), 글자색은 공통 `textTone()`(fg-primary → 흰색 → 검정 중 4.5:1 첫 번째). 4개 칩 모두 흰색(대비 16.1 / 6.19 / 4.61 / 6.19). HEX는 공통 `colorHex()`로 통일(로컬 `toHex` 삭제). 그리드는 기존 4열·최소 150px 유지, 간격 12px.
|
||||
- 공통 행 `.hm-typo-row`(Font Family 1행, Font Weight 3행, Specimens 10행): 정보(왼쪽) | 미리보기(오른쪽), 세로 중앙 정렬. 행 전환은 viewport가 아니라 자기 열 너비 기준(container query, `.hm-typo-rows`·`.hm-typo-group`): 440px 이상 정보 열 `min(240px, 40%)` + 24px, 미만 1열·미리보기 먼저(`order`). 1440px에서 Family·Specimens 왼쪽 열 미리보기 x=340, Weight·Specimens 오른쪽 열 x=1012로 일치(Specimens 열 간격 40px = 섹션 간격 24 + 들여쓰기 16).
|
||||
- Font Family·Font Weight는 공통 `StorySectionGrid`(`.hm-typo-type-grid`)로 720px 이상 2열, 미만 1열(800px에서는 각 열 304px라 내부 1열). Font Family: Primary Typeface(12 보조)·Noto Sans KR(700) | `--hm-font-family-base` 미리보기(28/36) → CSS 변수(보조, 값에 대체 폰트 순서 포함; 별도 Fallback 문구는 사용자 요청으로 삭제). Font Weight: 이름+값·토큰 | 20/28 미리보기, 행 위·아래 12px, 행 사이에만 얇은 구분선.
|
||||
- Typography Specimens: 표 2개(`.hm-typo-table`)를 컴포넌트 그룹(Page Header·Card·Label / Input·Button Standard) 목록으로 대체. 정보는 항목명(12/500)·계산값 `Size / Line / Weight · letter-spacing` 한 줄(모두 보조색), 샘플은 실제 크기(Primary). 구분선은 행마다 윗선 1줄(`--hm-sem-stroke-input-default`, 첫 줄이 그룹 제목 밑줄, 마지막 행 아래 없음), 그룹 제목 → 8px, 그룹 간 24px. 1100px 이상 텍스트/입력 2열. `data-typography-sample`(전체 라벨)·`data-typography-metrics` 유지. 그룹화로 표시 순서만 Page Header description이 Card title 앞으로 이동.
|
||||
- 검증 메모: 값 표시(`useResolvedProperties`)는 `requestAnimationFrame`을 쓰므로 Storybook 미리보기 패널이 숨겨진 상태에서는 HEX·굵기 값·토큰 값이 비어 보인다(코드 회귀 아님).
|
||||
|
||||
## 2026-11-11 — Storybook 섹션 간격·정보 영역 들여쓰기 공통화, Foundation 폰트 위계 (main 기준, 미커밋)
|
||||
|
||||
- 공통 프레임 `src/components/story-page.css`: H2 16/24/700 → 12px → 구분선 → 16px → 콘텐츠(섹션 헤더 padding-bottom 8→12, margin-bottom 12→16), 섹션 사이 `--sb-page-section-gap` 24→28px. `StorySectionGrid`는 row gap만 이 변수를 따르고 column gap 24px은 유지(Chart 2열 미리보기 폭 불변). SW `sw-story-stack`·Footer 너비 행, Foundation Layout도 변수로 28px.
|
||||
|
||||
@@ -35,6 +35,6 @@ Orange Dark 1·2의 원본 Needs Review 상태와 [이전 검수 보고서](../r
|
||||
|
||||
페이지 제목은 `Hanmac Color`(메뉴 `Foundation > Hanmac Color`)이며 별도 소개 문구는 표시하지 않는다. 기존 Story ID `foundation-color--all`은 `meta.id`로 유지한다. 과거 `한맥 브랜드 컬러` 소개 문구는 당시 이력이다. 그룹 설명의 단계 순서 나열 문구는 제공하지 않으며, 개별 컬러칩의 단계명과 HEX는 유지한다.
|
||||
|
||||
[Hanmac Color 대표 Story](../../src/foundation/color.stories.tsx)는 한맥그린·한맥브라운·한맥오렌지 3개 그룹에 각 8개, 총 24개 컬러칩만 표시한다. Green 8/8·Brown 8/8·Orange 8/8 확정이며 단계명·현재 HEX와 Default 기본색 강조를 유지한다. 2026-10-11 최종 표시 요청에 따라 Brown Deep, Orange Light·Mid·Dark·Deep의 별도 표시, Accent·Semantic·Gradients 섹션과 개발용 Token·CSS Variable·Runtime Key 설명은 제거한다. Legacy Button 색상과 Accent·Semantic·Gradient의 실제 Key·값·소비처는 보존한다. 기존 Component mapping을 새 값에 연결하지 않으며 public Key·Property·type·Default·API·schemaVersion 1과 서비스 partial JSON 호환성을 유지한다. [Runtime Theme](runtime-theme.md)의 geometry/상태 권한과 Designer의 기존 17개 상위 역할 입력 UI는 확대하지 않는다. 한맥오렌지의 미확정 브랜드 단계는 없다. [표시 정리 검증 기록](../reports/foundation-color-display-cleanup-2026-10-11.md).
|
||||
[Hanmac Color 대표 Story](../../src/foundation/color.stories.tsx)는 한맥그린·한맥브라운·한맥오렌지 3개 그룹에 각 8개, 총 24개 컬러칩만 표시한다. Green 8/8·Brown 8/8·Orange 8/8 확정이며 단계명·현재 HEX와 Default 기본색 표시를 유지한다. 2026-11-12 표시 정리로 세 그룹은 같은 Grid(팔레트 섹션 폭 1040px 이상 8열, 520px 이상 4열×2줄, 그 미만 2열×4줄, 콘텐츠 영역 전체 너비를 열 수만큼 균등 분할)를 쓰며 견본 사이 간격 없이 이어진 연속형 컬러 띠로 표시한다. 줄마다 첫 견본 왼쪽과 마지막 견본 오른쪽에만 `--hm-radius-control-md`를 적용하고 내부 경계는 직각이며 칩 외곽 박스·Default 테두리·구분선·그림자는 없다. 견본 높이는 120px이며 단계명(14px Bold)·용도 설명·HEX(12px)를 견본 내부(Padding 12px)에 표시한다. 단계명과 용도 설명은 왼쪽 상단, HEX는 오른쪽 하단이다. Default는 단계명 아래 `기본색`(12px Medium), Light 4는 `보더 하이라이트용`(12px Regular)을 표시하며 Badge·배경·테두리는 없다. 어두운 글자(`--hm-sem-fg-primary`·검정)를 쓰는 칩은 용도 설명과 HEX를 Medium(500)으로 표시해 채도 높은 중간 명도 배경에서 획이 흐려 보이지 않게 한다. 글자색은 `--hm-sem-fg-primary` → 흰색(`--hm-color-text-inverse`) → 검정(`--hm-sem-palette-black`) 순으로 견본 배경 대비 4.5:1을 처음 충족하는 토큰을 쓰며(검정은 한맥그린 Light 3·한맥오렌지 Default만 해당) 24색 모두 WCAG AA 4.5:1 이상이다(최저 브라운 Light 2 흰색 4.52:1). 2026-10-11 최종 표시 요청에 따라 Brown Deep, Orange Light·Mid·Dark·Deep의 별도 표시, Accent·Semantic·Gradients 섹션과 개발용 Token·CSS Variable·Runtime Key 설명은 제거한다. Legacy Button 색상과 Accent·Semantic·Gradient의 실제 Key·값·소비처는 보존한다. 기존 Component mapping을 새 값에 연결하지 않으며 public Key·Property·type·Default·API·schemaVersion 1과 서비스 partial JSON 호환성을 유지한다. [Runtime Theme](runtime-theme.md)의 geometry/상태 권한과 Designer의 기존 17개 상위 역할 입력 UI는 확대하지 않는다. 한맥오렌지의 미확정 브랜드 단계는 없다. [표시 정리 검증 기록](../reports/foundation-color-display-cleanup-2026-10-11.md).
|
||||
|
||||
[Company Colors Story](../../src/foundation/company-colors.stories.tsx)는 Foundation > Hanmac Color와 별도 메뉴(Hanmac Color 바로 아래)이며 7개 계열사·14개 컬러를 회사별로 표시한다(별도 소개 문구 없음). 데이터는 OrgChart의 기존 `companyColorPresets`를 그대로 읽고(메인 → 서브 순서, 메인은 Default 강조 재사용) HEX를 중복 정의하지 않는다. Theme 토큰·`themes/*.json`·공개 계약은 변경하지 않으며 Foundation > Hanmac Color의 3그룹 24칩은 그대로다.
|
||||
[Company Colors Story](../../src/foundation/company-colors.stories.tsx)는 Foundation > Hanmac Color와 별도 메뉴(Hanmac Color 바로 아래)이며 7개 계열사·14개 컬러를 회사별로 표시한다(별도 소개 문구 없음). 데이터는 OrgChart의 기존 `companyColorPresets`를 그대로 읽고(메인 → 서브 순서) HEX를 중복 정의하지 않는다. 회사 그룹은 콘텐츠 폭에 따라 3열(2/2/3 줄 배치, 장헌·장헌산업은 1열 시작) → 2열 → 1열이며, 컬러칩은 회사마다 3칸 Grid(간격 12px)에 독립된 직사각형(`--hm-radius-control-md`, 높이 120px, Padding 12px)으로 놓여 컬러가 적은 회사도 칩 폭은 1/3이다. 칩 안에는 Hanmac Color와 같은 방식으로 이름(`메인`·`서브 1`·`서브 2`, 14px Bold)을 왼쪽 상단, HEX(12px)를 오른쪽 하단에 두고, 글자색도 같은 대비 계산(`--hm-sem-fg-primary` → 흰색 → 검정, 4.5:1 첫 충족)과 어두운 글자일 때 HEX Medium을 쓴다. 메인 외곽 테두리·Badge·배경 강조는 없으며 메인·서브는 이름으로만 구분한다. Theme 토큰·`themes/*.json`·공개 계약은 변경하지 않으며 Foundation > Hanmac Color의 3그룹 24칩은 그대로다.
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { runtimeThemeContract, type RuntimeThemeToken } from "@hm-design/ui/theme"
|
||||
import { ReferencePage, ReferenceSection, useResolvedProperties } from "./reference"
|
||||
import { colorHex, ReferencePage, ReferenceSection, textTone, textToneProperties, useResolvedProperties } from "./reference"
|
||||
|
||||
// Keep the confirmed eight stages; presentation changes never alter Theme values.
|
||||
const stages = ["light-4", "light-3", "light-2", "light-1", "default", "dark-1", "dark-2", "dark-3"] as const
|
||||
@@ -11,25 +11,25 @@ const palettes = [
|
||||
] as const
|
||||
type ColorStage = typeof stages[number]
|
||||
type PalettePrefix = typeof palettes[number]["prefix"]
|
||||
const stageLabel = (stage: ColorStage) => stage === "default" ? "Default · 기본색" : stage === "light-4" ? "Light 4 · 보더 하이라이트용" : stage.replaceAll("-", " ").replace(/^./, letter => letter.toUpperCase())
|
||||
// Stage names stay plain; Default and Light 4 add a role line under the name (ColorChip).
|
||||
const stageLabel = (stage: ColorStage) => stage.replaceAll("-", " ").replace(/^./, letter => letter.toUpperCase())
|
||||
const stageNote: Partial<Record<ColorStage, string>> = { "light-4": "보더 하이라이트용", default: "기본색" }
|
||||
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))
|
||||
|
||||
// Read the current CSS value rather than duplicating Theme HEX values.
|
||||
function colorHex(value: string | undefined) {
|
||||
if (value === undefined) return "확인 중"
|
||||
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
|
||||
}
|
||||
// Palette values plus the swatch text candidates (textTone, reference.tsx).
|
||||
const colorProperties = [...palettes.flatMap(palette => stages.map(stage => runtimeThemeContract[paletteToken(palette.prefix, stage)].property)), ...textToneProperties]
|
||||
|
||||
function ColorChip({ prefix, stage, values }: { prefix: PalettePrefix; stage: ColorStage; values: Record<string, string> }) {
|
||||
const token = paletteToken(prefix, stage)
|
||||
const definition = runtimeThemeContract[token]
|
||||
return <article className="hm-brand-color-chip" data-color-stage={stage} data-color-token={token} data-base={stage === "default" ? "" : undefined}>
|
||||
<div className="hm-foundation-swatch" style={{ background: `var(${definition.property})` }} />
|
||||
<h3>{stageLabel(stage)}</h3><p data-color-value>{colorHex(values[definition.property])}</p>
|
||||
const hex = colorHex(values[definition.property])
|
||||
return <article className="hm-brand-color-chip" data-color-stage={stage} data-color-token={token} data-text-tone={textTone(hex, values)}>
|
||||
<div className="hm-foundation-swatch" style={{ background: `var(${definition.property})` }}>
|
||||
<div className="hm-color-name">
|
||||
<h3>{stageLabel(stage)}</h3>
|
||||
{stageNote[stage] && <p className="hm-color-note">{stageNote[stage]}</p>}
|
||||
</div>
|
||||
<p data-color-value>{hex}</p>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ function ColorReferencePage() {
|
||||
const values = useResolvedProperties(colorProperties)
|
||||
return <ReferencePage title="한맥 컬러">
|
||||
{palettes.map(palette => <ReferenceSection key={palette.name} title={palette.name} note="">
|
||||
<div className="hm-brand-color-row">{stages.map(stage => <ColorChip key={stage} prefix={palette.prefix} stage={stage} values={values} />)}</div>
|
||||
<div className="hm-brand-color-row" data-palette={palette.prefix}>{stages.map(stage => <ColorChip key={stage} prefix={palette.prefix} stage={stage} values={values} />)}</div>
|
||||
</ReferenceSection>)}
|
||||
</ReferencePage>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { companyColorPresets } from "@hm-design/org-chart"
|
||||
import { ReferencePage, ReferenceSection } from "./reference"
|
||||
import { ReferencePage, ReferenceSection, textTone, textToneProperties, useResolvedProperties } from "./reference"
|
||||
|
||||
// Company colors come from the existing OrgChart presets; HEX values are never duplicated here.
|
||||
// The chip markup and classes are shared with Foundation/Color (foundation.css).
|
||||
@@ -14,20 +14,23 @@ const companies = [
|
||||
...presetCompanies.filter(company => !listedIds.includes(company.companyId)),
|
||||
]
|
||||
|
||||
function CompanyColorChip({ label, hex, main }: { label: string; hex: string; main: boolean }) {
|
||||
return <article className="hm-brand-color-chip" data-company-color={hex} data-base={main ? "" : undefined}>
|
||||
<div className="hm-foundation-swatch" style={{ background: hex }} />
|
||||
<h3>{label}</h3><p data-color-value>{hex.toUpperCase()}</p>
|
||||
// Same swatch content as Hanmac Color (name top left, HEX bottom right, text tone by contrast); main and sub differ only by the name.
|
||||
function CompanyColorChip({ label, hex, values }: { label: string; hex: string; values: Record<string, string> }) {
|
||||
return <article className="hm-brand-color-chip" data-company-color={hex} data-text-tone={textTone(hex.toUpperCase(), values)}>
|
||||
<div className="hm-foundation-swatch" style={{ background: hex }}>
|
||||
<h3>{label}</h3><p data-color-value>{hex.toUpperCase()}</p>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
|
||||
function CompanyColorsPage() {
|
||||
const values = useResolvedProperties(textToneProperties)
|
||||
return <ReferencePage title="가족사 컬러">
|
||||
<div className="hm-company-grid">
|
||||
{companies.map(company => <ReferenceSection key={company.companyId} title={company.companyName} note="" id={`company-${company.companyId}`}>
|
||||
<div className="hm-brand-color-row" data-company-id={company.companyId}>
|
||||
<CompanyColorChip label="메인" hex={company.primary} main />
|
||||
{company.secondary.map((hex, index) => <CompanyColorChip key={hex} label={`서브 ${index + 1}`} hex={hex} main={false} />)}
|
||||
<CompanyColorChip label="메인" hex={company.primary} values={values} />
|
||||
{company.secondary.map((hex, index) => <CompanyColorChip key={hex} label={`서브 ${index + 1}`} hex={hex} values={values} />)}
|
||||
</div>
|
||||
</ReferenceSection>)}
|
||||
</div>
|
||||
|
||||
Vendored
+95
-46
@@ -26,13 +26,45 @@
|
||||
.hm-foundation-radius { width: 100%; height: 64px; margin-bottom: 16px; border: 1px solid var(--hm-sem-stroke-default); background: var(--hm-sem-bg-content); }
|
||||
.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. */
|
||||
/* Hanmac Color (rows with data-palette). The swatch content rules (chip reset, swatch text, text tone) are shared with Company Colors
|
||||
(data-company-id) and Typography Text Colors (data-text-colors).
|
||||
Continuous spectrum: the swatches touch (column gap 0) across the full section width and only each line's first/last swatch
|
||||
gets the outer radius. Columns follow the section width (container query): 8 from 1040px, 4 x 2 rows from 520px, otherwise
|
||||
2 x 4 rows, so a chip stays at least ~130px wide and every text line fits inside the 120px swatch. Each line is its own band. */
|
||||
.hm-foundation .sb-page-section-body:has(> .hm-brand-color-row[data-palette]) { container-type: inline-size; }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 0; overflow: visible; padding-bottom: 0; }
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) .hm-brand-color-chip { min-width: 0; padding: 0; border: 0; border-radius: 0; font-weight: inherit; word-break: keep-all; }
|
||||
/* Name and role line at the top left, HEX at the bottom right, all inside the swatch. Text tone comes from the Story
|
||||
(data-text-tone: fg-primary first, then white, then black, whichever first reaches 4.5:1); no shadows or backing plates. */
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) .hm-foundation-swatch { display: flex; flex-direction: column; justify-content: space-between; height: 120px; margin: 0; padding: 12px; border: 0; border-radius: 0; color: var(--hm-color-text-inverse); }
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) .hm-brand-color-chip[data-text-tone="primary"] .hm-foundation-swatch { color: var(--hm-sem-fg-primary); }
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) .hm-brand-color-chip[data-text-tone="black"] .hm-foundation-swatch { color: var(--hm-sem-palette-black); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(2n+1) .hm-foundation-swatch { border-radius: var(--hm-radius-control-md) 0 0 var(--hm-radius-control-md); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(2n) .hm-foundation-swatch { border-radius: 0 var(--hm-radius-control-md) var(--hm-radius-control-md) 0; }
|
||||
@container (min-width: 520px) {
|
||||
.hm-foundation .hm-brand-color-row[data-palette] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(n) .hm-foundation-swatch { border-radius: 0; }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(4n+1) .hm-foundation-swatch { border-radius: var(--hm-radius-control-md) 0 0 var(--hm-radius-control-md); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(4n) .hm-foundation-swatch { border-radius: 0 var(--hm-radius-control-md) var(--hm-radius-control-md) 0; }
|
||||
}
|
||||
@container (min-width: 1040px) {
|
||||
.hm-foundation .hm-brand-color-row[data-palette] { grid-template-columns: repeat(8, minmax(0, 1fr)); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(4n+1) .hm-foundation-swatch,
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:nth-child(4n) .hm-foundation-swatch { border-radius: 0; }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:first-child .hm-foundation-swatch { border-radius: var(--hm-radius-control-md) 0 0 var(--hm-radius-control-md); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:last-child .hm-foundation-swatch { border-radius: 0 var(--hm-radius-control-md) var(--hm-radius-control-md) 0; }
|
||||
}
|
||||
/* Name 14 Bold; role line 12 Regular (Default "기본색" 12 Medium); HEX 12. Dark text on a swatch reads thinner than white,
|
||||
so its role line and HEX use Medium. */
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) h3 { margin: 0; font-size: var(--hm-label-font-size); line-height: var(--hm-label-line-height); font-weight: var(--hm-font-weight-bold); color: inherit; }
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-text-colors]) .hm-color-note { margin: 0; font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); font-weight: var(--hm-input-font-weight); color: inherit; }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip[data-color-stage="default"] .hm-color-note,
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) .hm-brand-color-chip:is([data-text-tone="primary"], [data-text-tone="black"]) :is(.hm-color-note, p[data-color-value]) { font-weight: var(--hm-font-weight-medium); }
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) p[data-color-value] { align-self: flex-end; margin: 0; font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); color: inherit; font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* Color reference additions only; preserve the existing palette row. */
|
||||
.hm-foundation .hm-color-gradient-preview { height: 112px; }
|
||||
.hm-foundation .hm-color-gradient-stops { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
||||
.hm-foundation .hm-color-gradient-stops .hm-brand-color-chip { flex: 1 1 110px; }
|
||||
@@ -41,54 +73,71 @@
|
||||
.hm-foundation .hm-color-foreground-sample { padding: 16px; background: var(--hm-sem-bg-surface); }
|
||||
|
||||
/* Company Colors: two-level grid. Columns follow the Story content width (not the viewport): companies 3 -> 2 -> 1 (min 316px each),
|
||||
chips up to 3 per row (min 100px each, 3 -> 2 -> 1). Chip size stays at one third even when a company has fewer chips. */
|
||||
chips always in three equal tracks 12px apart, so a chip stays one third wide even when a company has fewer chips (min ~97px).
|
||||
Swatch content comes from the shared Hanmac rules; each chip is a separate rectangle with the outer radius and no border. */
|
||||
.hm-foundation .hm-company-grid { display: grid; gap: 48px 32px; grid-template-columns: repeat(auto-fill, minmax(max(316px, (100% - 64px) / 3), 1fr)); }
|
||||
.hm-foundation .hm-company-grid > .sb-page-section { min-width: 0; margin-top: 0; }
|
||||
/* Row starts for the 2 / 2 / 3 company layout (한맥·삼안 / 장헌·피티씨 / 장헌산업·한라산업개발·바론컨설턴트). */
|
||||
.hm-foundation .hm-company-grid > #company-jangheon,
|
||||
.hm-foundation .hm-company-grid > #company-jangheon-industry { grid-column-start: 1; }
|
||||
.hm-foundation .hm-brand-color-row[data-company-id] { display: grid; gap: 8px; overflow: visible; padding-bottom: 0; grid-template-columns: repeat(auto-fill, minmax(max(100px, (100% - 16px) / 3), 1fr)); }
|
||||
.hm-foundation .hm-brand-color-row[data-company-id] { display: grid; gap: 12px; overflow: visible; padding-bottom: 0; grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.hm-foundation .hm-brand-color-row:is([data-company-id], [data-text-colors]) .hm-foundation-swatch { border-radius: var(--hm-radius-control-md); }
|
||||
|
||||
/* Typography: compact specification page. Font Family as family + fallback over the CSS variable, Font Weight as three plain columns (no card box;
|
||||
the section divider already frames them), Specimens as comparison tables (two columns on wide screens, stacked rows on narrow screens).
|
||||
Document text levels (presentation only, existing tokens): item name 14/700 primary, description 14/400 secondary,
|
||||
token / HEX / extra info 12/18/400 secondary (.hm-typo-token). Section titles stay on the common Story page H2 (16/24/700). */
|
||||
/* Section rhythm (title → 12px → divider → 16px → content, sections 28px apart) and the content indent (.sb-page-indent on Font Family,
|
||||
Font Weight and Specimens; Text Colors chips stay on the edge) come from the common Story page (story-page.css). */
|
||||
.hm-foundation .hm-typo-family { display: grid; gap: 4px; margin: 0; }
|
||||
.hm-foundation .hm-typo-family p { margin: 0; }
|
||||
.hm-foundation .hm-typo-family strong { font-weight: var(--hm-font-weight-bold); }
|
||||
.hm-foundation .hm-typo-token { font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
||||
.hm-foundation .hm-typo-weights { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px 24px; }
|
||||
.hm-foundation .hm-typo-weight { min-width: 0; }
|
||||
.hm-foundation .hm-typo-weight p { margin: 0; }
|
||||
.hm-foundation .hm-typo-weight-preview { margin: 6px 0 4px !important; font-size: var(--hm-card-title-font-size); line-height: var(--hm-card-title-line-height); }
|
||||
.hm-foundation .hm-typo-tables { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px 32px; align-items: start; }
|
||||
.hm-foundation .hm-typo-table { width: 100%; border-collapse: collapse; }
|
||||
.hm-foundation .hm-typo-table th, .hm-foundation .hm-typo-table td { padding: 12px 16px 12px 0; border-bottom: 1px solid var(--hm-sem-stroke-input-default); text-align: left; vertical-align: baseline; font-weight: var(--hm-input-font-weight); }
|
||||
.hm-foundation .hm-typo-table thead th { color: var(--hm-sem-fg-secondary); font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
||||
.hm-foundation .hm-typo-table tbody th { width: 27%; font-weight: var(--hm-font-weight-bold); }
|
||||
.hm-foundation .hm-typo-table td:last-child { width: 26%; padding-right: 0; }
|
||||
.hm-foundation .hm-typo-metrics span, .hm-foundation .hm-typo-metrics small { display: block; font-variant-numeric: tabular-nums; }
|
||||
.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)); } }
|
||||
/* Typography Text Colors reuse the Hanmac color chips (.hm-brand-color-chip). Four equal tracks on wide screens;
|
||||
narrow screens fall back to 150px tracks (2 columns at 390px) and wrap the text inside the chip instead of scrolling. */
|
||||
.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; }
|
||||
/* Text Colors chip content only (the shared 13px chip text of the color pages is untouched): role name → usage → HEX as three levels.
|
||||
Grid rows stretch, so the four chips keep one height; keep-all wraps the long Readonly usage between words. */
|
||||
.hm-foundation .hm-text-color-row { grid-auto-rows: 1fr; }
|
||||
.hm-foundation .hm-text-color-row .hm-brand-color-chip { padding: 12px; word-break: keep-all; }
|
||||
.hm-foundation .hm-text-color-row .hm-foundation-swatch { margin-bottom: 12px; }
|
||||
.hm-foundation .hm-text-color-row h3 { margin: 0; font-size: var(--hm-label-font-size); line-height: var(--hm-label-line-height); font-weight: var(--hm-font-weight-bold); color: var(--hm-sem-fg-primary); }
|
||||
.hm-foundation .hm-text-color-row p { margin: 4px 0 0; font-size: var(--hm-label-font-size); line-height: var(--hm-label-line-height); color: var(--hm-sem-fg-secondary); }
|
||||
.hm-foundation .hm-text-color-row p[data-color-value] { margin-top: 4px; font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); font-variant-numeric: tabular-nums; }
|
||||
@media (max-width: 640px) {
|
||||
.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 tr { padding: 12px 0; border-bottom: 1px solid var(--hm-sem-stroke-input-default); }
|
||||
.hm-foundation .hm-typo-table th, .hm-foundation .hm-typo-table td { padding: 2px 0; border-bottom: 0; }
|
||||
/* Typography: minimal type specimen page (presentation only, existing tokens; no card box, outer border or shadow).
|
||||
Section titles, rhythm (title → 12px → divider → 16px → content, sections 28px apart) and the content indent (.sb-page-indent on
|
||||
Font Family, Font Weight and Specimens; Text Colors chips stay on the edge) come from the common Story page (story-page.css).
|
||||
Text levels: label / token 12/18/400 secondary, family and weight names 14/700 primary, specimen name and metrics 12/18 secondary;
|
||||
previews and samples carry the largest type. */
|
||||
/* Font Family and Font Weight side by side (StorySectionGrid) from 720px, stacked below. */
|
||||
.hm-foundation .hm-typo-type-grid { grid-template-columns: minmax(0, 1fr); }
|
||||
@media (min-width: 720px) { .hm-foundation .hm-typo-type-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
/* Shared row (Font Family, each Font Weight item, each Specimen): information on the left, preview on the right, both vertically
|
||||
centered. The row follows the width of its own column (container query on the Family / Weight content and on each Specimen
|
||||
group), not the viewport: from 440px the information column is min(240px, 40%), so it never squeezes the preview; below that
|
||||
the row is one column with the preview first. */
|
||||
.hm-foundation :is(.hm-typo-rows, .hm-typo-group) { container-type: inline-size; }
|
||||
.hm-foundation .hm-typo-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px 24px; align-items: center; }
|
||||
.hm-foundation .hm-typo-row :is(p, code) { margin: 0; }
|
||||
.hm-foundation :is(.hm-typo-row-meta, .hm-typo-row-preview) { display: grid; gap: 2px; min-width: 0; }
|
||||
.hm-foundation .hm-typo-row-preview { order: -1; }
|
||||
.hm-foundation .hm-typo-token, .hm-foundation .hm-typo-kicker { font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
||||
@container (min-width: 440px) {
|
||||
.hm-foundation .hm-typo-row { grid-template-columns: min(240px, 40%) minmax(0, 1fr); row-gap: 4px; }
|
||||
.hm-foundation .hm-typo-row-preview { order: 0; }
|
||||
}
|
||||
/* Font Family: Primary Typeface · family name | preview in --hm-font-family-base → CSS variable (its value lists the fallbacks). */
|
||||
.hm-foundation .hm-typo-kicker { color: var(--hm-sem-fg-secondary); }
|
||||
.hm-foundation .hm-typo-family-name { font-weight: var(--hm-font-weight-bold); }
|
||||
.hm-foundation .hm-typo-row .hm-typo-family-preview { margin-bottom: 4px; font-size: var(--hm-page-header-title-font-size); line-height: var(--hm-page-header-title-line-height); }
|
||||
/* Font Weight: one row per weight (name + value · token | preview), 12px above and below with a thin rule between rows only
|
||||
(the section divider sits above the first row). */
|
||||
.hm-foundation .hm-typo-weight { padding-block: 12px; }
|
||||
.hm-foundation .hm-typo-weight:first-child { padding-top: 0; }
|
||||
.hm-foundation .hm-typo-weight:last-child { padding-bottom: 0; }
|
||||
.hm-foundation .hm-typo-weight + .hm-typo-weight { border-top: 1px solid var(--hm-sem-stroke-input-default); }
|
||||
.hm-foundation .hm-typo-weight-preview { font-size: var(--hm-card-title-font-size); line-height: var(--hm-card-title-line-height); }
|
||||
/* Specimens: text styles and form controls as two columns from 1100px, groups 24px apart. The 40px column gap makes both columns
|
||||
exactly as wide as the Font Family / Font Weight content (section gap 24px + indent 16px), so the previews line up across sections.
|
||||
Each group is its title, then rows that each carry one thin top rule (the first one doubles as the title underline; no rule after
|
||||
the last row). Row: item name / computed Size / Line / Weight · letter-spacing (both secondary) | the sample at its real size. */
|
||||
.hm-foundation .hm-typo-specimens { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px 40px; align-items: start; }
|
||||
@media (min-width: 1100px) { .hm-foundation .hm-typo-specimens { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
.hm-foundation .hm-typo-groups { display: grid; gap: 24px; }
|
||||
.hm-foundation .hm-typo-group h3 { margin: 0 0 8px; }
|
||||
.hm-foundation .hm-typo-group ul { margin: 0; padding: 0; list-style: none; }
|
||||
.hm-foundation .hm-typo-specimen { padding-block: 12px; border-top: 1px solid var(--hm-sem-stroke-input-default); }
|
||||
.hm-foundation .hm-typo-specimen-meta { gap: 0; color: var(--hm-sem-fg-secondary); font-size: var(--hm-button-font-size-sm); line-height: var(--hm-button-line-height-sm); }
|
||||
.hm-foundation .hm-typo-specimen-name { font-weight: var(--hm-font-weight-medium); }
|
||||
.hm-foundation .hm-typo-metrics { display: flex; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
|
||||
.hm-foundation .hm-typo-metrics > span + span::before { content: "\00a0·\00a0"; }
|
||||
.hm-foundation .hm-typo-sample { color: var(--hm-sem-fg-primary); }
|
||||
/* Typography Text Colors (data-text-colors): separate chips like Company Colors (shared swatch rules above: 120px, 12px padding,
|
||||
radius, name and usage top left, HEX bottom right, text tone by contrast). Four equal tracks 12px apart on wide screens;
|
||||
narrow screens fall back to 150px tracks (2 columns at 375px) and wrap the text inside the chip instead of scrolling. */
|
||||
.hm-foundation .hm-text-color-row { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(max(150px, (100% - 36px) / 4), 1fr)); overflow: visible; padding-bottom: 0; }
|
||||
/* The four text colors are dark grays, so every chip uses white text; white Bold reads spread out on them, so the name uses Medium
|
||||
(user decision 2026-11-12; Hanmac and Company names stay Bold). */
|
||||
.hm-foundation .hm-brand-color-row[data-text-colors] h3 { font-weight: var(--hm-font-weight-medium); }
|
||||
|
||||
/* Layout: compact specification page (PC: three bands; narrow screens stack and scroll naturally).
|
||||
Section header spacing and the section gap come from the common Story page; the bands only own their grid. No fixed heights or clipping. */
|
||||
|
||||
@@ -23,6 +23,36 @@ export function useResolvedProperties(properties: readonly string[]) {
|
||||
return values
|
||||
}
|
||||
|
||||
// Swatch text candidates in order of preference (see textTone); read from the Theme with useResolvedProperties.
|
||||
export const textTones = [["primary", "--hm-sem-fg-primary"], ["inverse", "--hm-color-text-inverse"], ["black", "--hm-sem-palette-black"]] as const
|
||||
export const textToneProperties = textTones.map(([, property]) => property)
|
||||
|
||||
// Read the current CSS value rather than duplicating Theme HEX values.
|
||||
export function colorHex(value: string | undefined) {
|
||||
if (value === undefined) return "확인 중"
|
||||
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 relative luminance of a #RRGGBB value.
|
||||
function luminance(hex: string) {
|
||||
const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255).map(c => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4)
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
}
|
||||
// Swatch text takes the first tone that reaches WCAG AA 4.5:1: fg-primary, then white, then black (data-text-tone in foundation.css).
|
||||
export function textTone(hex: string, values: Record<string, string>) {
|
||||
if (!/^#[\dA-F]{6}$/.test(hex)) return undefined
|
||||
const background = luminance(hex)
|
||||
return textTones.find(([, property]) => {
|
||||
const text = colorHex(values[property])
|
||||
if (!/^#[\dA-F]{6}$/.test(text)) return false
|
||||
const [light, dark] = [luminance(text), background].sort((a, b) => b - a)
|
||||
return (light + 0.05) / (dark + 0.05) >= 4.5
|
||||
})?.[0]
|
||||
}
|
||||
|
||||
// The page frame, title and sections are the common Story page (src/components/story-page.tsx); .hm-foundation styles the reference content.
|
||||
export function ReferencePage({ title, intro, children }: { title: string; intro?: string; children: ReactNode }) {
|
||||
return <StoryPage className="hm-foundation"><StoryPageHeader title={title} description={intro} />{children}</StoryPage>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { typographyProperties, typographyReferences } from "./reference-data"
|
||||
import { ReferencePage, ReferenceSection, useResolvedProperties } from "./reference"
|
||||
import { StorySectionGrid } from "../components/story-page"
|
||||
import { colorHex, ReferencePage, ReferenceSection, textTone, textToneProperties, useResolvedProperties } from "./reference"
|
||||
|
||||
type TypographyReference = (typeof typographyReferences)[number]
|
||||
|
||||
@@ -10,7 +11,7 @@ const weights = [
|
||||
{ property: "--hm-font-weight-medium", label: "Medium" },
|
||||
{ property: "--hm-font-weight-bold", label: "Bold" },
|
||||
]
|
||||
// Left table: text styles. Right table: form controls. Together they cover every entry in typographyReferences.
|
||||
// Left column: text styles. Right column: form controls. Together they cover every entry in typographyReferences.
|
||||
const textLabels = ["Page Header · title", "Card · title", "Page Header · description", "Label"]
|
||||
const textRows = typographyReferences.filter(item => textLabels.includes(item.label))
|
||||
const controlRows = typographyReferences.filter(item => !textLabels.includes(item.label))
|
||||
@@ -23,22 +24,24 @@ const textColorRoles = [
|
||||
// Transparent readonly Input on the beige surface uses Secondary (Placeholder is below 4.5:1 there).
|
||||
{ label: "Readonly Placeholder", name: "읽기 전용 입력 안내", token: "--hm-sem-fg-secondary" },
|
||||
]
|
||||
const textColorProperties = [...new Set(textColorRoles.map(item => item.token))]
|
||||
// Chip colors plus the swatch text candidates (textTone, reference.tsx).
|
||||
const textColorProperties = [...new Set([...textColorRoles.map(item => item.token), ...textToneProperties])]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Same chip classes as Foundation/한맥 컬러 and 가족사 컬러 (foundation.css): swatch, then role name / usage / HEX on separate lines
|
||||
// so the item name, its description and the HEX value read as three levels (styles scoped to .hm-text-color-row).
|
||||
// Same chip as Foundation/가족사 컬러 (separate chips; shared swatch rules in foundation.css via data-text-colors): role name and
|
||||
// usage at the top left, HEX at the bottom right, text tone by contrast.
|
||||
function TextColorChips({ values }: { values: Record<string, string> }) {
|
||||
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}>
|
||||
<div className="hm-foundation-swatch" style={{ background: `var(${item.token})` }} />
|
||||
<h3>{item.label}</h3><p data-color-name>{item.name}</p><p data-color-value>{toHex(values[item.token])}</p>
|
||||
</article>)}</div>
|
||||
return <div className="hm-brand-color-row hm-text-color-row" data-text-colors>{textColorRoles.map(item => {
|
||||
const hex = colorHex(values[item.token])
|
||||
return <article className="hm-brand-color-chip" key={item.label} data-text-color={item.label} data-color-token={item.token} data-text-tone={textTone(hex, values)}>
|
||||
<div className="hm-foundation-swatch" style={{ background: `var(${item.token})` }}>
|
||||
<div className="hm-color-name">
|
||||
<h3>{item.label}</h3>
|
||||
<p className="hm-color-note" data-color-name>{item.name}</p>
|
||||
</div>
|
||||
<p data-color-value>{hex}</p>
|
||||
</div>
|
||||
</article>
|
||||
})}</div>
|
||||
}
|
||||
|
||||
function SpecimenRow({ label, size, line, weight }: TypographyReference & { weight?: string }) {
|
||||
@@ -53,18 +56,23 @@ function SpecimenRow({ label, size, line, weight }: TypographyReference & { weig
|
||||
})
|
||||
return () => { mounted = false }
|
||||
}, [])
|
||||
return <tr>
|
||||
<th scope="row">{label}</th>
|
||||
<td><div ref={ref} className="hm-typo-sample" data-typography-sample={label} lang="ko" style={{ fontSize: `var(${size})`, lineHeight: `var(${line})`, fontWeight: weight ? `var(${weight})` : "normal" }}>한맥 Hanmac 0123</div></td>
|
||||
<td className="hm-typo-metrics" data-typography-metrics><span>{metrics.main}</span><small className="hm-foundation-note">{metrics.spacing}</small></td>
|
||||
</tr>
|
||||
// The sample at its real component size leads; the item name and the computed metrics sit beside it as secondary text.
|
||||
return <li className="hm-typo-row hm-typo-specimen">
|
||||
<div className="hm-typo-row-meta hm-typo-specimen-meta">
|
||||
<span className="hm-typo-specimen-name">{label.split(" · ")[1] ?? label}</span>
|
||||
<span className="hm-typo-metrics" data-typography-metrics><span>{metrics.main}</span><span>{metrics.spacing}</span></span>
|
||||
</div>
|
||||
<div ref={ref} className="hm-typo-row-preview hm-typo-sample" data-typography-sample={label} lang="ko" style={{ fontSize: `var(${size})`, lineHeight: `var(${line})`, fontWeight: weight ? `var(${weight})` : "normal" }}>한맥 Hanmac 0123</div>
|
||||
</li>
|
||||
}
|
||||
|
||||
function SpecimenTable({ rows }: { rows: readonly TypographyReference[] }) {
|
||||
return <table className="hm-typo-table">
|
||||
<thead><tr><th scope="col">Component</th><th scope="col">Sample</th><th scope="col">Size / Line / Weight</th></tr></thead>
|
||||
<tbody>{rows.map(item => <SpecimenRow key={item.label} {...item} />)}</tbody>
|
||||
</table>
|
||||
// Rows grouped by component, the label part before " · " (Page Header, Card, Label, Input, Button Standard).
|
||||
const groupOf = (item: TypographyReference) => item.label.split(" · ")[0]
|
||||
function SpecimenGroups({ rows }: { rows: readonly TypographyReference[] }) {
|
||||
return <div className="hm-typo-groups">{[...new Set(rows.map(groupOf))].map(group => <section className="hm-typo-group" key={group} aria-label={group}>
|
||||
<h3>{group}</h3>
|
||||
<ul>{rows.filter(item => groupOf(item) === group).map(item => <SpecimenRow key={item.label} {...item} />)}</ul>
|
||||
</section>)}</div>
|
||||
}
|
||||
|
||||
function TypographyReferencePage() {
|
||||
@@ -75,21 +83,31 @@ function TypographyReferencePage() {
|
||||
<ReferenceSection title="Text Colors" note="">
|
||||
<TextColorChips values={colorValues} />
|
||||
</ReferenceSection>
|
||||
<ReferenceSection title="Font Family">
|
||||
<div className="hm-typo-family sb-page-indent">
|
||||
<p><strong>Noto Sans KR</strong> <span className="hm-foundation-note">→ 맑은 고딕 → 시스템 sans-serif</span></p>
|
||||
<code className="hm-foundation-note hm-typo-token">--hm-font-family-base: {values["--hm-font-family-base"]}</code>
|
||||
</div>
|
||||
</ReferenceSection>
|
||||
<ReferenceSection title="Font Weight">
|
||||
<div className="hm-typo-weights sb-page-indent">{weights.map(({ property, label }) => <article className="hm-typo-weight" key={property}>
|
||||
<p className="hm-typo-weight-meta"><strong>{label}</strong> <span className="hm-foundation-note">{values[property]}</span></p>
|
||||
<p className="hm-typo-weight-preview" style={{ fontWeight: `var(${property})` }}>한맥 Hanmac 0123</p>
|
||||
<code className="hm-foundation-note hm-typo-token">{property}</code>
|
||||
</article>)}</div>
|
||||
</ReferenceSection>
|
||||
<StorySectionGrid className="hm-typo-type-grid">
|
||||
<ReferenceSection title="Font Family">
|
||||
<div className="hm-typo-rows sb-page-indent"><div className="hm-typo-row hm-typo-family">
|
||||
<div className="hm-typo-row-meta">
|
||||
<p className="hm-typo-kicker">Primary Typeface</p>
|
||||
<p className="hm-typo-family-name">Noto Sans KR</p>
|
||||
</div>
|
||||
<div className="hm-typo-row-preview">
|
||||
<p className="hm-typo-family-preview" lang="ko" style={{ fontFamily: "var(--hm-font-family-base)" }}>한맥 Hanmac 0123</p>
|
||||
<code className="hm-foundation-note hm-typo-token">--hm-font-family-base: {values["--hm-font-family-base"]}</code>
|
||||
</div>
|
||||
</div></div>
|
||||
</ReferenceSection>
|
||||
<ReferenceSection title="Font Weight">
|
||||
<div className="hm-typo-rows hm-typo-weights sb-page-indent">{weights.map(({ property, label }) => <article className="hm-typo-row hm-typo-weight" key={property}>
|
||||
<div className="hm-typo-row-meta">
|
||||
<p className="hm-typo-weight-meta"><strong>{label}</strong> <span className="hm-foundation-note">{values[property]}</span></p>
|
||||
<code className="hm-foundation-note hm-typo-token">{property}</code>
|
||||
</div>
|
||||
<p className="hm-typo-row-preview hm-typo-weight-preview" style={{ fontWeight: `var(${property})` }}>한맥 Hanmac 0123</p>
|
||||
</article>)}</div>
|
||||
</ReferenceSection>
|
||||
</StorySectionGrid>
|
||||
<ReferenceSection title="Typography Specimens">
|
||||
<div className="hm-typo-tables sb-page-indent"><SpecimenTable rows={textRows} /><SpecimenTable rows={controlRows} /></div>
|
||||
<div className="hm-typo-specimens sb-page-indent"><SpecimenGroups rows={textRows} /><SpecimenGroups rows={controlRows} /></div>
|
||||
</ReferenceSection>
|
||||
</ReferencePage>
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user