@dowel-ui/react 0.5.0 → 0.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 +1 -1
- package/dist/blocks/admin-dashboard/meta.js +31 -0
- package/dist/blocks/admin-dashboard/meta.js.map +1 -0
- package/dist/blocks/agent-console/meta.js +27 -0
- package/dist/blocks/agent-console/meta.js.map +1 -0
- package/dist/blocks/ai-dashboard/meta.js +25 -0
- package/dist/blocks/ai-dashboard/meta.js.map +1 -0
- package/dist/blocks/ai-workspace/meta.js +33 -0
- package/dist/blocks/ai-workspace/meta.js.map +1 -0
- package/dist/blocks/analytics/meta.js +24 -0
- package/dist/blocks/analytics/meta.js.map +1 -0
- package/dist/blocks/billing/meta.js +25 -0
- package/dist/blocks/billing/meta.js.map +1 -0
- package/dist/blocks/command-center/meta.js +28 -0
- package/dist/blocks/command-center/meta.js.map +1 -0
- package/dist/blocks/crm/meta.js +32 -0
- package/dist/blocks/crm/meta.js.map +1 -0
- package/dist/blocks/onboarding/meta.js +23 -0
- package/dist/blocks/onboarding/meta.js.map +1 -0
- package/dist/components/accordion/accordion.js +1 -1
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/activity-feed/activity-feed.js +1 -1
- package/dist/components/activity-feed/activity-feed.js.map +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
- package/dist/components/ai-extraction-review/ai-extraction-review.js +1 -1
- package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -1
- package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
- package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
- package/dist/components/ai-response/ai-response.js +2 -2
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-tool/ai-tool.js +2 -2
- package/dist/components/ai-tool/ai-tool.js.map +1 -1
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/avatar/avatar.d.ts +1 -1
- package/dist/components/badge/badge.d.ts +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.js +141 -0
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/breadcrumb/index.js +2 -0
- package/dist/components/breadcrumb/meta.js +17 -0
- package/dist/components/breadcrumb/meta.js.map +1 -0
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/calendar/calendar.js +2 -2
- package/dist/components/calendar/calendar.js.map +1 -1
- package/dist/components/code-block/code-block.js +1 -1
- package/dist/components/code-block/code-block.js.map +1 -1
- package/dist/components/collapsible/collapsible.d.ts +30 -0
- package/dist/components/collapsible/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.js.map +1 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +2 -0
- package/dist/components/collapsible/meta.js +17 -0
- package/dist/components/collapsible/meta.js.map +1 -0
- package/dist/components/combobox/combobox.js +2 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/command/command.js +1 -1
- package/dist/components/command/command.js.map +1 -1
- package/dist/components/data-table/data-table.d.ts.map +1 -1
- package/dist/components/data-table/data-table.js +4 -1
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/dialog/dialog.js +2 -2
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/diff-viewer/diff-viewer.js +4 -4
- package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
- package/dist/components/direction/direction.d.ts +35 -0
- package/dist/components/direction/direction.d.ts.map +1 -0
- package/dist/components/direction/direction.js +14 -0
- package/dist/components/direction/direction.js.map +1 -0
- package/dist/components/direction/index.d.ts +2 -0
- package/dist/components/direction/index.js +2 -0
- package/dist/components/direction/meta.js +17 -0
- package/dist/components/direction/meta.js.map +1 -0
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/input/input.js +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/log-viewer/log-viewer.js +2 -2
- package/dist/components/log-viewer/log-viewer.js.map +1 -1
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +3 -2
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/permission-matrix/permission-matrix.js +3 -3
- package/dist/components/permission-matrix/permission-matrix.js.map +1 -1
- package/dist/components/progress/progress.d.ts +1 -1
- package/dist/components/record-diff/record-diff.js +4 -4
- package/dist/components/record-diff/record-diff.js.map +1 -1
- package/dist/components/select/select.js +2 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/sheet/sheet.d.ts.map +1 -1
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sheet/sheet.js.map +1 -1
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/meta.js +17 -0
- package/dist/components/sidebar/meta.js.map +1 -0
- package/dist/components/sidebar/sidebar.d.ts +69 -0
- package/dist/components/sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.js +256 -0
- package/dist/components/sidebar/sidebar.js.map +1 -0
- package/dist/components/spinner/spinner.d.ts +1 -1
- package/dist/components/table/table.js +2 -2
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tags-input/tags-input.js +1 -1
- package/dist/components/tags-input/tags-input.js.map +1 -1
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +2 -0
- package/dist/components/textarea/meta.js +17 -0
- package/dist/components/textarea/meta.js.map +1 -0
- package/dist/components/textarea/textarea.d.ts +43 -0
- package/dist/components/textarea/textarea.d.ts.map +1 -0
- package/dist/components/textarea/textarea.js +119 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.js +3 -3
- package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +2 -2
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +7 -2
- package/dist/lib/styles.d.ts +16 -1
- package/dist/lib/styles.d.ts.map +1 -1
- package/dist/lib/styles.js +16 -1
- package/dist/lib/styles.js.map +1 -1
- package/dist/registry/blocks.d.ts.map +1 -1
- package/dist/registry/blocks.js +27 -9
- package/dist/registry/blocks.js.map +1 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +60 -50
- package/dist/registry/components.js.map +1 -1
- package/dist/registry/schema.d.ts +8 -0
- package/dist/registry/schema.d.ts.map +1 -1
- package/dist/registry/schema.js.map +1 -1
- package/package.json +5 -4
- package/src/components/accordion/accordion.tsx +1 -1
- package/src/components/activity-feed/activity-feed.tsx +1 -1
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-extraction-review/ai-extraction-review.tsx +1 -1
- package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
- package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
- package/src/components/ai-response/ai-response.tsx +2 -2
- package/src/components/ai-tool/ai-tool.tsx +2 -2
- package/src/components/breadcrumb/breadcrumb.tsx +157 -0
- package/src/components/breadcrumb/index.ts +10 -0
- package/src/components/breadcrumb/meta.ts +19 -0
- package/src/components/calendar/calendar.tsx +2 -2
- package/src/components/code-block/code-block.tsx +1 -1
- package/src/components/collapsible/collapsible.tsx +63 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/collapsible/meta.ts +16 -0
- package/src/components/combobox/combobox.tsx +2 -2
- package/src/components/command/command.tsx +1 -1
- package/src/components/data-table/data-table.tsx +14 -3
- package/src/components/dialog/dialog.tsx +2 -2
- package/src/components/diff-viewer/diff-viewer.tsx +4 -4
- package/src/components/direction/direction.tsx +38 -0
- package/src/components/direction/index.ts +1 -0
- package/src/components/direction/meta.ts +19 -0
- package/src/components/drawer/drawer.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
- package/src/components/input/input.tsx +1 -1
- package/src/components/log-viewer/log-viewer.tsx +2 -2
- package/src/components/meter/meter.tsx +1 -1
- package/src/components/pagination/pagination.tsx +13 -2
- package/src/components/permission-matrix/permission-matrix.tsx +4 -4
- package/src/components/record-diff/record-diff.tsx +4 -4
- package/src/components/select/select.tsx +2 -2
- package/src/components/sheet/sheet.tsx +8 -4
- package/src/components/sidebar/index.ts +19 -0
- package/src/components/sidebar/meta.ts +25 -0
- package/src/components/sidebar/sidebar.tsx +374 -0
- package/src/components/table/table.tsx +3 -3
- package/src/components/tags-input/tags-input.tsx +1 -1
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/meta.ts +21 -0
- package/src/components/textarea/textarea.tsx +210 -0
- package/src/components/time-range-picker/time-range-picker.tsx +3 -3
- package/src/components/toast/toast.tsx +14 -6
- package/src/index.ts +5 -0
- package/src/lib/styles.ts +16 -0
- package/src/registry/blocks.ts +18 -0
- package/src/registry/components.ts +10 -0
- package/src/registry/schema.ts +8 -0
- package/src/blocks/admin-users/admin-users.tsx +0 -251
- package/src/blocks/admin-users/index.ts +0 -1
- package/src/blocks/admin-users/meta.ts +0 -27
- package/src/blocks/ai-chat/ai-chat.tsx +0 -280
- package/src/blocks/ai-chat/index.ts +0 -7
- package/src/blocks/ai-chat/meta.ts +0 -32
- package/src/blocks/dashboard/dashboard.tsx +0 -192
- package/src/blocks/dashboard/index.ts +0 -6
- package/src/blocks/dashboard/meta.ts +0 -18
- package/src/blocks/forgot-password/forgot-password.tsx +0 -158
- package/src/blocks/forgot-password/index.ts +0 -1
- package/src/blocks/forgot-password/meta.ts +0 -18
- package/src/blocks/login/index.ts +0 -1
- package/src/blocks/login/login.tsx +0 -178
- package/src/blocks/login/meta.ts +0 -18
- package/src/blocks/pricing/index.ts +0 -1
- package/src/blocks/pricing/meta.ts +0 -18
- package/src/blocks/pricing/pricing.tsx +0 -176
- package/src/blocks/settings/index.ts +0 -6
- package/src/blocks/settings/meta.ts +0 -29
- package/src/blocks/settings/settings.tsx +0 -317
- package/src/blocks/signup/index.ts +0 -1
- package/src/blocks/signup/meta.ts +0 -18
- package/src/blocks/signup/signup.tsx +0 -240
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, type ReactNode } from "react";
|
|
4
|
-
|
|
5
|
-
import { Badge } from "@/components/badge";
|
|
6
|
-
import { Button } from "@/components/button";
|
|
7
|
-
import {
|
|
8
|
-
Card,
|
|
9
|
-
CardContent,
|
|
10
|
-
CardDescription,
|
|
11
|
-
CardFooter,
|
|
12
|
-
CardHeader,
|
|
13
|
-
CardTitle,
|
|
14
|
-
} from "@/components/card";
|
|
15
|
-
import { Label } from "@/components/label";
|
|
16
|
-
import { Switch } from "@/components/switch";
|
|
17
|
-
import { cn } from "@/lib/utils";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A pricing section.
|
|
21
|
-
*
|
|
22
|
-
* The billing-period toggle is the part that usually goes wrong. It changes
|
|
23
|
-
* every price on the page at once, which is a large change nobody asked for in
|
|
24
|
-
* words — so the prices update in place and the change is announced, rather
|
|
25
|
-
* than silently rewriting the page under the reader.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export interface PricingPlan {
|
|
29
|
-
id: string;
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
/** Monthly price in whole currency units. `null` means "talk to us". */
|
|
33
|
-
monthly: number | null;
|
|
34
|
-
/** Yearly price per month. Omit to bill yearly at the monthly rate. */
|
|
35
|
-
yearlyPerMonth?: number;
|
|
36
|
-
features: string[];
|
|
37
|
-
/** Marks the recommended plan. At most one. */
|
|
38
|
-
featured?: boolean;
|
|
39
|
-
cta?: string;
|
|
40
|
-
onSelect?: () => void;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface PricingBlockProps {
|
|
44
|
-
title?: string;
|
|
45
|
-
description?: string;
|
|
46
|
-
plans: PricingPlan[];
|
|
47
|
-
currency?: string;
|
|
48
|
-
/** Shows the monthly/yearly toggle. */
|
|
49
|
-
showPeriodToggle?: boolean;
|
|
50
|
-
footnote?: ReactNode;
|
|
51
|
-
className?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function PricingBlock({
|
|
55
|
-
title = "Pricing",
|
|
56
|
-
description = "Start free. Upgrade when you need to.",
|
|
57
|
-
plans,
|
|
58
|
-
currency = "$",
|
|
59
|
-
showPeriodToggle = true,
|
|
60
|
-
footnote,
|
|
61
|
-
className,
|
|
62
|
-
}: PricingBlockProps) {
|
|
63
|
-
const [yearly, setYearly] = useState(false);
|
|
64
|
-
|
|
65
|
-
function priceOf(plan: PricingPlan): { amount: string; suffix: string } {
|
|
66
|
-
if (plan.monthly === null) return { amount: "Custom", suffix: "" };
|
|
67
|
-
const value = yearly ? (plan.yearlyPerMonth ?? plan.monthly) : plan.monthly;
|
|
68
|
-
return { amount: `${currency}${String(value)}`, suffix: "/month" };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<section aria-labelledby="pricing-heading" className={cn("flex flex-col gap-8", className)}>
|
|
73
|
-
<div className="text-center">
|
|
74
|
-
<h2 id="pricing-heading" className="text-2xl font-semibold tracking-tight text-balance">
|
|
75
|
-
{title}
|
|
76
|
-
</h2>
|
|
77
|
-
<p className="mx-auto mt-2 max-w-lg text-pretty text-muted-foreground">{description}</p>
|
|
78
|
-
|
|
79
|
-
{showPeriodToggle ? (
|
|
80
|
-
<div className="mt-6 flex items-center justify-center gap-3">
|
|
81
|
-
<Label htmlFor="billing-period" className="text-sm font-normal">
|
|
82
|
-
Monthly
|
|
83
|
-
</Label>
|
|
84
|
-
<Switch
|
|
85
|
-
id="billing-period"
|
|
86
|
-
checked={yearly}
|
|
87
|
-
onCheckedChange={setYearly}
|
|
88
|
-
aria-label="Bill yearly"
|
|
89
|
-
/>
|
|
90
|
-
<Label htmlFor="billing-period" className="text-sm font-normal">
|
|
91
|
-
Yearly
|
|
92
|
-
</Label>
|
|
93
|
-
<Badge size="sm" variant="success">
|
|
94
|
-
Save 20%
|
|
95
|
-
</Badge>
|
|
96
|
-
</div>
|
|
97
|
-
) : null}
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
{/* Every price on the page changes at once, which is a big change nobody
|
|
101
|
-
asked for out loud. Announcing it politely is the difference between a
|
|
102
|
-
helpful toggle and the page silently rewriting itself. */}
|
|
103
|
-
<p aria-live="polite" className="sr-only">
|
|
104
|
-
Showing {yearly ? "yearly" : "monthly"} pricing.
|
|
105
|
-
</p>
|
|
106
|
-
|
|
107
|
-
<div className="grid gap-6 md:grid-cols-3">
|
|
108
|
-
{plans.map((plan) => {
|
|
109
|
-
const price = priceOf(plan);
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<Card
|
|
113
|
-
key={plan.id}
|
|
114
|
-
className={cn("flex flex-col", plan.featured && "border-primary shadow-lg")}
|
|
115
|
-
>
|
|
116
|
-
<CardHeader>
|
|
117
|
-
<div className="flex items-center justify-between gap-2">
|
|
118
|
-
<CardTitle>{plan.name}</CardTitle>
|
|
119
|
-
{plan.featured ? <Badge size="sm">Recommended</Badge> : null}
|
|
120
|
-
</div>
|
|
121
|
-
<CardDescription>{plan.description}</CardDescription>
|
|
122
|
-
<p className="mt-3 flex items-baseline gap-1">
|
|
123
|
-
<span className="text-3xl font-semibold tabular-nums">{price.amount}</span>
|
|
124
|
-
{price.suffix ? (
|
|
125
|
-
<span className="text-sm text-muted-foreground">{price.suffix}</span>
|
|
126
|
-
) : null}
|
|
127
|
-
</p>
|
|
128
|
-
{yearly && plan.monthly !== null && plan.yearlyPerMonth !== undefined ? (
|
|
129
|
-
<p className="text-xs text-muted-foreground">Billed yearly.</p>
|
|
130
|
-
) : null}
|
|
131
|
-
</CardHeader>
|
|
132
|
-
|
|
133
|
-
<CardContent className="flex-1">
|
|
134
|
-
<ul className="flex flex-col gap-2 text-sm">
|
|
135
|
-
{plan.features.map((feature) => (
|
|
136
|
-
<li key={feature} className="flex items-start gap-2">
|
|
137
|
-
<svg
|
|
138
|
-
viewBox="0 0 24 24"
|
|
139
|
-
fill="none"
|
|
140
|
-
aria-hidden="true"
|
|
141
|
-
className="mt-0.5 size-4 shrink-0 text-success"
|
|
142
|
-
>
|
|
143
|
-
<path
|
|
144
|
-
d="m5 13 4 4L19 7"
|
|
145
|
-
stroke="currentColor"
|
|
146
|
-
strokeWidth="2.5"
|
|
147
|
-
strokeLinecap="round"
|
|
148
|
-
strokeLinejoin="round"
|
|
149
|
-
/>
|
|
150
|
-
</svg>
|
|
151
|
-
<span className="text-muted-foreground">{feature}</span>
|
|
152
|
-
</li>
|
|
153
|
-
))}
|
|
154
|
-
</ul>
|
|
155
|
-
</CardContent>
|
|
156
|
-
|
|
157
|
-
<CardFooter>
|
|
158
|
-
<Button
|
|
159
|
-
className="w-full"
|
|
160
|
-
variant={plan.featured ? "primary" : "outline"}
|
|
161
|
-
onClick={plan.onSelect}
|
|
162
|
-
>
|
|
163
|
-
{plan.cta ?? `Choose ${plan.name}`}
|
|
164
|
-
</Button>
|
|
165
|
-
</CardFooter>
|
|
166
|
-
</Card>
|
|
167
|
-
);
|
|
168
|
-
})}
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
{footnote ? (
|
|
172
|
-
<p className="text-center text-xs text-muted-foreground">{footnote}</p>
|
|
173
|
-
) : null}
|
|
174
|
-
</section>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { defineMeta } from "@/registry/schema";
|
|
2
|
-
|
|
3
|
-
export const meta = defineMeta({
|
|
4
|
-
name: "settings",
|
|
5
|
-
kind: "block",
|
|
6
|
-
title: "Settings",
|
|
7
|
-
description: "A settings page with a profile form, immediate toggles and a danger zone.",
|
|
8
|
-
category: "form",
|
|
9
|
-
status: "stable",
|
|
10
|
-
dependencies: [],
|
|
11
|
-
registryDependencies: [
|
|
12
|
-
"avatar",
|
|
13
|
-
"button",
|
|
14
|
-
"card",
|
|
15
|
-
"dialog",
|
|
16
|
-
"form",
|
|
17
|
-
"input",
|
|
18
|
-
"label",
|
|
19
|
-
"separator",
|
|
20
|
-
"switch",
|
|
21
|
-
"tabs",
|
|
22
|
-
],
|
|
23
|
-
files: ["settings.tsx"],
|
|
24
|
-
a11y:
|
|
25
|
-
"Switches apply immediately and have no Save button — a switch that needs saving is a broken " +
|
|
26
|
-
"promise, so staged changes live in the form instead. The delete confirmation names what will " +
|
|
27
|
-
"be destroyed and requires typing the account's email, so the destructive button cannot be " +
|
|
28
|
-
"reached by muscle memory.",
|
|
29
|
-
});
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, type FormEvent, type ReactNode } from "react";
|
|
4
|
-
|
|
5
|
-
import { Avatar, AvatarFallback } from "@/components/avatar";
|
|
6
|
-
import { Button } from "@/components/button";
|
|
7
|
-
import {
|
|
8
|
-
Card,
|
|
9
|
-
CardContent,
|
|
10
|
-
CardDescription,
|
|
11
|
-
CardFooter,
|
|
12
|
-
CardHeader,
|
|
13
|
-
CardTitle,
|
|
14
|
-
} from "@/components/card";
|
|
15
|
-
import {
|
|
16
|
-
Dialog,
|
|
17
|
-
DialogClose,
|
|
18
|
-
DialogContent,
|
|
19
|
-
DialogDescription,
|
|
20
|
-
DialogFooter,
|
|
21
|
-
DialogHeader,
|
|
22
|
-
DialogTitle,
|
|
23
|
-
DialogTrigger,
|
|
24
|
-
} from "@/components/dialog";
|
|
25
|
-
import {
|
|
26
|
-
Form,
|
|
27
|
-
FormControl,
|
|
28
|
-
FormDescription,
|
|
29
|
-
FormField,
|
|
30
|
-
FormLabel,
|
|
31
|
-
FormMessage,
|
|
32
|
-
} from "@/components/form";
|
|
33
|
-
import { Input } from "@/components/input";
|
|
34
|
-
import { Label } from "@/components/label";
|
|
35
|
-
import { Separator } from "@/components/separator";
|
|
36
|
-
import { Switch } from "@/components/switch";
|
|
37
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/tabs";
|
|
38
|
-
import { cn } from "@/lib/utils";
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* A settings page: profile, notifications and a danger zone.
|
|
42
|
-
*
|
|
43
|
-
* Two things are deliberate. Switches apply immediately and have no Save button
|
|
44
|
-
* — a switch that needs saving is a broken promise, so anything staged belongs
|
|
45
|
-
* in a form instead. And the destructive action asks for confirmation naming
|
|
46
|
-
* exactly what will be destroyed, rather than a generic "Are you sure?".
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
export interface SettingsProfile {
|
|
50
|
-
name: string;
|
|
51
|
-
email: string;
|
|
52
|
-
bio?: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface SettingsNotification {
|
|
56
|
-
id: string;
|
|
57
|
-
label: string;
|
|
58
|
-
description: string;
|
|
59
|
-
enabled: boolean;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface SettingsBlockProps {
|
|
63
|
-
profile: SettingsProfile;
|
|
64
|
-
notifications: SettingsNotification[];
|
|
65
|
-
/** Staged: the profile form has a Save button. */
|
|
66
|
-
onSaveProfile?: (profile: SettingsProfile) => void;
|
|
67
|
-
/** Immediate: a switch applies as soon as it is flipped. */
|
|
68
|
-
onToggleNotification?: (id: string, enabled: boolean) => void;
|
|
69
|
-
onDeleteAccount?: () => void;
|
|
70
|
-
pending?: boolean;
|
|
71
|
-
/** Extra tabs, if the product needs them. */
|
|
72
|
-
extraTabs?: { value: string; label: string; content: ReactNode }[];
|
|
73
|
-
className?: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function SettingsBlock({
|
|
77
|
-
profile,
|
|
78
|
-
notifications,
|
|
79
|
-
onSaveProfile,
|
|
80
|
-
onToggleNotification,
|
|
81
|
-
onDeleteAccount,
|
|
82
|
-
pending = false,
|
|
83
|
-
extraTabs = [],
|
|
84
|
-
className,
|
|
85
|
-
}: SettingsBlockProps) {
|
|
86
|
-
const [name, setName] = useState(profile.name);
|
|
87
|
-
const [email, setEmail] = useState(profile.email);
|
|
88
|
-
const [bio, setBio] = useState(profile.bio ?? "");
|
|
89
|
-
const [touched, setTouched] = useState(false);
|
|
90
|
-
const [confirmation, setConfirmation] = useState("");
|
|
91
|
-
|
|
92
|
-
const emailError =
|
|
93
|
-
touched && !email.includes("@") ? "Enter a valid email address." : undefined;
|
|
94
|
-
const nameError = touched && name.trim().length === 0 ? "Enter your name." : undefined;
|
|
95
|
-
|
|
96
|
-
function handleSave(event: FormEvent<HTMLFormElement>) {
|
|
97
|
-
event.preventDefault();
|
|
98
|
-
setTouched(true);
|
|
99
|
-
if (!email.includes("@") || name.trim().length === 0) return;
|
|
100
|
-
onSaveProfile?.({ name, email, bio });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
<div className={cn("flex flex-col gap-6", className)}>
|
|
105
|
-
<div>
|
|
106
|
-
<h1 className="text-xl font-semibold tracking-tight">Settings</h1>
|
|
107
|
-
<p className="mt-1 text-sm text-muted-foreground">
|
|
108
|
-
Manage your profile and how you are notified.
|
|
109
|
-
</p>
|
|
110
|
-
</div>
|
|
111
|
-
|
|
112
|
-
<Tabs defaultValue="profile">
|
|
113
|
-
<TabsList variant="underline">
|
|
114
|
-
<TabsTrigger value="profile" variant="underline">
|
|
115
|
-
Profile
|
|
116
|
-
</TabsTrigger>
|
|
117
|
-
<TabsTrigger value="notifications" variant="underline">
|
|
118
|
-
Notifications
|
|
119
|
-
</TabsTrigger>
|
|
120
|
-
{extraTabs.map((tab) => (
|
|
121
|
-
<TabsTrigger key={tab.value} value={tab.value} variant="underline">
|
|
122
|
-
{tab.label}
|
|
123
|
-
</TabsTrigger>
|
|
124
|
-
))}
|
|
125
|
-
<TabsTrigger value="danger" variant="underline">
|
|
126
|
-
Danger zone
|
|
127
|
-
</TabsTrigger>
|
|
128
|
-
</TabsList>
|
|
129
|
-
|
|
130
|
-
<TabsContent value="profile">
|
|
131
|
-
<Card>
|
|
132
|
-
<CardHeader>
|
|
133
|
-
<CardTitle asChild>
|
|
134
|
-
<h2>Profile</h2>
|
|
135
|
-
</CardTitle>
|
|
136
|
-
<CardDescription>This is how you appear to your team.</CardDescription>
|
|
137
|
-
</CardHeader>
|
|
138
|
-
<CardContent>
|
|
139
|
-
<Form onSubmit={handleSave} className="gap-4">
|
|
140
|
-
<div className="flex items-center gap-4">
|
|
141
|
-
<Avatar size="lg">
|
|
142
|
-
<AvatarFallback>
|
|
143
|
-
{name
|
|
144
|
-
.split(" ")
|
|
145
|
-
.map((part) => part[0] ?? "")
|
|
146
|
-
.slice(0, 2)
|
|
147
|
-
.join("")
|
|
148
|
-
.toUpperCase()}
|
|
149
|
-
</AvatarFallback>
|
|
150
|
-
</Avatar>
|
|
151
|
-
<Button type="button" variant="outline" size="sm" disabled={pending}>
|
|
152
|
-
Change photo
|
|
153
|
-
</Button>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
<FormField name="name" error={nameError}>
|
|
157
|
-
<FormLabel>Name</FormLabel>
|
|
158
|
-
<FormControl>
|
|
159
|
-
<Input
|
|
160
|
-
autoComplete="name"
|
|
161
|
-
value={name}
|
|
162
|
-
disabled={pending}
|
|
163
|
-
onChange={(event) => {
|
|
164
|
-
setName(event.target.value);
|
|
165
|
-
}}
|
|
166
|
-
/>
|
|
167
|
-
</FormControl>
|
|
168
|
-
<FormMessage />
|
|
169
|
-
</FormField>
|
|
170
|
-
|
|
171
|
-
<FormField name="email" error={emailError}>
|
|
172
|
-
<FormLabel>Email</FormLabel>
|
|
173
|
-
<FormControl>
|
|
174
|
-
<Input
|
|
175
|
-
type="email"
|
|
176
|
-
autoComplete="email"
|
|
177
|
-
value={email}
|
|
178
|
-
disabled={pending}
|
|
179
|
-
onChange={(event) => {
|
|
180
|
-
setEmail(event.target.value);
|
|
181
|
-
}}
|
|
182
|
-
/>
|
|
183
|
-
</FormControl>
|
|
184
|
-
<FormDescription>Used for sign-in and notifications.</FormDescription>
|
|
185
|
-
<FormMessage />
|
|
186
|
-
</FormField>
|
|
187
|
-
|
|
188
|
-
<FormField name="bio">
|
|
189
|
-
<FormLabel>Bio</FormLabel>
|
|
190
|
-
<FormControl>
|
|
191
|
-
<Input
|
|
192
|
-
value={bio}
|
|
193
|
-
disabled={pending}
|
|
194
|
-
placeholder="A sentence about you"
|
|
195
|
-
onChange={(event) => {
|
|
196
|
-
setBio(event.target.value);
|
|
197
|
-
}}
|
|
198
|
-
/>
|
|
199
|
-
</FormControl>
|
|
200
|
-
</FormField>
|
|
201
|
-
|
|
202
|
-
<div className="flex justify-end">
|
|
203
|
-
<Button type="submit" loading={pending}>
|
|
204
|
-
Save changes
|
|
205
|
-
</Button>
|
|
206
|
-
</div>
|
|
207
|
-
</Form>
|
|
208
|
-
</CardContent>
|
|
209
|
-
</Card>
|
|
210
|
-
</TabsContent>
|
|
211
|
-
|
|
212
|
-
<TabsContent value="notifications">
|
|
213
|
-
<Card>
|
|
214
|
-
<CardHeader>
|
|
215
|
-
<CardTitle asChild>
|
|
216
|
-
<h2>Notifications</h2>
|
|
217
|
-
</CardTitle>
|
|
218
|
-
<CardDescription>
|
|
219
|
-
These apply straight away — there is nothing to save.
|
|
220
|
-
</CardDescription>
|
|
221
|
-
</CardHeader>
|
|
222
|
-
<CardContent className="divide-y divide-border p-0">
|
|
223
|
-
{notifications.map((notification) => (
|
|
224
|
-
<div
|
|
225
|
-
key={notification.id}
|
|
226
|
-
className="flex items-start justify-between gap-4 px-6 py-4"
|
|
227
|
-
>
|
|
228
|
-
<div className="grid gap-1">
|
|
229
|
-
<Label htmlFor={notification.id}>{notification.label}</Label>
|
|
230
|
-
<p className="text-xs text-muted-foreground">{notification.description}</p>
|
|
231
|
-
</div>
|
|
232
|
-
<Switch
|
|
233
|
-
id={notification.id}
|
|
234
|
-
checked={notification.enabled}
|
|
235
|
-
onCheckedChange={(checked) => {
|
|
236
|
-
onToggleNotification?.(notification.id, checked);
|
|
237
|
-
}}
|
|
238
|
-
/>
|
|
239
|
-
</div>
|
|
240
|
-
))}
|
|
241
|
-
</CardContent>
|
|
242
|
-
</Card>
|
|
243
|
-
</TabsContent>
|
|
244
|
-
|
|
245
|
-
{extraTabs.map((tab) => (
|
|
246
|
-
<TabsContent key={tab.value} value={tab.value}>
|
|
247
|
-
{tab.content}
|
|
248
|
-
</TabsContent>
|
|
249
|
-
))}
|
|
250
|
-
|
|
251
|
-
<TabsContent value="danger">
|
|
252
|
-
<Card className="border-destructive/30">
|
|
253
|
-
<CardHeader>
|
|
254
|
-
<CardTitle asChild>
|
|
255
|
-
<h2>Delete this account</h2>
|
|
256
|
-
</CardTitle>
|
|
257
|
-
<CardDescription>
|
|
258
|
-
Everything in the workspace is removed permanently. This cannot be undone.
|
|
259
|
-
</CardDescription>
|
|
260
|
-
</CardHeader>
|
|
261
|
-
<CardContent>
|
|
262
|
-
<Separator className="mb-4" />
|
|
263
|
-
<p className="text-sm text-muted-foreground">
|
|
264
|
-
Your projects, deployments and audit history are deleted along with the account.
|
|
265
|
-
</p>
|
|
266
|
-
</CardContent>
|
|
267
|
-
<CardFooter className="justify-end">
|
|
268
|
-
<Dialog>
|
|
269
|
-
<DialogTrigger asChild>
|
|
270
|
-
<Button variant="destructive">Delete account</Button>
|
|
271
|
-
</DialogTrigger>
|
|
272
|
-
<DialogContent className="max-w-md">
|
|
273
|
-
<DialogHeader>
|
|
274
|
-
<DialogTitle>Delete {profile.name}’s account?</DialogTitle>
|
|
275
|
-
{/* Names what is destroyed. "Are you sure?" tells nobody
|
|
276
|
-
anything they did not already know. */}
|
|
277
|
-
<DialogDescription>
|
|
278
|
-
This permanently deletes the account, its projects and all deployment
|
|
279
|
-
history. It cannot be undone.
|
|
280
|
-
</DialogDescription>
|
|
281
|
-
</DialogHeader>
|
|
282
|
-
|
|
283
|
-
<div className="grid gap-2">
|
|
284
|
-
<Label htmlFor="confirm-delete">
|
|
285
|
-
Type <span className="font-mono">{profile.email}</span> to confirm
|
|
286
|
-
</Label>
|
|
287
|
-
<Input
|
|
288
|
-
id="confirm-delete"
|
|
289
|
-
value={confirmation}
|
|
290
|
-
autoComplete="off"
|
|
291
|
-
onChange={(event) => {
|
|
292
|
-
setConfirmation(event.target.value);
|
|
293
|
-
}}
|
|
294
|
-
/>
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
<DialogFooter>
|
|
298
|
-
<DialogClose asChild>
|
|
299
|
-
<Button variant="outline">Cancel</Button>
|
|
300
|
-
</DialogClose>
|
|
301
|
-
<Button
|
|
302
|
-
variant="destructive"
|
|
303
|
-
disabled={confirmation !== profile.email}
|
|
304
|
-
onClick={onDeleteAccount}
|
|
305
|
-
>
|
|
306
|
-
Delete account
|
|
307
|
-
</Button>
|
|
308
|
-
</DialogFooter>
|
|
309
|
-
</DialogContent>
|
|
310
|
-
</Dialog>
|
|
311
|
-
</CardFooter>
|
|
312
|
-
</Card>
|
|
313
|
-
</TabsContent>
|
|
314
|
-
</Tabs>
|
|
315
|
-
</div>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { SignupBlock, estimatePasswordStrength, type SignupBlockProps } from "./signup";
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { defineMeta } from "@/registry/schema";
|
|
2
|
-
|
|
3
|
-
export const meta = defineMeta({
|
|
4
|
-
name: "signup",
|
|
5
|
-
kind: "block",
|
|
6
|
-
title: "Sign up",
|
|
7
|
-
description: "An account creation form with a password strength hint and terms acceptance.",
|
|
8
|
-
category: "form",
|
|
9
|
-
status: "stable",
|
|
10
|
-
dependencies: [],
|
|
11
|
-
registryDependencies: ["button", "card", "checkbox", "form", "input", "progress"],
|
|
12
|
-
files: ["signup.tsx"],
|
|
13
|
-
a11y:
|
|
14
|
-
"The strength label is text and the bar beside it is aria-hidden, so the estimate is not " +
|
|
15
|
-
'announced twice. autoComplete="new-password" tells password managers to offer a generated ' +
|
|
16
|
-
"password rather than an existing one. Strength is a hint, never a gate: the form is validated " +
|
|
17
|
-
"on length alone.",
|
|
18
|
-
});
|