@ponti-studios/ui 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +14 -0
  2. package/package.json +45 -24
  3. package/src/animations.css +92 -15
  4. package/src/components/data-display/accordion.tsx +15 -6
  5. package/src/components/data-display/filter-chip.stories.tsx +38 -0
  6. package/src/components/data-display/filter-chip.tsx +9 -3
  7. package/src/components/data-display/sort-controls.tsx +1 -1
  8. package/src/components/data-display/table.tsx +3 -7
  9. package/src/components/feedback/empty-state.tsx +16 -6
  10. package/src/components/feedback/file-upload-status-badge.tsx +1 -1
  11. package/src/components/feedback/progress.tsx +2 -2
  12. package/src/components/feedback/update-guard.tsx +1 -1
  13. package/src/components/forms/calendar.tsx +6 -3
  14. package/src/components/forms/field.tsx +3 -3
  15. package/src/components/forms/input.tsx +1 -1
  16. package/src/components/forms/passkey-management.tsx +2 -2
  17. package/src/components/forms/radio-group.tsx +1 -1
  18. package/src/components/forms/select.tsx +11 -4
  19. package/src/components/forms/slider.tsx +16 -4
  20. package/src/components/forms/stepper.tsx +1 -1
  21. package/src/components/forms/textarea.tsx +1 -1
  22. package/src/components/layout/section-intro.tsx +1 -1
  23. package/src/components/navigation/app-navigation.tsx +4 -4
  24. package/src/components/navigation/pagination-controls.tsx +2 -2
  25. package/src/components/navigation/tabs.tsx +1 -1
  26. package/src/components/overlays/alert-dialog.tsx +30 -8
  27. package/src/components/overlays/command.tsx +123 -20
  28. package/src/components/overlays/dialog.tsx +4 -4
  29. package/src/components/overlays/dropdown-menu.tsx +137 -21
  30. package/src/components/overlays/popover.tsx +14 -3
  31. package/src/components/overlays/sheet.tsx +24 -8
  32. package/src/components/primitives/avatar.tsx +6 -2
  33. package/src/components/primitives/badge.tsx +5 -2
  34. package/src/components/primitives/button.tsx +16 -18
  35. package/src/components/primitives/card.stories.tsx +2 -2
  36. package/src/components/primitives/card.tsx +2 -5
  37. package/src/constants/chart-colors.ts +2 -2
  38. package/src/index.ts +1 -1
  39. package/src/native/index.ts +11 -0
  40. package/src/native/make-styles.ts +32 -0
  41. package/src/native/shadows.ts +38 -0
  42. package/src/native/use-theme-colors.ts +19 -0
  43. package/src/styles.css +154 -66
  44. package/src/tokens/chart-colors.stories.tsx +189 -0
  45. package/src/tokens/color-systems.stories.tsx +21 -3
  46. package/src/tokens/css-vars.ts +15 -0
  47. package/src/tokens/foundations.stories.tsx +66 -16
  48. package/src/tokens/generated/dark.colors.generated.ts +24 -50
  49. package/src/tokens/generated/dark.css +24 -48
  50. package/src/tokens/generated/foundations.css +2 -2
  51. package/src/tokens/generated/foundations.generated.ts +14 -6
  52. package/src/tokens/generated/light.colors.generated.ts +24 -50
  53. package/src/tokens/generated/light.css +24 -48
  54. package/src/tokens/index.ts +12 -15
  55. package/src/tokens/layout.stories.tsx +40 -3
  56. package/src/tokens.css +3 -0
  57. package/dist/index.js +0 -2508
  58. package/dist/index.js.map +0 -1
package/src/styles.css CHANGED
@@ -1,88 +1,176 @@
1
1
  @import "tailwindcss";
2
- @import "./tokens/generated/foundations.css";
3
- @import "./tokens/generated/light.css";
4
- @import "./tokens/generated/dark.css" (prefers-color-scheme: dark);
2
+ @import "./tokens.css";
5
3
  @import "./animations.css";
6
4
 
