@ponti-studios/ui 1.6.0 → 1.7.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 +27 -0
- package/src/styles/tokens/generated/dark.colors.generated.ts +15 -12
- package/src/styles/tokens/generated/dark.css +15 -12
- package/src/styles/tokens/generated/light.colors.generated.ts +14 -11
- package/src/styles/tokens/generated/light.css +14 -11
- package/src/styles/tokens/index.ts +0 -3
- package/src/styles/tokens/source/dark.tokens.json +24 -12
- package/src/styles/tokens/source/light.tokens.json +23 -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
|
@@ -17,14 +17,13 @@ const initialPasskeys: PasskeyRecord[] = [
|
|
|
17
17
|
{ id: "2", name: "iPhone", createdAt: "2025-03-02T00:00:00.000Z" },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
function PasskeyManagementPreview({
|
|
20
|
+
function PasskeyManagementPreview({ withDeleteError = false }: { withDeleteError?: boolean }) {
|
|
21
21
|
const [passkeys, setPasskeys] = useState(initialPasskeys);
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<div className="w-96">
|
|
25
25
|
<PasskeyManagement
|
|
26
26
|
passkeys={passkeys}
|
|
27
|
-
error={withError ? "Could not delete passkey. Please try again." : null}
|
|
28
27
|
onAdd={async () => {
|
|
29
28
|
setPasskeys((prev) => [
|
|
30
29
|
...prev,
|
|
@@ -37,6 +36,7 @@ function PasskeyManagementPreview({ withError = false }: { withError?: boolean }
|
|
|
37
36
|
return true;
|
|
38
37
|
}}
|
|
39
38
|
onDelete={async (id) => {
|
|
39
|
+
if (withDeleteError && id === "2") return false;
|
|
40
40
|
setPasskeys((prev) => prev.filter((pk) => pk.id !== id));
|
|
41
41
|
return true;
|
|
42
42
|
}}
|
|
@@ -68,14 +68,18 @@ export const Loading: Story = {
|
|
|
68
68
|
onAdd: async () => true,
|
|
69
69
|
onDelete: async () => true,
|
|
70
70
|
},
|
|
71
|
+
render: (args) => (
|
|
72
|
+
<div className="w-[calc(100vw-2rem)] max-w-md">
|
|
73
|
+
<PasskeyManagement {...args} />
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
71
76
|
};
|
|
72
77
|
|
|
73
78
|
export const WithError: Story = {
|
|
74
79
|
args: {
|
|
75
80
|
passkeys: initialPasskeys,
|
|
76
|
-
error: "Could not delete passkey. Please try again.",
|
|
77
81
|
onAdd: async () => true,
|
|
78
82
|
onDelete: async () => true,
|
|
79
83
|
},
|
|
80
|
-
render: () => <PasskeyManagementPreview
|
|
84
|
+
render: () => <PasskeyManagementPreview withDeleteError />,
|
|
81
85
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { KeyRound, Plus, Trash2 } from "lucide-react";
|
|
1
|
+
import { KeyRound, Plus, Trash2, X } from "lucide-react";
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
3
|
|
|
4
|
+
import { Spinner } from "../feedback/spinner";
|
|
4
5
|
import { Button } from "../primitives/button";
|
|
6
|
+
import { Card, CardHeader } from "../primitives/card";
|
|
5
7
|
|
|
6
8
|
export interface PasskeyRecord {
|
|
7
9
|
id: string;
|
|
@@ -17,6 +19,11 @@ export interface PasskeyManagementProps {
|
|
|
17
19
|
onDelete: (id: string) => Promise<boolean>;
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
type PasskeyActionError = {
|
|
23
|
+
message: string;
|
|
24
|
+
passkeyId?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
20
27
|
/** List, add, and remove passkeys. The host app owns the WebAuthn ceremony via `onAdd`/`onDelete`. */
|
|
21
28
|
export function PasskeyManagement({
|
|
22
29
|
passkeys: passkeysProp,
|
|
@@ -27,10 +34,10 @@ export function PasskeyManagement({
|
|
|
27
34
|
}: PasskeyManagementProps) {
|
|
28
35
|
const [adding, setAdding] = useState(false);
|
|
29
36
|
const [deletingId, setDeletingId] = useState<string | null>(null);
|
|
30
|
-
const [actionError, setActionError] = useState<
|
|
37
|
+
const [actionError, setActionError] = useState<PasskeyActionError | null>(null);
|
|
31
38
|
|
|
32
39
|
const passkeys = passkeysProp ?? [];
|
|
33
|
-
const error = externalError ?? actionError;
|
|
40
|
+
const error = externalError ?? (actionError?.passkeyId ? null : actionError?.message);
|
|
34
41
|
|
|
35
42
|
const handleAdd = useCallback(async () => {
|
|
36
43
|
setAdding(true);
|
|
@@ -38,10 +45,10 @@ export function PasskeyManagement({
|
|
|
38
45
|
try {
|
|
39
46
|
const success = await onAdd();
|
|
40
47
|
if (!success) {
|
|
41
|
-
setActionError("Passkey registration was cancelled or failed.");
|
|
48
|
+
setActionError({ message: "Passkey registration was cancelled or failed." });
|
|
42
49
|
}
|
|
43
50
|
} catch {
|
|
44
|
-
setActionError("An error occurred during passkey registration.");
|
|
51
|
+
setActionError({ message: "An error occurred during passkey registration." });
|
|
45
52
|
} finally {
|
|
46
53
|
setAdding(false);
|
|
47
54
|
}
|
|
@@ -57,7 +64,10 @@ export function PasskeyManagement({
|
|
|
57
64
|
throw new Error("Failed to delete passkey");
|
|
58
65
|
}
|
|
59
66
|
} catch {
|
|
60
|
-
setActionError(
|
|
67
|
+
setActionError({
|
|
68
|
+
message: "Could not delete passkey. Please try again.",
|
|
69
|
+
passkeyId: id,
|
|
70
|
+
});
|
|
61
71
|
} finally {
|
|
62
72
|
setDeletingId(null);
|
|
63
73
|
}
|
|
@@ -66,8 +76,8 @@ export function PasskeyManagement({
|
|
|
66
76
|
);
|
|
67
77
|
|
|
68
78
|
return (
|
|
69
|
-
<
|
|
70
|
-
<
|
|
79
|
+
<Card aria-labelledby="passkey-heading" className="gap-0 overflow-hidden w-full">
|
|
80
|
+
<CardHeader className="flex-row items-center border-b p-4">
|
|
71
81
|
<h2 id="passkey-heading" className="text-foreground text-sm font-medium">
|
|
72
82
|
Passkeys
|
|
73
83
|
</h2>
|
|
@@ -75,58 +85,84 @@ export function PasskeyManagement({
|
|
|
75
85
|
type="button"
|
|
76
86
|
size="sm"
|
|
77
87
|
onClick={() => void handleAdd()}
|
|
78
|
-
|
|
88
|
+
isLoading={adding}
|
|
89
|
+
loadingLabel="Adding passkey"
|
|
79
90
|
aria-label="Add a passkey"
|
|
91
|
+
className="ml-auto"
|
|
80
92
|
>
|
|
81
93
|
<Plus className="size-4" aria-hidden />
|
|
82
|
-
{adding ? "Adding..." : "Add passkey"}
|
|
83
94
|
</Button>
|
|
84
|
-
</
|
|
95
|
+
</CardHeader>
|
|
85
96
|
|
|
86
97
|
{error ? (
|
|
87
|
-
<p role="alert" className="text-destructive-text text-sm">
|
|
98
|
+
<p role="alert" className="text-destructive-text border-b px-4 py-3 text-sm">
|
|
88
99
|
{error}
|
|
89
100
|
</p>
|
|
90
101
|
) : null}
|
|
91
102
|
|
|
92
103
|
{isLoading ? (
|
|
93
|
-
<
|
|
104
|
+
<Spinner presentation="centered" size="sm" label="Loading passkeys" />
|
|
94
105
|
) : passkeys.length === 0 ? (
|
|
95
|
-
<div className="
|
|
106
|
+
<div className="text-muted-foreground flex items-center gap-3 p-4 text-sm">
|
|
96
107
|
<KeyRound className="size-4 shrink-0" aria-hidden />
|
|
97
108
|
<span>No passkeys registered. Add one to sign in faster.</span>
|
|
98
109
|
</div>
|
|
99
110
|
) : (
|
|
100
|
-
<ul className="
|
|
101
|
-
{passkeys.map((pk) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
<ul className="divide-border divide-y" aria-label="Registered passkeys">
|
|
112
|
+
{passkeys.map((pk) => {
|
|
113
|
+
const rowError = actionError?.passkeyId === pk.id ? actionError.message : null;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<li key={pk.id} className="flex flex-col gap-2 px-4 py-3 text-sm">
|
|
117
|
+
<div className="flex items-center justify-between gap-4">
|
|
118
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
119
|
+
<KeyRound className="text-muted-foreground size-4 shrink-0" aria-hidden />
|
|
120
|
+
<div className="min-w-0">
|
|
121
|
+
<span className="font-medium">{pk.name ?? "Passkey"}</span>
|
|
122
|
+
{pk.createdAt ? (
|
|
123
|
+
<span className="text-muted-foreground ml-2 text-xs">
|
|
124
|
+
Added {new Date(pk.createdAt).toLocaleDateString()}
|
|
125
|
+
</span>
|
|
126
|
+
) : null}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={() => void handleDelete(pk.id)}
|
|
132
|
+
disabled={deletingId === pk.id}
|
|
133
|
+
aria-busy={deletingId === pk.id}
|
|
134
|
+
aria-label={`Remove passkey ${pk.name ?? pk.id}`}
|
|
135
|
+
className="hover:text-destructive-text text-muted-foreground focus-visible:ring-ring rounded-sm focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
|
|
136
|
+
>
|
|
137
|
+
{deletingId === pk.id ? (
|
|
138
|
+
<Spinner size="sm" aria-hidden />
|
|
139
|
+
) : (
|
|
140
|
+
<Trash2 className="size-4" aria-hidden />
|
|
141
|
+
)}
|
|
142
|
+
</button>
|
|
115
143
|
</div>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
{rowError ? (
|
|
145
|
+
<div
|
|
146
|
+
role="alert"
|
|
147
|
+
className="bg-destructive/5 text-destructive-text flex items-start justify-between gap-3 rounded-md px-3 py-2 text-sm"
|
|
148
|
+
>
|
|
149
|
+
<span>{rowError}</span>
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
onClick={() => setActionError(null)}
|
|
153
|
+
aria-label="Dismiss passkey error"
|
|
154
|
+
title="Dismiss error"
|
|
155
|
+
className="hover:text-destructive focus-visible:ring-ring -m-1 shrink-0 rounded-sm p-1 focus-visible:ring-2 focus-visible:outline-none"
|
|
156
|
+
>
|
|
157
|
+
<X className="size-4" aria-hidden />
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
) : null}
|
|
161
|
+
</li>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
128
164
|
</ul>
|
|
129
165
|
)}
|
|
130
|
-
</
|
|
166
|
+
</Card>
|
|
131
167
|
);
|
|
132
168
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import * as React from "react";
|
|
3
2
|
|
|
4
|
-
import { Label } from "../primitives/label";
|
|
5
3
|
import { RadioGroup, RadioGroupItem } from "./radio-group";
|
|
6
4
|
|
|
7
5
|
const meta: Meta<typeof RadioGroup> = {
|
|
@@ -25,9 +23,9 @@ export const Default: Story = {
|
|
|
25
23
|
{options.map(({ value, label }) => (
|
|
26
24
|
<div key={value} className="flex items-center gap-3">
|
|
27
25
|
<RadioGroupItem value={value} id={`opt-${value}`} />
|
|
28
|
-
<
|
|
26
|
+
<label htmlFor={`opt-${value}`} className="cursor-pointer">
|
|
29
27
|
{label}
|
|
30
|
-
</
|
|
28
|
+
</label>
|
|
31
29
|
</div>
|
|
32
30
|
))}
|
|
33
31
|
</RadioGroup>
|
|
@@ -40,7 +38,7 @@ export const Disabled: Story = {
|
|
|
40
38
|
{options.map(({ value, label }) => (
|
|
41
39
|
<div key={value} className="flex items-center gap-3">
|
|
42
40
|
<RadioGroupItem value={value} id={`dis-${value}`} />
|
|
43
|
-
<
|
|
41
|
+
<label htmlFor={`dis-${value}`}>{label}</label>
|
|
44
42
|
</div>
|
|
45
43
|
))}
|
|
46
44
|
</RadioGroup>
|
|
@@ -23,7 +23,7 @@ const RadioGroupItem = React.forwardRef<
|
|
|
23
23
|
<RadioPrimitive.Root
|
|
24
24
|
ref={ref}
|
|
25
25
|
className={cn(
|
|
26
|
-
"border-
|
|
26
|
+
"border-ring bg-background flex aspect-square size-6 items-center justify-center rounded-full border",
|
|
27
27
|
"focus-visible:border-ring focus-visible:bg-muted/25 focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
|
|
28
28
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
29
29
|
className,
|
|
@@ -34,7 +34,7 @@ const RadioGroupItem = React.forwardRef<
|
|
|
34
34
|
keepMounted
|
|
35
35
|
className="data-unchecked:opacity-0 data-checked:opacity-100 data-starting-style:animate-fade-in data-ending-style:animate-fade-out"
|
|
36
36
|
>
|
|
37
|
-
<Circle className="fill-
|
|
37
|
+
<Circle className="fill-ring size-5" />
|
|
38
38
|
</RadioPrimitive.Indicator>
|
|
39
39
|
</RadioPrimitive.Root>
|
|
40
40
|
);
|
|
@@ -51,9 +51,9 @@ export const SelectedValue: Story = {
|
|
|
51
51
|
</Select>
|
|
52
52
|
),
|
|
53
53
|
play: async ({ canvasElement }) => {
|
|
54
|
-
await expect(
|
|
55
|
-
"
|
|
56
|
-
);
|
|
54
|
+
await expect(
|
|
55
|
+
within(canvasElement).getByRole("combobox", { name: "Selected fruit" }),
|
|
56
|
+
).toHaveTextContent("Banana");
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
59
|
|
|
@@ -76,18 +76,15 @@ export const ExplicitItems: Story = {
|
|
|
76
76
|
</Select>
|
|
77
77
|
),
|
|
78
78
|
play: async ({ canvasElement }) => {
|
|
79
|
-
await expect(
|
|
80
|
-
"
|
|
81
|
-
);
|
|
79
|
+
await expect(
|
|
80
|
+
within(canvasElement).getByRole("combobox", { name: "Country" }),
|
|
81
|
+
).toHaveTextContent("United States");
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
export const NullItem: Story = {
|
|
86
86
|
render: () => (
|
|
87
|
-
<Select
|
|
88
|
-
defaultValue={null}
|
|
89
|
-
items={[{ value: null, label: "All options" }]}
|
|
90
|
-
>
|
|
87
|
+
<Select defaultValue={null} items={[{ value: null, label: "All options" }]}>
|
|
91
88
|
<SelectTrigger aria-label="Filter">
|
|
92
89
|
<SelectValue placeholder="Choose a filter" />
|
|
93
90
|
</SelectTrigger>
|
|
@@ -142,9 +139,13 @@ export const ControlledNullChange: Story = {
|
|
|
142
139
|
const trigger = canvas.getByRole("combobox", { name: "Controlled filter" });
|
|
143
140
|
|
|
144
141
|
await userEvent.click(trigger);
|
|
145
|
-
await userEvent.click(
|
|
142
|
+
await userEvent.click(
|
|
143
|
+
await within(document.body).findByRole("option", { name: "All options" }),
|
|
144
|
+
);
|
|
146
145
|
await expect(trigger).toHaveTextContent("All options");
|
|
147
|
-
await expect(canvas.getByRole("status", { name: "Last selected value" })).toHaveTextContent(
|
|
146
|
+
await expect(canvas.getByRole("status", { name: "Last selected value" })).toHaveTextContent(
|
|
147
|
+
"null",
|
|
148
|
+
);
|
|
148
149
|
await userEvent.keyboard("{Escape}");
|
|
149
150
|
},
|
|
150
151
|
};
|
|
@@ -184,7 +185,9 @@ export const Disabled: Story = {
|
|
|
184
185
|
</Select>
|
|
185
186
|
),
|
|
186
187
|
play: async ({ canvasElement }) => {
|
|
187
|
-
await expect(
|
|
188
|
+
await expect(
|
|
189
|
+
within(canvasElement).getByRole("combobox", { name: "Select a fruit" }),
|
|
190
|
+
).toBeDisabled();
|
|
188
191
|
},
|
|
189
192
|
};
|
|
190
193
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { ParticleBackground } from "./particle-background";
|
|
2
|
+
export type { ParticleBackgroundPalette, ParticleBackgroundProps } from "./particle-background";
|
|
1
3
|
export { ScrollArea } from "./scroll-area";
|
|
2
4
|
export type { ScrollAreaProps } from "./scroll-area";
|
|
3
5
|
export { SectionIntro } from "./section-intro";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { ParticleBackground } from "./particle-background";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Layout/ParticleBackground",
|
|
7
|
+
component: ParticleBackground,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof ParticleBackground>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: (args) => (
|
|
16
|
+
<div className="bg-background relative h-80 w-[640px] overflow-hidden">
|
|
17
|
+
<ParticleBackground {...args} />
|
|
18
|
+
</div>
|
|
19
|
+
),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const Static: Story = {
|
|
23
|
+
args: { interactive: false },
|
|
24
|
+
render: (args) => (
|
|
25
|
+
<div className="bg-background relative h-80 w-[640px] overflow-hidden">
|
|
26
|
+
<ParticleBackground {...args} />
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Disabled: Story = {
|
|
32
|
+
args: { enabled: false },
|
|
33
|
+
render: (args) => (
|
|
34
|
+
<div className="bg-background relative h-80 w-[640px] overflow-hidden">
|
|
35
|
+
<ParticleBackground {...args} />
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
};
|