style(storybook): refine foundation and navigation stories
This commit is contained in:
1 parent
f6b92d781a
commit
8827eb2681
14 files changed
+215
-86
No files matched your search
@@ -15,12 +15,14 @@ function CompanyColorChip({ label, hex, main }: { label: string; hex: string; ma
|
||||
|
||||
function CompanyColorsPage() {
|
||||
return <ReferencePage title="Company Colors">
|
||||
{companies.map(company => <ReferenceSection key={company.companyId} title={company.companyName} note="">
|
||||
<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} />)}
|
||||
</div>
|
||||
</ReferenceSection>)}
|
||||
<div className="hm-company-grid">
|
||||
{companies.map(company => <ReferenceSection key={company.companyId} title={company.companyName} note="">
|
||||
<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} />)}
|
||||
</div>
|
||||
</ReferenceSection>)}
|
||||
</div>
|
||||
</ReferencePage>
|
||||
}
|
||||
const meta = { title: "Foundation/Company Colors", parameters: { layout: "fullscreen" } } satisfies Meta
|
||||
|
||||
Vendored
+50
-4
@@ -24,8 +24,6 @@
|
||||
.hm-foundation-specimen 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-spacing { max-width: 100%; height: 24px; margin-block: 12px; background: var(--hm-sem-state-active); }
|
||||
.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-foundation-layout-sample { display: grid; min-width: 0; background: var(--hm-sem-bg-content); border: 1px solid var(--hm-sem-stroke-input-default); }
|
||||
.hm-foundation-layout-card { min-width: 0; border: 1px solid var(--hm-card-border-color); border-radius: var(--hm-card-radius); background: var(--hm-card-surface); }
|
||||
.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); }
|
||||
@@ -39,5 +37,53 @@
|
||||
.hm-foundation .hm-color-gradient-stops .hm-brand-color-chip[data-gradient-stop-token] { flex-basis: 220px; }
|
||||
.hm-foundation .hm-color-foreground-sample { padding: 16px; background: var(--hm-sem-bg-surface); }
|
||||
|
||||
/* Company Colors: keep each chip close to the Color row width so 1-3 chips per company do not stretch across the page. */
|
||||
.hm-foundation .hm-brand-color-row[data-company-id] .hm-brand-color-chip { flex: 0 1 140px; }
|
||||
/* 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. */
|
||||
.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; }
|
||||
.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)); }
|
||||
|
||||
/* Typography: compact specification page. Font Family one line, Font Weight small cards, Specimens as comparison tables
|
||||
(two columns on wide screens, stacked rows on narrow screens). */
|
||||
.hm-foundation .hm-typo-family { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px; margin: 0; }
|
||||
.hm-foundation .hm-typo-weights { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px; }
|
||||
.hm-foundation .hm-typo-weight { padding: 12px 16px; }
|
||||
.hm-foundation .hm-typo-weight p { margin: 0; }
|
||||
.hm-foundation .hm-typo-weight-preview { margin: 4px 0 !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: 8px 12px 8px 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; }
|
||||
.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 (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: 8px 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; }
|
||||
}
|
||||
|
||||
/* Layout: compact specification page (PC: three bands; narrow screens stack and scroll naturally).
|
||||
Tightening applies only inside .hm-layout-ref; the common Story page and other Foundation pages are unchanged. No fixed heights or clipping. */
|
||||
.hm-foundation .hm-layout-ref { display: grid; gap: 32px; word-break: keep-all; }
|
||||
.hm-foundation .hm-layout-ref .sb-page-section { margin-top: 0; }
|
||||
.hm-foundation .hm-layout-ref .sb-page-section-header { padding-bottom: 8px; margin-bottom: 12px; }
|
||||
.hm-foundation .hm-layout-ref p, .hm-foundation .hm-layout-ref h3 { margin: 0; }
|
||||
.hm-foundation .hm-layout-ref .hm-foundation-card, .hm-foundation .hm-layout-ref .hm-foundation-panel { padding: 8px 16px; }
|
||||
.hm-foundation .hm-layout-ref code { font-size: var(--hm-button-font-size-sm); color: var(--hm-sem-fg-secondary); }
|
||||
.hm-foundation .hm-layout-ref .hm-layout-value { font-weight: var(--hm-font-weight-bold); }
|
||||
.hm-foundation .hm-layout-list { display: grid; gap: 8px 24px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr)); align-content: center; }
|
||||
.hm-foundation .hm-layout-spacing-grid { display: grid; gap: 12px; grid-template-columns: minmax(0, 1fr); }
|
||||
.hm-foundation .hm-layout-spacing-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
||||
.hm-foundation .hm-layout-spacing-foot { display: flex; align-items: center; gap: 12px; margin-top: 4px; min-height: 14px; }
|
||||
.hm-foundation .hm-layout-spacing-foot .hm-foundation-spacing { flex: none; height: 6px; margin: 0; }
|
||||
.hm-foundation .hm-layout-spacing-foot .hm-foundation-note { min-width: 0; font-size: var(--hm-button-font-size-sm); }
|
||||
.hm-foundation .hm-layout-radius-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); }
|
||||
.hm-foundation .hm-layout-ref .hm-layout-radius .hm-foundation-radius { height: 24px; margin: 0 0 6px; }
|
||||
.hm-foundation .hm-layout-radius .hm-layout-value { display: block; }
|
||||
.hm-foundation .hm-layout-radius .hm-layout-spacing-head { margin-top: 0; }
|
||||
@media (min-width: 480px) { .hm-foundation .hm-layout-spacing-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (min-width: 640px) { .hm-foundation .hm-layout-spacing-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
||||
@@ -2,14 +2,32 @@ import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { layoutProperties, radiusReferences, spacingReferences } from "./reference-data"
|
||||
import { ReferencePage, ReferenceSection, useResolvedProperties } from "./reference"
|
||||
|
||||
// Compact specification page: item name, key value and preview only. PC shows every section within one screen, narrow screens stack and scroll naturally.
|
||||
// Token names stay in the data (reference-data.ts) and in data-token attributes. Shortened wording (kept here for reference):
|
||||
// Desktop: "Desktop Navigation · LNB 표시 · GNB desktop menu" / Mobile: "Mobile Navigation · LNB 숨김 · GNB mobile menu"
|
||||
// Only verified items are shown (checked against the token definitions, CSS usage and docs on 2026-11-09):
|
||||
// - "Mobile 20px" for Dashboard padding is not implemented: --hm-dashboard-padding is 34px in every viewport (docs/patterns/dashboard-layout.md).
|
||||
// - --hm-radius-select is declared in default.css but has no consumer (Select uses --hm-select-radius -> --hm-radius-input), so it is not listed.
|
||||
const radiusLabels: Record<string, string> = {
|
||||
"--hm-radius-control-md": "Control",
|
||||
"--hm-radius-button": "Button",
|
||||
"--hm-radius-input": "Input",
|
||||
"--hm-radius-container": "Container",
|
||||
}
|
||||
const shownRadiusReferences = radiusReferences.filter(property => property in radiusLabels)
|
||||
|
||||
function Item({ name, value, note }: { name: string; value: string; note?: string }) {
|
||||
return <div><div className="hm-layout-spacing-head"><h3>{name}</h3><span className="hm-layout-value">{value}</span></div>{note ? <p className="hm-foundation-note">{note}</p> : null}</div>
|
||||
}
|
||||
|
||||
function LayoutReferencePage() {
|
||||
const values = useResolvedProperties(layoutProperties)
|
||||
return <ReferencePage title="Layout">
|
||||
<ReferenceSection title="Responsive Boundary"><div className="hm-foundation-grid"><article className="hm-foundation-panel"><h3>≥ 1024px · Desktop</h3><p>Desktop Navigation · LNB 표시 · GNB desktop menu</p></article><article className="hm-foundation-panel"><h3>≤ 1023px · Mobile</h3><p>Mobile Navigation · LNB 숨김 · GNB mobile menu</p></article></div></ReferenceSection>
|
||||
<ReferenceSection title="QA Reference Viewports"><div className="hm-foundation-grid"><div className="hm-foundation-panel"><h3>1280 × 900</h3><p>HM Desktop · QA reference</p></div><div className="hm-foundation-panel"><h3>390 × 844</h3><p>HM Mobile · QA reference</p></div></div></ReferenceSection>
|
||||
<ReferenceSection title="Spacing · Current Usage Reference"><div className="hm-foundation-grid">{spacingReferences.map(([property, name, responsive]) =><article className="hm-foundation-card" key={property}><h3>{name}</h3><code>{property}</code><p data-layout-value>{values[property]}</p><div className="hm-foundation-spacing" style={{ width: `var(${property})` }} aria-hidden="true" />{responsive ? <p className="hm-foundation-note">{responsive}</p> : null}</article>)}</div></ReferenceSection>
|
||||
<ReferenceSection title="Radius"><div className="hm-foundation-grid">{radiusReferences.map(property => <article className="hm-foundation-card" key={property}><div className="hm-foundation-radius" style={{ borderRadius: `var(${property})` }} /><code>{property}</code><p>{values[property]}</p></article>)}</div></ReferenceSection>
|
||||
<ReferenceSection title="Layout / Container Reference"><div className="hm-foundation-layout-sample" style={{ padding: "var(--hm-dashboard-padding)", gap: "var(--hm-dashboard-gap)" }}><div className="hm-foundation-layout-card" style={{ padding: "var(--hm-card-padding)" }}><strong>Dashboard content</strong><p>가용 폭을 사용하며 긴 텍스트는 내부에서 줄바꿈합니다.</p></div><div className="hm-foundation-layout-card" style={{ padding: "var(--hm-card-padding)" }}>다음 섹션 · gap: {values["--hm-dashboard-gap"]}</div></div></ReferenceSection>
|
||||
<div className="hm-layout-ref">
|
||||
<ReferenceSection title="Responsive"><div className="hm-foundation-panel hm-layout-list"><Item name="Desktop" value="≥ 1024px" note="LNB 표시" /><Item name="Mobile" value="≤ 1023px" note="LNB 숨김" /></div></ReferenceSection>
|
||||
<ReferenceSection title="Spacing"><div className="hm-layout-spacing-grid">{spacingReferences.map(([property, name]) =><article className="hm-foundation-card" key={property} data-token={property}><div className="hm-layout-spacing-head"><h3>{name}</h3><span className="hm-layout-value" data-layout-value>{values[property]}</span></div><div className="hm-layout-spacing-foot"><div className="hm-foundation-spacing" style={{ width: `var(${property})` }} aria-hidden="true" /></div></article>)}</div></ReferenceSection>
|
||||
<ReferenceSection title="Radius"><div className="hm-layout-radius-grid">{shownRadiusReferences.map(property => <article className="hm-foundation-card hm-layout-radius" key={property} data-token={property}><div className="hm-foundation-radius" style={{ borderRadius: `var(${property})` }} /><div className="hm-layout-spacing-head"><h3>{radiusLabels[property]}</h3><span className="hm-layout-value">{values[property]}</span></div></article>)}</div></ReferenceSection>
|
||||
</div>
|
||||
</ReferencePage>
|
||||
}
|
||||
const meta = { title: "Foundation/Layout", parameters: { layout: "fullscreen" } } satisfies Meta
|
||||
|
||||
@@ -1,18 +1,62 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { typographyProperties, typographyReferences } from "./reference-data"
|
||||
import { ReferencePage, ReferenceSection, Specimen, useResolvedProperties } from "./reference"
|
||||
import { ReferencePage, ReferenceSection, useResolvedProperties } from "./reference"
|
||||
|
||||
type TypographyReference = (typeof typographyReferences)[number]
|
||||
|
||||
const weights = [
|
||||
{ property: "--hm-input-font-weight", label: "Regular" },
|
||||
{ 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.
|
||||
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))
|
||||
|
||||
function SpecimenRow({ label, size, line, weight }: TypographyReference & { weight?: string }) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [metrics, setMetrics] = useState({ main: "", spacing: "" })
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
void document.fonts.ready.then(() => {
|
||||
if (!mounted || !ref.current) return
|
||||
const style = getComputedStyle(ref.current)
|
||||
setMetrics({ main: `${style.fontSize} / ${style.lineHeight} / ${style.fontWeight}`, spacing: `letter-spacing: ${style.letterSpacing}` })
|
||||
})
|
||||
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>
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
function TypographyReferencePage() {
|
||||
const values = useResolvedProperties(typographyProperties)
|
||||
return <ReferencePage title="Typography">
|
||||
<ReferenceSection title="Font Family"><div className="hm-foundation-panel"><p>Noto Sans KR → 맑은 고딕 → 시스템 sans-serif</p></div></ReferenceSection>
|
||||
<ReferenceSection title="Font Weight"><div className="hm-foundation-grid">{weights.map(({ property, label }) => <article className="hm-foundation-card" key={property}><code>{property}</code><p>{values[property]} — {label}</p><p style={{ fontWeight: `var(${property})` }}>한맥 프로젝트<br />Hanmac Project<br />0123456789</p></article>)}</div></ReferenceSection>
|
||||
<ReferenceSection title="Component Specimens"><div className="hm-foundation-specimens">{typographyReferences.map(item => <article className="hm-foundation-panel" key={item.label}><h3>{item.label}</h3><Specimen {...item} /></article>)}</div></ReferenceSection>
|
||||
<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>
|
||||
</ReferenceSection>
|
||||
<ReferenceSection title="Font Weight">
|
||||
<div className="hm-typo-weights">{weights.map(({ property, label }) => <article className="hm-foundation-card 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">{property}</code>
|
||||
</article>)}</div>
|
||||
</ReferenceSection>
|
||||
<ReferenceSection title="Typography Specimens">
|
||||
<div className="hm-typo-tables"><SpecimenTable rows={textRows} /><SpecimenTable rows={controlRows} /></div>
|
||||
</ReferenceSection>
|
||||
</ReferencePage>
|
||||
}
|
||||
const meta = { title: "Foundation/Typography", parameters: { layout: "fullscreen" } } satisfies Meta
|
||||
|
||||
Reference in new issue
Block a user