7
- /* Keep generated token names isolated from Tailwind's standard role names. */
8
- :root {
9
- --ponti-primary: var(--color-accent);
10
- --ponti-primary-foreground: var(--color-text-on-accent);
11
- --ponti-destructive: var(--color-destructive);
12
- --ponti-destructive-foreground: var(--color-text-on-destructive);
13
- --ponti-foreground: var(--color-text-primary);
14
- --ponti-muted-foreground: var(--color-text-secondary);
15
- --ponti-border: var(--color-border-default);
16
- --ponti-ring: var(--color-ring-focus);
17
- }
18
-
19
5
  /* shadcn New York semantic contract. Values are generated from the DTCG
20
6
  * source; this file only names the roles consumed by web components. */
21
7
  @theme inline {
22
- --color-background: var(--color-surface-canvas);
23
- --color-foreground: var(--ponti-foreground);
24
- --color-card: var(--color-surface-panel);
25
- --color-card-foreground: var(--color-text-primary);
26
- --color-popover: var(--color-surface-raised);
27
- --color-popover-foreground: var(--color-text-primary);
28
- --color-primary: var(--ponti-primary);
29
- --color-primary-foreground: var(--ponti-primary-foreground);
30
- --color-secondary: var(--color-surface-panel);
31
- --color-secondary-foreground: var(--color-text-primary);
32
- --color-muted: var(--color-surface-inset);
33
- --color-muted-foreground: var(--ponti-muted-foreground);
34
- --color-accent-foreground: var(--color-text-primary);
35
- --color-destructive-foreground: var(--ponti-destructive-foreground);
36
- --color-border: var(--ponti-border);
37
- --color-input: var(--ponti-border);
38
- --color-ring: var(--ponti-ring);
39
- --color-chart-1: var(--color-border-focus);
40
- --color-chart-2: var(--color-chart-positive);
41
- --color-chart-3: var(--color-border-focus);
42
- --color-chart-4: var(--color-text-warning);
43
- --color-chart-5: var(--color-border-strong);
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--text-primary);
10
+ --color-card: var(--card);
11
+ --color-card-foreground: var(--text-primary);
12
+ --color-popover: var(--popover);
13
+ --color-popover-foreground: var(--text-primary);
14
+ --color-primary: var(--primary);
15
+ --color-primary-foreground: var(--primary-foreground);
16
+ --color-secondary: var(--card);
17
+ --color-secondary-foreground: var(--text-primary);
18
+ --color-muted: var(--muted);
19
+ --color-muted-foreground: var(--text-secondary);
20
+ --color-accent: var(--primary);
21
+ --color-accent-foreground: var(--primary-foreground);
22
+ --color-destructive: var(--destructive);
23
+ --color-destructive-foreground: var(--destructive-foreground);
24
+ --color-success: var(--success);
25
+ --color-warning: var(--warning);
26
+ --color-border: var(--border-default);
27
+ --color-input: var(--border-default);
28
+ --color-ring: var(--focus-ring);
44
29
  }
45
30
 
46
- @utility pb-safe-area-inset-bottom { padding-bottom: env(safe-area-inset-bottom); }
47
- @utility mb-safe-area-inset-bottom { margin-bottom: env(safe-area-inset-bottom); }
48
- @utility min-h-screen { min-height: 100vh; min-height: 100dvh; }
49
- @utility min-h-screen-dynamic { min-height: 100dvh; }
50
- @utility h-screen { height: 100vh; height: 100dvh; }
31
+ @utility display-1 {
32
+ font-size: clamp(2.5rem, 5vw, 6rem);
33
+ font-family: var(--font-sans);
34
+ font-weight: var(--font-weight-semibold);
35
+ line-height: 1.1;
36
+ letter-spacing: var(--tracking-tight);
37
+ }
38
+ @utility display-2 {
39
+ font-size: clamp(2rem, 3vw, 3.5rem);
40
+ font-family: var(--font-sans);
41
+ font-weight: var(--font-weight-semibold);
42
+ line-height: 1.15;
43
+ letter-spacing: var(--tracking-tight);
44
+ }
45
+ @utility ref-tag {
46
+ font-family: var(--font-mono);
47
+ font-size: var(--text-xs);
48
+ color: var(--tertiary);
49
+ letter-spacing: var(--tracking-wide);
50
+ }
51
+ /* Quiet state-change "settle" — used on state-bearing indicators
52
+ * (Badge, StatusBadge, FilterChip) when their represented value changes. */
53
+ @utility state-settle {
54
+ transition:
55
+ transform 120ms ease-out,
56
+ opacity 120ms ease-out;
57
+ }
58
+ @utility pb-safe-area-inset-bottom {
59
+ padding-bottom: env(safe-area-inset-bottom);
60
+ }
61
+ @utility mb-safe-area-inset-bottom {
62
+ margin-bottom: env(safe-area-inset-bottom);
63
+ }
64
+ @utility min-h-screen {
65
+ min-height: 100vh;
66
+ min-height: 100dvh;
67
+ }
68
+ @utility min-h-screen-dynamic {
69
+ min-height: 100dvh;
70
+ }
71
+ @utility h-screen {
72
+ height: 100vh;
73
+ height: 100dvh;
74
+ }
51
75
 
