|
1 | 1 | import type { Meta, StoryObj } from "@storybook/react-vite" |
| 2 | +import type { ReactNode } from "react" |
2 | 3 |
|
3 | 4 | // --------------------------------------------------------------------------- |
4 | | -// Typescale definitions — sourced from src/index.tailwind.css |
| 5 | +// Typography reference — Inter Variable on the Tailwind font-size scale. |
| 6 | +// The kit intentionally has NO custom type scale (see DESIGN.md); components |
| 7 | +// compose Tailwind's `text-*` size + `font-*` weight utilities directly, plus |
| 8 | +// the `text-2xs` (10px) extension for dense UI. SW-2254 retired the former |
| 9 | +// (unused) MD3 typescale utilities. |
5 | 10 | // --------------------------------------------------------------------------- |
6 | 11 |
|
7 | | -interface TypescaleLevel { |
8 | | - name: string |
9 | | - role: string |
10 | | - size: string |
| 12 | +const SAMPLE_TEXT = "The quick brown fox jumps over the lazy dog" |
| 13 | + |
| 14 | +interface FontSize { |
11 | 15 | cssClass: string |
12 | | - lineHeight: string |
13 | | - weight: number |
14 | | - tracking: string |
| 16 | + size: string |
| 17 | + note: string |
| 18 | + tag?: "New" | "Default" |
15 | 19 | } |
16 | 20 |
|
17 | | -const TYPESCALE_LEVELS: TypescaleLevel[] = [ |
18 | | - { name: "Display Large", role: "display", size: "57px", cssClass: "text-display-lg", lineHeight: "64px", weight: 400, tracking: "-0.25px" }, |
19 | | - { name: "Display Medium", role: "display", size: "45px", cssClass: "text-display-md", lineHeight: "52px", weight: 400, tracking: "0px" }, |
20 | | - { name: "Display Small", role: "display", size: "36px", cssClass: "text-display-sm", lineHeight: "44px", weight: 400, tracking: "0px" }, |
21 | | - { name: "Headline Large", role: "headline", size: "32px", cssClass: "text-headline-lg", lineHeight: "40px", weight: 400, tracking: "0px" }, |
22 | | - { name: "Headline Medium", role: "headline", size: "28px", cssClass: "text-headline-md", lineHeight: "36px", weight: 400, tracking: "0px" }, |
23 | | - { name: "Headline Small", role: "headline", size: "24px", cssClass: "text-headline-sm", lineHeight: "32px", weight: 400, tracking: "0px" }, |
24 | | - { name: "Title Large", role: "title", size: "22px", cssClass: "text-title-lg", lineHeight: "28px", weight: 500, tracking: "0px" }, |
25 | | - { name: "Title Medium", role: "title", size: "16px", cssClass: "text-title-md", lineHeight: "24px", weight: 500, tracking: "0.15px" }, |
26 | | - { name: "Title Small", role: "title", size: "14px", cssClass: "text-title-sm", lineHeight: "20px", weight: 500, tracking: "0.1px" }, |
27 | | - { name: "Body Large", role: "body", size: "16px", cssClass: "text-body-lg", lineHeight: "24px", weight: 400, tracking: "0.5px" }, |
28 | | - { name: "Body Medium", role: "body", size: "14px", cssClass: "text-body-md", lineHeight: "20px", weight: 400, tracking: "0.25px" }, |
29 | | - { name: "Body Small", role: "body", size: "12px", cssClass: "text-body-sm", lineHeight: "16px", weight: 400, tracking: "0.4px" }, |
30 | | - { name: "Label Large", role: "label", size: "14px", cssClass: "text-label-lg", lineHeight: "20px", weight: 500, tracking: "0.1px" }, |
31 | | - { name: "Label Medium", role: "label", size: "12px", cssClass: "text-label-md", lineHeight: "16px", weight: 500, tracking: "0.5px" }, |
32 | | - { name: "Label Small", role: "label", size: "11px", cssClass: "text-label-sm", lineHeight: "16px", weight: 500, tracking: "0.5px" }, |
| 21 | +// Tailwind v4 built-in font sizes; only `text-2xs` is custom (src/index.tailwind.css). |
| 22 | +const FONT_SIZES: FontSize[] = [ |
| 23 | + { cssClass: "text-2xs", size: "10px", note: "Dense labels, chips, chart axis ticks, table metadata", tag: "New" }, |
| 24 | + { cssClass: "text-xs", size: "12px", note: "Captions, badges, secondary metadata" }, |
| 25 | + { cssClass: "text-sm", size: "14px", note: "Default body & UI text — the most common size", tag: "Default" }, |
| 26 | + { cssClass: "text-base", size: "16px", note: "Inputs, emphasized body, small titles" }, |
| 27 | + { cssClass: "text-lg", size: "18px", note: "Sub-headings" }, |
| 28 | + { cssClass: "text-xl", size: "20px", note: "Section headings" }, |
| 29 | + { cssClass: "text-2xl", size: "24px", note: "Page titles" }, |
| 30 | + { cssClass: "text-3xl", size: "30px", note: "Display headings" }, |
| 31 | + { cssClass: "text-4xl", size: "36px", note: "Largest — hero / marketing" }, |
33 | 32 | ] |
34 | 33 |
|
35 | | -const SAMPLE_TEXT = "The quick brown fox jumps over the lazy dog" |
36 | | - |
37 | | -const ROLE_COLORS: Record<string, string> = { |
38 | | - display: "bg-primary/10 text-primary", |
39 | | - headline: "bg-accent/10 text-accent", |
40 | | - title: "bg-positive/10 text-positive", |
41 | | - body: "bg-info/10 text-info", |
42 | | - label: "bg-warning/10 text-warning", |
| 34 | +interface FontWeight { |
| 35 | + cssClass: string |
| 36 | + weight: number |
| 37 | + note: string |
43 | 38 | } |
44 | 39 |
|
| 40 | +// Inter Variable ships the full 100–900 range; these are the weights the kit uses. |
| 41 | +const FONT_WEIGHTS: FontWeight[] = [ |
| 42 | + { cssClass: "font-normal", weight: 400, note: "Body text" }, |
| 43 | + { cssClass: "font-medium", weight: 500, note: "Labels, buttons, emphasis" }, |
| 44 | + { cssClass: "font-semibold", weight: 600, note: "Section headings" }, |
| 45 | + { cssClass: "font-bold", weight: 700, note: "Page titles" }, |
| 46 | +] |
| 47 | + |
45 | 48 | // --------------------------------------------------------------------------- |
46 | 49 | // Components |
47 | 50 | // --------------------------------------------------------------------------- |
48 | 51 |
|
| 52 | +function Code({ children }: { children: ReactNode }) { |
| 53 | + return <code className="rounded bg-muted px-1 py-0.5 text-xs">{children}</code> |
| 54 | +} |
| 55 | + |
49 | 56 | function CopyButton({ text }: { text: string }) { |
50 | 57 | return ( |
51 | 58 | <button |
52 | 59 | type="button" |
53 | 60 | onClick={() => navigator.clipboard.writeText(text)} |
54 | | - className="inline-flex items-center rounded-md border border-border bg-transparent px-2 py-0.5 text-xs font-mono text-muted-foreground hover:bg-muted cursor-pointer select-all transition-colors" |
| 61 | + className="inline-flex items-center whitespace-nowrap rounded-md border border-border bg-transparent px-2 py-0.5 text-xs font-mono text-muted-foreground hover:bg-muted cursor-pointer select-all transition-colors" |
55 | 62 | title={`Copy: ${text}`} |
56 | 63 | > |
57 | 64 | {text} |
58 | 65 | </button> |
59 | 66 | ) |
60 | 67 | } |
61 | 68 |
|
62 | | -function TypographyPage() { |
63 | | - let lastRole = "" |
| 69 | +function TagBadge({ tag }: { tag: "New" | "Default" }) { |
| 70 | + const cls = tag === "New" ? "bg-positive/10 text-positive" : "bg-info/10 text-info" |
| 71 | + return <span className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${cls}`}>{tag}</span> |
| 72 | +} |
| 73 | + |
| 74 | +const TH = "px-4 py-2.5 text-left font-medium text-muted-foreground" |
| 75 | +const TD = "px-4 py-2.5" |
| 76 | +const TR = "border-b border-border last:border-b-0 hover:bg-muted/30 transition-colors" |
64 | 77 |
|
| 78 | +function TypographyPage() { |
65 | 79 | return ( |
66 | 80 | <div className="mx-auto max-w-7xl space-y-10 p-12"> |
67 | 81 | <div className="space-y-2"> |
68 | | - <h1 className="text-2xl font-bold text-foreground">Typography Scale</h1> |
69 | | - <p className="text-sm text-muted-foreground max-w-2xl"> |
70 | | - Material Design 3 typescale with 15 levels across 5 roles (Display, Headline, Title, Body, Label), |
71 | | - tuned for Inter Variable. Each level is available as a single Tailwind utility class that sets |
72 | | - font-size, line-height, font-weight, and letter-spacing. |
| 82 | + <h1 className="text-2xl font-bold text-foreground">Typography</h1> |
| 83 | + <p className="max-w-2xl text-sm text-muted-foreground"> |
| 84 | + The UI kit uses <span className="font-medium text-foreground">Inter Variable</span> on Tailwind's |
| 85 | + native font-size scale — there is no custom type scale. Compose size and weight utilities directly |
| 86 | + (e.g. <Code>text-sm font-medium</Code>). The one addition is <Code>text-2xs</Code> (10px) for dense UI. |
73 | 87 | </p> |
74 | 88 | </div> |
75 | 89 |
|
76 | | - {/* Quick reference table */} |
| 90 | + {/* Font family */} |
| 91 | + <section className="space-y-3"> |
| 92 | + <h2 className="text-lg font-semibold text-foreground">Font Family</h2> |
| 93 | + <div className="space-y-1 rounded-lg border border-border bg-card p-6"> |
| 94 | + <p className="text-3xl text-foreground">Inter Variable</p> |
| 95 | + <p className="text-sm text-muted-foreground"> |
| 96 | + Self-hosted variable font (weights 100–900), applied globally via <Code>--font-sans</Code> /{" "} |
| 97 | + <Code>font-sans</Code>. |
| 98 | + </p> |
| 99 | + </div> |
| 100 | + </section> |
| 101 | + |
| 102 | + {/* Font sizes */} |
77 | 103 | <section className="space-y-3"> |
78 | | - <h2 className="text-lg font-semibold text-foreground">Reference</h2> |
| 104 | + <h2 className="text-lg font-semibold text-foreground">Font Sizes</h2> |
| 105 | + <p className="max-w-2xl text-sm text-muted-foreground"> |
| 106 | + Tailwind's built-in <Code>text-*</Code> scale, plus the kit's <Code>text-2xs</Code>. Sized for |
| 107 | + data-dense scientific UIs — <Code>text-sm</Code> is the de-facto body size. |
| 108 | + </p> |
79 | 109 | <div className="overflow-x-auto rounded-lg border border-border bg-card"> |
80 | 110 | <table className="w-full text-sm"> |
81 | 111 | <thead> |
82 | 112 | <tr className="border-b border-border bg-muted/50"> |
83 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Role</th> |
84 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Level</th> |
85 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Tailwind Class</th> |
86 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Size</th> |
87 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Line Height</th> |
88 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Weight</th> |
89 | | - <th className="px-4 py-2.5 text-left font-medium text-muted-foreground">Tracking</th> |
| 113 | + <th className={TH}>Tailwind Class</th> |
| 114 | + <th className={TH}>Size</th> |
| 115 | + <th className={TH}>Sample</th> |
| 116 | + <th className={TH}>Usage</th> |
90 | 117 | </tr> |
91 | 118 | </thead> |
92 | 119 | <tbody> |
93 | | - {TYPESCALE_LEVELS.map((level) => { |
94 | | - const showRole = level.role !== lastRole |
95 | | - lastRole = level.role |
96 | | - return ( |
97 | | - <tr key={level.cssClass} className="border-b border-border last:border-b-0 hover:bg-muted/30 transition-colors"> |
98 | | - <td className="px-4 py-2.5"> |
99 | | - {showRole && ( |
100 | | - <span className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${ROLE_COLORS[level.role]}`}> |
101 | | - {level.role} |
102 | | - </span> |
103 | | - )} |
104 | | - </td> |
105 | | - <td className="px-4 py-2.5 font-medium text-foreground">{level.name}</td> |
106 | | - <td className="px-4 py-2.5"><CopyButton text={level.cssClass} /></td> |
107 | | - <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{level.size}</td> |
108 | | - <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{level.lineHeight}</td> |
109 | | - <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{level.weight}</td> |
110 | | - <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{level.tracking}</td> |
111 | | - </tr> |
112 | | - ) |
113 | | - })} |
| 120 | + {FONT_SIZES.map((s) => ( |
| 121 | + <tr key={s.cssClass} className={TR}> |
| 122 | + <td className={TD}> |
| 123 | + <div className="flex items-center gap-2"> |
| 124 | + <CopyButton text={s.cssClass} /> |
| 125 | + {s.tag && <TagBadge tag={s.tag} />} |
| 126 | + </div> |
| 127 | + </td> |
| 128 | + <td className={`${TD} font-mono text-xs text-muted-foreground`}>{s.size}</td> |
| 129 | + <td className={TD}> |
| 130 | + <span className={`${s.cssClass} text-foreground`}>{SAMPLE_TEXT}</span> |
| 131 | + </td> |
| 132 | + <td className={`${TD} text-xs text-muted-foreground`}>{s.note}</td> |
| 133 | + </tr> |
| 134 | + ))} |
114 | 135 | </tbody> |
115 | 136 | </table> |
116 | 137 | </div> |
117 | 138 | </section> |
118 | 139 |
|
119 | | - {/* Visual samples */} |
| 140 | + {/* Font weights */} |
120 | 141 | <section className="space-y-3"> |
121 | | - <h2 className="text-lg font-semibold text-foreground">Samples</h2> |
122 | | - <div className="space-y-6"> |
123 | | - {TYPESCALE_LEVELS.map((level) => ( |
124 | | - <div key={level.cssClass} className="space-y-1"> |
125 | | - <div className="flex items-center gap-2"> |
126 | | - <span className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${ROLE_COLORS[level.role]}`}> |
127 | | - {level.role} |
128 | | - </span> |
129 | | - <span className="text-xs text-muted-foreground">{level.name}</span> |
130 | | - <CopyButton text={level.cssClass} /> |
131 | | - </div> |
132 | | - <p className={`${level.cssClass} text-foreground`}> |
133 | | - {SAMPLE_TEXT} |
134 | | - </p> |
135 | | - </div> |
136 | | - ))} |
| 142 | + <h2 className="text-lg font-semibold text-foreground">Font Weights</h2> |
| 143 | + <p className="max-w-2xl text-sm text-muted-foreground"> |
| 144 | + Inter Variable supports 100–900; these are the weights the kit uses. Pair with any size. |
| 145 | + </p> |
| 146 | + <div className="overflow-x-auto rounded-lg border border-border bg-card"> |
| 147 | + <table className="w-full text-sm"> |
| 148 | + <thead> |
| 149 | + <tr className="border-b border-border bg-muted/50"> |
| 150 | + <th className={TH}>Tailwind Class</th> |
| 151 | + <th className={TH}>Weight</th> |
| 152 | + <th className={TH}>Sample</th> |
| 153 | + <th className={TH}>Usage</th> |
| 154 | + </tr> |
| 155 | + </thead> |
| 156 | + <tbody> |
| 157 | + {FONT_WEIGHTS.map((w) => ( |
| 158 | + <tr key={w.cssClass} className={TR}> |
| 159 | + <td className={TD}> |
| 160 | + <CopyButton text={w.cssClass} /> |
| 161 | + </td> |
| 162 | + <td className={`${TD} font-mono text-xs text-muted-foreground`}>{w.weight}</td> |
| 163 | + <td className={TD}> |
| 164 | + <span className={`text-lg ${w.cssClass} text-foreground`}>{SAMPLE_TEXT}</span> |
| 165 | + </td> |
| 166 | + <td className={`${TD} text-xs text-muted-foreground`}>{w.note}</td> |
| 167 | + </tr> |
| 168 | + ))} |
| 169 | + </tbody> |
| 170 | + </table> |
137 | 171 | </div> |
138 | 172 | </section> |
139 | 173 | </div> |
|
0 commit comments