@vvedantb/ui 0.17.1 → 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 (129) hide show
  1. package/README.md +251 -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/markdown.css +318 -0
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { IconFile } from "@tabler/icons-react";
4
+
5
+ import { useCn } from "../ui/ui-config";
6
+
7
+ function FileChipLabel({
8
+ name,
9
+ className,
10
+ }: {
11
+ name: string;
12
+ className?: string;
13
+ }) {
14
+ const merge = useCn();
15
+ return (
16
+ <span
17
+ className={merge(
18
+ "inline-flex w-fit max-w-full items-center gap-1.5 rounded-md border border-border bg-secondary px-1.5 py-0.5 font-mono text-xs text-foreground",
19
+ className,
20
+ )}
21
+ >
22
+ <IconFile className="size-3 shrink-0" />
23
+ <span className="truncate">{name}</span>
24
+ </span>
25
+ );
26
+ }
27
+
28
+ export interface FileChipProps {
29
+ name: string;
30
+ path?: string;
31
+ onOpenFile?: (path: string) => void;
32
+ }
33
+
34
+ /**
35
+ * A file name in an activity line or step body. Opens the file viewer when the
36
+ * full path and an `onOpenFile` handler are both known; plain text otherwise.
37
+ */
38
+ export function FileChip({ name, path, onOpenFile }: FileChipProps) {
39
+ if (!path || !onOpenFile) return <FileChipLabel name={name} />;
40
+ return (
41
+ <button
42
+ type="button"
43
+ title={path}
44
+ onClick={(event) => {
45
+ event.stopPropagation();
46
+ onOpenFile(path);
47
+ }}
48
+ className="inline-flex min-w-0 max-w-full cursor-pointer"
49
+ >
50
+ <FileChipLabel name={name} className="transition-colors hover:bg-surface-secondary" />
51
+ </button>
52
+ );
53
+ }
@@ -9,62 +9,99 @@ import {
9
9
  TooltipProvider,
10
10
  TooltipTrigger,
11
11
  } from "../ui/tooltip";
12
- import { cn } from "../utils/cn";
12
+ import { useCn, useUiConfig } from "../ui/ui-config";
13
13
  import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
14
14
  import { createContext, useContext, useEffect, useState } from "react";
15
+ /**
16
+ * `UiConfig.Message`: classes replace a part's built-in classes (`root`, and
17
+ * the `user` / `assistant` role classes of `Message`; `content` is the whole
18
+ * `MessageContent` list). `lazyActionTooltips` mounts a `MessageAction`
19
+ * tooltip on first hover or focus instead of with the button.
20
+ */
21
+ export interface MessageConfig {
22
+ lazyActionTooltips?: boolean;
23
+ classes?: {
24
+ root?: string;
25
+ user?: string;
26
+ assistant?: string;
27
+ content?: string;
28
+ branchSelector?: string;
29
+ /** Replaces the branch arrows' `size={14}` with a class. */
30
+ branchIcon?: string;
31
+ };
32
+ }
15
33
  export type MessageProps = HTMLAttributes<HTMLDivElement> & {
16
34
  from: UIMessage["role"];
17
35
  };
18
- export const Message = ({ className, from, ...props }: MessageProps) => (
19
- <div
20
- className={cn(
21
- "group flex w-full max-w-[95%] flex-col gap-2",
22
- from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
23
- className,
24
- )}
25
- {...props}
26
- />
27
- );
36
+ export const Message = ({ className, from, ...props }: MessageProps) => {
37
+ const classes = useUiConfig("Message")?.classes;
38
+ const merge = useCn();
39
+ return (
40
+ <div
41
+ className={merge(
42
+ classes?.root ?? "group flex w-full max-w-[95%] flex-col gap-2",
43
+ from === "user"
44
+ ? (classes?.user ?? "is-user ml-auto justify-end")
45
+ : (classes?.assistant ?? "is-assistant"),
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ );
51
+ };
28
52
  export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
29
53
  export const MessageContent = ({
30
54
  children,
31
55
  className,
32
56
  ...props
33
- }: MessageContentProps) => (
34
- <div
35
- className={cn(
36
- "flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
37
- "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-md group-[.is-user]:bg-primary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-primary-foreground",
38
- "group-[.is-assistant]:px-0 group-[.is-assistant]:text-foreground",
39
- className,
40
- )}
41
- {...props}
42
- >
43
- {children}
44
- </div>
45
- );
57
+ }: MessageContentProps) => {
58
+ const content = useUiConfig("Message")?.classes?.content;
59
+ const merge = useCn();
60
+ return (
61
+ <div
62
+ className={merge(
63
+ content ?? [
64
+ "flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
65
+ "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-md group-[.is-user]:bg-primary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-primary-foreground",
66
+ "group-[.is-assistant]:px-0 group-[.is-assistant]:text-foreground",
67
+ ],
68
+ className,
69
+ )}
70
+ {...props}
71
+ >
72
+ {children}
73
+ </div>
74
+ );
75
+ };
46
76
  export type MessageActionsBarProps = ComponentProps<"div">;