52
76
  /* Shared layout rhythm. Product components own their layout; this does not
53
77
  * define visual component families. */
54
- .page-shell { @apply relative mx-auto flex w-full flex-col gap-12 px-4 py-16 md:gap-16 md:px-6 md:py-20; }
55
- .page-shell-detail { @apply relative mx-auto flex w-full flex-col gap-10 px-4 py-16 md:gap-12 md:px-6 md:py-20; }
56
- .page-bleed { @apply flex w-full flex-col; }
57
- .layout-stack { @apply flex flex-col gap-4; }
58
- .list-row { @apply flex gap-4 px-0 py-6 md:gap-6 md:py-8; }
59
- .list-media { @apply relative isolate flex shrink-0 items-center justify-center overflow-hidden rounded-md border border-border bg-card p-2 shadow-none; }
60
- .list-media-wide { @apply h-14 w-32; }
61
- .list-media-square { @apply size-10 sm:size-12; }
62
- .list-media-placeholder { @apply pointer-events-none absolute inset-0 flex items-center justify-center gap-1.5 transition-opacity duration-150; }
63
- .section { @apply flex flex-col gap-4 border-b border-border px-4 py-12 md:px-6 md:py-16; }
64
- .section-hero { @apply gap-6 py-16 md:py-20; }
65
- .detail-hero { @apply gap-4 py-10 md:gap-6 md:py-16; }
66
- .section-compact { @apply gap-4 py-8 md:py-12; }
67
- .detail-navigation { @apply flex flex-col gap-6 sm:flex-row sm:justify-between; }
68
- .detail-navigation-link { @apply flex min-h-11 flex-col justify-center gap-1; }
78
+ .page-shell {
79
+ @apply relative mx-auto flex w-full flex-col gap-12 px-4 py-16 md:gap-16 md:px-6 md:py-20;
80
+ }
81
+ .page-shell-detail {
82
+ @apply relative mx-auto flex w-full flex-col gap-10 px-4 py-16 md:gap-12 md:px-6 md:py-20;
83
+ }
84
+ .page-bleed {
85
+ @apply flex w-full flex-col;
86
+ }
87
+ .layout-stack {
88
+ @apply flex flex-col gap-4;
89
+ }
90
+ .list-row {
91
+ @apply flex gap-4 px-0 py-6 md:gap-6 md:py-8;
92
+ }
93
+ .list-media {
94
+ @apply border-border bg-card relative isolate flex shrink-0 items-center justify-center overflow-hidden rounded-md border p-2 shadow-none;
95
+ }
96
+ .list-media-wide {
97
+ @apply h-14 w-32;
98
+ }
99
+ .list-media-square {
100
+ @apply size-10 sm:size-12;
101
+ }
102
+ .list-media-placeholder {
103
+ @apply pointer-events-none absolute inset-0 flex items-center justify-center gap-1.5 transition-opacity duration-150;
104
+ }
105
+ .section {
106
+ @apply border-border flex flex-col gap-4 border-b px-4 py-12 md:px-6 md:py-16;
107
+ }
108
+ .section-hero {
109
+ @apply gap-6 py-16 md:py-20;
110
+ }
111
+ .detail-hero {
112
+ @apply gap-4 py-10 md:gap-6 md:py-16;
113
+ }
114
+ .section-compact {
115
+ @apply gap-4 py-8 md:py-12;
116
+ }
117
+ .detail-navigation {
118
+ @apply flex flex-col gap-6 sm:flex-row sm:justify-between;
119
+ }
120
+ .detail-navigation-link {
121
+ @apply flex min-h-9 flex-col justify-center gap-1;
122
+ }
69
123
 
