@vvedantb/ui 0.17.0 → 0.18.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 (130) hide show
  1. package/README.md +253 -3
  2. package/package.json +13 -5
  3. package/shell/src/SidebarResize.tsx +1 -4
  4. package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
  5. package/src/ai-elements/activity-question-utils.ts +26 -0
  6. package/src/ai-elements/activity-question.tsx +159 -0
  7. package/src/ai-elements/activity-shared.tsx +459 -0
  8. package/src/ai-elements/activity-step-detail.tsx +115 -0
  9. package/src/ai-elements/activity-step-label.ts +981 -0
  10. package/src/ai-elements/activity-tasks-utils.ts +199 -0
  11. package/src/ai-elements/activity-tasks.tsx +770 -0
  12. package/src/ai-elements/artifact.tsx +162 -52
  13. package/src/ai-elements/attachments.tsx +62 -32
  14. package/src/ai-elements/code-block.tsx +76 -22
  15. package/src/ai-elements/context.tsx +370 -0
  16. package/src/ai-elements/conversation.tsx +186 -62
  17. package/src/ai-elements/file-chip.tsx +53 -0
  18. package/src/ai-elements/message.tsx +161 -50
  19. package/src/ai-elements/model-picker-content.tsx +505 -0
  20. package/src/ai-elements/model-picker-types.ts +56 -0
  21. package/src/ai-elements/model-picker.tsx +301 -0
  22. package/src/ai-elements/model-selector.tsx +102 -21
  23. package/src/ai-elements/plan.tsx +130 -0
  24. package/src/ai-elements/prompt-input-settings.tsx +144 -0
  25. package/src/ai-elements/prompt-input-speech.tsx +189 -0
  26. package/src/ai-elements/prompt-input-traits.tsx +281 -0
  27. package/src/ai-elements/prompt-input.tsx +320 -134
  28. package/src/ai-elements/provider-icon.tsx +133 -0
  29. package/src/ai-elements/queue.tsx +293 -9
  30. package/src/ai-elements/shimmer.tsx +72 -1
  31. package/src/ai-elements/simple-model-ladder.tsx +108 -0
  32. package/src/ai-elements/test-results.tsx +510 -0
  33. package/src/ai-elements/trace.tsx +243 -0
  34. package/src/ai-elements/use-streaming-disclosure.ts +50 -0
  35. package/src/ai-elements/web-preview.tsx +349 -0
  36. package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
  37. package/src/index.ts +66 -12
  38. package/src/kibo/avatar-stack.tsx +40 -0
  39. package/src/kibo/contribution-graph.tsx +539 -0
  40. package/src/kibo/cursor.tsx +72 -0
  41. package/src/kibo/gantt-features.tsx +439 -0
  42. package/src/kibo/gantt-header.tsx +181 -0
  43. package/src/kibo/gantt-provider.tsx +657 -0
  44. package/src/kibo/gantt-sidebar.tsx +157 -0
  45. package/src/kibo/gantt-timeline.tsx +154 -0
  46. package/src/kibo/index.ts +151 -0
  47. package/src/kibo/kanban.tsx +338 -0
  48. package/src/kibo/kanbanDropTarget.ts +25 -0
  49. package/src/kibo/list.tsx +162 -0
  50. package/src/kibo/theme-switcher.tsx +109 -0
  51. package/src/kibo/video-player.tsx +237 -0
  52. package/src/markdown/Markdown.tsx +112 -0
  53. package/src/markdown/MarkdownBlockquote.tsx +45 -0
  54. package/src/markdown/MarkdownCodeBlock.tsx +197 -0
  55. package/src/markdown/MarkdownInlineCode.tsx +40 -0
  56. package/src/markdown/MarkdownLink.tsx +92 -0
  57. package/src/markdown/codeFence.ts +57 -0
  58. package/src/markdown/codeHighlight.ts +138 -0
  59. package/src/markdown/incrementalBlocks.ts +182 -0
  60. package/src/markdown/index.ts +3 -0
  61. package/src/markdown/markdownClipboard.ts +178 -0
  62. package/src/markdown/mermaid.ts +155 -0
  63. package/src/markdown/rehypeGithubAlerts.ts +80 -0
  64. package/src/motion/presets.ts +24 -0
  65. package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
  66. package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
  67. package/src/ui/_menu-parts.tsx +29 -0
  68. package/src/ui/accordion.alt.tsx +46 -53
  69. package/src/ui/accordion.tsx +59 -10
  70. package/src/ui/avatar.tsx +56 -0
  71. package/src/ui/badge.tsx +45 -21
  72. package/src/ui/border-beam.tsx +97 -0
  73. package/src/ui/button-group.tsx +50 -21
  74. package/src/ui/button.tsx +67 -45
  75. package/src/ui/calendar.tsx +48 -5
  76. package/src/ui/card.tsx +77 -8
  77. package/src/ui/carousel.tsx +60 -0
  78. package/src/ui/centered-spinner.tsx +30 -0
  79. package/src/ui/checkbox.tsx +34 -16
  80. package/src/ui/clear-input.alt.tsx +33 -75
  81. package/src/ui/clear-input.tsx +140 -15
  82. package/src/ui/collapsible.tsx +40 -2
  83. package/src/ui/command.tsx +143 -70
  84. package/src/ui/context-menu.tsx +159 -93
  85. package/src/ui/crossfade-icon.tsx +123 -0
  86. package/src/ui/dialog.tsx +154 -70
  87. package/src/ui/dropdown-menu.tsx +177 -117
  88. package/src/ui/hover-card.tsx +59 -17
  89. package/src/ui/input-group.alt.tsx +83 -83
  90. package/src/ui/input-group.tsx +129 -58
  91. package/src/ui/input.tsx +13 -4
  92. package/src/ui/label.tsx +11 -8
  93. package/src/ui/list-row.tsx +232 -0
  94. package/src/ui/loading-state.tsx +215 -0
  95. package/src/ui/popover.tsx +56 -23
  96. package/src/ui/progress.tsx +28 -16
  97. package/src/ui/refresh-spin-icon.tsx +41 -0
  98. package/src/ui/search-input.tsx +66 -15
  99. package/src/ui/select.tsx +171 -100
  100. package/src/ui/separator.tsx +23 -14
  101. package/src/ui/slider.tsx +21 -7
  102. package/src/ui/sonner.tsx +68 -30
  103. package/src/ui/spinner.tsx +229 -11
  104. package/src/ui/status-dot.tsx +84 -0
  105. package/src/ui/surface.tsx +49 -0
  106. package/src/ui/switch.tsx +29 -15
  107. package/src/ui/table.tsx +38 -16
  108. package/src/ui/tabs.tsx +123 -35
  109. package/src/ui/tabsSliding.ts +33 -1
  110. package/src/ui/textarea.tsx +13 -4
  111. package/src/ui/tooltip.tsx +50 -14
  112. package/src/ui/ui-config.tsx +149 -0
  113. package/src/utils/cat-color.ts +123 -0
  114. package/src/utils/dayjs.ts +6 -0
  115. package/src/utils/gesture.ts +59 -0
  116. package/src/utils/runtimeVisibility.ts +167 -0
  117. package/src/utils/sharedClock.ts +96 -0
  118. package/src/utils/surface-radius.ts +16 -0
  119. package/src/utils/useDragSensors.ts +58 -0
  120. package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
  121. package/tokens/examples/tokens-moved/README.md +20 -0
  122. package/tokens/examples/tokens-moved/index.html +12 -0
  123. package/tokens/examples/tokens-moved/main.tsx +6 -0
  124. package/tokens/examples/tokens-moved/package.json +14 -0
  125. package/tokens/examples/tokens-moved/styles.css +12 -0
  126. package/tokens/examples/tokens-moved/tsconfig.json +14 -0
  127. package/tokens/examples/tokens-moved/vite.config.ts +11 -0
  128. package/tokens/src/css/border-beam.css +116 -0
  129. package/tokens/src/css/compat-shadcn-rgb.css +6 -0
  130. package/tokens/src/css/markdown.css +318 -0
