@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
package/src/ui/table.tsx CHANGED
@@ -1,14 +1,40 @@
1
1
  import * as React from "react";
2
- import { cn } from "../utils/cn";
2
+ import { useCn, useUiConfig } from "./ui-config";
3
+
4
+ export interface TableConfig {
5
+ /** Replaces the built-in classes of a part. A part left out keeps its default. */
6
+ classes?: {
7
+ /** The scroll wrapper `<div>` around the `<table>`. */
8
+ wrapper?: string;
9
+ table?: string;
10
+ header?: string;
11
+ body?: string;
12
+ footer?: string;
13
+ row?: string;
14
+ head?: string;
15
+ cell?: string;
16
+ caption?: string;
17
+ };
18
+ }
19
+
20
+ /** The app's class string for `part` (else `fallback`), merged with the caller `className`. */
21
+ function useTableClass(
22
+ part: keyof NonNullable<TableConfig["classes"]>,
23
+ fallback: string,
24
+ className: string | undefined,
25
+ ): string {
26
+ const merge = useCn();
27
+ return merge(useUiConfig("Table")?.classes?.[part] ?? fallback, className);
28
+ }
3
29
 
4
30
  const Table = React.forwardRef<
5
31
  HTMLTableElement,
6
32
  React.HTMLAttributes<HTMLTableElement>