47
77
  export const MessageActionsBar = ({
48
78
  className,
49
79
  children,
50
80
  ...props
51
- }: MessageActionsBarProps) => (
52
- <div className={cn("flex items-center gap-1", className)} {...props}>
53
- {children}
54
- </div>
55
- );
81
+ }: MessageActionsBarProps) => {
82
+ const merge = useCn();
83
+ return (
84
+ <div className={merge("flex items-center gap-1", className)} {...props}>
85
+ {children}
86
+ </div>
87
+ );
88
+ };
56
89
  export type MessageActionProps = ComponentProps<typeof Button> & {
57
90
  tooltip?: string;
58
91
  label?: string;
59
92
  };
60
- export const MessageAction = ({
93
+ export const MessageAction = (props: MessageActionProps) => {
94
+ const lazy = useUiConfig("Message")?.lazyActionTooltips;
95
+ return lazy ? <LazyMessageAction {...props} /> : <EagerMessageAction {...props} />;
96
+ };
97
+ function EagerMessageAction({
61
98
  tooltip,
62
99
  children,
63
100
  label,
64
101
  variant = "ghost",
65
102
  size = "icon-sm",
66
103
  ...props
67
- }: MessageActionProps) => {
104
+ }: MessageActionProps) {
68
105
  const button = (
69
106
  <Button size={size} type="button" variant={variant} {...props}>
70
107
  {children}
@@ -84,7 +121,60 @@ export const MessageAction = ({
84
121
  );
85
122
  }
86
123
  return button;
87
- };
124
+ }
125
+ /**
126
+ * The tooltip mounts on first hover or focus, not with the button. Each Radix
127
+ * tooltip is a dozen components plus a re-render once it has measured its
128
+ * anchor; a long chat has hundreds of these buttons.
129
+ */
130
+ function LazyMessageAction({
131
+ tooltip,
132
+ children,
133
+ label,
134
+ variant = "ghost",
135
+ size = "icon-sm",
136
+ onPointerEnter,
137
+ onFocus,
138
+ ...props
139
+ }: MessageActionProps) {
140
+ const [armedBy, setArmedBy] = useState<"pointer" | "focus" | null>(null);
141
+ const button = (
142
+ <Button
143
+ size={size}
144
+ type="button"
145
+ variant={variant}
146
+ onPointerEnter={(event) => {
147
+ onPointerEnter?.(event);
148
+ if (tooltip && armedBy === null) setArmedBy("pointer");
149
+ }}
150
+ onFocus={(event) => {
151
+ onFocus?.(event);
152
+ if (tooltip && armedBy === null) setArmedBy("focus");
153
+ }}
154
+ // Arming remounts the button inside the tooltip trigger; a keyboard
155
+ // user keeps focus on it (and so sees the tooltip).
156
+ autoFocus={armedBy === "focus"}
157
+ {...props}
158
+ >
159
+ {children}
160
+ <span className="sr-only">{label || tooltip}</span>
161
+ </Button>
162
+ );
163
+ if (tooltip && armedBy !== null) {
164
+ return (
165
+ <TooltipProvider>
166
+ {/* The focus that armed it landed before the tooltip existed. */}
167
+ <Tooltip defaultOpen={armedBy === "focus"}>
168
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
169
+ <TooltipContent>
170
+ <p>{tooltip}</p>
171
+ </TooltipContent>
172
+ </Tooltip>
173
+ </TooltipProvider>
174
+ );
175
+ }
176
+ return button;
177
+ }
88
178
  interface MessageBranchContextType {
89
179
  currentBranch: number;
90
180
  totalBranches: number;
@@ -131,6 +221,7 @@ export const MessageBranch = ({
131
221
  currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
132
222
  handleBranchChange(newBranch);
133
223
  };
224
+ const merge = useCn();
134
225
  const contextValue = {
135
226
  branches,
136
227
  currentBranch,
@@ -142,7 +233,7 @@ export const MessageBranch = ({
142
233
  return (
143
234
  <MessageBranchContext.Provider value={contextValue}>
144
235
  <div
145
- className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
236
+ className={merge("grid w-full gap-2 [&>div]:pb-0", className)}
146
237
  {...props}
147
238
  />
148
239
  </MessageBranchContext.Provider>
@@ -154,6 +245,7 @@ export const MessageBranchContent = ({
154
245
  ...props
155
246
  }: MessageBranchContentProps) => {
156
247
  const { currentBranch, setBranches, branches } = useMessageBranch();
248
+ const merge = useCn();
157
249
  const childrenArray = Array.isArray(children) ? children : [children];
158
250
  useEffect(() => {
159
251
  if (branches.length !== childrenArray.length) {
@@ -162,7 +254,7 @@ export const MessageBranchContent = ({
162
254
  }, [childrenArray, branches, setBranches]);
163
255
  return childrenArray.map((branch, index) => (
164
256
  <div
165
- className={cn(
257
+ className={merge(
166
258
  "grid gap-2 overflow-hidden [&>div]:pb-0",
167
259
  index === currentBranch ? "block" : "hidden",
168
260
  )}
@@ -179,13 +271,16 @@ export const MessageBranchSelector = ({
179
271
  ...props
180
272
  }: MessageBranchSelectorProps) => {
181
273
  const { totalBranches } = useMessageBranch();
274
+ const branchSelector = useUiConfig("Message")?.classes?.branchSelector;
275
+ const merge = useCn();
182
276
  if (totalBranches <= 1) {
183
277
  return null;
184
278
  }
185
279
  return (
186
280
  <ButtonGroup
187
- className={cn(
188
- "[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",
281
+ className={merge(
282
+ branchSelector ??
283
+ "[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",
189
284
  className,
190
285
  )}
191
286
  orientation="horizontal"
@@ -199,6 +294,7 @@ export const MessageBranchPrevious = ({
199
294
  ...props
200
295
  }: MessageBranchPreviousProps) => {
201
296
  const { goToPrevious, totalBranches } = useMessageBranch();
297
+ const branchIcon = useUiConfig("Message")?.classes?.branchIcon;
202
298
  return (
203
299
  <Button
204
300
  aria-label="Previous branch"
@@ -209,7 +305,12 @@ export const MessageBranchPrevious = ({
209
305
  variant="ghost"
210
306
  {...props}
211
307
  >
212
- {children ?? <IconChevronLeft size={14} />}
308
+ {children ??
309
+ (branchIcon === undefined ? (
310
+ <IconChevronLeft size={14} />
311
+ ) : (
312
+ <IconChevronLeft className={branchIcon} />
313
+ ))}
213
314
  </Button>
214
315
  );
215
316
  };
@@ -219,6 +320,7 @@ export const MessageBranchNext = ({
219
320
  ...props
220
321
  }: MessageBranchNextProps) => {
221
322
  const { goToNext, totalBranches } = useMessageBranch();
323
+ const branchIcon = useUiConfig("Message")?.classes?.branchIcon;
222
324
  return (
223
325
  <Button
224
326
  aria-label="Next branch"
@@ -229,7 +331,12 @@ export const MessageBranchNext = ({
229
331
  variant="ghost"
230
332
  {...props}
231
333
  >
232
- {children ?? <IconChevronRight size={14} />}
334
+ {children ??
335
+ (branchIcon === undefined ? (
336
+ <IconChevronRight size={14} />
337
+ ) : (
338
+ <IconChevronRight className={branchIcon} />
339
+ ))}
233
340
  </Button>
234
341
  );
235
342
  };
@@ -239,9 +346,10 @@ export const MessageBranchPage = ({
239
346
  ...props
240
347
  }: MessageBranchPageProps) => {
241
348
  const { currentBranch, totalBranches } = useMessageBranch();
349
+ const merge = useCn();
242
350
  return (
243
351
  <ButtonGroupText
244
- className={cn(
352
+ className={merge(
245
353
  "border-none bg-transparent text-muted-foreground shadow-none",
246
354
  className,
247
355
  )}
@@ -256,14 +364,17 @@ export const MessageToolbar = ({
256
364
  className,
257
365
  children,
258
366
  ...props
259
- }: MessageToolbarProps) => (
260
- <div
261
- className={cn(
262
- "mt-4 flex w-full items-center justify-between gap-4",
263
- className,
264
- )}
265
- {...props}
266
- >
267
- {children}
268
- </div>
269
- );
367
+ }: MessageToolbarProps) => {
368
+ const merge = useCn();
369
+ return (
370
+ <div
371
+ className={merge(
372
+ "mt-4 flex w-full items-center justify-between gap-4",
373
+ className,
374
+ )}
375
+ {...props}
376
+ >
377
+ {children}
378
+ </div>
379
+ );
380
+ };