@agent-native/toolkit 0.9.1 → 0.10.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 (162) hide show
  1. package/agent-native.eject.json +71 -0
  2. package/dist/chat-history/ChatHistoryRail.d.ts +15 -1
  3. package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
  4. package/dist/chat-history/ChatHistoryRail.js +26 -13
  5. package/dist/chat-history/ChatHistoryRail.js.map +1 -1
  6. package/dist/chat-history/ChatHistoryRail.spec.js +77 -1
  7. package/dist/chat-history/ChatHistoryRail.spec.js.map +1 -1
  8. package/dist/chat-history/index.d.ts +2 -1
  9. package/dist/chat-history/index.d.ts.map +1 -1
  10. package/dist/chat-history/index.js +2 -1
  11. package/dist/chat-history/index.js.map +1 -1
  12. package/dist/chat-history/useChatHistoryRailController.d.ts +26 -0
  13. package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -0
  14. package/dist/chat-history/useChatHistoryRailController.js +36 -0
  15. package/dist/chat-history/useChatHistoryRailController.js.map +1 -0
  16. package/dist/chat-history/useChatHistoryRailController.spec.d.ts +2 -0
  17. package/dist/chat-history/useChatHistoryRailController.spec.d.ts.map +1 -0
  18. package/dist/chat-history/useChatHistoryRailController.spec.js +101 -0
  19. package/dist/chat-history/useChatHistoryRailController.spec.js.map +1 -0
  20. package/dist/chat-history.css +2 -0
  21. package/dist/composer/ComposerPlusMenu.js +1 -1
  22. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  23. package/dist/composer/PromptComposer.d.ts.map +1 -1
  24. package/dist/composer/PromptComposer.js +22 -4
  25. package/dist/composer/PromptComposer.js.map +1 -1
  26. package/dist/composer/useRealtimeVoiceMode.d.ts +7 -0
  27. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  28. package/dist/composer/useRealtimeVoiceMode.js +91 -19
  29. package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
  30. package/dist/composer/useRealtimeVoiceMode.spec.js +58 -9
  31. package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -1
  32. package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts +7 -0
  33. package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts.map +1 -0
  34. package/dist/conformance/__fixtures__/css-in-js-adapter.js +84 -0
  35. package/dist/conformance/__fixtures__/css-in-js-adapter.js.map +1 -0
  36. package/dist/conformance/conformance.spec.d.ts +2 -0
  37. package/dist/conformance/conformance.spec.d.ts.map +1 -0
  38. package/dist/conformance/conformance.spec.js +58 -0
  39. package/dist/conformance/conformance.spec.js.map +1 -0
  40. package/dist/conformance/index.d.ts +4 -0
  41. package/dist/conformance/index.d.ts.map +1 -0
  42. package/dist/conformance/index.js +4 -0
  43. package/dist/conformance/index.js.map +1 -0
  44. package/dist/conformance/runner.d.ts +12 -0
  45. package/dist/conformance/runner.d.ts.map +1 -0
  46. package/dist/conformance/runner.js +441 -0
  47. package/dist/conformance/runner.js.map +1 -0
  48. package/dist/conformance/types.d.ts +26 -0
  49. package/dist/conformance/types.d.ts.map +1 -0
  50. package/dist/conformance/types.js +13 -0
  51. package/dist/conformance/types.js.map +1 -0
  52. package/dist/conformance/version.d.ts +6 -0
  53. package/dist/conformance/version.d.ts.map +1 -0
  54. package/dist/conformance/version.js +14 -0
  55. package/dist/conformance/version.js.map +1 -0
  56. package/dist/design-system/components.d.ts +64 -0
  57. package/dist/design-system/components.d.ts.map +1 -0
  58. package/dist/design-system/components.js +50 -0
  59. package/dist/design-system/components.js.map +1 -0
  60. package/dist/design-system/context.d.ts +11 -0
  61. package/dist/design-system/context.d.ts.map +1 -0
  62. package/dist/design-system/context.js +17 -0
  63. package/dist/design-system/context.js.map +1 -0
  64. package/dist/design-system/default-adapter.d.ts +3 -0
  65. package/dist/design-system/default-adapter.d.ts.map +1 -0
  66. package/dist/design-system/default-adapter.js +166 -0
  67. package/dist/design-system/default-adapter.js.map +1 -0
  68. package/dist/design-system/default-adapter.spec.d.ts +2 -0
  69. package/dist/design-system/default-adapter.spec.d.ts.map +1 -0
  70. package/dist/design-system/default-adapter.spec.js +96 -0
  71. package/dist/design-system/default-adapter.spec.js.map +1 -0
  72. package/dist/design-system/definition.d.ts +9 -0
  73. package/dist/design-system/definition.d.ts.map +1 -0
  74. package/dist/design-system/definition.js +4 -0
  75. package/dist/design-system/definition.js.map +1 -0
  76. package/dist/design-system/design-system.spec.d.ts +2 -0
  77. package/dist/design-system/design-system.spec.d.ts.map +1 -0
  78. package/dist/design-system/design-system.spec.js +121 -0
  79. package/dist/design-system/design-system.spec.js.map +1 -0
  80. package/dist/design-system/error-boundary.d.ts +18 -0
  81. package/dist/design-system/error-boundary.d.ts.map +1 -0
  82. package/dist/design-system/error-boundary.js +16 -0
  83. package/dist/design-system/error-boundary.js.map +1 -0
  84. package/dist/design-system/index.d.ts +8 -0
  85. package/dist/design-system/index.d.ts.map +1 -0
  86. package/dist/design-system/index.js +8 -0
  87. package/dist/design-system/index.js.map +1 -0
  88. package/dist/design-system/theme.d.ts +30 -0
  89. package/dist/design-system/theme.d.ts.map +1 -0
  90. package/dist/design-system/theme.js +95 -0
  91. package/dist/design-system/theme.js.map +1 -0
  92. package/dist/design-system/theme.spec.d.ts +2 -0
  93. package/dist/design-system/theme.spec.d.ts.map +1 -0
  94. package/dist/design-system/theme.spec.js +43 -0
  95. package/dist/design-system/theme.spec.js.map +1 -0
  96. package/dist/design-system/types.d.ts +288 -0
  97. package/dist/design-system/types.d.ts.map +1 -0
  98. package/dist/design-system/types.js +2 -0
  99. package/dist/design-system/types.js.map +1 -0
  100. package/dist/provider.d.ts +4 -3
  101. package/dist/provider.d.ts.map +1 -1
  102. package/dist/provider.js +46 -4
  103. package/dist/provider.js.map +1 -1
  104. package/dist/ui/button.d.ts +5 -0
  105. package/dist/ui/button.d.ts.map +1 -1
  106. package/dist/ui/button.js +25 -4
  107. package/dist/ui/button.js.map +1 -1
  108. package/dist/ui/dialog.d.ts +4 -0
  109. package/dist/ui/dialog.d.ts.map +1 -1
  110. package/dist/ui/dialog.js +2 -2
  111. package/dist/ui/dialog.js.map +1 -1
  112. package/dist/ui/popover.d.ts +1 -0
  113. package/dist/ui/popover.d.ts.map +1 -1
  114. package/dist/ui/popover.js +2 -2
  115. package/dist/ui/popover.js.map +1 -1
  116. package/dist/ui/select.d.ts +3 -1
  117. package/dist/ui/select.d.ts.map +1 -1
  118. package/dist/ui/select.js +1 -1
  119. package/dist/ui/select.js.map +1 -1
  120. package/dist/ui/sidebar.d.ts +6 -2
  121. package/dist/ui/sidebar.d.ts.map +1 -1
  122. package/dist/ui/sidebar.js +12 -15
  123. package/dist/ui/sidebar.js.map +1 -1
  124. package/dist/ui/tooltip.d.ts +3 -1
  125. package/dist/ui/tooltip.d.ts.map +1 -1
  126. package/dist/ui/tooltip.js +2 -2
  127. package/dist/ui/tooltip.js.map +1 -1
  128. package/package.json +31 -1
  129. package/src/chat-history/ChatHistoryRail.spec.tsx +174 -0
  130. package/src/chat-history/ChatHistoryRail.tsx +100 -39
  131. package/src/chat-history/index.ts +9 -0
  132. package/src/chat-history/useChatHistoryRailController.spec.tsx +131 -0
  133. package/src/chat-history/useChatHistoryRailController.ts +76 -0
  134. package/src/chat-history.css +2 -0
  135. package/src/composer/ComposerPlusMenu.tsx +11 -8
  136. package/src/composer/PromptComposer.tsx +27 -6
  137. package/src/composer/useRealtimeVoiceMode.spec.ts +82 -8
  138. package/src/composer/useRealtimeVoiceMode.tsx +107 -22
  139. package/src/conformance/__fixtures__/css-in-js-adapter.tsx +401 -0
  140. package/src/conformance/conformance.spec.tsx +83 -0
  141. package/src/conformance/index.ts +3 -0
  142. package/src/conformance/runner.tsx +710 -0
  143. package/src/conformance/types.ts +45 -0
  144. package/src/conformance/version.ts +22 -0
  145. package/src/design-system/components.tsx +146 -0
  146. package/src/design-system/context.tsx +31 -0
  147. package/src/design-system/default-adapter.spec.tsx +143 -0
  148. package/src/design-system/default-adapter.tsx +915 -0
  149. package/src/design-system/definition.ts +14 -0
  150. package/src/design-system/design-system.spec.tsx +183 -0
  151. package/src/design-system/error-boundary.tsx +37 -0
  152. package/src/design-system/index.ts +7 -0
  153. package/src/design-system/theme.spec.ts +61 -0
  154. package/src/design-system/theme.ts +145 -0
  155. package/src/design-system/types.ts +363 -0
  156. package/src/provider.tsx +98 -5
  157. package/src/ui/button.tsx +95 -6
  158. package/src/ui/dialog.tsx +48 -23
  159. package/src/ui/popover.tsx +16 -2
  160. package/src/ui/select.tsx +7 -3
  161. package/src/ui/sidebar.tsx +47 -48
  162. package/src/ui/tooltip.tsx +7 -3