7
33
  >(({ className, ...props }, ref) => (
8
- <div className="relative w-full overflow-auto rounded-lg max-sm:min-w-0 max-sm:max-w-full">
34
+ <div className={useUiConfig("Table")?.classes?.wrapper ?? "relative w-full overflow-auto rounded-lg max-sm:min-w-0 max-sm:max-w-full"}>
9
35
  <table
10
36
  ref={ref}
11
- className={cn("w-full caption-bottom text-sm", className)}
37
+ className={useTableClass("table", "w-full caption-bottom text-sm", className)}
12
38
  {...props}
13
39
  />
14
40
  </div>
@@ -21,7 +47,7 @@ const TableHeader = React.forwardRef<
21
47
  >(({ className, ...props }, ref) => (
22
48
  <thead
23
49
  ref={ref}
24
- className={cn("[&_tr]:border-b [&_tr]:border-separator", className)}
50
+ className={useTableClass("header", "[&_tr]:border-b [&_tr]:border-separator", className)}
25
51
  {...props}
26
52
  />
27
53
  ));
@@ -33,7 +59,7 @@ const TableBody = React.forwardRef<
33
59
  >(({ className, ...props }, ref) => (
34
60
  <tbody
35
61
  ref={ref}
36
- className={cn("[&_tr:last-child]:border-0", className)}
62
+ className={useTableClass("body", "[&_tr:last-child]:border-0", className)}
37
63
  {...props}
38
64
  />
39
65
  ));
@@ -45,10 +71,7 @@ const TableFooter = React.forwardRef<
45
71
  >(({ className, ...props }, ref) => (
46
72
  <tfoot
47
73
  ref={ref}
48
- className={cn(
49
- "border-t border-separator bg-surface-secondary/30 font-medium [&>tr]:last:border-b-0",
50
- className,
51
- )}
74
+ className={useTableClass("footer", "border-t border-separator bg-surface-secondary/30 font-medium [&>tr]:last:border-b-0", className)}
52
75
  {...props}
53
76
  />
54
77
  ));
@@ -60,7 +83,8 @@ const TableRow = React.forwardRef<
60
83
  >(({ className, ...props }, ref) => (
61
84
  <tr
62
85
  ref={ref}
63
- className={cn(
86
+ className={useTableClass(
87
+ "row",
64
88
  "border-b border-separator transition-colors duration-200 ease-smooth hover:bg-surface-tertiary/50 data-[state=selected]:bg-surface-secondary",
65
89
  className,
66
90
  )}
@@ -75,7 +99,8 @@ const TableHead = React.forwardRef<
75
99
  >(({ className, ...props }, ref) => (
76
100
  <th
77
101
  ref={ref}
78
- className={cn(
102
+ className={useTableClass(
103
+ "head",
79
104
  "h-11 px-4 text-left align-middle text-xs font-semibold uppercase tracking-widest text-muted-foreground [&:has([role=checkbox])]:pr-0",
80
105
  className,
81
106
  )}
@@ -90,10 +115,7 @@ const TableCell = React.forwardRef<
90
115
  >(({ className, ...props }, ref) => (
91
116
  <td
92
117
  ref={ref}
93
- className={cn(
94
- "p-4 align-middle text-sm [&:has([role=checkbox])]:pr-0",
95
- className,
96
- )}
118
+ className={useTableClass("cell", "p-4 align-middle text-sm [&:has([role=checkbox])]:pr-0", className)}
97
119
  {...props}
98
120
  />
99
121
  ));
@@ -105,7 +127,7 @@ const TableCaption = React.forwardRef<
105
127
  >(({ className, ...props }, ref) => (
106
128
  <caption
107
129
  ref={ref}
108
- className={cn("mt-4 text-sm text-muted-foreground", className)}
130
+ className={useTableClass("caption", "mt-4 text-sm text-muted-foreground", className)}
109
131
  {...props}
110
132
  />
111
133
  ));
package/src/ui/tabs.tsx CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as React from "react";
2
2
  import * as TabsPrimitive from "@radix-ui/react-tabs";
3
- import { cn } from "../utils/cn";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
4
  import { assignRef } from "../utils/ref";
5
- import { syncTabsPill } from "./tabsSliding";
5
+ import { syncTabsPill, type TabsPillMotion } from "./tabsSliding";
6
+ import { useCn, useUiConfig } from "./ui-config";
6
7
 
7
8
  const Tabs = TabsPrimitive.Root;
8
9
 
@@ -11,6 +12,32 @@ const Tabs = TabsPrimitive.Root;
11
12
  // `soft`: borderless wrapping pills, the active one filled. Uses no `t-tabs`
12
13
  // CSS, so triggers can hold an absolutely placed child (e.g. a progress bar).
13
14
  type TabsListVariant = "pills" | "segmented" | "soft";
15
+ type TabsListSize = "default" | "sm";
16
+
17
+ /**
18
+ * `UiConfig.Tabs`. `classes` replace built-in part classes (`list` replaces
19
+ * the list classes of every variant, `trigger` those of every trigger).
20
+ * `variant` is the default for the `TabsList` prop; `pillMotion` picks how the
21
+ * segmented pill moves: `x` (default) or `xy`, both axes on transform for
22
+ * lists that wrap or run in a column.
23
+ */
24
+ export interface TabsConfig {
25
+ variant?: TabsListVariant;
26
+ pillMotion?: TabsPillMotion;
27
+ classes?: {
28
+ list?: string;
29
+ size?: Partial<Record<TabsListSize, string>>;
30
+ pill?: string;
31
+ trigger?: string;
32
+ content?: string;
33
+ };
34
+ }
35
+
36
+ // `sm` shrinks the triggers from the list, so callers do not size each one.
37
+ const tabsListSizeClasses: Record<TabsListSize, string> = {
38
+ default: "",
39
+ sm: "[&_.t-tab]:px-2.5 [&_.t-tab]:py-1 [&_.t-tab]:text-xs",
40
+ };
14
41
 
15
42
  const TabsListVariantContext = React.createContext<TabsListVariant>("pills");
16
43
 
@@ -18,8 +45,14 @@ const TabsList = React.forwardRef<
18
45
  React.ComponentRef<typeof TabsPrimitive.List>,
19
46
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
20
47
  variant?: TabsListVariant;
48
+ /** `sm` makes the triggers smaller. No classes for `default`. */
49
+ size?: TabsListSize;
21
50
  }
22
- >(({ className, children, variant = "pills", ...props }, ref) => {
51
+ >(({ className, children, variant: variantProp, size, ...props }, ref) => {
52
+ const config = useUiConfig("Tabs");
53
+ const cn = useCn();
54
+ const variant = variantProp ?? config?.variant ?? "pills";
55
+ const pillMotion = config?.pillMotion ?? "x";
23
56
  const listRef = React.useRef<React.ComponentRef<
24
57
  typeof TabsPrimitive.List
25
58
  > | null>(null);
@@ -42,11 +75,11 @@ const TabsList = React.forwardRef<
42
75
  }
43
76
 
44
77
  const snap = () => {
45
- syncTabsPill(list, pill, false);
78
+ syncTabsPill(list, pill, false, pillMotion);
46
79
  };
47
80
 
48
81
  const animate = () => {
49
- syncTabsPill(list, pill, true);
82
+ syncTabsPill(list, pill, true, pillMotion);
50
83
  };
51
84
 
52
85
  snap();
@@ -83,25 +116,32 @@ const TabsList = React.forwardRef<
83
116
  resizeObserver.disconnect();
84
117
  mutationObserver.disconnect();
85
118
  };
86
- }, [variant]);
119
+ }, [variant, pillMotion]);
87
120
 
88
121
  return (
89
122
  <TabsListVariantContext value={variant}>
90
123
  <TabsPrimitive.List
91
124
  ref={mergedRef}
92
125
  className={cn(
93
- variant === "soft"
94
- ? "flex flex-wrap items-center justify-center gap-1.5"
95
- : "t-tabs max-sm:max-w-full max-sm:justify-center-safe max-sm:overflow-x-auto max-sm:scrollbar-none",
96
- variant === "segmented" && "t-tabs--segmented",
97
- variant === "pills" && "t-tabs--pills",
126
+ config?.classes?.list ?? [
127
+ variant === "soft"
128
+ ? "flex flex-wrap items-center justify-center gap-1.5"
129
+ : "t-tabs max-sm:max-w-full max-sm:justify-center-safe max-sm:overflow-x-auto max-sm:scrollbar-none",
130
+ variant === "segmented" && "t-tabs--segmented",
131
+ variant === "pills" && "t-tabs--pills",
132
+ ],
133
+ config?.classes?.size?.[size ?? "default"] ?? tabsListSizeClasses[size ?? "default"],
98
134
  className,
99
135
  )}
100
136
  data-variant={variant}
101
137
  {...props}
102
138
  >
103
139
  {variant === "segmented" ? (
104
- <span ref={pillRef} className="t-tabs-pill" aria-hidden="true" />
140
+ <span
141
+ ref={pillRef}
142
+ className={config?.classes?.pill ?? "t-tabs-pill"}
143
+ aria-hidden="true"
144
+ />
105
145
  ) : null}
106
146
  {children}
107
147
  </TabsPrimitive.List>
@@ -113,39 +153,87 @@ TabsList.displayName = TabsPrimitive.List.displayName;
113
153
  const TabsTrigger = React.forwardRef<
114
154
  React.ComponentRef<typeof TabsPrimitive.Trigger>,
115
155
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
116
- >(({ className, ...props }, ref) => (
117
- <TabsPrimitive.Trigger
118
- ref={ref}
119
- className={cn(
120
- React.use(TabsListVariantContext) === "soft"
121
- ? "relative flex items-center gap-2 overflow-hidden rounded-full px-4 py-2 text-sm text-muted-foreground transition-[transform,background-color] hover:bg-surface-secondary active:scale-[0.97] data-[state=active]:bg-surface-secondary data-[state=active]:text-foreground"
122
- : "t-tab inline-flex items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
123
- className,
124
- )}
125
- {...props}
126
- />
127
- ));
156
+ >(({ className, ...props }, ref) => {
157
+ const trigger = useUiConfig("Tabs")?.classes?.trigger;
158
+ const cn = useCn();
159
+ const soft = React.use(TabsListVariantContext) === "soft";
160
+ return (
161
+ <TabsPrimitive.Trigger
162
+ ref={ref}
163
+ className={cn(
164
+ trigger ??
165
+ (soft
166
+ ? "relative flex items-center gap-2 overflow-hidden rounded-full px-4 py-2 text-sm text-muted-foreground transition-[transform,background-color] hover:bg-surface-secondary active:scale-[0.97] data-[state=active]:bg-surface-secondary data-[state=active]:text-foreground"
167
+ : "t-tab inline-flex items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"),
168
+ className,
169
+ )}
170
+ {...props}
171
+ />
172
+ );
173
+ });
128
174
  TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
129
175
 
130
176
  const TabsContent = React.forwardRef<
131
177
  React.ComponentRef<typeof TabsPrimitive.Content>,
132
178
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
133
- >(({ className, ...props }, ref) => (
134
- <TabsPrimitive.Content
135
- ref={ref}
136
- className={cn(
137
- "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2",
138
- className,
139
- )}
140
- {...props}
141
- />
142
- ));
179
+ >(({ className, ...props }, ref) => {
180
+ const content = useUiConfig("Tabs")?.classes?.content;
181
+ const cn = useCn();
182
+ return (
183
+ <TabsPrimitive.Content
184
+ ref={ref}
185
+ className={cn(
186
+ content ??
187
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2",
188
+ className,
189
+ )}
190
+ {...props}
191
+ />
192
+ );
193
+ });
143
194
  TabsContent.displayName = TabsPrimitive.Content.displayName;
144
195
 
196
+ const tabsBarVariants = cva(
197
+ "flex shrink-0 flex-wrap items-center gap-2 border-b border-border",
198
+ {
199
+ variants: {
200
+ size: {
201
+ default: "px-3 py-2",
202
+ sm: "px-2 py-1.5",
203
+ },
204
+ },
205
+ defaultVariants: {
206
+ size: "default",
207
+ },
208
+ },
209
+ );
210
+
211
+ /**
212
+ * Page- or panel-level tab strip: a `TabsList` on the hairline that divides it
213
+ * from the content below, with an optional trailing `actions` slot for toolbar
214
+ * controls. `size="sm"` for nested panels; `className` overrides the padding.
215
+ */
216
+ const TabsBar = React.forwardRef<
217
+ HTMLDivElement,
218
+ React.ComponentPropsWithoutRef<"div"> &
219
+ VariantProps<typeof tabsBarVariants> & { actions?: React.ReactNode }
220
+ >(({ className, children, actions, size, ...props }, ref) => {
221
+ const cn = useCn();
222
+ return (
223
+ <div ref={ref} className={cn(tabsBarVariants({ size }), className)} {...props}>
224
+ {children}
225
+ {actions ? (
226
+ <div className="ml-auto flex items-center gap-2">{actions}</div>
227
+ ) : null}
228
+ </div>
229
+ );
230
+ });
231
+ TabsBar.displayName = "TabsBar";
232
+
145
233
  // raw radix primitives, use these when you need a tabs layout that the
146
234
  // styled `tabs`/`tabslist`/`tabstrigger`/`tabscontent` defaults don't fit
147
235
  // (e.g a vertical sidebar of tabs inside a popover) the styled exports
148
236
  // above are tuned for horizontal pill, style tab bars
149
237
  export { TabsPrimitive };
150
238
 
151
- export { Tabs, TabsList, TabsTrigger, TabsContent };
239
+ export { Tabs, TabsBar, TabsList, TabsTrigger, TabsContent };
@@ -16,6 +16,30 @@ function findActiveTab(list: HTMLElement): HTMLElement | null {
16
16
  return tabs[0] ?? null;
17
17
  }
18
18
 
19
+ // `xy`: both axes on transform (top pinned to 0), so a column list slides the
20
+ // same way a row does; `top` is a layout property and does not tween.
21
+ function moveTabsPillXY(
22
+ pill: HTMLElement,
23
+ tab: HTMLElement,
24
+ animate: boolean,
25
+ ): void {
26
+ const transform = `translate(${tab.offsetLeft}px, ${tab.offsetTop}px)`;
27
+ const width = `${tab.offsetWidth}px`;
28
+ const height = `${tab.offsetHeight}px`;
29
+ const previousTransition = pill.style.transition;
30
+ if (!animate) {
31
+ pill.style.transition = "none";
32
+ }
33
+ pill.style.top = "0px";
34
+ pill.style.transform = transform;
35
+ pill.style.width = width;
36
+ pill.style.height = height;
37
+ if (!animate) {
38
+ void pill.offsetWidth;
39
+ pill.style.transition = previousTransition;
40
+ }
41
+ }
42
+
19
43
  function moveTabsPill(
20
44
  pill: HTMLElement,
21
45
  tab: HTMLElement,
@@ -44,13 +68,21 @@ function moveTabsPill(
44
68
  pill.style.height = `${height}px`;
45
69
  }
46
70
 
71
+ export type TabsPillMotion = "x" | "xy";
72
+
47
73
  export function syncTabsPill(
48
74
  list: HTMLElement,
49
75
  pill: HTMLElement,
50
76
  animate: boolean,
77
+ motion: TabsPillMotion = "x",
51
78
  ): void {
52
79
  const tab = findActiveTab(list);
53
- if (tab) {
80
+ if (!tab) {
81
+ return;
82
+ }
83
+ if (motion === "xy") {
84
+ moveTabsPillXY(pill, tab, animate);
85
+ } else {
54
86
  moveTabsPill(pill, tab, animate);
55
87
  }
56
88
  }
@@ -1,16 +1,25 @@
1
1
  import * as React from "react";
2
- import { cn } from "../utils/cn";
3
2
  import { fieldFocusClass } from "./_field-classes";
3
+ import { useCn, useUiConfig } from "./ui-config";
4
+
5
+ /** `UiConfig.Textarea`: `classes.base` replaces the built-in field classes. */
6
+ export interface TextareaConfig {
7
+ classes?: { base?: string };
8
+ }
4
9
 
5
10
  const Textarea = React.forwardRef<
6
11
  HTMLTextAreaElement,
7
12
  React.ComponentProps<"textarea">
8
13
  >(({ className, ...props }, ref) => {
14
+ const base = useUiConfig("Textarea")?.classes?.base;
15
+ const merge = useCn();
9
16
  return (
10
17
  <textarea
11
- className={cn(
12
- "flex min-h-20 w-full max-sm:min-w-0 rounded-field border border-border bg-field-background px-3.5 py-2.5 text-base sm:text-sm placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50",
13
- fieldFocusClass,
18
+ className={merge(
19
+ base ?? [
20
+ "flex min-h-20 w-full max-sm:min-w-0 rounded-field border border-border bg-field-background px-3.5 py-2.5 text-base sm:text-sm placeholder:text-field-placeholder disabled:cursor-not-allowed disabled:opacity-50",
21
+ fieldFocusClass,
22
+ ],
14
23
  className,
15
24
  )}
16
25
  ref={ref}
@@ -1,25 +1,61 @@
1
1
  import * as React from "react";
2
2
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
- import { cn } from "../utils/cn";
3
+ import { useCn, useUiConfig } from "./ui-config";
4
4
 
5
- const TooltipProvider = TooltipPrimitive.Provider;
5
+ /** App defaults for `Tooltip*` (see `UiConfigProvider`). */
6
+ export interface TooltipConfig {
7
+ /** Replaces the built-in classes of a part. */
8
+ classes?: { content?: string };
9
+ /** Default for `TooltipProvider` `delayDuration` (Radix: 700ms). */
10
+ delayDuration?: number;
11
+ /** Default for `TooltipContent` `sideOffset`. */
12
+ sideOffset?: number;
13
+ /** Default for `TooltipContent` `portal`. */
14
+ portal?: boolean;
15
+ }
16
+
17
+ function TooltipProvider({
18
+ delayDuration,
19
+ ...props
20
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
21
+ const config = useUiConfig("Tooltip");
22
+ return (
23
+ <TooltipPrimitive.Provider
24
+ delayDuration={delayDuration ?? config?.delayDuration}
25
+ {...props}
26
+ />
27
+ );
28
+ }
6
29
  const Tooltip = TooltipPrimitive.Root;
7
30
  const TooltipTrigger = TooltipPrimitive.Trigger;
8
31
 
9
32
  const TooltipContent = React.forwardRef<
10
33
  React.ComponentRef<typeof TooltipPrimitive.Content>,
11
- React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
12
- >(({ className, sideOffset = 4, ...props }, ref) => (
13
- <TooltipPrimitive.Content
14
- ref={ref}
15
- sideOffset={sideOffset}
16
- className={cn(
17
- "z-50 max-w-[calc(100vw-2rem)] overflow-hidden rounded-lg bg-overlay px-2.5 py-1.5 text-xs text-overlay-foreground smooth-shadow-ring-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
18
- className,
19
- )}
20
- {...props}
21
- />
22
- ));
34
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
35
+ /** Render into `document.body` so a clipping parent cannot cut it off. */
36
+ portal?: boolean;
37
+ }
38
+ >(({ className, sideOffset, portal, ...props }, ref) => {
39
+ const merge = useCn();
40
+ const config = useUiConfig("Tooltip");
41
+ const content = (
42
+ <TooltipPrimitive.Content
43
+ ref={ref}
44
+ sideOffset={sideOffset ?? config?.sideOffset ?? 4}
45
+ className={merge(
46
+ config?.classes?.content ??
47
+ "z-50 max-w-[calc(100vw-2rem)] overflow-hidden rounded-lg bg-overlay px-2.5 py-1.5 text-xs text-overlay-foreground smooth-shadow-ring-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ );
53
+ return (portal ?? config?.portal) ? (
54
+ <TooltipPrimitive.Portal>{content}</TooltipPrimitive.Portal>
55
+ ) : (
56
+ content
57
+ );
58
+ });
23
59
  TooltipContent.displayName = TooltipPrimitive.Content.displayName;
24
60
 
25
61
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -0,0 +1,149 @@
1
+ import * as React from "react";
2
+ import type { ClassValue } from "clsx";
3
+ import { cn } from "../utils/cn";
4
+ import type { AccordionConfig } from "./accordion";
5
+ import type { ActivityQuestionCardConfig } from "../ai-elements/activity-question";
6
+ import type { ActivityTasksConfig } from "../ai-elements/activity-shared";
7
+ import type { ArtifactConfig } from "../ai-elements/artifact";
8
+ import type { AttachmentsConfig } from "../ai-elements/attachments";
9
+ import type { BadgeConfig } from "./badge";
10
+ import type { ButtonConfig } from "./button";
11
+ import type { ButtonGroupConfig } from "./button-group";
12
+ import type { CalendarConfig } from "./calendar";
13
+ import type { CardConfig } from "./card";
14
+ import type { CarouselConfig } from "./carousel";
15
+ import type { CheckboxConfig } from "./checkbox";
16
+ import type { SpinnerConfig } from "./spinner";
17
+ import type { ClearInputConfig } from "./clear-input";
18
+ import type { CodeBlockConfig } from "../ai-elements/code-block";
19
+ import type { CollapsibleConfig } from "./collapsible";
20
+ import type { CommandConfig } from "./command";
21
+ import type { ContextMenuConfig } from "./context-menu";
22
+ import type { ConversationConfig } from "../ai-elements/conversation";
23
+ import type { DialogConfig } from "./dialog";
24
+ import type { DropdownMenuConfig } from "./dropdown-menu";
25
+ import type { GanttConfig } from "../kibo/gantt-provider";
26
+ import type { HoverCardConfig } from "./hover-card";
27
+ import type { InputConfig } from "./input";
28
+ import type { InputGroupConfig } from "./input-group";
29
+ import type { KanbanConfig } from "../kibo/kanban";
30
+ import type { LabelConfig } from "./label";
31
+ import type { ListRowConfig } from "./list-row";
32
+ import type { LoadingStateConfig } from "./loading-state";
33
+ import type { MessageConfig } from "../ai-elements/message";
34
+ import type { ModelPickerConfig } from "../ai-elements/model-picker";
35
+ import type { PopoverConfig } from "./popover";
36
+ import type { ProgressConfig } from "./progress";
37
+ import type { PromptInputConfig } from "../ai-elements/prompt-input";
38
+ import type { PromptInputSettingsConfig } from "../ai-elements/prompt-input-settings";
39
+ import type { QueueConfig } from "../ai-elements/queue";
40
+ import type { SearchInputConfig } from "./search-input";
41
+ import type { SelectConfig } from "./select";
42
+ import type { SeparatorConfig } from "./separator";
43
+ import type { ShimmerConfig } from "../ai-elements/shimmer";
44
+ import type { SliderConfig } from "./slider";
45
+ import type { SonnerToasterConfig } from "./sonner";
46
+ import type { StatusDotConfig } from "./status-dot";
47
+ import type { SwitchConfig } from "./switch";
48
+ import type { TableConfig } from "./table";
49
+ import type { TabsConfig } from "./tabs";
50
+ import type { TextareaConfig } from "./textarea";
51
+ import type { ThemeSwitcherConfig } from "../kibo/theme-switcher";
52
+ import type { TooltipConfig } from "./tooltip";
53
+ import type { TraitsPanelConfig } from "../ai-elements/prompt-input-traits";
54
+ import type { VideoPlayerConfig } from "../kibo/video-player";
55
+
56
+ // App-level component defaults. An app wraps its tree in `UiConfigProvider`
57
+ // to give components its own classes and behaviour without passing props at
58
+ // every call site. Without a provider (or with an empty entry) every component
59
+ // renders its built-in default, so apps that do not opt in see no change.
60
+ //
61
+ // Conventions for an entry:
62
+ // - `classes` replaces built-in classes part by part. A part left out keeps
63
+ // its default. A caller `className` still merges after it with `cn`.
64
+ // - Other keys are default prop values. An explicit prop always wins.
65
+ //
66
+ // Each component declares its entry type next to its code and adds it here.
67
+ export interface UiConfig {
68
+ /**
69
+ * Class merger used in place of the built-in `cn` by components that read
70
+ * it. Set it when the app's own tailwind-merge setup differs (for example
71
+ * when it does not treat `rounded-surface` as a radius class), so a caller
72
+ * `className` merges with the app's classes the way it always has.
73
+ */
74
+ cn?: (...inputs: ClassValue[]) => string;
75
+ Accordion?: AccordionConfig;
76
+ ActivityQuestionCard?: ActivityQuestionCardConfig;
77
+ ActivityTasks?: ActivityTasksConfig;
78
+ Artifact?: ArtifactConfig;
79
+ Attachments?: AttachmentsConfig;
80
+ Badge?: BadgeConfig;
81
+ Button?: ButtonConfig;
82
+ ButtonGroup?: ButtonGroupConfig;
83
+ Calendar?: CalendarConfig;
84
+ Card?: CardConfig;
85
+ Carousel?: CarouselConfig;
86
+ Checkbox?: CheckboxConfig;
87
+ ClearInput?: ClearInputConfig;
88
+ CodeBlock?: CodeBlockConfig;
89
+ Collapsible?: CollapsibleConfig;
90
+ Command?: CommandConfig;
91
+ ContextMenu?: ContextMenuConfig;
92
+ Conversation?: ConversationConfig;
93
+ Dialog?: DialogConfig;
94
+ DropdownMenu?: DropdownMenuConfig;
95
+ Gantt?: GanttConfig;
96
+ HoverCard?: HoverCardConfig;
97
+ Input?: InputConfig;
98
+ InputGroup?: InputGroupConfig;
99
+ Kanban?: KanbanConfig;
100
+ Label?: LabelConfig;
101
+ ListRow?: ListRowConfig;
102
+ LoadingState?: LoadingStateConfig;
103
+ Message?: MessageConfig;
104
+ ModelPicker?: ModelPickerConfig;
105
+ Popover?: PopoverConfig;
106
+ Progress?: ProgressConfig;
107
+ PromptInput?: PromptInputConfig;
108
+ PromptInputSettings?: PromptInputSettingsConfig;
109
+ Queue?: QueueConfig;
110
+ SearchInput?: SearchInputConfig;
111
+ Select?: SelectConfig;
112
+ Separator?: SeparatorConfig;
113
+ Shimmer?: ShimmerConfig;
114
+ Slider?: SliderConfig;
115
+ SonnerToaster?: SonnerToasterConfig;
116
+ Spinner?: SpinnerConfig;
117
+ StatusDot?: StatusDotConfig;
118
+ Switch?: SwitchConfig;
119
+ Table?: TableConfig;
120
+ Tabs?: TabsConfig;
121
+ Textarea?: TextareaConfig;
122
+ ThemeSwitcher?: ThemeSwitcherConfig;
123
+ Tooltip?: TooltipConfig;
124
+ TraitsPanel?: TraitsPanelConfig;
125
+ VideoPlayer?: VideoPlayerConfig;
126
+ }
127
+
128
+ const UiConfigContext = React.createContext<UiConfig>({});
129
+
130
+ export interface UiConfigProviderProps {
131
+ config: UiConfig;
132
+ children?: React.ReactNode;
133
+ }
134
+
135
+ function UiConfigProvider({ config, children }: UiConfigProviderProps) {
136
+ return <UiConfigContext.Provider value={config}>{children}</UiConfigContext.Provider>;
137
+ }
138
+
139
+ /** The app's entry for one component, or `undefined` when the app sets none. */
140
+ function useUiConfig<K extends keyof UiConfig>(name: K): UiConfig[K] | undefined {
141
+ return React.useContext(UiConfigContext)[name];
142
+ }
143
+
144
+ /** The app's class merger (`UiConfig.cn`), or the built-in `cn`. */
145
+ function useCn(): (...inputs: ClassValue[]) => string {
146
+ return React.useContext(UiConfigContext).cn ?? cn;
147
+ }
148
+
149
+ export { UiConfigProvider, useCn, useUiConfig };