@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.
Files changed (218) hide show
  1. package/README.md +1 -1
  2. package/dist/blocks/admin-dashboard/meta.js +31 -0
  3. package/dist/blocks/admin-dashboard/meta.js.map +1 -0
  4. package/dist/blocks/agent-console/meta.js +27 -0
  5. package/dist/blocks/agent-console/meta.js.map +1 -0
  6. package/dist/blocks/ai-dashboard/meta.js +25 -0
  7. package/dist/blocks/ai-dashboard/meta.js.map +1 -0
  8. package/dist/blocks/ai-workspace/meta.js +33 -0
  9. package/dist/blocks/ai-workspace/meta.js.map +1 -0
  10. package/dist/blocks/analytics/meta.js +24 -0
  11. package/dist/blocks/analytics/meta.js.map +1 -0
  12. package/dist/blocks/billing/meta.js +25 -0
  13. package/dist/blocks/billing/meta.js.map +1 -0
  14. package/dist/blocks/command-center/meta.js +28 -0
  15. package/dist/blocks/command-center/meta.js.map +1 -0
  16. package/dist/blocks/crm/meta.js +32 -0
  17. package/dist/blocks/crm/meta.js.map +1 -0
  18. package/dist/blocks/onboarding/meta.js +23 -0
  19. package/dist/blocks/onboarding/meta.js.map +1 -0
  20. package/dist/components/accordion/accordion.js +1 -1
  21. package/dist/components/accordion/accordion.js.map +1 -1
  22. package/dist/components/activity-feed/activity-feed.js +1 -1
  23. package/dist/components/activity-feed/activity-feed.js.map +1 -1
  24. package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
  25. package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
  26. package/dist/components/ai-extraction-review/ai-extraction-review.js +1 -1
  27. package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -1
  28. package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
  29. package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
  30. package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
  31. package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
  32. package/dist/components/ai-response/ai-response.js +2 -2
  33. package/dist/components/ai-response/ai-response.js.map +1 -1
  34. package/dist/components/ai-tool/ai-tool.js +2 -2
  35. package/dist/components/ai-tool/ai-tool.js.map +1 -1
  36. package/dist/components/alert/alert.d.ts +1 -1
  37. package/dist/components/avatar/avatar.d.ts +1 -1
  38. package/dist/components/badge/badge.d.ts +1 -1
  39. package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
  40. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
  41. package/dist/components/breadcrumb/breadcrumb.js +141 -0
  42. package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
  43. package/dist/components/breadcrumb/index.d.ts +2 -0
  44. package/dist/components/breadcrumb/index.js +2 -0
  45. package/dist/components/breadcrumb/meta.js +17 -0
  46. package/dist/components/breadcrumb/meta.js.map +1 -0
  47. package/dist/components/button/button.d.ts +1 -1
  48. package/dist/components/calendar/calendar.js +2 -2
  49. package/dist/components/calendar/calendar.js.map +1 -1
  50. package/dist/components/code-block/code-block.js +1 -1
  51. package/dist/components/code-block/code-block.js.map +1 -1
  52. package/dist/components/collapsible/collapsible.d.ts +30 -0
  53. package/dist/components/collapsible/collapsible.d.ts.map +1 -0
  54. package/dist/components/collapsible/collapsible.js +38 -0
  55. package/dist/components/collapsible/collapsible.js.map +1 -0
  56. package/dist/components/collapsible/index.d.ts +2 -0
  57. package/dist/components/collapsible/index.js +2 -0
  58. package/dist/components/collapsible/meta.js +17 -0
  59. package/dist/components/collapsible/meta.js.map +1 -0
  60. package/dist/components/combobox/combobox.js +2 -2
  61. package/dist/components/combobox/combobox.js.map +1 -1
  62. package/dist/components/command/command.js +1 -1
  63. package/dist/components/command/command.js.map +1 -1
  64. package/dist/components/data-table/data-table.d.ts.map +1 -1
  65. package/dist/components/data-table/data-table.js +4 -1
  66. package/dist/components/data-table/data-table.js.map +1 -1
  67. package/dist/components/dialog/dialog.js +2 -2
  68. package/dist/components/dialog/dialog.js.map +1 -1
  69. package/dist/components/diff-viewer/diff-viewer.js +4 -4
  70. package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
  71. package/dist/components/direction/direction.d.ts +35 -0
  72. package/dist/components/direction/direction.d.ts.map +1 -0
  73. package/dist/components/direction/direction.js +14 -0
  74. package/dist/components/direction/direction.js.map +1 -0
  75. package/dist/components/direction/index.d.ts +2 -0
  76. package/dist/components/direction/index.js +2 -0
  77. package/dist/components/direction/meta.js +17 -0
  78. package/dist/components/direction/meta.js.map +1 -0
  79. package/dist/components/drawer/drawer.js +1 -1
  80. package/dist/components/drawer/drawer.js.map +1 -1
  81. package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  82. package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
  83. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
  84. package/dist/components/input/input.js +1 -1
  85. package/dist/components/input/input.js.map +1 -1
  86. package/dist/components/log-viewer/log-viewer.js +2 -2
  87. package/dist/components/log-viewer/log-viewer.js.map +1 -1
  88. package/dist/components/meter/meter.js +1 -1
  89. package/dist/components/meter/meter.js.map +1 -1
  90. package/dist/components/pagination/pagination.d.ts.map +1 -1
  91. package/dist/components/pagination/pagination.js +3 -2
  92. package/dist/components/pagination/pagination.js.map +1 -1
  93. package/dist/components/permission-matrix/permission-matrix.js +3 -3
  94. package/dist/components/permission-matrix/permission-matrix.js.map +1 -1
  95. package/dist/components/progress/progress.d.ts +1 -1
  96. package/dist/components/record-diff/record-diff.js +4 -4
  97. package/dist/components/record-diff/record-diff.js.map +1 -1
  98. package/dist/components/select/select.js +2 -2
  99. package/dist/components/select/select.js.map +1 -1
  100. package/dist/components/sheet/sheet.d.ts.map +1 -1
  101. package/dist/components/sheet/sheet.js +2 -2
  102. package/dist/components/sheet/sheet.js.map +1 -1
  103. package/dist/components/sidebar/index.d.ts +2 -0
  104. package/dist/components/sidebar/index.js +2 -0
  105. package/dist/components/sidebar/meta.js +17 -0
  106. package/dist/components/sidebar/meta.js.map +1 -0
  107. package/dist/components/sidebar/sidebar.d.ts +69 -0
  108. package/dist/components/sidebar/sidebar.d.ts.map +1 -0
  109. package/dist/components/sidebar/sidebar.js +256 -0
  110. package/dist/components/sidebar/sidebar.js.map +1 -0
  111. package/dist/components/spinner/spinner.d.ts +1 -1
  112. package/dist/components/table/table.js +2 -2
  113. package/dist/components/table/table.js.map +1 -1
  114. package/dist/components/tags-input/tags-input.js +1 -1
  115. package/dist/components/tags-input/tags-input.js.map +1 -1
  116. package/dist/components/textarea/index.d.ts +2 -0
  117. package/dist/components/textarea/index.js +2 -0
  118. package/dist/components/textarea/meta.js +17 -0
  119. package/dist/components/textarea/meta.js.map +1 -0
  120. package/dist/components/textarea/textarea.d.ts +43 -0
  121. package/dist/components/textarea/textarea.d.ts.map +1 -0
  122. package/dist/components/textarea/textarea.js +119 -0
  123. package/dist/components/textarea/textarea.js.map +1 -0
  124. package/dist/components/time-range-picker/time-range-picker.js +3 -3
  125. package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
  126. package/dist/components/toast/toast.d.ts +1 -1
  127. package/dist/components/toast/toast.d.ts.map +1 -1
  128. package/dist/components/toast/toast.js +2 -2
  129. package/dist/components/toast/toast.js.map +1 -1
  130. package/dist/index.d.ts +12 -2
  131. package/dist/index.js +7 -2
  132. package/dist/lib/styles.d.ts +16 -1
  133. package/dist/lib/styles.d.ts.map +1 -1
  134. package/dist/lib/styles.js +16 -1
  135. package/dist/lib/styles.js.map +1 -1
  136. package/dist/registry/blocks.d.ts.map +1 -1
  137. package/dist/registry/blocks.js +27 -9
  138. package/dist/registry/blocks.js.map +1 -1
  139. package/dist/registry/components.d.ts.map +1 -1
  140. package/dist/registry/components.js +60 -50
  141. package/dist/registry/components.js.map +1 -1
  142. package/dist/registry/schema.d.ts +8 -0
  143. package/dist/registry/schema.d.ts.map +1 -1
  144. package/dist/registry/schema.js.map +1 -1
  145. package/package.json +5 -4
  146. package/src/components/accordion/accordion.tsx +1 -1
  147. package/src/components/activity-feed/activity-feed.tsx +1 -1
  148. package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
  149. package/src/components/ai-extraction-review/ai-extraction-review.tsx +1 -1
  150. package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
  151. package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
  152. package/src/components/ai-response/ai-response.tsx +2 -2
  153. package/src/components/ai-tool/ai-tool.tsx +2 -2
  154. package/src/components/breadcrumb/breadcrumb.tsx +157 -0
  155. package/src/components/breadcrumb/index.ts +10 -0
  156. package/src/components/breadcrumb/meta.ts +19 -0
  157. package/src/components/calendar/calendar.tsx +2 -2
  158. package/src/components/code-block/code-block.tsx +1 -1
  159. package/src/components/collapsible/collapsible.tsx +63 -0
  160. package/src/components/collapsible/index.ts +6 -0
  161. package/src/components/collapsible/meta.ts +16 -0
  162. package/src/components/combobox/combobox.tsx +2 -2
  163. package/src/components/command/command.tsx +1 -1
  164. package/src/components/data-table/data-table.tsx +14 -3
  165. package/src/components/dialog/dialog.tsx +2 -2
  166. package/src/components/diff-viewer/diff-viewer.tsx +4 -4
  167. package/src/components/direction/direction.tsx +38 -0
  168. package/src/components/direction/index.ts +1 -0
  169. package/src/components/direction/meta.ts +19 -0
  170. package/src/components/drawer/drawer.tsx +1 -1
  171. package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
  172. package/src/components/input/input.tsx +1 -1
  173. package/src/components/log-viewer/log-viewer.tsx +2 -2
  174. package/src/components/meter/meter.tsx +1 -1
  175. package/src/components/pagination/pagination.tsx +13 -2
  176. package/src/components/permission-matrix/permission-matrix.tsx +4 -4
  177. package/src/components/record-diff/record-diff.tsx +4 -4
  178. package/src/components/select/select.tsx +2 -2
  179. package/src/components/sheet/sheet.tsx +8 -4
  180. package/src/components/sidebar/index.ts +19 -0
  181. package/src/components/sidebar/meta.ts +25 -0
  182. package/src/components/sidebar/sidebar.tsx +374 -0
  183. package/src/components/table/table.tsx +3 -3
  184. package/src/components/tags-input/tags-input.tsx +1 -1
  185. package/src/components/textarea/index.ts +1 -0
  186. package/src/components/textarea/meta.ts +21 -0
  187. package/src/components/textarea/textarea.tsx +210 -0
  188. package/src/components/time-range-picker/time-range-picker.tsx +3 -3
  189. package/src/components/toast/toast.tsx +14 -6
  190. package/src/index.ts +5 -0
  191. package/src/lib/styles.ts +16 -0
  192. package/src/registry/blocks.ts +18 -0
  193. package/src/registry/components.ts +10 -0
  194. package/src/registry/schema.ts +8 -0
  195. package/src/blocks/admin-users/admin-users.tsx +0 -251
  196. package/src/blocks/admin-users/index.ts +0 -1
  197. package/src/blocks/admin-users/meta.ts +0 -27
  198. package/src/blocks/ai-chat/ai-chat.tsx +0 -280
  199. package/src/blocks/ai-chat/index.ts +0 -7
  200. package/src/blocks/ai-chat/meta.ts +0 -32
  201. package/src/blocks/dashboard/dashboard.tsx +0 -192
  202. package/src/blocks/dashboard/index.ts +0 -6
  203. package/src/blocks/dashboard/meta.ts +0 -18
  204. package/src/blocks/forgot-password/forgot-password.tsx +0 -158
  205. package/src/blocks/forgot-password/index.ts +0 -1
  206. package/src/blocks/forgot-password/meta.ts +0 -18
  207. package/src/blocks/login/index.ts +0 -1
  208. package/src/blocks/login/login.tsx +0 -178
  209. package/src/blocks/login/meta.ts +0 -18
  210. package/src/blocks/pricing/index.ts +0 -1
  211. package/src/blocks/pricing/meta.ts +0 -18
  212. package/src/blocks/pricing/pricing.tsx +0 -176
  213. package/src/blocks/settings/index.ts +0 -6
  214. package/src/blocks/settings/meta.ts +0 -29
  215. package/src/blocks/settings/settings.tsx +0 -317
  216. package/src/blocks/signup/index.ts +0 -1
  217. package/src/blocks/signup/meta.ts +0 -18
  218. package/src/blocks/signup/signup.tsx +0 -240