@@ -47,9 +47,10 @@ import {
47
47
  SelectTrigger,
48
48
  SelectValue,
49
49
  } from "../ui/radix-select";
50
+ import { CrossfadeIconSlot } from "../ui/crossfade-icon";
50
51
  import { Spinner } from "../ui/spinner";
51
52
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
52
- import { cn } from "../utils/cn";
53
+ import { useCn, useUiConfig } from "../ui/ui-config";
53
54
  import { segmentExpandedStateClass } from "../ui/surface-classes";
54
55
  import {
55
56
  IconCornerDownLeft,
@@ -67,6 +68,21 @@ import {
67
68
  useRef,
68
69
  useState,
69
70
  } from "react";
71
+ /** `existing` with `file` in its place when the ids match (old blob URL revoked). */
72
+ const replaceFile = (
73
+ existing: FileUIPart & { id: string },
74
+ id: string,
75
+ file: File,
76
+ ): FileUIPart & { id: string } => {
77
+ if (existing.id !== id) return existing;
78
+ if (existing.url) URL.revokeObjectURL(existing.url);
79
+ return {
80
+ ...existing,
81
+ filename: file.name,
82
+ mediaType: file.type,
83
+ url: URL.createObjectURL(file),
84
+ };
85
+ };
70
86
  const convertBlobUrlToDataUrl = async (url: string): Promise<string | null> => {
71
87
  try {
72
88
  const response = await fetch(url);
@@ -81,12 +97,39 @@ const convertBlobUrlToDataUrl = async (url: string): Promise<string | null> => {
81
97
  return null;
82
98
  }
83
99
  };
100
+ /**
101
+ * `UiConfig.PromptInput`. `classes` replace built-in classes: `group` (the
102
+ * input group, default `overflow-hidden`), `groupIdle` / `groupDragOver`
103
+ * (added while no drag / an accepted drag is over the form), `groupDefault`
104
+ * (added when no `containerClassName` is given), `textarea`, `footer`,
105
+ * `submit` and `tabItem`. `submitIcons` replace the submit
106
+ * button's icon per status; `submitCrossfade` cross-fades between them.
107
+ * `menuTriggerAsChild` makes `PromptInputActionMenuTrigger` the trigger
108
+ * itself rather than a button inside the menu's trigger button.
109
+ */
110
+ export interface PromptInputConfig {
111
+ classes?: {
112
+ group?: string;
113
+ groupIdle?: string;
114
+ groupDragOver?: string;
115
+ groupDefault?: string;
116
+ textarea?: string;
117
+ footer?: string;
118
+ submit?: string;
119
+ tabItem?: string;
120
+ };
121
+ submitIcons?: Partial<Record<"send" | "submitted" | "streaming" | "error", ReactNode>>;
122
+ submitCrossfade?: boolean;
123
+ menuTriggerAsChild?: boolean;
124
+ }
84
125
  export interface AttachmentsContext {
85
126
  files: (FileUIPart & {
86
127
  id: string;
87
128
  })[];
88
129
  add: (files: File[] | FileList) => void;
89
130
  remove: (id: string) => void;
131
+ /** Replace one attachment in place (same id and position). No re-validation. */
132
+ replace: (id: string, file: File) => void;
90
133
  clear: () => void;
91
134
  openFileDialog: () => void;
92
135
  fileInputRef: RefObject<HTMLInputElement | null>;
@@ -119,7 +162,8 @@ export const usePromptInputController = () => {
119
162
  }
120
163
  return ctx;
121
164
  };
122
- const useOptionalPromptInputController = () =>
165
+ /** The provider's controller, or `null` outside `PromptInputProvider`. */
166
+ export const useOptionalPromptInputController = () =>
123
167
  useContext(PromptInputController);
124
168
  export const useProviderAttachments = () => {
125
169
  const ctx = useContext(ProviderAttachmentsContext);
@@ -169,6 +213,9 @@ export const PromptInputProvider = ({
169
213
  return prev.filter((f) => f.id !== id);
170
214
  });
171
215
  };
216
+ const replace = (id: string, file: File) => {
217
+ setAttachmentFiles((prev) => prev.map((existing) => replaceFile(existing, id, file)));
218
+ };
172
219
  const clear = () => {
173
220
  setAttachmentFiles((prev) => {
174
221
  for (const f of prev) {
@@ -199,6 +246,7 @@ export const PromptInputProvider = ({
199
246
  files: attachmentFiles,
200
247
  openFileDialog,
201
248
  remove,
249
+ replace,
202
250
  };
203
251
  const __registerFileInput = (
204
252
  ref: RefObject<HTMLInputElement | null>,
@@ -289,7 +337,16 @@ export type PromptInputProps = Omit<
289
337
  syncHiddenInput?: boolean;
290
338
  maxFiles?: number;
291
339
  maxFileSize?: number;
340
+ /** Merged onto the inner `InputGroup`. */
292
341
  containerClassName?: string;
342
+ /** Same as `containerClassName`. */
343
+ inputGroupClassName?: string;
344
+ /**
345
+ * Accepts a non-file drag of this MIME type: it counts as a drag over the
346
+ * form (the `groupDragOver` classes) and `onDrop` receives its string
347
+ * payload. Other types keep the browser's default drop.
348
+ */
349
+ dataDrop?: { type: string; onDrop: (payload: string) => void };
293
350
  onError?: (err: {
294
351
  code: "max_files" | "max_file_size" | "accept";
295
352
  message: string;
@@ -308,15 +365,23 @@ export const PromptInput = ({
308
365
  maxFiles,
309
366
  maxFileSize,
310
367
  containerClassName,
368
+ inputGroupClassName,
369
+ dataDrop,
311
370
  onError,
312
371
  onSubmit,
313
372
  children,
314
373
  ...props
315
374
  }: PromptInputProps) => {
375
+ const classes = useUiConfig("PromptInput")?.classes;
376
+ const merge = useCn();
316
377
  const controller = useOptionalPromptInputController();
317
378
  const usingProvider = !!controller;
318
379
  const inputRef = useRef<HTMLInputElement | null>(null);
319
380
  const formRef = useRef<HTMLFormElement | null>(null);
381
+ // Enter/leave pairs, not a boolean: `dragleave` also fires when the pointer
382
+ // moves onto a child of the form.
383
+ const dragEnterDepth = useRef(0);
384
+ const [isDragOver, setIsDragOver] = useState(false);
320
385
  const [items, setItems] = useState<
321
386
  (FileUIPart & {
322
387
  id: string;
@@ -346,6 +411,10 @@ export const PromptInput = ({
346
411
  const prefix = pattern.slice(0, -1);
347
412
  return f.type.startsWith(prefix);
348
413
  }
414
+ // Extension patterns (`.html`): browsers often omit the MIME type.
415
+ if (pattern.startsWith(".")) {
416
+ return f.name.toLowerCase().endsWith(pattern.toLowerCase());
417
+ }
349
418
  return f.type === pattern;
350
419
  });
351
420
  };
@@ -403,6 +472,8 @@ export const PromptInput = ({
403
472
  if (found?.url) URL.revokeObjectURL(found.url);
404
473
  return prev.filter((file) => file.id !== id);
405
474
  });
475
+ const replaceLocal = (id: string, file: File) =>
476
+ setItems((prev) => prev.map((existing) => replaceFile(existing, id, file)));
406
477
  const addWithProviderValidation = (fileList: File[] | FileList) => {
407
478
  const incoming = [...fileList];
408
479
  const accepted = incoming.filter((f) => matchesAccept(f));
@@ -452,6 +523,7 @@ export const PromptInput = ({
452
523
  const clearReferencedSources = () => setReferencedSources([]);
453
524
  const add = usingProvider ? addWithProviderValidation : addLocal;
454
525
  const remove = usingProvider ? controller.attachments.remove : removeLocal;
526
+ const replace = usingProvider ? controller.attachments.replace : replaceLocal;
455
527
  const openFileDialog = usingProvider
456
528
  ? controller.attachments.openFileDialog
457
529
  : openFileDialogLocal;
@@ -468,44 +540,54 @@ export const PromptInput = ({
468
540
  inputRef.current.value = "";
469
541
  }
470
542
  }, [files, syncHiddenInput]);
543
+ // Drops land on the form, or anywhere in the document with `globalDrop`.
471
544
  useEffect(() => {
472
- const form = formRef.current;
473
- if (!form) return;
474
- if (globalDrop) return;
475
- const onDragOver = (e: DragEvent) => {
476
- if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
477
- };
478
- const onDrop = (e: DragEvent) => {
479
- if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
480
- if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
481
- add(e.dataTransfer.files);
482
- }
545
+ const target: EventTarget | null = globalDrop ? document : formRef.current;
546
+ if (!target) return;
547
+ // Types are readable during the drag; the payload only on drop.
548
+ const carriesData = (event: DragEvent) =>
549
+ dataDrop !== undefined &&
550
+ event.dataTransfer?.types?.includes(dataDrop.type) === true;
551
+ const isAcceptedDrag = (event: DragEvent) =>
552
+ event.dataTransfer?.types?.includes("Files") === true || carriesData(event);
553
+ const onDragEnter = (event: Event) => {
554
+ if (!(event instanceof DragEvent) || !isAcceptedDrag(event)) return;
555
+ dragEnterDepth.current += 1;
556
+ setIsDragOver(true);
483
557
  };
484
- form.addEventListener("dragover", onDragOver);
485
- form.addEventListener("drop", onDrop);
486
- return () => {
487
- form.removeEventListener("dragover", onDragOver);
488
- form.removeEventListener("drop", onDrop);
558
+ const onDragOver = (event: Event) => {
559
+ if (event instanceof DragEvent && isAcceptedDrag(event)) event.preventDefault();
489
560
  };
490
- }, [add, globalDrop]);
491
- useEffect(() => {
492
- if (!globalDrop) return;
493
- const onDragOver = (e: DragEvent) => {
494
- if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
561
+ const onDragLeave = (event: Event) => {
562
+ if (!(event instanceof DragEvent) || !isAcceptedDrag(event)) return;
563
+ dragEnterDepth.current = Math.max(0, dragEnterDepth.current - 1);
564
+ if (dragEnterDepth.current === 0) setIsDragOver(false);
495
565
  };
496
- const onDrop = (e: DragEvent) => {
497
- if (e.dataTransfer?.types?.includes("Files")) e.preventDefault();
498
- if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
499
- add(e.dataTransfer.files);
566
+ const onDrop = (event: Event) => {
567
+ if (!(event instanceof DragEvent)) return;
568
+ if (isAcceptedDrag(event)) event.preventDefault();
569
+ dragEnterDepth.current = 0;
570
+ setIsDragOver(false);
571
+ if (dataDrop !== undefined && carriesData(event)) {
572
+ const payload = event.dataTransfer?.getData(dataDrop.type);
573
+ if (payload) dataDrop.onDrop(payload);
574
+ return;
575
+ }
576
+ if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
577
+ add(event.dataTransfer.files);
500
578
  }
501
579
  };
502
- document.addEventListener("dragover", onDragOver);
503
- document.addEventListener("drop", onDrop);
580
+ target.addEventListener("dragenter", onDragEnter);
581
+ target.addEventListener("dragover", onDragOver);
582
+ target.addEventListener("dragleave", onDragLeave);
583
+ target.addEventListener("drop", onDrop);
504
584
  return () => {
505
- document.removeEventListener("dragover", onDragOver);
506
- document.removeEventListener("drop", onDrop);
585
+ target.removeEventListener("dragenter", onDragEnter);
586
+ target.removeEventListener("dragover", onDragOver);
587
+ target.removeEventListener("dragleave", onDragLeave);
588
+ target.removeEventListener("drop", onDrop);
507
589
  };
508
- }, [add, globalDrop]);
590
+ }, [add, globalDrop, dataDrop]);
509
591
  useEffect(
510
592
  () => () => {
511
593
  if (!usingProvider) {
@@ -527,6 +609,7 @@ export const PromptInput = ({
527
609
  files: files.map((item) => ({ ...item, id: item.id })),
528
610
  openFileDialog,
529
611
  remove,
612
+ replace,
530
613
  };
531
614
  const refsCtx = {
532
615
  add: (incoming: SourceDocumentUIPart[] | SourceDocumentUIPart) => {
@@ -592,12 +675,18 @@ export const PromptInput = ({
592
675
  type="file"
593
676
  />
594
677
  <form
595
- className={cn("w-full", className)}
678
+ className={merge("w-full", className)}
596
679
  onSubmit={handleSubmit}
597
680
  ref={formRef}
598
681
  {...props}
599
682
  >
600
- <InputGroup className={cn("overflow-hidden", containerClassName)}>
683
+ <InputGroup
684
+ className={merge(
685
+ classes?.group ?? "overflow-hidden",
686
+ isDragOver ? classes?.groupDragOver : classes?.groupIdle,
687
+ containerClassName ?? inputGroupClassName ?? classes?.groupDefault,
688
+ )}
689
+ >
601
690
  {children}
602
691
  </InputGroup>
603
692
  </form>
@@ -618,9 +707,12 @@ export type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;
618
707
  export const PromptInputBody = ({
619
708
  className,
620
709
  ...props
621
- }: PromptInputBodyProps) => (
622
- <div className={cn("contents", className)} {...props} />
623
- );
710
+ }: PromptInputBodyProps) => {
711
+ const merge = useCn();
712
+ return (
713
+ <div className={merge("contents", className)} {...props} />
714
+ );
715
+ };
624
716
  export type PromptInputTextareaProps = ComponentProps<
625
717
  typeof InputGroupTextarea
626
718
  >;
@@ -633,6 +725,8 @@ export const PromptInputTextarea = ({
633
725
  }: PromptInputTextareaProps) => {
634
726
  const controller = useOptionalPromptInputController();
635
727
  const attachments = usePromptInputAttachments();
728
+ const textarea = useUiConfig("PromptInput")?.classes?.textarea;
729
+ const merge = useCn();
636
730
  const [isComposing, setIsComposing] = useState(false);
637
731
  const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
638
732
  onKeyDown?.(e);
@@ -686,7 +780,7 @@ export const PromptInputTextarea = ({
686
780
  : { onChange };
687
781
  return (
688
782
  <InputGroupTextarea
689
- className={cn("field-sizing-content max-h-48 min-h-16", className)}
783
+ className={merge(textarea ?? "field-sizing-content max-h-48 min-h-16", className)}
690
784
  name="message"
691
785
  onCompositionEnd={handleCompositionEnd}
692
786
  onCompositionStart={handleCompositionStart}
@@ -705,13 +799,16 @@ export type PromptInputHeaderProps = Omit<
705
799
  export const PromptInputHeader = ({
706
800
  className,
707
801
  ...props
708
- }: PromptInputHeaderProps) => (
709
- <InputGroupAddon
710
- align="block-end"
711
- className={cn("order-first flex-wrap gap-1", className)}
712
- {...props}
713
- />
714
- );
802
+ }: PromptInputHeaderProps) => {
803
+ const merge = useCn();
804
+ return (
805
+ <InputGroupAddon
806
+ align="block-end"
807
+ className={merge("order-first flex-wrap gap-1", className)}
808
+ {...props}
809
+ />
810
+ );
811
+ };
715
812
  export type PromptInputFooterProps = Omit<
716
813
  ComponentProps<typeof InputGroupAddon>,
717
814
  "align"
@@ -719,23 +816,30 @@ export type PromptInputFooterProps = Omit<
719
816
  export const PromptInputFooter = ({
720
817
  className,
721
818
  ...props
722
- }: PromptInputFooterProps) => (
723
- <InputGroupAddon
724
- align="block-end"
725
- className={cn("justify-between gap-1", className)}
726
- {...props}
727
- />
728
- );
819
+ }: PromptInputFooterProps) => {
820
+ const footer = useUiConfig("PromptInput")?.classes?.footer;
821
+ const merge = useCn();
822
+ return (
823
+ <InputGroupAddon
824
+ align="block-end"
825
+ className={merge(footer ?? "justify-between gap-1", className)}
826
+ {...props}
827
+ />
828
+ );
829
+ };
729
830
  export type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;
730
831
  export const PromptInputTools = ({
731
832
  className,
732
833
  ...props
733
- }: PromptInputToolsProps) => (
734
- <div
735
- className={cn("flex min-w-0 items-center gap-1", className)}
736
- {...props}
737
- />
738
- );
834
+ }: PromptInputToolsProps) => {
835
+ const merge = useCn();
836
+ return (
837
+ <div
838
+ className={merge("flex min-w-0 items-center gap-1", className)}
839
+ {...props}
840
+ />
841
+ );
842
+ };
739
843
  export type PromptInputButtonTooltip =
740
844
  | string
741
845
  | {
@@ -753,11 +857,12 @@ export const PromptInputButton = ({
753
857
  tooltip,
754
858
  ...props
755
859
  }: PromptInputButtonProps) => {
860
+ const merge = useCn();
756
861
  const newSize =
757
862
  size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");
758
863
  const button = (
759
864
  <InputGroupButton
760
- className={cn(className)}
865
+ className={merge(className)}
761
866
  size={newSize}
762
867
  type="button"
763
868
  variant={variant}
@@ -788,31 +893,39 @@ export const PromptInputActionMenuTrigger = ({
788
893
  className,
789
894
  children,
790
895
  ...props
791
- }: PromptInputActionMenuTriggerProps) => (
792
- <DropdownTrigger>
896
+ }: PromptInputActionMenuTriggerProps) => {
897
+ const asChild = useUiConfig("PromptInput")?.menuTriggerAsChild;
898
+ const button = (
793
899
  <PromptInputButton className={className} {...props}>
794
900
  {children ?? <IconPlus className="size-4" />}
795
901
  </PromptInputButton>
796
- </DropdownTrigger>
797
- );
902
+ );
903
+ return asChild ? <DropdownTrigger asChild>{button}</DropdownTrigger> : <DropdownTrigger>{button}</DropdownTrigger>;
904
+ };
798
905
  export type PromptInputActionMenuContentProps = ComponentProps<
799
906
  typeof DropdownMenu
800
907
  >;
801
908
  export const PromptInputActionMenuContent = ({
802
909
  className,
803
910
  ...props
804
- }: PromptInputActionMenuContentProps) => (
805
- <DropdownMenu align="start" className={cn(className)} {...props} />
806
- );
911
+ }: PromptInputActionMenuContentProps) => {
912
+ const merge = useCn();
913
+ return (
914
+ <DropdownMenu align="start" className={merge(className)} {...props} />
915
+ );
916
+ };
807
917
  export type PromptInputActionMenuItemProps = ComponentProps<
808
918
  typeof DropdownItem
809
919
  >;
810
920
  export const PromptInputActionMenuItem = ({
811
921
  className,
812
922
  ...props
813
- }: PromptInputActionMenuItemProps) => (
814
- <DropdownItem className={cn(className)} {...props} />
815
- );
923
+ }: PromptInputActionMenuItemProps) => {
924
+ const merge = useCn();
925
+ return (
926
+ <DropdownItem className={merge(className)} {...props} />
927
+ );
928
+ };
816
929
  export type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
817
930
  status?: ChatStatus;
818
931
  onStop?: () => void;
@@ -827,14 +940,21 @@ export const PromptInputSubmit = ({
827
940
  children,
828
941
  ...props
829
942
  }: PromptInputSubmitProps) => {
943
+ const config = useUiConfig("PromptInput");
944
+ const icons = config?.submitIcons;
945
+ const merge = useCn();
830
946
  const isGenerating = status === "submitted" || status === "streaming";
831
- let Icon = <IconCornerDownLeft className="size-4" />;
947
+ const iconKey =
948
+ status === "submitted" || status === "streaming" || status === "error"
949
+ ? status
950
+ : "send";
951
+ let Icon = icons?.send ?? <IconCornerDownLeft className="size-4" />;
832
952
  if (status === "submitted") {
833
- Icon = <Spinner size="sm" />;
953
+ Icon = icons?.submitted ?? <Spinner size="sm" />;
834
954
  } else if (status === "streaming") {
835
- Icon = <IconSquare className="size-4" />;
955
+ Icon = icons?.streaming ?? <IconSquare className="size-4" />;
836
956
  } else if (status === "error") {
837
- Icon = <IconX className="size-4" />;
957
+ Icon = icons?.error ?? <IconX className="size-4" />;
838
958
  }
839
959
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
840
960
  if (isGenerating && onStop) {
@@ -847,63 +967,91 @@ export const PromptInputSubmit = ({
847
967
  return (
848
968
  <InputGroupButton
849
969
  aria-label={isGenerating ? "Stop" : "Submit"}
850
- className={cn(className)}
970
+ className={merge(config?.classes?.submit, className)}
851
971
  onClick={handleClick}
852
972
  size={size}
853
973
  type={isGenerating && onStop ? "button" : "submit"}
854
974
  variant={variant}
855
975
  {...props}
856
976
  >
857
- {children ?? Icon}
977
+ {children ??
978
+ (config?.submitCrossfade ? (
979
+ // Keyed by status, so send, spinner, stop and error cross-fade.
980
+ <CrossfadeIconSlot iconKey={iconKey}>{Icon}</CrossfadeIconSlot>
981
+ ) : (
982
+ Icon
983
+ ))}
858
984
  </InputGroupButton>
859
985
  );
860
986
  };
987
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
861
988
  export type PromptInputModelSelectProps = ComponentProps<typeof Select>;
989
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
862
990
  export const PromptInputModelSelect = (props: PromptInputModelSelectProps) => (
863
991
  <Select {...props} />
864
992
  );
993
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
865
994
  export type PromptInputModelSelectTriggerProps = ComponentProps<
866
995
  typeof SelectTrigger
867
996
  >;
997
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
868
998
  export const PromptInputModelSelectTrigger = ({
869
999
  className,
870
1000
  ...props
871
- }: PromptInputModelSelectTriggerProps) => (
872
- <SelectTrigger
873
- className={cn(
874
- "border-none bg-transparent font-medium text-muted-foreground shadow-none transition-[color,transform] duration-150 ease-out active:scale-[0.97]",
875
- "hover:bg-surface-secondary hover:text-foreground",
876
- segmentExpandedStateClass,
877
- className,
878
- )}
879
- {...props}
880
- />
881
- );
1001
+ }: PromptInputModelSelectTriggerProps) => {
1002
+ const merge = useCn();
1003
+ return (
1004
+ <SelectTrigger
1005
+ className={merge(
1006
+ "border-none bg-transparent font-medium text-muted-foreground shadow-none transition-[color,transform] duration-150 ease-out active:scale-[0.97]",
1007
+ "hover:bg-surface-secondary hover:text-foreground",
1008
+ segmentExpandedStateClass,
1009
+ className,
1010
+ )}
1011
+ {...props}
1012
+ />
1013
+ );
1014
+ };
1015
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
882
1016
  export type PromptInputModelSelectContentProps = ComponentProps<
883
1017
  typeof SelectContent
884
1018
  >;
1019
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
885
1020
  export const PromptInputModelSelectContent = ({
886
1021
  className,
887
1022
  ...props
888
- }: PromptInputModelSelectContentProps) => (
889
- <SelectContent className={cn(className)} {...props} />
890
- );
1023
+ }: PromptInputModelSelectContentProps) => {
1024
+ const merge = useCn();
1025
+ return (
1026
+ <SelectContent className={merge(className)} {...props} />
1027
+ );
1028
+ };
1029
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
891
1030
  export type PromptInputModelSelectItemProps = ComponentProps<typeof SelectItem>;
1031
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
892
1032
  export const PromptInputModelSelectItem = ({
893
1033
  className,
894
1034
  ...props
895
- }: PromptInputModelSelectItemProps) => (
896
- <SelectItem className={cn(className)} {...props} />
897
- );
1035
+ }: PromptInputModelSelectItemProps) => {
1036
+ const merge = useCn();
1037
+ return (
1038
+ <SelectItem className={merge(className)} {...props} />
1039
+ );
1040
+ };
1041
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
898
1042
  export type PromptInputModelSelectValueProps = ComponentProps<
899
1043
  typeof SelectValue
900
1044
  >;
1045
+ /** @deprecated Use `ModelPicker`. Removed in the next minor release. */
901
1046
  export const PromptInputModelSelectValue = ({
902
1047
  className,
903
1048
  ...props
904
- }: PromptInputModelSelectValueProps) => (
905
- <SelectValue className={cn(className)} {...props} />
906
- );
1049
+ }: PromptInputModelSelectValueProps) => {
1050
+ const merge = useCn();
1051
+ return (
1052
+ <SelectValue className={merge(className)} {...props} />
1053
+ );
1054
+ };
907
1055
  export type PromptInputHoverCardProps = ComponentProps<typeof HoverCard>;
908
1056
  export const PromptInputHoverCard = ({
909
1057
  openDelay = 0,
@@ -931,88 +1079,126 @@ export type PromptInputTabsListProps = HTMLAttributes<HTMLDivElement>;
931
1079
  export const PromptInputTabsList = ({
932
1080
  className,
933
1081
  ...props
934
- }: PromptInputTabsListProps) => <div className={cn(className)} {...props} />;
1082
+ }: PromptInputTabsListProps) => {
1083
+ const merge = useCn();
1084
+ return <div className={merge(className)} {...props} />;
1085
+ };
935
1086
  export type PromptInputTabProps = HTMLAttributes<HTMLDivElement>;
936
1087
  export const PromptInputTab = ({
937
1088
  className,
938
1089
  ...props
939
- }: PromptInputTabProps) => <div className={cn(className)} {...props} />;
1090
+ }: PromptInputTabProps) => {
1091
+ const merge = useCn();
1092
+ return <div className={merge(className)} {...props} />;
1093
+ };
940
1094
  export type PromptInputTabLabelProps = HTMLAttributes<HTMLHeadingElement>;
941
1095
  export const PromptInputTabLabel = ({
942
1096
  className,
943
1097
  ...props
944
- }: PromptInputTabLabelProps) => (
945
- <h3
946
- className={cn("mb-2 px-3 font-medium text-muted-foreground text-xs", className)}
947
- {...props}
948
- />
949
- );
1098
+ }: PromptInputTabLabelProps) => {
1099
+ const merge = useCn();
1100
+ return (
1101
+ <h3
1102
+ className={merge("mb-2 px-3 font-medium text-muted-foreground text-xs", className)}
1103
+ {...props}
1104
+ />
1105
+ );
1106
+ };
950
1107
  export type PromptInputTabBodyProps = HTMLAttributes<HTMLDivElement>;
951
1108
  export const PromptInputTabBody = ({
952
1109
  className,
953
1110
  ...props
954
- }: PromptInputTabBodyProps) => (
955
- <div className={cn("space-y-1", className)} {...props} />
956
- );
1111
+ }: PromptInputTabBodyProps) => {
1112
+ const merge = useCn();
1113
+ return (
1114
+ <div className={merge("space-y-1", className)} {...props} />
1115
+ );
1116
+ };
957
1117
  export type PromptInputTabItemProps = HTMLAttributes<HTMLDivElement>;
958
1118
  export const PromptInputTabItem = ({
959
1119
  className,
960
1120
  ...props
961
- }: PromptInputTabItemProps) => (
962
- <div
963
- className={cn(
964
- "flex items-center gap-2 rounded-md px-3 py-2 text-xs transition-[background-color,transform] duration-150 ease-out active:scale-[0.99] hover:bg-surface-secondary",
965
- className,
966
- )}
967
- {...props}
968
- />
969
- );
1121
+ }: PromptInputTabItemProps) => {
1122
+ const tabItem = useUiConfig("PromptInput")?.classes?.tabItem;
1123
+ const merge = useCn();
1124
+ return (
1125
+ <div
1126
+ className={merge(
1127
+ tabItem ??
1128
+ "flex items-center gap-2 rounded-md px-3 py-2 text-xs transition-[background-color,transform] duration-150 ease-out active:scale-[0.99] hover:bg-surface-secondary",
1129
+ className,
1130
+ )}
1131
+ {...props}
1132
+ />
1133
+ );
1134
+ };
970
1135
  export type PromptInputCommandProps = ComponentProps<typeof Command>;
971
1136
  export const PromptInputCommand = ({
972
1137
  className,
973
1138
  ...props
974
- }: PromptInputCommandProps) => <Command className={cn(className)} {...props} />;
1139
+ }: PromptInputCommandProps) => {
1140
+ const merge = useCn();
1141
+ return <Command className={merge(className)} {...props} />;
1142
+ };
975
1143
  export type PromptInputCommandInputProps = ComponentProps<typeof CommandInput>;
