@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,251 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- columnVisibilityFeature,
5
- createPaginatedRowModel,
6
- createSortedRowModel,
7
- rowPaginationFeature,
8
- rowSortingFeature,
9
- tableFeatures,
10
- useTable,
11
- } from "@tanstack/react-table";
12
- import { useMemo, useState } from "react";
13
-
14
- import { Avatar, AvatarFallback } from "@/components/avatar";
15
- import { Badge } from "@/components/badge";
16
- import { Button } from "@/components/button";
17
- import {
18
- DataTable,
19
- DataTableColumnHeader,
20
- DataTablePagination,
21
- DataTableViewOptions,
22
- } from "@/components/data-table";
23
- import {
24
- DropdownMenu,
25
- DropdownMenuContent,
26
- DropdownMenuItem,
27
- DropdownMenuLabel,
28
- DropdownMenuSeparator,
29
- DropdownMenuTrigger,
30
- } from "@/components/dropdown-menu";
31
- import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/empty-state";
32
- import { Input } from "@/components/input";
33
- import { Label } from "@/components/label";
34
- import { cn } from "@/lib/utils";
35
-
36
- /**
37
- * A user administration table.
38
- *
39
- * The filter is the part worth reading. Results change as you type, which for
40
- * a screen reader user is a change they never asked for and cannot see — so the
41
- * row count is announced politely, and the search input is a labelled control
42
- * rather than a placeholder-only box.
43
- */
44
-
45
- export interface AdminUser {
46
- id: string;
47
- name: string;
48
- email: string;
49
- role: "Owner" | "Admin" | "Member" | "Viewer";
50
- status: "Active" | "Invited" | "Suspended";
51
- lastActive: string;
52
- }
53
-
54
- export interface AdminUsersBlockProps {
55
- users: AdminUser[];
56
- onInvite?: () => void;
57
- onEdit?: (user: AdminUser) => void;
58
- onRemove?: (user: AdminUser) => void;
59
- className?: string;
60
- }
61
-
62
- const features = tableFeatures({
63
- rowSortingFeature,
64
- rowPaginationFeature,
65
- columnVisibilityFeature,
66
- sortedRowModel: createSortedRowModel(),
67
- paginatedRowModel: createPaginatedRowModel(),
68
- });
69
-
70
- function initials(name: string): string {
71
- return name
72
- .split(" ")
73
- .map((part) => part[0] ?? "")
74
- .slice(0, 2)
75
- .join("")
76
- .toUpperCase();
77
- }
78
-
79
- export function AdminUsersBlock({
80
- users,
81
- onInvite,
82
- onEdit,
83
- onRemove,
84
- className,
85
- }: AdminUsersBlockProps) {
86
- const [query, setQuery] = useState("");
87
-
88
- // Filtered here rather than through the table's filtering feature, so the
89
- // block stays usable without wiring that feature up.
90
- const filtered = useMemo(() => {
91
- const needle = query.trim().toLowerCase();
92
- if (!needle) return users;
93
- return users.filter(
94
- (user) =>
95
- user.name.toLowerCase().includes(needle) || user.email.toLowerCase().includes(needle),
96
- );
97
- }, [users, query]);
98
-
99
- const table = useTable({
100
- features,
101
- data: filtered,
102
- initialState: { pagination: { pageIndex: 0, pageSize: 10 } },
103
- columns: [
104
- {
105
- accessorKey: "name",
106
- header: ({ column }) => <DataTableColumnHeader column={column} title="User" />,
107
- cell: ({ row }) => (
108
- <div className="flex items-center gap-3">
109
- <Avatar size="sm">
110
- <AvatarFallback>{initials(row.original.name)}</AvatarFallback>
111
- </Avatar>
112
- <div className="min-w-0">
113
- <p className="truncate text-sm font-medium">{row.original.name}</p>
114
- <p className="truncate text-xs text-muted-foreground">{row.original.email}</p>
115
- </div>
116
- </div>
117
- ),
118
- },
119
- {
120
- accessorKey: "role",
121
- header: ({ column }) => <DataTableColumnHeader column={column} title="Role" />,
122
- },
123
- {
124
- accessorKey: "status",
125
- header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
126
- cell: ({ row }) => (
127
- <Badge
128
- size="sm"
129
- variant={
130
- row.original.status === "Active"
131
- ? "success"
132
- : row.original.status === "Suspended"
133
- ? "destructive"
134
- : "secondary"
135
- }
136
- >
137
- {row.original.status}
138
- </Badge>
139
- ),
140
- },
141
- {
142
- accessorKey: "lastActive",
143
- header: ({ column }) => <DataTableColumnHeader column={column} title="Last active" />,
144
- cell: ({ row }) => (
145
- <span className="text-sm text-muted-foreground">{row.original.lastActive}</span>
146
- ),
147
- },
148
- {
149
- id: "actions",
150
- // Not an empty header: a th with no text is a column a screen reader
151
- // cannot name. Hidden visually, present in the table semantics.
152
- header: () => <span className="sr-only">Actions</span>,
153
- enableSorting: false,
154
- cell: ({ row }) => (
155
- <DropdownMenu>
156
- <DropdownMenuTrigger asChild>
157
- <Button
158
- variant="ghost"
159
- size="icon-sm"
160
- // Named per row: four identical "Actions" buttons tell a screen
161
- // reader user nothing about which row they are on.
162
- aria-label={`Actions for ${row.original.name}`}
163
- >
164
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
165
- <circle cx="12" cy="5" r="1.5" fill="currentColor" />
166
- <circle cx="12" cy="12" r="1.5" fill="currentColor" />
167
- <circle cx="12" cy="19" r="1.5" fill="currentColor" />
168
- </svg>
169
- </Button>
170
- </DropdownMenuTrigger>
171
- <DropdownMenuContent align="end">
172
- <DropdownMenuLabel>{row.original.name}</DropdownMenuLabel>
173
- <DropdownMenuSeparator />
174
- <DropdownMenuItem
175
- onSelect={() => {
176
- onEdit?.(row.original);
177
- }}
178
- >
179
- Edit member
180
- </DropdownMenuItem>
181
- <DropdownMenuItem
182
- variant="destructive"
183
- onSelect={() => {
184
- onRemove?.(row.original);
185
- }}
186
- >
187
- Remove from team
188
- </DropdownMenuItem>
189
- </DropdownMenuContent>
190
- </DropdownMenu>
191
- ),
192
- },
193
- ],
194
- });
195
-
196
- return (
197
- <div className={cn("flex flex-col gap-4", className)}>
198
- <div className="flex flex-wrap items-end justify-between gap-3">
199
- <div>
200
- <h1 className="text-xl font-semibold tracking-tight">Team members</h1>
201
- <p className="mt-1 text-sm text-muted-foreground">
202
- {users.length} {users.length === 1 ? "person" : "people"} in this workspace.
203
- </p>
204
- </div>
205
- <Button onClick={onInvite}>Invite people</Button>
206
- </div>
207
-
208
- <div className="flex flex-wrap items-center gap-2">
209
- <div className="grid flex-1 gap-1.5 sm:max-w-xs">
210
- {/* A real label, not a placeholder: placeholders vanish as soon as
211
- anyone types, taking the field's name with them. */}
212
- <Label htmlFor="user-filter" className="sr-only">
213
- Filter by name or email
214
- </Label>
215
- <Input
216
- id="user-filter"
217
- inputSize="sm"
218
- type="search"
219
- placeholder="Filter by name or email…"
220
- value={query}
221
- onChange={(event) => {
222
- setQuery(event.target.value);
223
- }}
224
- />
225
- </div>
226
- <DataTableViewOptions table={table} />
227
- </div>
228
-
229
- {/* Filtering changes the results without the user asking, so the count is
230
- announced. Polite, so it waits for a pause in typing. */}
231
- <p aria-live="polite" className="sr-only">
232
- {filtered.length} {filtered.length === 1 ? "person" : "people"} match the filter.
233
- </p>
234
-
235
- <DataTable
236
- table={table}
237
- aria-label="Team members"
238
- empty={
239
- <EmptyState size="sm">
240
- <EmptyStateTitle>No one matches “{query}”</EmptyStateTitle>
241
- <EmptyStateDescription>
242
- Try a different name or email address.
243
- </EmptyStateDescription>
244
- </EmptyState>
245
- }
246
- />
247
-
248
- <DataTablePagination table={table} pageSizes={[10, 25, 50]} />
249
- </div>
250
- );
251
- }
@@ -1 +0,0 @@
1
- export { AdminUsersBlock, type AdminUser, type AdminUsersBlockProps } from "./admin-users";
@@ -1,27 +0,0 @@
1
- import { defineMeta } from "@/registry/schema";
2
-
3
- export const meta = defineMeta({
4
- name: "admin-users",
5
- kind: "block",
6
- title: "Admin — users",
7
- description: "A team administration table with filtering, sorting and per-row actions.",
8
- category: "data",
9
- status: "stable",
10
- dependencies: ["@tanstack/react-table"],
11
- registryDependencies: [
12
- "avatar",
13
- "badge",
14
- "button",
15
- "data-table",
16
- "dropdown-menu",
17
- "empty-state",
18
- "input",
19
- "label",
20
- ],
21
- files: ["admin-users.tsx"],
22
- a11y:
23
- "Filtering changes the results without the user asking, so the matching count is announced " +
24
- "politely. The filter has a real label rather than only a placeholder, which disappears the " +
25
- "moment anyone types. Each row's action button is named after its row — four identical " +
26
- '"Actions" buttons say nothing about which row you are on.',
27
- });
@@ -1,280 +0,0 @@
1
- "use client";
2
-
3
- import { useState, type FormEvent, type ReactNode } from "react";
4
-
5
- import {
6
- Conversation,
7
- ConversationMessages,
8
- ConversationScrollButton,
9
- ConversationStatus,
10
- } from "@/components/ai-conversation";
11
- import {
12
- Message,
13
- MessageActions,
14
- MessageAvatar,
15
- MessageBody,
16
- MessageFooter,
17
- } from "@/components/ai-message";
18
- import { ModelSelector, type ModelOption } from "@/components/ai-model-selector";
19
- import {
20
- PromptInput,
21
- PromptInputSubmit,
22
- PromptInputTextarea,
23
- PromptInputToolbar,
24
- } from "@/components/ai-prompt-input";
25
- import { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-reasoning";
26
- import { Response, ThinkingIndicator } from "@/components/ai-response";
27
- import { Source, Sources, SourcesContent, SourcesTrigger } from "@/components/ai-sources";
28
- import { TokenCount } from "@/components/ai-token-usage";
29
- import {
30
- Tool,
31
- ToolContent,
32
- ToolHeader,
33
- ToolPayload,
34
- ToolSection,
35
- type ToolStatus,
36
- } from "@/components/ai-tool";
37
- import { Button } from "@/components/button";
38
- import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/empty-state";
39
- import { cn } from "@/lib/utils";
40
-
41
- /**
42
- * A complete chat surface.
43
- *
44
- * Assembled from the AI components rather than reimplementing any of them, so
45
- * every decision they encode still holds here: the transcript is a list and not
46
- * a live region, state is announced separately, and the composer will not send
47
- * mid-IME-composition.
48
- */
49
-
50
- export interface ChatToolCall {
51
- id: string;
52
- name: string;
53
- status: ToolStatus;
54
- arguments?: string;
55
- result?: string;
56
- }
57
-
58
- export interface ChatSource {
59
- index: number;
60
- title: string;
61
- origin?: string;
62
- href?: string;
63
- }
64
-
65
- export interface ChatMessage {
66
- id: string;
67
- from: "user" | "assistant";
68
- content: string;
69
- reasoning?: string;
70
- tools?: ChatToolCall[];
71
- sources?: ChatSource[];
72
- tokens?: number;
73
- /** Still arriving: shows the caret. */
74
- streaming?: boolean;
75
- }
76
-
77
- export interface AiChatBlockProps {
78
- messages: ChatMessage[];
79
- onSend?: (message: string) => void;
80
- onStop?: () => void;
81
- /** A response is in flight. */
82
- busy?: boolean;
83
- /** Shown before the first token arrives. */
84
- waiting?: boolean;
85
- models?: ModelOption[];
86
- model?: string;
87
- onModelChange?: (model: string) => void;
88
- /** Shown when the transcript is empty. */
89
- empty?: ReactNode;
90
- placeholder?: string;
91
- className?: string;
92
- }
93
-
94
- export function AiChatBlock({
95
- messages,
96
- onSend,
97
- onStop,
98
- busy = false,
99
- waiting = false,
100
- models,
101
- model,
102
- onModelChange,
103
- empty,
104
- placeholder = "Ask anything…",
105
- className,
106
- }: AiChatBlockProps) {
107
- const [draft, setDraft] = useState("");
108
-
109
- function handleSubmit(event: FormEvent<HTMLFormElement>) {
110
- event.preventDefault();
111
- const trimmed = draft.trim();
112
- if (!trimmed) return;
113
- onSend?.(trimmed);
114
- setDraft("");
115
- }
116
-
117
- return (
118
- <div className={cn("flex h-full min-h-0 flex-col", className)}>
119
- <Conversation>
120
- {messages.length === 0 ? (
121
- <div className="grid h-full place-items-center p-8">
122
- {empty ?? (
123
- <EmptyState>
124
- <EmptyStateTitle>Start a conversation</EmptyStateTitle>
125
- <EmptyStateDescription>
126
- Ask a question, paste an error, or describe what you are trying to build.
127
- </EmptyStateDescription>
128
- </EmptyState>
129
- )}
130
- </div>
131
- ) : (
132
- <ConversationMessages>
133
- {messages.map((message) => (
134
- <Message key={message.id} from={message.from}>
135
- {message.from === "assistant" ? <MessageAvatar>AI</MessageAvatar> : null}
136
- <MessageBody from={message.from} className="min-w-0 flex-1">
137
- {message.reasoning ? (
138
- <Reasoning className="mb-2">
139
- <ReasoningTrigger streaming={message.streaming} />
140
- <ReasoningContent>{message.reasoning}</ReasoningContent>
141
- </Reasoning>
142
- ) : null}
143
-
144
- {message.tools?.map((tool) => (
145
- <Tool key={tool.id} status={tool.status} className="mb-2">
146
- <ToolHeader name={tool.name} status={tool.status} />
147
- <ToolContent>
148
- {tool.arguments ? (
149
- <ToolSection label="Arguments">
150
- <ToolPayload label={`${tool.name} arguments`}>
151
- {tool.arguments}
152
- </ToolPayload>
153
- </ToolSection>
154
- ) : null}
155
- {tool.result ? (
156
- <ToolSection label="Result">
157
- <ToolPayload label={`${tool.name} result`}>
158
- {tool.result}
159
- </ToolPayload>
160
- </ToolSection>
161
- ) : null}
162
- </ToolContent>
163
- </Tool>
164
- ))}
165
-
166
- {message.from === "assistant" ? (
167
- <Response streaming={message.streaming}>{message.content}</Response>
168
- ) : (
169
- message.content
170
- )}
171
-
172
- {message.sources && message.sources.length > 0 ? (
173
- <Sources className="mt-3">
174
- <SourcesTrigger count={message.sources.length} />
175
- <SourcesContent>
176
- {message.sources.map((source) => (
177
- <Source
178
- key={source.index}
179
- index={source.index}
180
- title={source.title}
181
- origin={source.origin}
182
- href={source.href ?? "#"}
183
- />
184
- ))}
185
- </SourcesContent>
186
- </Sources>
187
- ) : null}
188
-
189
- {message.from === "assistant" && !message.streaming ? (
190
- <>
191
- <MessageActions>
192
- <Button variant="ghost" size="icon-sm" aria-label="Copy response">
193
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
194
- <rect
195
- x="9"
196
- y="9"
197
- width="12"
198
- height="12"
199
- rx="2"
200
- stroke="currentColor"
201
- strokeWidth="2"
202
- />
203
- <path
204
- d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"
205
- stroke="currentColor"
206
- strokeWidth="2"
207
- strokeLinecap="round"
208
- />
209
- </svg>
210
- </Button>
211
- <Button variant="ghost" size="icon-sm" aria-label="Regenerate response">
212
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
213
- <path
214
- d="M20 12a8 8 0 1 1-2.3-5.6M20 4v5h-5"
215
- stroke="currentColor"
216
- strokeWidth="2"
217
- strokeLinecap="round"
218
- strokeLinejoin="round"
219
- />
220
- </svg>
221
- </Button>
222
- </MessageActions>
223
- {message.tokens === undefined ? null : (
224
- <MessageFooter>
225
- <TokenCount value={message.tokens} />
226
- </MessageFooter>
227
- )}
228
- </>
229
- ) : null}
230
- </MessageBody>
231
- </Message>
232
- ))}
233
-
234
- {waiting ? (
235
- <Message from="assistant">
236
- <MessageAvatar>AI</MessageAvatar>
237
- <MessageBody from="assistant">
238
- <ThinkingIndicator />
239
- </MessageBody>
240
- </Message>
241
- ) : null}
242
- </ConversationMessages>
243
- )}
244
-
245
- <ConversationScrollButton />
246
- </Conversation>
247
-
248
- {/* State, never content — the transcript above stays navigable at the
249
- reader's own pace. */}
250
- <ConversationStatus className="px-4 pb-1">
251
- {waiting ? "Waiting for a response" : busy ? "Generating response" : ""}
252
- </ConversationStatus>
253
-
254
- <div className="border-t border-border p-4">
255
- <PromptInput busy={busy} onSubmit={handleSubmit}>
256
- <PromptInputTextarea
257
- aria-label="Message"
258
- placeholder={placeholder}
259
- value={draft}
260
- onChange={(event) => {
261
- setDraft(event.target.value);
262
- }}
263
- />
264
- <PromptInputToolbar>
265
- {models && models.length > 0 ? (
266
- <ModelSelector
267
- aria-label="Model"
268
- models={models}
269
- value={model}
270
- onValueChange={onModelChange}
271
- triggerSize="sm"
272
- />
273
- ) : null}
274
- <PromptInputSubmit onStop={onStop} />
275
- </PromptInputToolbar>
276
- </PromptInput>
277
- </div>
278
- </div>
279
- );
280
- }
@@ -1,7 +0,0 @@
1
- export {
2
- AiChatBlock,
3
- type AiChatBlockProps,
4
- type ChatMessage,
5
- type ChatSource,
6
- type ChatToolCall,
7
- } from "./ai-chat";
@@ -1,32 +0,0 @@
1
- import { defineMeta } from "@/registry/schema";
2
-
3
- export const meta = defineMeta({
4
- name: "ai-chat",
5
- kind: "block",
6
- title: "AI Chat",
7
- description:
8
- "A complete chat surface: transcript, reasoning, tool calls, sources and composer.",
9
- category: "ai",
10
- status: "stable",
11
- dependencies: [],
12
- registryDependencies: [
13
- "ai-conversation",
14
- "ai-message",
15
- "ai-model-selector",
16
- "ai-prompt-input",
17
- "ai-reasoning",
18
- "ai-response",
19
- "ai-sources",
20
- "ai-token-usage",
21
- "ai-tool",
22
- "button",
23
- "empty-state",
24
- ],
25
- files: ["ai-chat.tsx"],
26
- a11y:
27
- "Assembled from the AI components rather than reimplementing them, so their decisions still " +
28
- "hold: the transcript is a list and not a live region, state is announced separately through " +
29
- "ConversationStatus, and the composer will not send while an IME composition is active. Tool " +
30
- "payloads are named per tool, so two open tool calls do not present two regions called " +
31
- '"Result".',
32
- });