@@ -1,192 +0,0 @@
1
- "use client";
2
-
3
- import type { ReactNode } from "react";
4
-
5
- import {
6
- ActivityContent,
7
- ActivityDescription,
8
- ActivityFeed,
9
- ActivityIndicator,
10
- ActivityItem,
11
- ActivityTime,
12
- ActivityTitle,
13
- } from "@/components/activity-feed";
14
- import { Badge } from "@/components/badge";
15
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/card";
16
- import { Skeleton } from "@/components/skeleton";
17
- import { cn } from "@/lib/utils";
18
-
19
- /**
20
- * An overview page: headline numbers and what happened recently.
21
- *
22
- * The trend on a stat is stated in words as well as by direction and colour —
23
- * "up 12.4%" rather than a green triangle — because a triangle is unreadable to
24
- * a screen reader and ambiguous to anyone who cannot tell the colours apart.
25
- */
26
-
27
- export interface DashboardStat {
28
- id: string;
29
- label: string;
30
- value: string;
31
- /** Signed change, e.g. 12.4 or -3.1. Omit when there is nothing to compare to. */
32
- change?: number;
33
- /** What the change is measured against. */
34
- comparison?: string;
35
- /** Whether a rise is good. Churn going up is not. */
36
- higherIsBetter?: boolean;
37
- }
38
-
39
- export interface DashboardEvent {
40
- id: string;
41
- title: string;
42
- detail?: string;
43
- /** Machine-readable timestamp. */
44
- at: string;
45
- /** Human label, e.g. "12 minutes ago". */
46
- label: string;
47
- tone?: "default" | "success" | "warning" | "destructive";
48
- }
49
-
50
- export interface DashboardBlockProps {
51
- title?: string;
52
- description?: string;
53
- stats: DashboardStat[];
54
- events?: DashboardEvent[];
55
- /** Renders placeholders instead of content. */
56
- loading?: boolean;
57
- /** Anything to put beside the heading — a date range picker, an action. */
58
- actions?: ReactNode;
59
- className?: string;
60
- }
61
-
62
- function formatChange(change: number): string {
63
- const rounded = Math.abs(change).toFixed(1);
64
- return `${change >= 0 ? "up" : "down"} ${rounded}%`;
65
- }
66
-
67
- export function DashboardBlock({
68
- title = "Overview",
69
- description = "How things are going.",
70
- stats,
71
- events = [],
72
- loading = false,
73
- actions,
74
- className,
75
- }: DashboardBlockProps) {
76
- return (
77
- <div className={cn("flex flex-col gap-6", className)}>
78
- <div className="flex flex-wrap items-end justify-between gap-3">
79
- <div>
80
- <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
81
- <p className="mt-1 text-sm text-muted-foreground">{description}</p>
82
- </div>
83
- {actions}
84
- </div>
85
-
86
- <section aria-label="Key metrics" className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
87
- {stats.map((stat) => {
88
- const good =
89
- stat.change === undefined
90
- ? undefined
91
- : (stat.higherIsBetter ?? true)
92
- ? stat.change >= 0
93
- : stat.change < 0;
94
-
95
- return (
96
- <Card key={stat.id}>
97
- <CardHeader className="pb-2">
98
- <CardDescription>{stat.label}</CardDescription>
99
- {/* Not a heading: a number is data, and making it one both
100
- breaks the heading order and litters the outline. */}
101
- <p className="text-2xl leading-tight font-semibold tracking-tight tabular-nums">
102
- {loading ? <Skeleton className="h-7 w-24" /> : stat.value}
103
- </p>
104
- </CardHeader>
105
- <CardContent>
106
- {loading ? (
107
- <Skeleton className="h-4 w-32" />
108
- ) : stat.change === undefined ? null : (
109
- <p className={cn("text-xs", good ? "text-success" : "text-destructive")}>
110
- {/* In words: direction and magnitude, not a coloured arrow. */}
111
- {formatChange(stat.change)}
112
- {stat.comparison ? (
113
- <span className="text-muted-foreground"> {stat.comparison}</span>
114
- ) : null}
115
- </p>
116
- )}
117
- </CardContent>
118
- </Card>
119
- );
120
- })}
121
- </section>
122
-
123
- {events.length > 0 || loading ? (
124
- <Card>
125
- <CardHeader>
126
- <CardTitle asChild>
127
- <h2>Recent activity</h2>
128
- </CardTitle>
129
- <CardDescription>The last few things that happened.</CardDescription>
130
- </CardHeader>
131
- <CardContent>
132
- {loading ? (
133
- <div className="flex flex-col gap-4">
134
- {[0, 1, 2].map((index) => (
135
- <div key={index} className="flex gap-3">
136
- <Skeleton className="size-6 rounded-full" />
137
- <div className="flex-1 space-y-2">
138
- <Skeleton className="h-4 w-48" />
139
- <Skeleton className="h-3 w-24" />
140
- </div>
141
- </div>
142
- ))}
143
- </div>
144
- ) : (
145
- <ActivityFeed>
146
- {events.map((event, index) => (
147
- <ActivityItem key={event.id} last={index === events.length - 1}>
148
- <ActivityIndicator
149
- className={cn(
150
- event.tone === "success" && "border-success/40 text-success",
151
- event.tone === "warning" && "border-warning/40 text-warning",
152
- event.tone === "destructive" &&
153
- "border-destructive/40 text-destructive",
154
- )}
155
- />
156
- <ActivityContent>
157
- <ActivityTitle className="flex flex-wrap items-center gap-2">
158
- {event.title}
159
- {event.tone && event.tone !== "default" ? (
160
- <Badge
161
- size="sm"
162
- variant={
163
- event.tone === "success"
164
- ? "success"
165
- : event.tone === "warning"
166
- ? "warning"
167
- : "destructive"
168
- }
169
- >
170
- {event.tone === "success"
171
- ? "Succeeded"
172
- : event.tone === "warning"
173
- ? "Warning"
174
- : "Failed"}
175
- </Badge>
176
- ) : null}
177
- </ActivityTitle>
178
- {event.detail ? (
179
- <ActivityDescription>{event.detail}</ActivityDescription>
180
- ) : null}
181
- <ActivityTime dateTime={event.at}>{event.label}</ActivityTime>
182
- </ActivityContent>
183
- </ActivityItem>
184
- ))}
185
- </ActivityFeed>
186
- )}
187
- </CardContent>
188
- </Card>
189
- ) : null}
190
- </div>
191
- );
192
- }
@@ -1,6 +0,0 @@
1
- export {
2
- DashboardBlock,
3
- type DashboardBlockProps,
4
- type DashboardEvent,
5
- type DashboardStat,
6
- } from "./dashboard";
@@ -1,18 +0,0 @@
1
- import { defineMeta } from "@/registry/schema";
2
-
3
- export const meta = defineMeta({
4
- name: "dashboard",
5
- kind: "block",
6
- title: "Dashboard",
7
- description: "An overview page with headline metrics and a recent activity feed.",
8
- category: "data",
9
- status: "stable",
10
- dependencies: [],
11
- registryDependencies: ["activity-feed", "badge", "card", "skeleton"],
12
- files: ["dashboard.tsx"],
13
- a11y:
14
- 'Each metric states its change in words — "up 12.4%" — rather than by a coloured arrow, ' +
15
- "which is unreadable to a screen reader and ambiguous to anyone who cannot distinguish the " +
16
- "colours. Whether a rise is good is configurable, since churn going up is not. The metrics " +
17
- "sit in a named region so they can be jumped to.",
18
- });
@@ -1,158 +0,0 @@
1
- "use client";
2
-
3
- import { useState, type FormEvent } from "react";
4
-
5
- import { Button } from "@/components/button";
6
- import {
7
- Card,
8
- CardContent,
9
- CardDescription,
10
- CardFooter,
11
- CardHeader,
12
- CardTitle,
13
- } from "@/components/card";
14
- import {
15
- EmptyState,
16
- EmptyStateDescription,
17
- EmptyStateIcon,
18
- EmptyStateTitle,
19
- } from "@/components/empty-state";
20
- import { Form, FormControl, FormField, FormLabel, FormMessage } from "@/components/form";
21
- import { Input } from "@/components/input";
22
- import { cn } from "@/lib/utils";
23
-
24
- /**
25
- * A password reset request.
26
- *
27
- * The confirmation deliberately does not say whether the address was found.
28
- * Telling an anonymous visitor which emails have accounts turns this form into
29
- * an account enumeration oracle, and the same neutral message covers both cases
30
- * without being a lie.
31
- */
32
-
33
- export interface ForgotPasswordBlockProps {
34
- onSubmit?: (values: { email: string }) => void;
35
- /** Switches to the confirmation view. */
36
- sent?: boolean;
37
- error?: string;
38
- pending?: boolean;
39
- loginHref?: string;
40
- className?: string;
41
- }
42
-
43
- export function ForgotPasswordBlock({
44
- onSubmit,
45
- sent = false,
46
- error,
47
- pending = false,
48
- loginHref = "/login",
49
- className,
50
- }: ForgotPasswordBlockProps) {
51
- const [email, setEmail] = useState("");
52
- const [touched, setTouched] = useState(false);
53
-
54
- const emailError =
55
- touched && !email.includes("@") ? "Enter a valid email address." : undefined;
56
-
57
- function handleSubmit(event: FormEvent<HTMLFormElement>) {
58
- event.preventDefault();
59
- setTouched(true);
60
- if (!email.includes("@")) return;
61
- onSubmit?.({ email });
62
- }
63
-
64
- if (sent) {
65
- return (
66
- <Card className={cn("w-full max-w-sm", className)}>
67
- <CardContent className="pt-6">
68
- {/* Announced, because it replaces the form the user just submitted. */}
69
- <EmptyState role="status" aria-live="polite">
70
- <EmptyStateIcon>
71
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
72
- <rect
73
- x="3"
74
- y="5"
75
- width="18"
76
- height="14"
77
- rx="2"
78
- stroke="currentColor"
79
- strokeWidth="2"
80
- />
81
- <path
82
- d="m3 7 9 6 9-6"
83
- stroke="currentColor"
84
- strokeWidth="2"
85
- strokeLinecap="round"
86
- />
87
- </svg>
88
- </EmptyStateIcon>
89
- <EmptyStateTitle>Check your inbox</EmptyStateTitle>
90
- <EmptyStateDescription>
91
- If an account exists for {email}, a reset link is on its way. It expires in an
92
- hour.
93
- </EmptyStateDescription>
94
- </EmptyState>
95
- </CardContent>
96
- <CardFooter className="justify-center">
97
- <a
98
- href={loginHref}
99
- className="text-sm font-medium text-primary underline underline-offset-4"
100
- >
101
- Back to sign in
102
- </a>
103
- </CardFooter>
104
- </Card>
105
- );
106
- }
107
-
108
- return (
109
- <Card className={cn("w-full max-w-sm", className)}>
110
- <CardHeader>
111
- <CardTitle>Reset your password</CardTitle>
112
- <CardDescription>
113
- Enter the address you signed up with and we will send you a link.
114
- </CardDescription>
115
- </CardHeader>
116
-
117
- <CardContent>
118
- <Form onSubmit={handleSubmit} className="gap-4">
119
- {error ? (
120
- <p role="alert" className="text-sm font-medium text-destructive">
121
- {error}
122
- </p>
123
- ) : null}
124
-
125
- <FormField name="email" error={emailError}>
126
- <FormLabel>Email</FormLabel>
127
- <FormControl>
128
- <Input
129
- type="email"
130
- autoComplete="email"
131
- placeholder="you@example.com"
132
- value={email}
133
- disabled={pending}
134
- onChange={(event) => {
135
- setEmail(event.target.value);
136
- }}
137
- />
138
- </FormControl>
139
- <FormMessage />
140
- </FormField>
141
-
142
- <Button type="submit" loading={pending} className="w-full">
143
- Send reset link
144
- </Button>
145
- </Form>
146
- </CardContent>
147
-
148
- <CardFooter className="justify-center">
149
- <a
150
- href={loginHref}
151
- className="text-sm text-muted-foreground underline underline-offset-4"
152
- >
153
- Back to sign in
154
- </a>
155
- </CardFooter>
156
- </Card>
157
- );
158
- }
@@ -1 +0,0 @@
1
- export { ForgotPasswordBlock, type ForgotPasswordBlockProps } from "./forgot-password";
@@ -1,18 +0,0 @@
1
- import { defineMeta } from "@/registry/schema";
2
-
3
- export const meta = defineMeta({
4
- name: "forgot-password",
5
- kind: "block",
6
- title: "Forgot password",
7
- description:
8
- "A password reset request, with a confirmation that does not leak account existence.",
9
- category: "form",
10
- status: "stable",
11
- dependencies: [],
12
- registryDependencies: ["button", "card", "empty-state", "form", "input"],
13
- files: ["forgot-password.tsx"],
14
- a11y:
15
- "The confirmation replaces the form, so it is a polite live region — otherwise a screen " +
16
- "reader user submits and hears nothing. The wording is deliberately neutral about whether " +
17
- "the address exists: confirming it would turn this form into an account enumeration oracle.",
18
- });
@@ -1 +0,0 @@
1
- export { LoginBlock, type LoginBlockProps } from "./login";
@@ -1,178 +0,0 @@
1
- "use client";
2
-
3
- import { useState, type FormEvent } from "react";
4
-
5
- import { Button } from "@/components/button";
6
- import {
7
- Card,
8
- CardContent,
9
- CardDescription,
10
- CardFooter,
11
- CardHeader,
12
- CardTitle,
13
- } from "@/components/card";
14
- import { Checkbox } from "@/components/checkbox";
15
- import { Form, FormControl, FormField, FormLabel, FormMessage } from "@/components/form";
16
- import { Input } from "@/components/input";
17
- import { Label } from "@/components/label";
18
- import { Separator } from "@/components/separator";
19
- import { cn } from "@/lib/utils";
20
-
21
- /**
22
- * A sign-in form.
23
- *
24
- * Wired for the parts that are easy to get wrong rather than left as markup:
25
- * the email field is typed and autocompleted correctly, errors are announced,
26
- * and the submit button reports its own busy state instead of leaving people
27
- * wondering whether the click registered.
28
- *
29
- * Validation here is the minimum that makes the form usable offline. Real
30
- * validation belongs to whatever the form is submitted to — pass `error` for a
31
- * server response.
32
- */
33
-
34
- export interface LoginBlockProps {
35
- /** Called with the submitted credentials. */
36
- onSubmit?: (values: { email: string; password: string; remember: boolean }) => void;
37
- /** A server-side error, shown above the fields. */
38
- error?: string;
39
- /** Disables the form and shows the submit button as busy. */
40
- pending?: boolean;
41
- /** Where "Forgot password?" goes. */
42
- forgotHref?: string;
43
- /** Where "Create an account" goes. */
44
- signupHref?: string;
45
- className?: string;
46
- }
47
-
48
- export function LoginBlock({
49
- onSubmit,
50
- error,
51
- pending = false,
52
- forgotHref = "/forgot-password",
53
- signupHref = "/signup",
54
- className,
55
- }: LoginBlockProps) {
56
- const [email, setEmail] = useState("");
57
- const [password, setPassword] = useState("");
58
- const [remember, setRemember] = useState(false);
59
- const [touched, setTouched] = useState(false);
60
-
61
- const emailError =
62
- touched && !email.includes("@") ? "Enter a valid email address." : undefined;
63
- const passwordError = touched && password.length === 0 ? "Enter your password." : undefined;
64
-
65
- function handleSubmit(event: FormEvent<HTMLFormElement>) {
66
- event.preventDefault();
67
- setTouched(true);
68
- if (!email.includes("@") || password.length === 0) return;
69
- onSubmit?.({ email, password, remember });
70
- }
71
-
72
- return (
73
- <Card className={cn("w-full max-w-sm", className)}>
74
- <CardHeader>
75
- <CardTitle>Sign in</CardTitle>
76
- <CardDescription>Welcome back. Enter your details to continue.</CardDescription>
77
- </CardHeader>
78
-
79
- <CardContent>
80
- <Form onSubmit={handleSubmit} className="gap-4">
81
- {/* A server error is assertive: it arrived after the user acted, and
82
- it is the reason nothing happened. */}
83
- {error ? (
84
- <p role="alert" className="text-sm font-medium text-destructive">
85
- {error}
86
- </p>
87
- ) : null}
88
-
89
- <FormField name="email" error={emailError}>
90
- <FormLabel>Email</FormLabel>
91
- <FormControl>
92
- <Input
93
- type="email"
94
- autoComplete="email"
95
- placeholder="you@example.com"
96
- value={email}
97
- disabled={pending}
98
- onChange={(event) => {
99
- setEmail(event.target.value);
100
- }}
101
- />
102
- </FormControl>
103
- <FormMessage />
104
- </FormField>
105
-
106
- <FormField name="password" error={passwordError}>
107
- <div className="flex items-center justify-between">
108
- <FormLabel>Password</FormLabel>
109
- <a
110
- href={forgotHref}
111
- className="text-xs font-medium text-muted-foreground underline underline-offset-4 hover:text-foreground"
112
- >
113
- Forgot password?
114
- </a>
115
- </div>
116
- <FormControl>
117
- <Input
118
- type="password"
119
- autoComplete="current-password"
120
- value={password}
121
- disabled={pending}
122
- onChange={(event) => {
123
- setPassword(event.target.value);
124
- }}
125
- />
126
- </FormControl>
127
- <FormMessage />
128
- </FormField>
129
-
130
- <div className="flex items-center gap-2">
131
- <Checkbox
132
- id="remember-me"
133
- checked={remember}
134
- disabled={pending}
135
- onCheckedChange={(checked) => {
136
- setRemember(checked === true);
137
- }}
138
- />
139
- <Label htmlFor="remember-me" className="text-sm font-normal">
140
- Keep me signed in
141
- </Label>
142
- </div>
143
-
144
- <Button type="submit" loading={pending} className="w-full">
145
- Sign in
146
- </Button>
147
- </Form>
148
-
149
- <div className="my-5 flex items-center gap-3">
150
- <Separator className="flex-1" />
151
- <span className="text-2xs text-muted-foreground uppercase">or</span>
152
- <Separator className="flex-1" />
153
- </div>
154
-
155
- <div className="grid gap-2">
156
- <Button variant="outline" className="w-full" disabled={pending}>
157
- Continue with GitHub
158
- </Button>
159
- <Button variant="outline" className="w-full" disabled={pending}>
160
- Continue with Google
161
- </Button>
162
- </div>
163
- </CardContent>
164
-
165
- <CardFooter className="justify-center">
166
- <p className="text-sm text-muted-foreground">
167
- Don&rsquo;t have an account?{" "}
168
- <a
169
- href={signupHref}
170
- className="font-medium text-primary underline underline-offset-4"
171
- >
172
- Create one
173
- </a>
174
- </p>
175
- </CardFooter>
176
- </Card>
177
- );
178
- }
@@ -1,18 +0,0 @@
1
- import { defineMeta } from "@/registry/schema";
2
-
3
- export const meta = defineMeta({
4
- name: "login",
5
- kind: "block",
6
- title: "Login",
7
- description: "A sign-in form with validation, social providers and a busy state.",
8
- category: "form",
9
- status: "stable",
10
- dependencies: [],
11
- registryDependencies: ["button", "card", "checkbox", "form", "input", "label", "separator"],
12
- files: ["login.tsx"],
13
- a11y:
14
- "Email and password fields carry the right type and autocomplete, so password managers and " +
15
- "mobile keyboards behave. Field errors are tied to their inputs through the Form wiring; a " +
16
- "server error is an assertive alert, because it arrived after the user acted and explains why " +
17
- "nothing happened. The submit button reports its own busy state rather than going quiet.",
18
- });
@@ -1 +0,0 @@
1
- export { PricingBlock, type PricingBlockProps, type PricingPlan } from "./pricing";
@@ -1,18 +0,0 @@
1
- import { defineMeta } from "@/registry/schema";
2
-
3
- export const meta = defineMeta({
4
- name: "pricing",
5
- kind: "block",
6
- title: "Pricing",
7
- description: "A pricing section with plan cards and a monthly/yearly toggle.",
8
- category: "layout",
9
- status: "stable",
10
- dependencies: [],
11
- registryDependencies: ["badge", "button", "card", "label", "switch"],
12
- files: ["pricing.tsx"],
13
- a11y:
14
- "Switching the billing period rewrites every price on the page, so the change is announced " +
15
- "politely rather than happening silently. Each plan's call to action names the plan — three " +
16
- 'buttons all reading "Choose" are indistinguishable out of context. Feature ticks are ' +
17
- "decorative; the feature text carries the meaning.",
18
- });