@@ -0,0 +1,915 @@
1
+ import { IconSearch } from "@tabler/icons-react";
2
+ import {
3
+ createElement,
4
+ type CSSProperties,
5
+ type KeyboardEvent,
6
+ type MouseEvent as ReactMouseEvent,
7
+ type ReactNode,
8
+ type Ref,
9
+ } from "react";
10
+
11
+ import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
12
+ import { Badge } from "../ui/badge.js";
13
+ import { ButtonBase } from "../ui/button.js";
14
+ import { Card } from "../ui/card.js";
15
+ import { Checkbox as DefaultCheckboxPrimitive } from "../ui/checkbox.js";
16
+ import {
17
+ Dialog,
18
+ DialogContent,
19
+ DialogDescription,
20
+ DialogFooter,
21
+ DialogHeader,
22
+ DialogTitle,
23
+ DialogTrigger,
24
+ } from "../ui/dialog.js";
25
+ import {
26
+ DropdownMenu,
27
+ DropdownMenuCheckboxItem,
28
+ DropdownMenuContent,
29
+ DropdownMenuItem,
30
+ DropdownMenuLabel,
31
+ DropdownMenuSeparator,
32
+ DropdownMenuShortcut,
33
+ DropdownMenuSub,
34
+ DropdownMenuSubContent,
35
+ DropdownMenuSubTrigger,
36
+ DropdownMenuTrigger,
37
+ } from "../ui/dropdown-menu.js";
38
+ import { Input } from "../ui/input.js";
39
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
40
+ import {
41
+ Select,
42
+ SelectContent,
43
+ SelectItem,
44
+ SelectTrigger,
45
+ SelectValue,
46
+ } from "../ui/select.js";
47
+ import { Skeleton as DefaultSkeletonPrimitive } from "../ui/skeleton.js";
48
+ import { Spinner as DefaultSpinnerPrimitive } from "../ui/spinner.js";
49
+ import { Switch as DefaultSwitchPrimitive } from "../ui/switch.js";
50
+ import {
51
+ Tabs as DefaultTabsPrimitive,
52
+ TabsContent,
53
+ TabsList,
54
+ TabsTrigger,
55
+ } from "../ui/tabs.js";
56
+ import { Textarea } from "../ui/textarea.js";
57
+ import {
58
+ Tooltip,
59
+ TooltipContent,
60
+ TooltipProvider,
61
+ TooltipTrigger,
62
+ } from "../ui/tooltip.js";
63
+ import { cn } from "../utils.js";
64
+ import type { DesignSystemComponents, MenuItem } from "./types.js";
65
+
66
+ function buttonVariant(
67
+ intent: "primary" | "neutral" | "danger" = "neutral",
68
+ emphasis: "solid" | "outline" | "ghost" = "solid",
69
+ ) {
70
+ if (emphasis === "ghost") return "ghost" as const;
71
+ if (emphasis === "outline") return "outline" as const;
72
+ if (intent === "primary") return "default" as const;
73
+ if (intent === "danger") return "destructive" as const;
74
+ return "secondary" as const;
75
+ }
76
+
77
+ function buttonSize(size: "compact" | "default" | "large" = "default") {
78
+ if (size === "compact") return "sm" as const;
79
+ if (size === "large") return "lg" as const;
80
+ return "default" as const;
81
+ }
82
+
83
+ const DefaultActionButton: DesignSystemComponents["ActionButton"] = ({
84
+ children,
85
+ intent,
86
+ emphasis,
87
+ size,
88
+ pending,
89
+ disabled,
90
+ type = "button",
91
+ leadingIcon,
92
+ trailingIcon,
93
+ onPress,
94
+ elementRef,
95
+ ...props
96
+ }) => (
97
+ <ButtonBase
98
+ {...props}
99
+ ref={elementRef}
100
+ type={type}
101
+ variant={buttonVariant(intent, emphasis)}
102
+ size={buttonSize(size)}
103
+ disabled={disabled || pending}
104
+ onClick={(event) => onPress?.(event)}
105
+ >
106
+ {pending ? <DefaultSpinnerPrimitive aria-hidden="true" /> : leadingIcon}
107
+ {children}
108
+ {trailingIcon}
109
+ </ButtonBase>
110
+ );
111
+
112
+ const DefaultIconButton: DesignSystemComponents["IconButton"] = ({
113
+ label,
114
+ icon,
115
+ intent,
116
+ emphasis = "ghost",
117
+ size,
118
+ pending,
119
+ disabled,
120
+ type = "button",
121
+ onPress,
122
+ elementRef,
123
+ className,
124
+ ...props
125
+ }) => (
126
+ <ButtonBase
127
+ {...props}
128
+ ref={elementRef}
129
+ type={type}
130
+ variant={buttonVariant(intent, emphasis)}
131
+ size="icon"
132
+ className={cn(
133
+ size === "compact" && "h-8 w-8",
134
+ size === "large" && "h-12 w-12",
135
+ className,
136
+ )}
137
+ disabled={disabled || pending}
138
+ onClick={(event) => onPress?.(event)}
139
+ aria-label={label}
140
+ data-size={size}
141
+ >
142
+ {pending ? <DefaultSpinnerPrimitive aria-hidden="true" /> : icon}
143
+ </ButtonBase>
144
+ );
145
+
146
+ function FieldShell({
147
+ label,
148
+ description,
149
+ errorMessage,
150
+ className,
151
+ style,
152
+ children,
153
+ }: {
154
+ label?: ReactNode;
155
+ description?: ReactNode;
156
+ errorMessage?: ReactNode;
157
+ className?: string;
158
+ style?: CSSProperties;
159
+ children: ReactNode;
160
+ }) {
161
+ return (
162
+ <label className={cn("grid gap-1.5", className)} style={style}>
163
+ {label ? <span className="text-sm font-medium">{label}</span> : null}
164
+ {children}
165
+ {errorMessage ? (
166
+ <span className="text-xs text-destructive" role="alert">
167
+ {errorMessage}
168
+ </span>
169
+ ) : description ? (
170
+ <span className="text-xs text-muted-foreground">{description}</span>
171
+ ) : null}
172
+ </label>
173
+ );
174
+ }
175
+
176
+ const DefaultTextField: DesignSystemComponents["TextField"] = ({
177
+ value,
178
+ onChange,
179
+ label,
180
+ description,
181
+ errorMessage,
182
+ invalid,
183
+ inputRef,
184
+ leadingContent,
185
+ trailingContent,
186
+ className,
187
+ style,
188
+ onBlur,
189
+ onFocus,
190
+ onKeyDown,
191
+ ...props
192
+ }) => (
193
+ <FieldShell
194
+ label={label}
195
+ description={description}
196
+ errorMessage={errorMessage}
197
+ className={className}
198
+ style={style}
199
+ >
200
+ <div className="flex items-center gap-2">
201
+ {leadingContent}
202
+ <Input
203
+ {...props}
204
+ ref={inputRef}
205
+ value={value}
206
+ aria-invalid={invalid || undefined}
207
+ onChange={(event) => onChange(event.currentTarget.value)}
208
+ onBlur={onBlur}
209
+ onFocus={onFocus}
210
+ onKeyDown={onKeyDown}
211
+ />
212
+ {trailingContent}
213
+ </div>
214
+ </FieldShell>
215
+ );
216
+
217
+ const DefaultTextArea: DesignSystemComponents["TextArea"] = ({
218
+ value,
219
+ onChange,
220
+ label,
221
+ description,
222
+ errorMessage,
223
+ invalid,
224
+ textAreaRef,
225
+ className,
226
+ style,
227
+ onBlur,
228
+ onFocus,
229
+ onKeyDown,
230
+ ...props
231
+ }) => (
232
+ <FieldShell
233
+ label={label}
234
+ description={description}
235
+ errorMessage={errorMessage}
236
+ className={className}
237
+ style={style}
238
+ >
239
+ <Textarea
240
+ {...props}
241
+ ref={textAreaRef}
242
+ value={value}
243
+ aria-invalid={invalid || undefined}
244
+ onChange={(event) => onChange(event.currentTarget.value)}
245
+ onBlur={onBlur}
246
+ onFocus={onFocus}
247
+ onKeyDown={onKeyDown}
248
+ />
249
+ </FieldShell>
250
+ );
251
+
252
+ const DefaultSpinner: DesignSystemComponents["Spinner"] = ({
253
+ label,
254
+ size,
255
+ className,
256
+ ...props
257
+ }) => (
258
+ <DefaultSpinnerPrimitive
259
+ {...props}
260
+ aria-label={label}
261
+ className={cn(
262
+ size === "compact" && "size-3",
263
+ size === "large" && "size-5",
264
+ className,
265
+ )}
266
+ data-size={size}
267
+ role={label ? "status" : undefined}
268
+ />
269
+ );
270
+
271
+ const DefaultSkeleton: DesignSystemComponents["Skeleton"] = ({
272
+ width,
273
+ height,
274
+ shape = "rectangle",
275
+ className,
276
+ style,
277
+ ...props
278
+ }) => (
279
+ <DefaultSkeletonPrimitive
280
+ {...props}
281
+ aria-hidden="true"
282
+ className={cn(
283
+ shape === "line" && "rounded-sm",
284
+ shape === "rectangle" && "rounded-md",
285
+ shape === "circle" && "rounded-full",
286
+ className,
287
+ )}
288
+ style={{ width, height, ...style }}
289
+ data-shape={shape}
290
+ />
291
+ );
292
+
293
+ const DefaultStatus: DesignSystemComponents["Status"] = ({
294
+ children,
295
+ tone = "neutral",
296
+ icon,
297
+ size,
298
+ className,
299
+ ...props
300
+ }) => (
301
+ <Badge
302
+ {...props}
303
+ className={cn(
304
+ size === "compact" && "text-xs",
305
+ size === "large" && "px-3 py-1 text-sm",
306
+ className,
307
+ )}
308
+ variant={tone === "danger" ? "destructive" : "secondary"}
309
+ data-tone={tone}
310
+ data-size={size}
311
+ >
312
+ {icon}
313
+ {children}
314
+ </Badge>
315
+ );
316
+
317
+ const DefaultSurface: DesignSystemComponents["Surface"] = ({
318
+ children,
319
+ as = "div",
320
+ elevation = "low",
321
+ padding = "default",
322
+ interactive,
323
+ onPress,
324
+ className,
325
+ ...props
326
+ }) => {
327
+ const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {
328
+ if (!interactive || (event.key !== "Enter" && event.key !== " ")) return;
329
+ event.preventDefault();
330
+ onPress?.(event);
331
+ };
332
+ const elementProps = {
333
+ ...props,
334
+ className: cn(
335
+ "rounded-lg border bg-card text-card-foreground",
336
+ elevation === "none" && "shadow-none",
337
+ elevation === "low" && "shadow-sm",
338
+ elevation === "medium" && "shadow-md",
339
+ padding === "compact" && "p-3",
340
+ padding === "default" && "p-4",
341
+ padding === "spacious" && "p-6",
342
+ interactive && "cursor-pointer",
343
+ className,
344
+ ),
345
+ onClick: (event: ReactMouseEvent<HTMLElement>) => onPress?.(event),
346
+ onKeyDown,
347
+ role: interactive ? "button" : undefined,
348
+ tabIndex: interactive ? 0 : undefined,
349
+ children,
350
+ };
351
+ if (as === "div") return <Card {...elementProps} />;
352
+ return createElement(as, elementProps);
353
+ };
354
+
355
+ const DefaultAvatar: DesignSystemComponents["Avatar"] = ({
356
+ name,
357
+ src,
358
+ fallback,
359
+ size,
360
+ status,
361
+ imageRef,
362
+ className,
363
+ ...props
364
+ }) => (
365
+ <Avatar
366
+ {...props}
367
+ className={cn(
368
+ size === "compact" && "h-8 w-8",
369
+ size === "default" && "h-10 w-10",
370
+ size === "large" && "h-12 w-12",
371
+ className,
372
+ )}
373
+ data-size={size}
374
+ data-status={status}
375
+ >
376
+ {src ? <AvatarImage ref={imageRef} src={src} alt={name} /> : null}
377
+ <AvatarFallback>
378
+ {fallback ?? name.slice(0, 2).toUpperCase()}
379
+ </AvatarFallback>
380
+ {status ? (
381
+ <span
382
+ aria-label={status}
383
+ className={cn(
384
+ "absolute bottom-0 end-0 h-2.5 w-2.5 rounded-full border-2 border-background",
385
+ status === "online" && "bg-green-500",
386
+ status === "offline" && "bg-muted-foreground",
387
+ status === "busy" && "bg-destructive",
388
+ status === "away" && "bg-yellow-500",
389
+ )}
390
+ role="img"
391
+ />
392
+ ) : null}
393
+ </Avatar>
394
+ );
395
+
396
+ const DefaultTooltip: DesignSystemComponents["Tooltip"] = ({
397
+ trigger,
398
+ content,
399
+ open,
400
+ defaultOpen,
401
+ onOpenChange,
402
+ delayMs,
403
+ disabled,
404
+ portalContainer,
405
+ placement = "top",
406
+ align = "center",
407
+ collisionPadding,
408
+ className,
409
+ style,
410
+ }) => {
411
+ if (disabled) return trigger;
412
+ return (
413
+ <TooltipProvider delayDuration={delayMs}>
414
+ <Tooltip
415
+ open={open}
416
+ defaultOpen={defaultOpen}
417
+ onOpenChange={onOpenChange}
418
+ >
419
+ <TooltipTrigger asChild>{trigger}</TooltipTrigger>
420
+ <TooltipContent
421
+ container={portalContainer}
422
+ side={placement}
423
+ align={align}
424
+ collisionPadding={collisionPadding}
425
+ className={className}
426
+ style={style}
427
+ >
428
+ {content}
429
+ </TooltipContent>
430
+ </Tooltip>
431
+ </TooltipProvider>
432
+ );
433
+ };
434
+
435
+ function DefaultMenuItems({
436
+ items,
437
+ onAction,
438
+ closeOnAction,
439
+ }: {
440
+ items: readonly MenuItem[];
441
+ onAction: (id: string | number) => void;
442
+ closeOnAction: boolean;
443
+ }) {
444
+ return items.map((item) =>
445
+ item.children?.length ? (
446
+ <DropdownMenuSub key={item.id}>
447
+ <DropdownMenuSubTrigger disabled={item.disabled}>
448
+ {item.icon}
449
+ {item.label}
450
+ </DropdownMenuSubTrigger>
451
+ <DropdownMenuSubContent>
452
+ <DefaultMenuItems
453
+ items={item.children}
454
+ onAction={onAction}
455
+ closeOnAction={closeOnAction}
456
+ />
457
+ </DropdownMenuSubContent>
458
+ </DropdownMenuSub>
459
+ ) : item.selected !== undefined ? (
460
+ <DropdownMenuCheckboxItem
461
+ key={item.id}
462
+ checked={item.selected}
463
+ disabled={item.disabled}
464
+ onSelect={(event) => {
465
+ if (!closeOnAction) event.preventDefault();
466
+ onAction(item.id);
467
+ }}
468
+ className={item.intent === "danger" ? "text-destructive" : undefined}
469
+ >
470
+ {item.icon}
471
+ <span className="min-w-0 flex-1">{item.label}</span>
472
+ {item.shortcut ? (
473
+ <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>
474
+ ) : null}
475
+ </DropdownMenuCheckboxItem>
476
+ ) : (
477
+ <DropdownMenuItem
478
+ key={item.id}
479
+ disabled={item.disabled}
480
+ onSelect={(event) => {
481
+ if (!closeOnAction) event.preventDefault();
482
+ onAction(item.id);
483
+ }}
484
+ className={item.intent === "danger" ? "text-destructive" : undefined}
485
+ >
486
+ {item.icon}
487
+ <span className="min-w-0 flex-1">{item.label}</span>
488
+ {item.shortcut ? (
489
+ <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>
490
+ ) : null}
491
+ </DropdownMenuItem>
492
+ ),
493
+ );
494
+ }
495
+
496
+ const DefaultMenu: DesignSystemComponents["Menu"] = ({
497
+ trigger,
498
+ items,
499
+ sections,
500
+ open,
501
+ defaultOpen,
502
+ onOpenChange,
503
+ onAction,
504
+ portalContainer,
505
+ placement = "bottom",
506
+ align = "start",
507
+ collisionPadding,
508
+ className,
509
+ style,
510
+ closeOnAction = true,
511
+ }) => (
512
+ <DropdownMenu
513
+ open={open}
514
+ defaultOpen={defaultOpen}
515
+ onOpenChange={onOpenChange}
516
+ >
517
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
518
+ <DropdownMenuContent
519
+ container={portalContainer}
520
+ side={placement}
521
+ align={align}
522
+ collisionPadding={collisionPadding}
523
+ className={className}
524
+ style={style}
525
+ >
526
+ {sections ? (
527
+ sections.map((section, index) => (
528
+ <div key={section.id}>
529
+ {index > 0 ? <DropdownMenuSeparator /> : null}
530
+ {section.label ? (
531
+ <DropdownMenuLabel>{section.label}</DropdownMenuLabel>
532
+ ) : null}
533
+ <DefaultMenuItems
534
+ items={section.items}
535
+ onAction={onAction}
536
+ closeOnAction={closeOnAction}
537
+ />
538
+ </div>
539
+ ))
540
+ ) : items ? (
541
+ <DefaultMenuItems
542
+ items={items}
543
+ onAction={onAction}
544
+ closeOnAction={closeOnAction}
545
+ />
546
+ ) : null}
547
+ </DropdownMenuContent>
548
+ </DropdownMenu>
549
+ );
550
+
551
+ const DefaultPopover: DesignSystemComponents["Popover"] = ({
552
+ trigger,
553
+ children,
554
+ open,
555
+ defaultOpen,
556
+ onOpenChange,
557
+ modal,
558
+ dismissible = true,
559
+ portalContainer,
560
+ placement = "bottom",
561
+ align = "center",
562
+ collisionPadding,
563
+ className,
564
+ style,
565
+ }) => (
566
+ <Popover
567
+ open={open}
568
+ defaultOpen={defaultOpen}
569
+ onOpenChange={onOpenChange}
570
+ modal={modal}
571
+ >
572
+ <PopoverTrigger asChild>{trigger}</PopoverTrigger>
573
+ <PopoverContent
574
+ container={portalContainer}
575
+ side={placement}
576
+ align={align}
577
+ collisionPadding={collisionPadding}
578
+ className={className}
579
+ style={style}
580
+ onEscapeKeyDown={(event) => {
581
+ if (!dismissible) event.preventDefault();
582
+ }}
583
+ onInteractOutside={(event) => {
584
+ if (!dismissible) event.preventDefault();
585
+ }}
586
+ >
587
+ {children}
588
+ </PopoverContent>
589
+ </Popover>
590
+ );
591
+
592
+ const DefaultDialog: DesignSystemComponents["Dialog"] = ({
593
+ open,
594
+ onOpenChange,
595
+ title,
596
+ description,
597
+ children,
598
+ footer,
599
+ trigger,
600
+ size = "medium",
601
+ dismissible = true,
602
+ closeLabel,
603
+ initialFocusRef,
604
+ restoreFocusRef,
605
+ portalContainer,
606
+ className,
607
+ style,
608
+ }) => (
609
+ <Dialog open={open} onOpenChange={onOpenChange}>
610
+ {trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
611
+ <DialogContent
612
+ container={portalContainer}
613
+ hideClose={!dismissible}
614
+ closeLabel={closeLabel}
615
+ style={style}
616
+ className={cn(
617
+ size === "small" && "max-w-sm",
618
+ size === "medium" && "max-w-lg",
619
+ size === "large" && "max-w-2xl",
620
+ size === "fullscreen" &&
621
+ "inset-0 h-[100dvh] max-h-none w-full max-w-none translate-x-0 translate-y-0 rounded-none",
622
+ className,
623
+ )}
624
+ data-size={size}
625
+ onOpenAutoFocus={(event) => {
626
+ if (!initialFocusRef?.current) return;
627
+ event.preventDefault();
628
+ initialFocusRef.current.focus();
629
+ }}
630
+ onCloseAutoFocus={(event) => {
631
+ if (!restoreFocusRef?.current) return;
632
+ event.preventDefault();
633
+ restoreFocusRef.current.focus();
634
+ }}
635
+ onEscapeKeyDown={(event) => {
636
+ if (!dismissible) event.preventDefault();
637
+ }}
638
+ onInteractOutside={(event) => {
639
+ if (!dismissible) event.preventDefault();
640
+ }}
641
+ >
642
+ <DialogHeader>
643
+ <DialogTitle>{title}</DialogTitle>
644
+ {description ? (
645
+ <DialogDescription>{description}</DialogDescription>
646
+ ) : null}
647
+ </DialogHeader>
648
+ {children}
649
+ {footer ? <DialogFooter>{footer}</DialogFooter> : null}
650
+ </DialogContent>
651
+ </Dialog>
652
+ );
653
+
654
+ const DefaultPicker: DesignSystemComponents["Picker"] = ({
655
+ mode,
656
+ options,
657
+ value,
658
+ onChange,
659
+ label,
660
+ description,
661
+ errorMessage,
662
+ placeholder,
663
+ searchValue = "",
664
+ onSearchChange,
665
+ open,
666
+ onOpenChange,
667
+ emptyContent,
668
+ loadingContent,
669
+ loading,
670
+ required,
671
+ disabled,
672
+ invalid,
673
+ pickerRef,
674
+ portalContainer,
675
+ className,
676
+ style,
677
+ ...props
678
+ }) => {
679
+ const selected = options.find((option) => option.value === value);
680
+ const filtered =
681
+ mode === "combobox" && searchValue
682
+ ? options.filter((option) =>
683
+ [option.textValue, option.label, ...(option.keywords ?? [])]
684
+ .filter((part): part is string => typeof part === "string")
685
+ .join(" ")
686
+ .toLowerCase()
687
+ .includes(searchValue.toLowerCase()),
688
+ )
689
+ : options;
690
+
691
+ if (mode === "select") {
692
+ return (
693
+ <FieldShell
694
+ label={label}
695
+ description={description}
696
+ errorMessage={errorMessage}
697
+ className={className}
698
+ style={style}
699
+ >
700
+ <Select
701
+ value={value == null ? undefined : String(value)}
702
+ open={open}
703
+ onOpenChange={onOpenChange}
704
+ onValueChange={(next) => {
705
+ const option = options.find(
706
+ (candidate) => String(candidate.value) === next,
707
+ );
708
+ onChange(option?.value ?? null);
709
+ }}
710
+ required={required}
711
+ disabled={disabled}
712
+ >
713
+ <SelectTrigger
714
+ {...props}
715
+ ref={pickerRef as Ref<HTMLButtonElement>}
716
+ aria-invalid={invalid || undefined}
717
+ >
718
+ <SelectValue placeholder={placeholder}>
719
+ {selected?.label}
720
+ </SelectValue>
721
+ </SelectTrigger>
722
+ <SelectContent container={portalContainer}>
723
+ {loading
724
+ ? loadingContent
725
+ : options.length
726
+ ? options.map((option) => (
727
+ <SelectItem
728
+ key={option.value}
729
+ value={String(option.value)}
730
+ disabled={option.disabled}
731
+ >
732
+ {option.label}
733
+ </SelectItem>
734
+ ))
735
+ : emptyContent}
736
+ </SelectContent>
737
+ </Select>
738
+ </FieldShell>
739
+ );
740
+ }
741
+
742
+ return (
743
+ <FieldShell
744
+ label={label}
745
+ description={description}
746
+ errorMessage={errorMessage}
747
+ className={className}
748
+ style={style}
749
+ >
750
+ <Popover open={open} onOpenChange={onOpenChange}>
751
+ <PopoverTrigger asChild>
752
+ <ButtonBase
753
+ ref={pickerRef as Ref<HTMLButtonElement>}
754
+ type="button"
755
+ variant="outline"
756
+ disabled={disabled}
757
+ aria-invalid={invalid || undefined}
758
+ >
759
+ {selected?.label ?? placeholder}
760
+ </ButtonBase>
761
+ </PopoverTrigger>
762
+ <PopoverContent container={portalContainer} className="w-72 p-2">
763
+ <label className="flex items-center gap-2 border-b px-2 pb-2">
764
+ <IconSearch className="size-4 text-muted-foreground" />
765
+ <Input
766
+ value={searchValue}
767
+ onChange={(event) => onSearchChange?.(event.currentTarget.value)}
768
+ className="h-8 border-0 p-0 shadow-none focus-visible:ring-0"
769
+ />
770
+ </label>
771
+ <div className="max-h-64 overflow-auto py-1" role="listbox">
772
+ {loading
773
+ ? loadingContent
774
+ : filtered.length
775
+ ? filtered.map((option) => (
776
+ <button
777
+ key={option.value}
778
+ type="button"
779
+ role="option"
780
+ aria-selected={option.value === value}
781
+ disabled={option.disabled}
782
+ className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-start text-sm hover:bg-accent disabled:opacity-50"
783
+ onClick={() => {
784
+ onChange(option.value);
785
+ onOpenChange?.(false);
786
+ }}
787
+ >
788
+ {option.icon}
789
+ <span>{option.label}</span>
790
+ </button>
791
+ ))
792
+ : emptyContent}
793
+ </div>
794
+ </PopoverContent>
795
+ </Popover>
796
+ </FieldShell>
797
+ );
798
+ };
799
+
800
+ const DefaultCheckbox: DesignSystemComponents["Checkbox"] = ({
801
+ checked,
802
+ onChange,
803
+ label,
804
+ description,
805
+ indeterminate,
806
+ inputRef,
807
+ invalid,
808
+ ...props
809
+ }) => (
810
+ <label className="flex items-start gap-2">
811
+ <DefaultCheckboxPrimitive
812
+ {...props}
813
+ ref={inputRef}
814
+ checked={indeterminate ? "indeterminate" : checked}
815
+ aria-invalid={invalid || undefined}
816
+ onCheckedChange={(next) => onChange(next === true)}
817
+ />
818
+ {label || description ? (
819
+ <span className="grid gap-0.5">
820
+ {label ? <span className="text-sm">{label}</span> : null}
821
+ {description ? (
822
+ <span className="text-xs text-muted-foreground">{description}</span>
823
+ ) : null}
824
+ </span>
825
+ ) : null}
826
+ </label>
827
+ );
828
+
829
+ const DefaultSwitch: DesignSystemComponents["Switch"] = ({
830
+ checked,
831
+ onChange,
832
+ label,
833
+ description,
834
+ inputRef,
835
+ ...props
836
+ }) => (
837
+ <label className="flex items-start gap-2">
838
+ <DefaultSwitchPrimitive
839
+ {...props}
840
+ ref={inputRef}
841
+ checked={checked}
842
+ onCheckedChange={onChange}
843
+ />
844
+ {label || description ? (
845
+ <span className="grid gap-0.5">
846
+ {label ? <span className="text-sm">{label}</span> : null}
847
+ {description ? (
848
+ <span className="text-xs text-muted-foreground">{description}</span>
849
+ ) : null}
850
+ </span>
851
+ ) : null}
852
+ </label>
853
+ );
854
+
855
+ const DefaultTabs: DesignSystemComponents["Tabs"] = ({
856
+ items,
857
+ value,
858
+ onChange,
859
+ orientation,
860
+ activationMode,
861
+ className,
862
+ style,
863
+ ...props
864
+ }) => (
865
+ <DefaultTabsPrimitive
866
+ {...props}
867
+ value={String(value)}
868
+ onValueChange={(next) => {
869
+ const item = items.find((candidate) => String(candidate.value) === next);
870
+ if (item) onChange(item.value);
871
+ }}
872
+ orientation={orientation}
873
+ activationMode={activationMode}
874
+ className={className}
875
+ style={style}
876
+ >
877
+ <TabsList>
878
+ {items.map((item) => (
879
+ <TabsTrigger
880
+ key={item.value}
881
+ value={String(item.value)}
882
+ disabled={item.disabled}
883
+ >
884
+ {item.icon}
885
+ {item.label}
886
+ </TabsTrigger>
887
+ ))}
888
+ </TabsList>
889
+ {items.map((item) => (
890
+ <TabsContent key={item.value} value={String(item.value)}>
891
+ {item.content}
892
+ </TabsContent>
893
+ ))}
894
+ </DefaultTabsPrimitive>
895
+ );
896
+
897
+ export const defaultDesignSystemComponents: DesignSystemComponents = {
898
+ ActionButton: DefaultActionButton,
899
+ IconButton: DefaultIconButton,
900
+ TextField: DefaultTextField,
901
+ TextArea: DefaultTextArea,
902
+ Spinner: DefaultSpinner,
903
+ Skeleton: DefaultSkeleton,
904
+ Status: DefaultStatus,
905
+ Surface: DefaultSurface,
906
+ Avatar: DefaultAvatar,
907
+ Tooltip: DefaultTooltip,
908
+ Menu: DefaultMenu,
909
+ Popover: DefaultPopover,
910
+ Dialog: DefaultDialog,
911
+ Picker: DefaultPicker,
912
+ Checkbox: DefaultCheckbox,
913
+ Switch: DefaultSwitch,
914
+ Tabs: DefaultTabs,
915
+ };