976
1144
  export const PromptInputCommandInput = ({
977
1145
  className,
978
1146
  ...props
979
- }: PromptInputCommandInputProps) => (
980
- <CommandInput className={cn(className)} {...props} />
981
- );
1147
+ }: PromptInputCommandInputProps) => {
1148
+ const merge = useCn();
1149
+ return (
1150
+ <CommandInput className={merge(className)} {...props} />
1151
+ );
1152
+ };
982
1153
  export type PromptInputCommandListProps = ComponentProps<typeof CommandList>;
983
1154
  export const PromptInputCommandList = ({
984
1155
  className,
985
1156
  ...props
986
- }: PromptInputCommandListProps) => (
987
- <CommandList className={cn(className)} {...props} />
988
- );
1157
+ }: PromptInputCommandListProps) => {
1158
+ const merge = useCn();
1159
+ return (
1160
+ <CommandList className={merge(className)} {...props} />
1161
+ );
1162
+ };
989
1163
  export type PromptInputCommandEmptyProps = ComponentProps<typeof CommandEmpty>;
990
1164
  export const PromptInputCommandEmpty = ({
991
1165
  className,
992
1166
  ...props
993
- }: PromptInputCommandEmptyProps) => (
994
- <CommandEmpty className={cn(className)} {...props} />
995
- );
1167
+ }: PromptInputCommandEmptyProps) => {
1168
+ const merge = useCn();
1169
+ return (
1170
+ <CommandEmpty className={merge(className)} {...props} />
1171
+ );
1172
+ };
996
1173
  export type PromptInputCommandGroupProps = ComponentProps<typeof CommandGroup>;
