@ponti-studios/ui 1.6.0 → 1.8.0

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 (79) hide show
  1. package/README.md +7 -11
  2. package/package.json +6 -31
  3. package/src/components/feedback/alert.tsx +8 -4
  4. package/src/components/feedback/index.ts +0 -4
  5. package/src/components/feedback/progress.stories.tsx +15 -0
  6. package/src/components/feedback/progress.tsx +14 -3
  7. package/src/components/feedback/update-guard.stories.tsx +4 -8
  8. package/src/components/feedback/update-guard.tsx +41 -272
  9. package/src/components/filters/filter-select.tsx +3 -5
  10. package/src/components/forms/date-picker.stories.tsx +158 -18
  11. package/src/components/forms/date-picker.tsx +286 -64
  12. package/src/components/forms/date-utils.ts +7 -2
  13. package/src/components/forms/drop-zone.tsx +11 -12
  14. package/src/components/forms/index.ts +2 -6
  15. package/src/components/forms/passkey-management.stories.tsx +8 -4
  16. package/src/components/forms/passkey-management.tsx +78 -42
  17. package/src/components/forms/radio-group.stories.tsx +3 -5
  18. package/src/components/forms/radio-group.tsx +2 -2
  19. package/src/components/forms/select.stories.tsx +16 -13
  20. package/src/components/forms/textarea.stories.tsx +1 -1
  21. package/src/components/layout/index.ts +2 -0
  22. package/src/components/layout/particle-background.stories.tsx +38 -0
  23. package/src/components/layout/particle-background.tsx +378 -0
  24. package/src/components/layout/scroll-area.stories.tsx +12 -4
  25. package/src/components/layout/scroll-area.tsx +41 -4
  26. package/src/components/navigation/tabs.stories.tsx +0 -21
  27. package/src/components/overlays/dialog.stories.tsx +38 -1
  28. package/src/components/overlays/dialog.tsx +53 -12
  29. package/src/components/overlays/index.ts +0 -11
  30. package/src/components/primitives/avatar-placeholder.svg +13 -0
  31. package/src/components/primitives/avatar.stories.tsx +2 -1
  32. package/src/components/primitives/badge.stories.tsx +13 -3
  33. package/src/components/primitives/badge.tsx +1 -1
  34. package/src/components/primitives/button.tsx +1 -1
  35. package/src/components/primitives/card.tsx +6 -2
  36. package/src/components/primitives/index.ts +0 -3
  37. package/src/index.ts +1 -3
  38. package/src/lib/index.ts +2 -3
  39. package/src/styles/animations.css +62 -0
  40. package/src/styles/index.css +31 -4
  41. package/src/styles/tokens/color-systems.stories.tsx +173 -25
  42. package/src/styles/tokens/generated/dark.colors.generated.ts +20 -12
  43. package/src/styles/tokens/generated/dark.css +20 -12
  44. package/src/styles/tokens/generated/light.colors.generated.ts +19 -11
  45. package/src/styles/tokens/generated/light.css +19 -11
  46. package/src/styles/tokens/index.ts +0 -3
  47. package/src/styles/tokens/scripts/check-source.mjs +7 -0
  48. package/src/styles/tokens/source/dark.tokens.json +44 -12
  49. package/src/styles/tokens/source/light.tokens.json +43 -11
  50. package/src/styles/tokens/typography.stories.tsx +157 -0
  51. package/src/components/feedback/file-upload-status.stories.tsx +0 -57
  52. package/src/components/feedback/file-upload-status.tsx +0 -160
  53. package/src/components/feedback/progress-bar.stories.tsx +0 -39
  54. package/src/components/feedback/progress-bar.tsx +0 -22
  55. package/src/components/forms/calendar.stories.tsx +0 -30
  56. package/src/components/forms/calendar.tsx +0 -75
  57. package/src/components/forms/date-field.stories.tsx +0 -202
  58. package/src/components/forms/date-field.tsx +0 -179
  59. package/src/components/overlays/alert-dialog.stories.tsx +0 -45
  60. package/src/components/overlays/alert-dialog.tsx +0 -116
  61. package/src/components/primitives/label.stories.tsx +0 -29
  62. package/src/components/primitives/label.tsx +0 -20
  63. package/src/components/primitives/status-badge.stories.tsx +0 -52
  64. package/src/components/primitives/status-badge.tsx +0 -33
  65. package/src/lib/create-memory-storage.ts +0 -24
  66. package/src/native/card.tsx +0 -36
  67. package/src/native/icon-button.tsx +0 -41
  68. package/src/native/index.ts +0 -38
  69. package/src/native/list-row.tsx +0 -67
  70. package/src/native/make-styles.ts +0 -32
  71. package/src/native/shadows.ts +0 -38
  72. package/src/native/text-field.tsx +0 -34
  73. package/src/native/uniwind.d.ts +0 -1
  74. package/src/native/use-theme-colors.ts +0 -19
  75. package/src/styles/tokens/foundations.stories.tsx +0 -109
  76. package/src/styles/tokens/generated/native.generated.ts +0 -266
  77. package/src/styles/tokens/normalize-native.ts +0 -88
  78. package/src/styles/tokens/source/native.tokens.json +0 -204
  79. package/src/styles/tokens/style-dictionary.native.mjs +0 -26
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title desc">
2
+ <title id="title">Portrait placeholder</title>
3
+ <desc id="desc">An illustrated portrait with warm coral and blue tones.</desc>
4
+ <rect width="256" height="256" fill="#dce8f2"/>
5
+ <circle cx="202" cy="54" r="34" fill="#ffb36b"/>
6
+ <path d="M0 214c36-34 74-47 112-39 37 8 63 34 79 81H0z" fill="#185a72"/>
7
+ <path d="M83 256c5-49 25-78 73-78 47 0 68 29 73 78z" fill="#ff704d"/>
8
+ <path d="M91 111c0-43 28-73 66-73s66 30 66 73c0 42-25 72-66 72s-66-30-66-72z" fill="#c97852"/>
9
+ <path d="M88 111c-2-46 25-79 70-79 42 0 68 29 68 72-14-16-31-26-51-30-23 27-52 39-87 37z" fill="#243746"/>
10
+ <circle cx="128" cy="116" r="5" fill="#243746"/>
11
+ <circle cx="176" cy="116" r="5" fill="#243746"/>
12
+ <path d="M137 145c8 6 17 6 25 0" fill="none" stroke="#243746" stroke-linecap="round" stroke-width="5"/>
13
+ </svg>
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
 