70
124
  @layer base {
71
- :root { color-scheme: light; }
72
- @media (prefers-color-scheme: dark) { :root { color-scheme: dark; } }
73
- html, body {
125
+ :root {
126
+ color-scheme: light;
127
+ }
128
+ @media (prefers-color-scheme: dark) {
129
+ :root {
130
+ color-scheme: dark;
131
+ }
132
+ }
133
+ html,
134
+ body {
74
135
  background-color: var(--color-background);
75
136
  color: var(--color-foreground);
76
137
  font-family: var(--font-sans);
77
138
  -webkit-font-smoothing: antialiased;
78
139
  -moz-osx-font-smoothing: grayscale;
79
140
  }
80
- * { border-color: var(--color-border, currentColor); }
81
- *:focus { outline: none; }
82
- *:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; border-radius: inherit; }
83
- button { cursor: pointer; font-family: inherit; }
84
- h1, h2, h3, h4, h5, h6 { font-family: var(--font-sans); font-weight: 600; letter-spacing: var(--tracking-tight); }
141
+ * {
142
+ border-color: var(--color-border, currentColor);
143
+ }
144
+ *:focus {
145
+ outline: none;
146
+ }
147
+ *:focus-visible {
148
+ outline: 2px solid var(--color-ring);
149
+ outline-offset: 2px;
150
+ border-radius: inherit;
151
+ }
152
+ button {
153
+ cursor: pointer;
154
+ font-family: inherit;
155
+ }
156
+ h1,
157
+ h2,
158
+ h3,
159
+ h4,
160
+ h5,
161
+ h6 {
162
+ font-family: var(--font-sans);
163
+ font-weight: 600;
164
+ letter-spacing: var(--tracking-tight);
165
+ }
85
166
  @media (prefers-reduced-motion: reduce) {
86
- *, ::before, ::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
167
+ *,
168
+ ::before,
169
+ ::after {
170
+ animation-duration: 0.01ms !important;
171
+ animation-iteration-count: 1 !important;
172
+ transition-duration: 0.01ms !important;
173
+ scroll-behavior: auto !important;
174
+ }
87
175
  }
88
176
  }