997
1174
  export const PromptInputCommandGroup = ({
998
1175
  className,
999
1176
  ...props
1000
- }: PromptInputCommandGroupProps) => (
1001
- <CommandGroup className={cn(className)} {...props} />
1002
- );
1177
+ }: PromptInputCommandGroupProps) => {
1178
+ const merge = useCn();
1179
+ return (
1180
+ <CommandGroup className={merge(className)} {...props} />
1181
+ );
1182
+ };
1003
1183
  export type PromptInputCommandItemProps = ComponentProps<typeof CommandItem>;
1004
1184
  export const PromptInputCommandItem = ({
1005
1185
  className,
1006
1186
  ...props
1007
- }: PromptInputCommandItemProps) => (
1008
- <CommandItem className={cn(className)} {...props} />
1009
- );
1187
+ }: PromptInputCommandItemProps) => {
1188
+ const merge = useCn();
1189
+ return (
1190
+ <CommandItem className={merge(className)} {...props} />
1191
+ );
1192
+ };
1010
1193
  export type PromptInputCommandSeparatorProps = ComponentProps<
1011
1194
  typeof CommandSeparator
1012
1195
  >;
1013
1196
  export const PromptInputCommandSeparator = ({
1014
1197
  className,
1015
1198
  ...props
1016
- }: PromptInputCommandSeparatorProps) => (
1017
- <CommandSeparator className={cn(className)} {...props} />
1018
- );
1199
+ }: PromptInputCommandSeparatorProps) => {
1200
+ const merge = useCn();
1201
+ return (
1202
+ <CommandSeparator className={merge(className)} {...props} />
1203
+ );
1204
+ };