3
3
  import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage } from "./avatar";
4
+ import avatarPlaceholder from "./avatar-placeholder.svg";
4
5
 
5
6
  const meta = {
6
7
  title: "Primitives/Avatar",
@@ -14,7 +15,7 @@ type Story = StoryObj<typeof meta>;
14
15
  export const WithImage: Story = {
15
16
  render: () => (
16
17
  <Avatar>
17
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
18
+ <AvatarImage src={avatarPlaceholder} alt="Illustrated portrait" />
18
19
  <AvatarFallback>CN</AvatarFallback>
19
20
  </Avatar>
20
21
  ),
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
 
3
+ import { LucideAlertCircle, LucideDiamond, LucideNut } from "lucide-react";
3
4
  import { Badge } from "./badge";
4
5
 
5
6
  const meta = {
@@ -24,9 +25,18 @@ export const Default: Story = {
24
25
  export const Variants: Story = {
25
26
  render: () => (
26
27
  <div className="flex flex-wrap gap-3">
27
- <Badge variant="default">Default</Badge>
28
- <Badge variant="secondary">Secondary</Badge>
29
- <Badge variant="destructive">Destructive</Badge>
28
+ <Badge variant="default">
29
+ <LucideDiamond className="size-3" />
30
+ Default
31
+ </Badge>
32
+ <Badge variant="secondary">
33
+ <LucideNut className="size-3" />
34
+ Secondary
35
+ </Badge>
36
+ <Badge variant="destructive">
37
+ <LucideAlertCircle className="size-3" />
38
+ Destructive
39
+ </Badge>
30
40
  <Badge variant="outline">Outline</Badge>
31
41
  </div>
32
42
  ),
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
 
6
6
  const badgeVariants = cva(
7
- "state-settle inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded border border-transparent px-1.5 py-px text-xs font-medium [&>svg]:size-3 [&>svg]:pointer-events-none aria-invalid:border-destructive aria-invalid:ring-destructive/20",
7
+ "state-settle inline-flex w-fit shrink-0 items-center justify-center gap-2 overflow-hidden whitespace-nowrap rounded border border-transparent px-1.5 py-px text-xs font-medium [&>svg]:size-3 [&>svg]:pointer-events-none aria-invalid:border-destructive aria-invalid:ring-destructive/20",
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -20,7 +20,7 @@ const buttonVariants = cva(
20
20
  outline: "border-input bg-background hover:bg-muted hover:text-muted-foreground",
21
21
  secondary: "border-input bg-secondary text-secondary-foreground hover:bg-secondary/80",
22
22
  ghost: "border-transparent bg-transparent hover:bg-muted hover:text-muted-foreground",
23
- link: "border-transparent bg-transparent px-0 text-primary underline-offset-4 hover:underline",
23
+ link: "border-transparent bg-transparent px-0 text-accent-text underline-offset-4 hover:underline",
24
24
  },
25
25
  size: {
26
26
  sm: "min-h-8 px-2.5 text-xs",
@@ -25,8 +25,12 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
25
25
  );
26
26
  }
27
27
 
28
- function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
29
- return <div data-slot="card-title" className={cn("text-xl", className)} {...props} />;
28
+ function CardTitle({ className, ...props }: React.ComponentProps<"h3">) {
29
+ return (
30
+ <h3 data-slot="card-title" className={cn("text-xl", className)} {...props}>
31
+ {props.children}
32
+ </h3>
33
+ );
30
34
  }
31
35
 
32
36
  function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
@@ -10,6 +10,3 @@ export {
10
10
  CardHeader,
11
11
  CardTitle,
12
12
  } from "./card";
13
- export { Label } from "./label";
14
- export { StatusBadge } from "./status-badge";
15
- export type { StatusBadgeConfig, StatusBadgeProps } from "./status-badge";
package/src/index.ts CHANGED
@@ -9,17 +9,16 @@ export { useMediaQuery } from "./hooks/use-media-query";
9
9
  export { useIsMobile } from "./hooks/use-mobile";
10
10
  export { normalizeOtp, OTP_LENGTH } from "./lib/auth";
11
11
  export { copyToClipboard } from "./lib/clipboard";
12
- export { createMemoryStorage } from "./lib/create-memory-storage";
13
12
  export { cn } from "./lib/utils";
14
13
  export { colors } from "./styles/tokens/css-vars";
15
14
  export {
16
15
  animations,
16
+ breakpoints,
17
17
  colorThemes,
18
18
  colorTokenNames,
19
19
  containers,
20
20
  easing,
21
21
  fontFamilies,
22
- fontFamiliesNative,
23
22
  fontWeights,
24
23
  leading,
25
24
  radii,
@@ -29,7 +28,6 @@ export {
29
28
  textSizes,
30
29
  tracking,
31
30
  transitionDurations,
32
- breakpoints,
33
31
  zIndices,
34
32
  } from "./styles/tokens/index";
35
33
  export type {
package/src/lib/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { cn } from "./utils";
2
- export { copyToClipboard } from "./clipboard";
3
- export { createMemoryStorage } from "./create-memory-storage";
4
1
  export { normalizeOtp, OTP_LENGTH } from "./auth";
2
+ export { copyToClipboard } from "./clipboard";
3
+ export { cn } from "./utils";
@@ -90,6 +90,14 @@
90
90
  opacity: 0;
91
91
  }
92
92
  }
93
+ @keyframes progress-shimmer {
94
+ from {
95
+ transform: translateX(-100%);
96
+ }
97
+ to {
98
+ transform: translateX(100%);
99
+ }
100
+ }
93
101
 
94
102
  .data-open\:animate-in[data-open],
95
103
  .data-open\:animate-in[data-state="open"] {
@@ -125,6 +133,44 @@
125
133
  animation: sheet-slide-out-bottom 200ms var(--ease-drawer);
126
134
  }
127
135
 
136
+ [data-slot="dialog-overlay"][data-open],
137
+ [data-slot="dialog-overlay"][data-state="open"] {
138
+ animation: fade-in 200ms var(--ease-out);
139
+ }
140
+ [data-slot="dialog-overlay"][data-closed],
141
+ [data-slot="dialog-overlay"][data-state="closed"] {
142
+ /* Must stay in lockstep with [data-slot="dialog-content"]'s close duration below (both 200ms) —
143
+ Base UI gates the whole portal's unmount on this element's own close-animation duration, not
144
+ the content panel's, so a mismatch here yanks the sliding/zooming panel off mid-animation. */
145
+ animation: fade-out 200ms var(--ease-in);
146
+ }
147
+
148
+ /* Mobile: bottom sheet, slides up from the edge it's anchored to. */
149
+ [data-slot="dialog-content"][data-open],
150
+ [data-slot="dialog-content"][data-state="open"] {
151
+ animation: sheet-slide-in-bottom 300ms var(--ease-drawer);
152
+ }
153
+ [data-slot="dialog-content"][data-closed],
154
+ [data-slot="dialog-content"][data-state="closed"] {
155
+ animation: sheet-slide-out-bottom 200ms var(--ease-drawer);
156
+ }
157
+
158
+ /* Desktop: centered card, fades and scales in place instead of sliding. */
159
+ @media (min-width: 640px) {
160
+ [data-slot="dialog-content"][data-open],
161
+ [data-slot="dialog-content"][data-state="open"] {
162
+ animation:
163
+ fade-in 150ms var(--ease-out),
164
+ zoom-in 150ms var(--ease-out);
165
+ }
166
+ [data-slot="dialog-content"][data-closed],
167
+ [data-slot="dialog-content"][data-state="closed"] {
168
+ animation:
169
+ fade-out 200ms var(--ease-in),
170
+ zoom-out 200ms var(--ease-in);
171
+ }
172
+ }
173
+
128
174
  @media (min-width: 768px) {
129
175
  [data-slot="sheet-content"][data-open],
130
176
  [data-slot="sheet-content"][data-state="open"] {
@@ -162,4 +208,20 @@
162
208
  [data-slot="sheet-content"][data-state="closed"] {
163
209
  animation: fade-out 120ms var(--ease-in) !important;
164
210
  }
211
+ [data-slot="dialog-overlay"][data-open],
212
+ [data-slot="dialog-overlay"][data-state="open"] {
213
+ animation: fade-in 120ms var(--ease-out) !important;
214
+ }
215
+ [data-slot="dialog-overlay"][data-closed],
216
+ [data-slot="dialog-overlay"][data-state="closed"] {
217
+ animation: fade-out 120ms var(--ease-in) !important;
218
+ }
219
+ [data-slot="dialog-content"][data-open],
220
+ [data-slot="dialog-content"][data-state="open"] {
221
+ animation: fade-in var(--duration-150) var(--ease-out) !important;
222
+ }
223
+ [data-slot="dialog-content"][data-closed],
224
+ [data-slot="dialog-content"][data-state="closed"] {
225
+ animation: fade-out 120ms var(--ease-in) !important;
226
+ }
165
227
  }
@@ -29,6 +29,7 @@
29
29
  --animate-fade-out: fade-out 120ms ease-in;
30
30
  --animate-zoom-in: zoom-in 150ms ease-out;
31
31
  --animate-zoom-out: zoom-out 120ms ease-in;
32
+ --animate-progress-shimmer: progress-shimmer 1.1s linear infinite;
32
33
  }
33
34
 
34
35
  @utility fade-in-0 {
@@ -53,16 +54,19 @@
53
54
  --color-foreground: var(--text-primary);
54
55
  --color-card: var(--card);
55
56
  --color-card-foreground: var(--text-primary);
56
- --color-popover: var(--background);
57
+ --color-popover: var(--popover);
57
58
  --color-popover-foreground: var(--text-primary);
58
59
  --color-primary: var(--primary);
59
60
  --color-primary-foreground: var(--primary-foreground);
60
- --color-secondary: var(--card);
61
+ --color-secondary: var(--secondary);
61
62
  --color-secondary-foreground: var(--text-primary);
62
63
  --color-muted: var(--muted);
63
64
  --color-muted-foreground: var(--text-secondary);
64
- --color-accent: var(--primary);
65
- --color-accent-foreground: var(--primary-foreground);
65
+ --color-accent: var(--accent);
66
+ --color-accent-foreground: var(--accent-foreground);
67
+ /* Distinct from --color-accent: this pairs with the page background for
68
+ * standalone text/icons/borders (inline links, upload affordances). */
69
+ --color-accent-text: var(--text-accent);
66
70
  --color-destructive: var(--destructive);
67
71
  --color-destructive-foreground: var(--destructive-foreground);
68
72
  /* Distinct from --color-destructive: that pairs with white for solid
@@ -71,7 +75,11 @@
71
75
  * messages, alert copy) and must differ per theme to stay AA-compliant. */
72
76
  --color-destructive-text: var(--text-destructive);
73
77
  --color-success: var(--success);
78
+ --color-success-foreground: var(--success-foreground);
79
+ --color-success-text: var(--text-success);
74
80
  --color-warning: var(--warning);
81
+ --color-warning-foreground: var(--warning-foreground);
82
+ --color-warning-text: var(--text-warning);
75
83
  --color-border: var(--border-default);
76
84
  --color-input: var(--border-default);
77
85
  --color-ring: var(--focus-ring);
@@ -178,6 +186,15 @@
178
186
  @utility scroll-fade-none {
179
187
  --scroll-fade-mask: none;
180
188
  }
189
+ /* Hides the native scrollbar so a custom overlay affordance (ScrollArea's
190
+ * Scrollbar/Thumb) can stand in for it. */
191
+ @utility scrollbar-hide {
192
+ scrollbar-width: none;
193
+ -ms-overflow-style: none;
194
+ &::-webkit-scrollbar {
195
+ display: none;
196
+ }
197
+ }
181
198
  @utility h-screen {
182
199
  height: 100vh;
183
200
  height: 100dvh;
@@ -279,6 +296,16 @@
279
296
  cursor: pointer;
280
297
  font-family: inherit;
281
298
  }
299
+ label {
300
+ color: var(--color-foreground);
301
+ font-size: var(--text-sm);
302
+ font-weight: var(--font-weight-medium);
303
+ line-height: 1;
304
+ }
305
+ :where(.peer:disabled) ~ label {
306
+ cursor: not-allowed;
307
+ opacity: 0.7;
308
+ }
282
309
  h1,
283
310
  h2,
284
311
  h3,
@@ -2,40 +2,188 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
2
2
 
3
3
  import { Button } from "../../components/primitives/button";
4
4
  import { colors } from "./css-vars";
5
- import { colorTokenNames } from "./index";
5
+ import type { ColorToken } from "./index";
6
6
 
7
7
  const meta: Meta = { title: "Foundations/Colors", parameters: { layout: "fullscreen" } };
8
8
  export default meta;
9
9
  type Story = StoryObj<typeof meta>;
10
10
 
11
+ type Role = {
12
+ token: ColorToken;
13
+ purpose: string;
14
+ foreground?: ColorToken;
15
+ };
16
+
17
+ const surfaces: Role[] = [
18
+ { token: "background", purpose: "The page and app shell surface." },
19
+ { token: "card", purpose: "Content containers and grouped information." },
20
+ { token: "popover", purpose: "Dropdowns, dialogs, and floating panels." },
21
+ { token: "muted", purpose: "Quiet, disabled, and loading surfaces." },
22
+ { token: "secondary", purpose: "Secondary actions and selectable chips." },
23
+ {
24
+ token: "accent",
25
+ purpose: "Tinted emphasis and hover surfaces.",
26
+ foreground: "accent-foreground",
27
+ },
28
+ ];
29
+
30
+ const text: Role[] = [
31
+ { token: "text-primary", purpose: "Primary reading text." },
32
+ { token: "text-secondary", purpose: "Supporting labels and descriptions." },
33
+ { token: "tertiary", purpose: "Metadata, timestamps, and placeholders." },
34
+ { token: "text-accent", purpose: "Standalone links and accent affordances." },
35
+ { token: "text-success", purpose: "Standalone positive status text and icons." },
36
+ { token: "text-warning", purpose: "Standalone cautionary status text and icons." },
37
+ { token: "text-destructive", purpose: "Standalone error and destructive text." },
38
+ ];
39
+
40
+ const actions: Role[] = [
41
+ {
42
+ token: "primary",
43
+ purpose: "The strongest action on a screen.",
44
+ foreground: "primary-foreground",
45
+ },
46
+ {
47
+ token: "destructive",
48
+ purpose: "Irreversible or dangerous actions.",
49
+ foreground: "destructive-foreground",
50
+ },
51
+ {
52
+ token: "success",
53
+ purpose: "Positive indicators and confirmed states.",
54
+ foreground: "success-foreground",
55
+ },
56
+ {
57
+ token: "warning",
58
+ purpose: "Caution indicators and attention states.",
59
+ foreground: "warning-foreground",
60
+ },
61
+ ];
62
+
63
+ const system: Role[] = [
64
+ { token: "border-default", purpose: "Default borders, inputs, and dividers." },
65
+ { token: "focus-ring", purpose: "Keyboard focus indication." },
66
+ { token: "overlay-scrim", purpose: "Backdrop behind modal surfaces." },
67
+ ];
68
+
69
+ const charts: Role[] = [
70
+ { token: "chart-1", purpose: "Categorical data series one." },
71
+ { token: "chart-2", purpose: "Categorical data series two." },
72
+ { token: "chart-3", purpose: "Categorical data series three." },
73
+ { token: "chart-4", purpose: "Categorical data series four." },
74
+ { token: "chart-5", purpose: "Categorical data series five." },
75
+ { token: "chart-positive", purpose: "Positive trend in a visualization." },
76
+ { token: "chart-negative", purpose: "Negative trend in a visualization." },
77
+ { token: "chart-neutral", purpose: "Neutral or baseline trend." },
78
+ ];
79
+
80
+ function RoleCard({ token, purpose, foreground }: Role) {
81
+ const isText = token.startsWith("text-") || token === "tertiary";
82
+ const isSurface = surfaces.some((role) => role.token === token);
83
+ const swatchOnly = !isText && !isSurface && !foreground;
84
+ const background = isText ? "background" : token;
85
+ const contentColor = isText ? token : (foreground ?? "text-primary");
86
+
87
+ return (
88
+ <article className="border-border overflow-hidden rounded-md border bg-card">
89
+ <div
90
+ className="flex min-h-24 items-end p-3"
91
+ style={{
92
+ backgroundColor: colors[swatchOnly ? "card" : background],
93
+ color: colors[swatchOnly ? "text-primary" : contentColor],
94
+ }}
95
+ >
96
+ {swatchOnly ? (
97
+ <span
98
+ aria-hidden="true"
99
+ className="h-full min-h-16 w-full rounded-sm"
100
+ style={{ backgroundColor: colors[token] }}
101
+ />
102
+ ) : (
103
+ <span className="font-mono text-xs">{token}</span>
104
+ )}
105
+ </div>
106
+ <div className="grid gap-1 p-3">
107
+ <code className="text-sm font-medium">--{token}</code>
108
+ <p className="text-muted-foreground text-sm">{purpose}</p>
109
+ {foreground && (
110
+ <p className="text-muted-foreground text-xs">Paired content: --{foreground}</p>
111
+ )}
112
+ </div>
113
+ </article>
114
+ );
115
+ }
116
+
117
+ function RoleSection({
118
+ title,
119
+ description,
120
+ roles,
121
+ }: {
122
+ title: string;
123
+ description: string;
124
+ roles: Role[];
125
+ }) {
126
+ return (
127
+ <section className="grid gap-3">
128
+ <div className="grid gap-1">
129
+ <h2 className="text-lg font-semibold">{title}</h2>
130
+ <p className="text-muted-foreground text-sm">{description}</p>
131
+ </div>
132
+ <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
133
+ {roles.map((role) => (
134
+ <RoleCard key={role.token} {...role} />
135
+ ))}
136
+ </div>
137
+ </section>
138
+ );
139
+ }
140
+
11
141
  export const SystemAppearance: Story = {
12
142
  render: () => (
13
- <main className="bg-background text-foreground grid min-h-screen gap-8 p-6 md:p-10">
14
- <header className="grid gap-2">
15
- <h1 className="text-2xl font-semibold">System color roles</h1>
16
- <p className="text-muted-foreground max-w-prose text-sm">
17
- These semantic roles resolve from the operating system’s light or dark appearance. CSS is
18
- the single source of their values.
143
+ <main className="bg-background text-foreground grid min-h-screen gap-10 p-6 md:p-10">
144
+ <header className="grid max-w-3xl gap-2">
145
+ <p className="text-accent-text text-xs font-medium uppercase tracking-widest">
146
+ Foundations
19
147
  </p>
148
+ <h1 className="text-2xl font-semibold">Color roles</h1>
149
+ <p className="text-muted-foreground text-sm">
150
+ Every color has a job. These roles resolve from the operating system’s light or dark
151
+ appearance and are the only colors components should consume.
152
+ </p>
153
+ <div className="flex flex-wrap gap-2 pt-2">
154
+ <Button>Primary action</Button>
155
+ <Button variant="secondary">Secondary action</Button>
156
+ <Button variant="outline">Outline action</Button>
157
+ <Button variant="destructive">Delete item</Button>
158
+ </div>
20
159
  </header>
21
- <div className="flex flex-wrap gap-2">
22
- <Button>Primary action</Button>
23
- <Button variant="secondary">Secondary action</Button>
24
- <Button variant="outline">Outline action</Button>
25
- <Button variant="destructive">Delete item</Button>
26
- </div>
27
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
28
- {colorTokenNames.map((token) => (
29
- <div
30
- key={token}
31
- className="border-border grid min-h-20 content-end rounded-md border p-3"
32
- style={{ backgroundColor: colors[token] }}
33
- >
34
- <span className="bg-background text-foreground w-fit rounded px-1 font-mono text-xs">
35
- {token}
36
- </span>
37
- </div>
38
- ))}
160
+
161
+ <div className="grid gap-10">
162
+ <RoleSection
163
+ title="Surfaces"
164
+ description="Backgrounds establish hierarchy. Use the lightest appropriate surface and let spacing do the rest."
165
+ roles={surfaces}
166
+ />
167
+ <RoleSection
168
+ title="Text"
169
+ description="Text roles describe hierarchy or state. Use a text role when content sits directly on a surface."
170
+ roles={text}
171
+ />
172
+ <RoleSection
173
+ title="Actions and status"
174
+ description="Fill roles communicate action or state and always pair with their matching foreground."
175
+ roles={actions}
176
+ />
177
+ <RoleSection
178
+ title="System"
179
+ description="Structural colors support boundaries, focus, and modal layering without competing with content."
180
+ roles={system}
181
+ />
182
+ <RoleSection
183
+ title="Charts"
184
+ description="Chart roles are for data visualization only. They do not communicate interaction or severity in product UI."
185
+ roles={charts}
186
+ />
39
187
  </div>
40
188
  </main>
41
189
  ),
@@ -3,29 +3,37 @@
3
3
  */
4
4
 
5
5
  export default {
6
- background: "#111113",
7
- card: "#18191b",
8
- popover: "#212225",
9
- muted: "#18191b",
10
- "text-primary": "#edeef0",
11
- "text-secondary": "#b0b4ba",
12
- tertiary: "#777b84",
13
- primary: "#3e63dd",
6
+ background: "#171714",
7
+ card: "#1d1d19",
8
+ secondary: "#2b2b25",
9
+ popover: "#22221e",
10
+ muted: "#1d1d19",
11
+ "text-primary": "#ffffff",
12
+ "text-secondary": "#aaa79f",
13
+ tertiary: "#d0ccc2",
14
+ primary: "#178277",
15
+ accent: "#21433f",
14
16
  destructive: "#ce2c31",
15
17
  success: "#30a46c",
16
18
  warning: "#ffc53d",
19
+ "text-success": "#55d89a",
20
+ "text-warning": "#ffd86a",
21
+ "success-foreground": "#15110f",
22
+ "warning-foreground": "#15110f",
17
23
  "primary-foreground": "#ffffff",
24
+ "accent-foreground": "#ffffff",
18
25
  "destructive-foreground": "#ffffff",
19
26
  "text-destructive": "#ff9592",
20
- "border-default": "#43484e",
21
- "focus-ring": "#3e63dd",
27
+ "text-accent": "#1b988b",
28
+ "border-default": "#6b6b66",
29
+ "focus-ring": "#1b988b",
22
30
  "chart-1": "#3e63dd",
23
31
  "chart-2": "#6e56cf",
24
- "chart-3": "#12a594",
32
+ "chart-3": "#c73178",
25
33
  "chart-4": "#f76b15",
26
34
  "chart-5": "#696e77",
27
35
  "chart-positive": "#30a46c",
28
36
  "chart-negative": "#e5484d",
29
- "chart-neutral": "#5a6169",
37
+ "chart-neutral": "#878d94",
30
38
  "overlay-scrim": "#000000",
31
39
  };
@@ -3,29 +3,37 @@
3
3
  */
4
4
 
5
5
  :root {
6
- --background: #111113;
7
- --card: #18191b;
8
- --popover: #212225;
9
- --muted: #18191b;
10
- --text-primary: #edeef0;
11
- --text-secondary: #b0b4ba;
12
- --tertiary: #777b84;
13
- --primary: #3e63dd;
6
+ --background: #171714;
7
+ --card: #1d1d19;
8
+ --secondary: #2b2b25;
9
+ --popover: #22221e;
10
+ --muted: #1d1d19;
11
+ --text-primary: #ffffff;
12
+ --text-secondary: #aaa79f;
13
+ --tertiary: #d0ccc2;
14
+ --primary: #178277;
15
+ --accent: #21433f;
14
16
  --destructive: #ce2c31;
15
17
  --success: #30a46c;
16
18
  --warning: #ffc53d;
19
+ --text-success: #55d89a;
20
+ --text-warning: #ffd86a;
21
+ --success-foreground: #15110f;
22
+ --warning-foreground: #15110f;
17
23
  --primary-foreground: #ffffff;
24
+ --accent-foreground: #ffffff;
18
25
  --destructive-foreground: #ffffff;
19
26
  --text-destructive: #ff9592;
20
- --border-default: #43484e;
21
- --focus-ring: #3e63dd;
27
+ --text-accent: #1b988b;
28
+ --border-default: #6b6b66;
29
+ --focus-ring: #1b988b;
22
30
  --chart-1: #3e63dd;
23
31
  --chart-2: #6e56cf;
24
- --chart-3: #12a594;
32
+ --chart-3: #c73178;
25
33
  --chart-4: #f76b15;
26
34
  --chart-5: #696e77;
27
35
  --chart-positive: #30a46c;
28
36
  --chart-negative: #e5484d;
29
- --chart-neutral: #5a6169;
37
+ --chart-neutral: #878d94;
30
38
  --overlay-scrim: #000000;
31
39
  }
@@ -3,25 +3,33 @@
3
3
  */
4
4
 
5
5
  export default {
6
- background: "#fcfcfd",
7
- card: "#f9f9fb",
8
- popover: "#f0f0f3",
9
- muted: "#f9f9fb",
10
- "text-primary": "#1c2024",
11
- "text-secondary": "#60646c",
12
- tertiary: "#80838d",
13
- primary: "#3e63dd",
6
+ background: "#f4f0e8",
7
+ card: "#fbf8f2",
8
+ secondary: "#e8e4da",
9
+ popover: "#f6f2ea",
10
+ muted: "#efe9dc",
11
+ "text-primary": "#11110f",
12
+ "text-secondary": "#6a6352",
13
+ tertiary: "#6d6659",
14
+ primary: "#107066",
15
+ accent: "#d7efeb",
14
16
  destructive: "#ce2c31",
15
17
  success: "#30a46c",
16
18
  warning: "#ffc53d",
19
+ "text-success": "#1f7a50",
20
+ "text-warning": "#8a5a00",
21
+ "success-foreground": "#15110f",
22
+ "warning-foreground": "#15110f",
17
23
  "primary-foreground": "#ffffff",
24
+ "accent-foreground": "#11110f",
18
25
  "destructive-foreground": "#ffffff",
19
26
  "text-destructive": "#c82c31",
20
- "border-default": "#cdced6",
21
- "focus-ring": "#3e63dd",
27
+ "text-accent": "#107066",
28
+ "border-default": "#8f8576",
29
+ "focus-ring": "#107066",
22
30
  "chart-1": "#3e63dd",
23
31
  "chart-2": "#6e56cf",
24
- "chart-3": "#12a594",
32
+ "chart-3": "#c73178",
25
33
  "chart-4": "#f76b15",
26
34
  "chart-5": "#8b8d98",
27
35
  "chart-positive": "#1f7a50",