@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.
- package/README.md +7 -11
- package/package.json +6 -31
- package/src/components/feedback/alert.tsx +8 -4
- package/src/components/feedback/index.ts +0 -4
- package/src/components/feedback/progress.stories.tsx +15 -0
- package/src/components/feedback/progress.tsx +14 -3
- package/src/components/feedback/update-guard.stories.tsx +4 -8
- package/src/components/feedback/update-guard.tsx +41 -272
- package/src/components/filters/filter-select.tsx +3 -5
- package/src/components/forms/date-picker.stories.tsx +158 -18
- package/src/components/forms/date-picker.tsx +286 -64
- package/src/components/forms/date-utils.ts +7 -2
- package/src/components/forms/drop-zone.tsx +11 -12
- package/src/components/forms/index.ts +2 -6
- package/src/components/forms/passkey-management.stories.tsx +8 -4
- package/src/components/forms/passkey-management.tsx +78 -42
- package/src/components/forms/radio-group.stories.tsx +3 -5
- package/src/components/forms/radio-group.tsx +2 -2
- package/src/components/forms/select.stories.tsx +16 -13
- package/src/components/forms/textarea.stories.tsx +1 -1
- package/src/components/layout/index.ts +2 -0
- package/src/components/layout/particle-background.stories.tsx +38 -0
- package/src/components/layout/particle-background.tsx +378 -0
- package/src/components/layout/scroll-area.stories.tsx +12 -4
- package/src/components/layout/scroll-area.tsx +41 -4
- package/src/components/navigation/tabs.stories.tsx +0 -21
- package/src/components/overlays/dialog.stories.tsx +38 -1
- package/src/components/overlays/dialog.tsx +53 -12
- package/src/components/overlays/index.ts +0 -11
- package/src/components/primitives/avatar-placeholder.svg +13 -0
- package/src/components/primitives/avatar.stories.tsx +2 -1
- package/src/components/primitives/badge.stories.tsx +13 -3
- package/src/components/primitives/badge.tsx +1 -1
- package/src/components/primitives/button.tsx +1 -1
- package/src/components/primitives/card.tsx +6 -2
- package/src/components/primitives/index.ts +0 -3
- package/src/index.ts +1 -3
- package/src/lib/index.ts +2 -3
- package/src/styles/animations.css +62 -0
- package/src/styles/index.css +31 -4
- package/src/styles/tokens/color-systems.stories.tsx +173 -25
- package/src/styles/tokens/generated/dark.colors.generated.ts +20 -12
- package/src/styles/tokens/generated/dark.css +20 -12
- package/src/styles/tokens/generated/light.colors.generated.ts +19 -11
- package/src/styles/tokens/generated/light.css +19 -11
- package/src/styles/tokens/index.ts +0 -3
- package/src/styles/tokens/scripts/check-source.mjs +7 -0
- package/src/styles/tokens/source/dark.tokens.json +44 -12
- package/src/styles/tokens/source/light.tokens.json +43 -11
- package/src/styles/tokens/typography.stories.tsx +157 -0
- package/src/components/feedback/file-upload-status.stories.tsx +0 -57
- package/src/components/feedback/file-upload-status.tsx +0 -160
- package/src/components/feedback/progress-bar.stories.tsx +0 -39
- package/src/components/feedback/progress-bar.tsx +0 -22
- package/src/components/forms/calendar.stories.tsx +0 -30
- package/src/components/forms/calendar.tsx +0 -75
- package/src/components/forms/date-field.stories.tsx +0 -202
- package/src/components/forms/date-field.tsx +0 -179
- package/src/components/overlays/alert-dialog.stories.tsx +0 -45
- package/src/components/overlays/alert-dialog.tsx +0 -116
- package/src/components/primitives/label.stories.tsx +0 -29
- package/src/components/primitives/label.tsx +0 -20
- package/src/components/primitives/status-badge.stories.tsx +0 -52
- package/src/components/primitives/status-badge.tsx +0 -33
- package/src/lib/create-memory-storage.ts +0 -24
- package/src/native/card.tsx +0 -36
- package/src/native/icon-button.tsx +0 -41
- package/src/native/index.ts +0 -38
- package/src/native/list-row.tsx +0 -67
- package/src/native/make-styles.ts +0 -32
- package/src/native/shadows.ts +0 -38
- package/src/native/text-field.tsx +0 -34
- package/src/native/uniwind.d.ts +0 -1
- package/src/native/use-theme-colors.ts +0 -19
- package/src/styles/tokens/foundations.stories.tsx +0 -109
- package/src/styles/tokens/generated/native.generated.ts +0 -266
- package/src/styles/tokens/normalize-native.ts +0 -88
- package/src/styles/tokens/source/native.tokens.json +0 -204
- 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=
|
|
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">
|
|
28
|
-
|
|
29
|
-
|
|
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-
|
|
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-
|
|
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<"
|
|
29
|
-
return
|
|
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">) {
|
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
|
}
|
package/src/styles/index.css
CHANGED
|
@@ -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(--
|
|
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(--
|
|
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(--
|
|
65
|
-
--color-accent-foreground: var(--
|
|
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 {
|
|
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-
|
|
14
|
-
<header className="grid gap-2">
|
|
15
|
-
<
|
|
16
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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: "#
|
|
7
|
-
card: "#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"text-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
"
|
|
21
|
-
"
|
|
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": "#
|
|
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": "#
|
|
37
|
+
"chart-neutral": "#878d94",
|
|
30
38
|
"overlay-scrim": "#000000",
|
|
31
39
|
};
|
|
@@ -3,29 +3,37 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
:root {
|
|
6
|
-
--background: #
|
|
7
|
-
--card: #
|
|
8
|
-
--
|
|
9
|
-
--
|
|
10
|
-
--
|
|
11
|
-
--text-
|
|
12
|
-
--
|
|
13
|
-
--
|
|
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
|
-
--
|
|
21
|
-
--
|
|
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: #
|
|
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: #
|
|
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: "#
|
|
7
|
-
card: "#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"text-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
"
|
|
21
|
-
"
|
|
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": "#
|
|
32
|
+
"chart-3": "#c73178",
|
|
25
33
|
"chart-4": "#f76b15",
|
|
26
34
|
"chart-5": "#8b8d98",
|
|
27
35
|
"chart-positive": "#1f7a50",
|