@@ -0,0 +1,189 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { CHART_CSS_VARS } from "../constants/chart-colors";
4
+
5
+ const meta: Meta = { title: "Foundations/Charts", parameters: { layout: "fullscreen" } };
6
+ export default meta;
7
+ type Story = StoryObj<typeof meta>;
8
+
9
+ const SERIES = [
10
+ { key: "chart1", label: "chart-1" },
11
+ { key: "chart2", label: "chart-2" },
12
+ { key: "chart3", label: "chart-3" },
13
+ { key: "chart4", label: "chart-4" },
14
+ { key: "chart5", label: "chart-5" },
15
+ ] as const;
16
+
17
+ const STATUS = [
18
+ { key: "positive", label: "chart-positive", example: "▲ Up 12%" },
19
+ { key: "negative", label: "chart-negative", example: "▼ Down 4%" },
20
+ { key: "neutral", label: "chart-neutral", example: "— Flat" },
21
+ ] as const;
22
+
23
+ // A handful of arbitrary multi-series datasets, purely to show how the
24
+ // ordinal chart-1..5 palette reads next to itself in a real chart shape.
25
+ const BAR_DATA = [
26
+ [42, 68, 30, 55, 90],
27
+ [70, 25, 60, 40, 35],
28
+ [30, 55, 80, 20, 65],
29
+ ];
30
+
31
+ const LINE_POINTS: Record<string, Array<[number, number]>> = {
32
+ chart1: [
33
+ [0, 60],
34
+ [20, 40],
35
+ [40, 50],
36
+ [60, 20],
37
+ [80, 30],
38
+ [100, 10],
39
+ ],
40
+ chart2: [
41
+ [0, 80],
42
+ [20, 70],
43
+ [40, 75],
44
+ [60, 55],
45
+ [80, 60],
46
+ [100, 45],
47
+ ],
48
+ chart3: [
49
+ [0, 30],
50
+ [20, 45],
51
+ [40, 25],
52
+ [60, 50],
53
+ [80, 35],
54
+ [100, 55],
55
+ ],
56
+ chart4: [
57
+ [0, 90],
58
+ [20, 85],
59
+ [40, 65],
60
+ [60, 70],
61
+ [80, 50],
62
+ [100, 60],
63
+ ],
64
+ chart5: [
65
+ [0, 15],
66
+ [20, 30],
67
+ [40, 20],
68
+ [60, 40],
69
+ [80, 25],
70
+ [100, 20],
71
+ ],
72
+ };
73
+
74
+ export const CategoricalPalette: Story = {
75
+ render: () => (
76
+ <main className="bg-background text-foreground grid min-h-screen gap-10 p-6 md:p-10">
77
+ <header className="grid gap-2">
78
+ <p className="text-muted-foreground text-sm">Foundations / charts</p>
79
+ <h1 className="text-4xl font-semibold tracking-tight">Chart colors</h1>
80
+ <p className="text-muted-foreground max-w-2xl text-base">
81
+ <code className="font-mono">chart-1</code> through{" "}
82
+ <code className="font-mono">chart-5</code> are an ordinal palette for arbitrary
83
+ categorical series (e.g. one line per country) — there's no fixed meaning to a given slot,
84
+ so they're assigned by index, not by what the data represents. That's different from{" "}
85
+ <code className="font-mono">chart-positive</code>/
86
+ <code className="font-mono">chart-negative</code>/
87
+ <code className="font-mono">chart-neutral</code> below, which carry real meaning
88
+ (up/down/flat) and should stay semantic in any chart that shows a directional delta.
89
+ </p>
90
+ </header>
91
+
92
+ <section className="grid gap-4">
93
+ <h2 className="text-2xl font-semibold">Series swatches</h2>
94
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
95
+ {SERIES.map(({ key, label }) => (
96
+ <div
97
+ key={key}
98
+ className="border-border grid min-h-20 content-end rounded-md border p-3"
99
+ style={{ backgroundColor: CHART_CSS_VARS[key] }}
100
+ >
101
+ <span className="bg-background text-foreground w-fit rounded px-1 font-mono text-xs">
102
+ {label}
103
+ </span>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ </section>
108
+
109
+ <section className="border-border bg-card grid gap-5 rounded-xl border p-6">
110
+ <h2 className="text-2xl font-semibold">Grouped bar chart (mock)</h2>
111
+ <p className="text-muted-foreground max-w-2xl text-sm">
112
+ Three unrelated categories, five series each — a stress test for how distinguishable the
113
+ palette stays side by side.
114
+ </p>
115
+ <div className="flex items-end gap-8" style={{ height: 220 }}>
116
+ {BAR_DATA.map((group, groupIndex) => (
117
+ // biome-ignore lint/suspicious/noArrayIndexKey: static mock dataset
118
+ <div key={groupIndex} className="flex h-full items-end gap-1.5">
119
+ {group.map((value, seriesIndex) => (
120
+ <div
121
+ key={SERIES[seriesIndex].key}
122
+ className="w-6 rounded-t-sm"
123
+ style={{
124
+ height: `${value}%`,
125
+ backgroundColor: CHART_CSS_VARS[SERIES[seriesIndex].key],
126
+ }}
127
+ title={SERIES[seriesIndex].label}
128
+ />
129
+ ))}
130
+ </div>
131
+ ))}
132
+ </div>
133
+ <div className="flex flex-wrap gap-4">
134
+ {SERIES.map(({ key, label }) => (
135
+ <div key={key} className="flex items-center gap-2">
136
+ <span
137
+ className="size-2.5 rounded-full"
138
+ style={{ backgroundColor: CHART_CSS_VARS[key] }}
139
+ aria-hidden="true"
140
+ />
141
+ <span className="text-muted-foreground font-mono text-xs">{label}</span>
142
+ </div>
143
+ ))}
144
+ </div>
145
+ </section>
146
+
147
+ <section className="border-border bg-card grid gap-5 rounded-xl border p-6">
148
+ <h2 className="text-2xl font-semibold">Multi-line chart (mock)</h2>
149
+ <svg viewBox="0 0 100 100" preserveAspectRatio="none" className="h-56 w-full">
150
+ <title>Multi-series line chart using the chart-1..5 palette</title>
151
+ {Object.entries(LINE_POINTS).map(([key, points]) => (
152
+ <polyline
153
+ key={key}
154
+ fill="none"
155
+ strokeWidth={1.5}
156
+ vectorEffect="non-scaling-stroke"
157
+ stroke={CHART_CSS_VARS[key as keyof typeof CHART_CSS_VARS]}
158
+ points={points.map(([x, y]) => `${x},${y}`).join(" ")}
159
+ />
160
+ ))}
161
+ </svg>
162
+ </section>
163
+
164
+ <section className="grid gap-4">
165
+ <h2 className="text-2xl font-semibold">Semantic status colors</h2>
166
+ <p className="text-muted-foreground max-w-2xl text-sm">
167
+ Use these — not a numbered slot — whenever a value has an inherent direction.
168
+ </p>
169
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
170
+ {STATUS.map(({ key, label, example }) => (
171
+ <div key={key} className="border-border grid gap-2 rounded-md border p-3">
172
+ <div className="flex items-center gap-2">
173
+ <span
174
+ className="size-2.5 rounded-full"
175
+ style={{ backgroundColor: CHART_CSS_VARS[key] }}
176
+ aria-hidden="true"
177
+ />
178
+ <span className="text-muted-foreground font-mono text-xs">{label}</span>
179
+ </div>
180
+ <span className="text-lg font-semibold" style={{ color: CHART_CSS_VARS[key] }}>
181
+ {example}
182
+ </span>
183
+ </div>
184
+ ))}
185
+ </div>
186
+ </section>
187
+ </main>
188
+ ),
189
+ };
@@ -12,11 +12,29 @@ export const SystemAppearance: Story = {
12
12
  <main className="bg-background text-foreground grid min-h-screen gap-8 p-6 md:p-10">
13
13
  <header className="grid gap-2">
14
14
  <h1 className="text-2xl font-semibold">System color roles</h1>
15
- <p className="text-muted-foreground max-w-prose text-sm">These semantic roles resolve from the operating system’s light or dark appearance. CSS is the single source of their values.</p>
15
+ <p className="text-muted-foreground max-w-prose text-sm">
16
+ These semantic roles resolve from the operating system’s light or dark appearance. CSS is
17
+ the single source of their values.
18
+ </p>
16
19
  </header>
17
- <div className="flex flex-wrap gap-2"><Button>Primary action</Button><Button variant="secondary">Secondary action</Button><Button variant="outline">Outline action</Button><Button variant="destructive">Delete item</Button></div>
20
+ <div className="flex flex-wrap gap-2">
21
+ <Button>Primary action</Button>
22
+ <Button variant="secondary">Secondary action</Button>
23
+ <Button variant="outline">Outline action</Button>
24
+ <Button variant="destructive">Delete item</Button>
25
+ </div>
18
26
  <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
19
- {colorTokenNames.map((token) => <div key={token} className="border-border grid min-h-20 content-end rounded-md border p-3" style={{ backgroundColor: `var(--color-${token})` }}><span className="bg-background text-foreground w-fit rounded px-1 font-mono text-xs">{token}</span></div>)}
27
+ {colorTokenNames.map((token) => (
28
+ <div
29
+ key={token}
30
+ className="border-border grid min-h-20 content-end rounded-md border p-3"
31
+ style={{ backgroundColor: `var(--color-${token})` }}
32
+ >
33
+ <span className="bg-background text-foreground w-fit rounded px-1 font-mono text-xs">
34
+ {token}
35
+ </span>
36
+ </div>
37
+ ))}
20
38
  </div>
21
39
  </main>
22
40
  ),
@@ -0,0 +1,15 @@
1
+ import { colorTokenNames, type ColorToken } from "./index";
2
+
3
+ /**
4
+ * Color tokens as CSS `var()` references, for consumers whose runtime resolves
5
+ * the CSS cascade (i.e. web). Kept out of `./tokens` because React Native has
6
+ * no cascade — a `var(--token)` string there fails silently, rendering as an
7
+ * unparseable color rather than throwing.
8
+ */
9
+ export const colors = colorTokenNames.reduce(
10
+ (accumulator, token) => {
11
+ accumulator[token] = `var(--${token})`;
12
+ return accumulator;
13
+ },
14
+ {} as Record<ColorToken, `var(--${string})`>,
15
+ );
@@ -6,27 +6,47 @@ type Story = StoryObj<typeof meta>;
6
6
 
7
7
  export const SpacingAndType: Story = {
8
8
  render: () => (
9
- <main className="grid min-h-screen gap-10 bg-background p-6 text-foreground md:p-10">
9
+ <main className="bg-background text-foreground grid min-h-screen gap-10 p-6 md:p-10">
10
10
  <section className="grid gap-6">
11
11
  <header className="grid gap-2">
12
- <p className="text-sm text-muted-foreground">Foundations / rhythm</p>
12
+ <p className="text-muted-foreground text-sm">Foundations / rhythm</p>
13
13
  <h1 className="text-4xl font-semibold tracking-tight">Tailwind defaults, directly.</h1>
14
- <p className="max-w-2xl text-base text-muted-foreground">The specimen uses the shared Tailwind spacing and type scales.</p>
14
+ <p className="text-muted-foreground max-w-2xl text-base">
15
+ The specimen uses the shared Tailwind spacing and type scales.
16
+ </p>
15
17
  </header>
16
18
  <div className="grid gap-2">
17
19
  {[1, 2, 3, 4, 6, 8, 12, 16].map((size) => (
18
20
  <div key={size} className="flex items-center gap-3">
19
- <span className="w-12 font-mono text-xs tabular-nums text-muted-foreground">{size}</span>
20
- <div className="h-3 rounded-sm bg-accent" style={{ width: `calc(var(--spacing) * ${size})` }} aria-hidden="true" />
21
+ <span className="text-muted-foreground w-12 font-mono text-xs tabular-nums">
22
+ {size}
23
+ </span>
24
+ <div
25
+ className="bg-accent h-3 rounded-sm"
26
+ style={{ width: `calc(var(--spacing) * ${size})` }}
27
+ aria-hidden="true"
28
+ />
21
29
  </div>
22
30
  ))}
23
31
  </div>
24
32
  </section>
25
- <section className="grid gap-5 rounded-xl border border-border bg-card p-6">
33
+ <section className="border-border bg-card grid gap-5 rounded-xl border p-6">
26
34
  <h2 className="text-2xl font-semibold">Typography scale</h2>
27
35
  <div className="grid gap-4">
28
- {(["text-xs", "text-sm", "text-base", "text-lg", "text-xl", "text-2xl", "text-4xl"] as const).map((size) => (
29
- <p key={size} className={`${size} text-foreground`}>{size}</p>
36
+ {(
37
+ [
38
+ "text-xs",
39
+ "text-sm",
40
+ "text-base",
41
+ "text-lg",
42
+ "text-xl",
43
+ "text-2xl",
44
+ "text-4xl",
45
+ ] as const
46
+ ).map((size) => (
47
+ <p key={size} className={`${size} text-foreground`}>
48
+ {size}
49
+ </p>
30
50
  ))}
31
51
  </div>
32
52
  </section>
@@ -36,10 +56,26 @@ export const SpacingAndType: Story = {
36
56
 
37
57
  export const Surfaces: Story = {
38
58
  render: () => (
39
- <main className="grid min-h-screen gap-6 bg-background p-6 text-foreground md:p-10">
40
- <header className="grid gap-2"><p className="text-sm text-muted-foreground">Foundations / surfaces</p><h1 className="text-4xl font-semibold tracking-tight">Standard semantic layers.</h1></header>
59
+ <main className="bg-background text-foreground grid min-h-screen gap-6 p-6 md:p-10">
60
+ <header className="grid gap-2">
61
+ <p className="text-muted-foreground text-sm">Foundations / surfaces</p>
62
+ <h1 className="text-4xl font-semibold tracking-tight">Standard semantic layers.</h1>
63
+ </header>
41
64
  <div className="grid gap-4 md:grid-cols-2">
42
- {[["Background", "bg-background"], ["Card", "bg-card"], ["Popover", "bg-popover"], ["Muted", "bg-muted"]].map(([name, className]) => <div key={name} className={`${className} grid min-h-40 content-start gap-2 rounded-xl border border-border p-5`}><h2 className="text-lg font-semibold">{name}</h2><p className="text-sm text-muted-foreground">{className}</p></div>)}
65
+ {[
66
+ ["Background", "bg-background"],
67
+ ["Card", "bg-card"],
68
+ ["Popover", "bg-popover"],
69
+ ["Muted", "bg-muted"],
70
+ ].map(([name, className]) => (
71
+ <div
72
+ key={name}
73
+ className={`${className} border-border grid min-h-40 content-start gap-2 rounded-xl border p-5`}
74
+ >
75
+ <h2 className="text-lg font-semibold">{name}</h2>
76
+ <p className="text-muted-foreground text-sm">{className}</p>
77
+ </div>
78
+ ))}
43
79
  </div>
44
80
  </main>
45
81
  ),
@@ -47,12 +83,26 @@ export const Surfaces: Story = {
47
83
 
48
84
  export const Motion: Story = {
49
85
  render: () => (
50
- <main className="grid min-h-screen gap-8 bg-background p-6 text-foreground md:p-10">
51
- <header className="grid gap-2"><p className="text-sm text-muted-foreground">Foundations / motion</p><h1 className="text-4xl font-semibold tracking-tight">Motion stays standard and reducible.</h1></header>
86
+ <main className="bg-background text-foreground grid min-h-screen gap-8 p-6 md:p-10">
87
+ <header className="grid gap-2">
88
+ <p className="text-muted-foreground text-sm">Foundations / motion</p>
89
+ <h1 className="text-4xl font-semibold tracking-tight">
90
+ Motion stays standard and reducible.
91
+ </h1>
92
+ </header>
52
93
  <div className="grid gap-4 sm:grid-cols-3">
53
- <div className="motion-safe:animate-in motion-safe:fade-in-0 grid min-h-32 place-content-center gap-2 rounded-xl border border-border bg-card p-5 motion-reduce:animate-none"><span className="text-lg font-semibold">Enter</span><span className="text-sm text-muted-foreground">150ms</span></div>
54
- <button className="grid min-h-32 place-content-center gap-2 rounded-xl border border-border bg-card p-5 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"><span className="text-lg font-semibold">Press</span><span className="text-sm text-muted-foreground">System focus</span></button>
55
- <div className="grid min-h-32 place-content-center gap-2 rounded-xl border border-border bg-card p-5 motion-safe:animate-pulse motion-reduce:animate-none"><span className="text-lg font-semibold">Loading</span><span className="text-sm text-muted-foreground">Stable geometry</span></div>
94
+ <div className="motion-safe:animate-in motion-safe:fade-in-0 border-border bg-card grid min-h-32 place-content-center gap-2 rounded-xl border p-5 motion-reduce:animate-none">
95
+ <span className="text-lg font-semibold">Enter</span>
96
+ <span className="text-muted-foreground text-sm">150ms</span>
97
+ </div>
98
+ <button className="border-border bg-card hover:bg-accent focus-visible:ring-ring grid min-h-32 place-content-center gap-2 rounded-xl border p-5 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none motion-reduce:transition-none">
99
+ <span className="text-lg font-semibold">Press</span>
100
+ <span className="text-muted-foreground text-sm">System focus</span>
101
+ </button>
102
+ <div className="border-border bg-card grid min-h-32 place-content-center gap-2 rounded-xl border p-5 motion-safe:animate-pulse motion-reduce:animate-none">
103
+ <span className="text-lg font-semibold">Loading</span>
104
+ <span className="text-muted-foreground text-sm">Stable geometry</span>
105
+ </div>
56
106
  </div>
57
107
  </main>
58
108
  ),