@timbal-ai/timbal-react 1.4.0 → 1.5.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 (53) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +18 -4
  3. package/dist/app.cjs +3532 -1483
  4. package/dist/app.d.cts +75 -30
  5. package/dist/app.d.ts +75 -30
  6. package/dist/app.esm.js +29 -7
  7. package/dist/{chart-artifact-CMnDys2t.d.ts → chart-artifact-2OTDTRwM.d.ts} +194 -40
  8. package/dist/{chart-artifact-C8-Py6lc.d.cts → chart-artifact-CS3qyGIY.d.cts} +194 -40
  9. package/dist/chat.cjs +264 -107
  10. package/dist/chat.d.cts +2 -2
  11. package/dist/chat.d.ts +2 -2
  12. package/dist/chat.esm.js +4 -3
  13. package/dist/chunk-5ECRZ5O7.esm.js +899 -0
  14. package/dist/{chunk-QU7ET55D.esm.js → chunk-AZL2WANO.esm.js} +320 -177
  15. package/dist/{chunk-OH23AX2V.esm.js → chunk-B4XAC4G7.esm.js} +430 -780
  16. package/dist/chunk-EDEKQYSU.esm.js +10 -0
  17. package/dist/{chunk-GQBYZRD7.esm.js → chunk-IGHBLJV3.esm.js} +38 -27
  18. package/dist/{chunk-OFWC4MIY.esm.js → chunk-JYDJRGDE.esm.js} +5 -3
  19. package/dist/{chunk-VOWNCS3F.esm.js → chunk-SZDYIRMB.esm.js} +1567 -490
  20. package/dist/chunk-TZI3ID3C.esm.js +232 -0
  21. package/dist/{chunk-THBA27QY.esm.js → chunk-WMKPT5BV.esm.js} +242 -123
  22. package/dist/{chunk-VXMM2HX7.esm.js → chunk-ZNYAETFD.esm.js} +1 -1
  23. package/dist/{circular-progress-Ci8L-Hfa.d.cts → circular-progress-CDsJwIPF.d.cts} +19 -77
  24. package/dist/{circular-progress-Ci8L-Hfa.d.ts → circular-progress-CDsJwIPF.d.ts} +19 -77
  25. package/dist/index.cjs +5506 -3626
  26. package/dist/index.d.cts +7 -6
  27. package/dist/index.d.ts +7 -6
  28. package/dist/index.esm.js +45 -33
  29. package/dist/kanban-U5xNe9py.d.cts +212 -0
  30. package/dist/kanban-U5xNe9py.d.ts +212 -0
  31. package/dist/{layout-BTJyU8wd.d.ts → layout-B8r6Jbat.d.ts} +1 -1
  32. package/dist/{layout-C2G-FcER.d.cts → layout-Cu7Ijn04.d.cts} +1 -1
  33. package/dist/site.cjs +358 -0
  34. package/dist/site.d.cts +184 -0
  35. package/dist/site.d.ts +184 -0
  36. package/dist/site.esm.js +322 -0
  37. package/dist/studio.cjs +702 -343
  38. package/dist/studio.d.cts +1 -1
  39. package/dist/studio.d.ts +1 -1
  40. package/dist/studio.esm.js +7 -5
  41. package/dist/styles.css +56 -0
  42. package/dist/{timbal-v2-button-CNfdwGq4.d.cts → timbal-v2-button-B7vPs7gg.d.cts} +2 -2
  43. package/dist/{timbal-v2-button-CNfdwGq4.d.ts → timbal-v2-button-B7vPs7gg.d.ts} +2 -2
  44. package/dist/ui.cjs +1504 -659
  45. package/dist/ui.d.cts +11 -4
  46. package/dist/ui.d.ts +11 -4
  47. package/dist/ui.esm.js +35 -26
  48. package/dist/{welcome-DXqsGTwH.d.ts → welcome-DduQAC4K.d.ts} +4 -0
  49. package/dist/{welcome-BFGRoNfK.d.cts → welcome-NXZlcihe.d.cts} +4 -0
  50. package/package.json +9 -1
  51. package/dist/button-BoyX5pM_.d.cts +0 -18
  52. package/dist/button-BoyX5pM_.d.ts +0 -18
  53. package/dist/chunk-UCGVL7ZY.esm.js +0 -52
@@ -0,0 +1,899 @@
1
+ import {
2
+ overlayListPanelClass,
3
+ overlaySurfaceClass
4
+ } from "./chunk-AZL2WANO.esm.js";
5
+ import {
6
+ cn
7
+ } from "./chunk-EDEKQYSU.esm.js";
8
+
9
+ // src/ui/copy-button.tsx
10
+ import * as React from "react";
11
+ import { CheckIcon, CopyIcon } from "lucide-react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function CopyButton({
14
+ value,
15
+ timeout = 1500,
16
+ onCopied,
17
+ className,
18
+ children,
19
+ onClick,
20
+ ...props
21
+ }) {
22
+ const [copied, setCopied] = React.useState(false);
23
+ const timer = React.useRef(void 0);
24
+ React.useEffect(() => () => clearTimeout(timer.current), []);
25
+ const handleClick = async (event) => {
26
+ onClick?.(event);
27
+ if (event.defaultPrevented) return;
28
+ try {
29
+ await navigator.clipboard.writeText(value);
30
+ setCopied(true);
31
+ onCopied?.(value);
32
+ clearTimeout(timer.current);
33
+ timer.current = setTimeout(() => setCopied(false), timeout);
34
+ } catch {
35
+ }
36
+ };
37
+ const Icon = copied ? CheckIcon : CopyIcon;
38
+ return /* @__PURE__ */ jsxs(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ "data-slot": "copy-button",
43
+ "data-copied": copied || void 0,
44
+ "aria-label": copied ? "Copied" : "Copy",
45
+ onClick: handleClick,
46
+ className: cn(
47
+ "inline-flex items-center justify-center gap-1.5 rounded-md text-sm font-medium text-muted-foreground transition-colors",
48
+ "hover:bg-accent hover:text-foreground data-[copied=true]:text-emerald-600 dark:data-[copied=true]:text-emerald-400",
49
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15",
50
+ children ? "h-8 px-2" : "size-8",
51
+ className
52
+ ),
53
+ ...props,
54
+ children: [
55
+ /* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0", "aria-hidden": true }),
56
+ children
57
+ ]
58
+ }
59
+ );
60
+ }
61
+
62
+ // src/ui/checkbox.tsx
63
+ import { Checkbox as CheckboxPrimitive } from "radix-ui";
64
+ import { CheckIcon as CheckIcon2 } from "lucide-react";
65
+ import { jsx as jsx2 } from "react/jsx-runtime";
66
+ function Checkbox({
67
+ className,
68
+ ...props
69
+ }) {
70
+ return /* @__PURE__ */ jsx2(
71
+ CheckboxPrimitive.Root,
72
+ {
73
+ "data-slot": "checkbox",
74
+ className: cn(
75
+ "peer size-4 shrink-0 rounded-[4px] border border-border bg-gradient-to-b from-elevated-from to-elevated-to shadow-card outline-none transition-[box-shadow,background-color,border-color]",
76
+ "focus-visible:ring-2 focus-visible:ring-foreground/10 disabled:cursor-not-allowed disabled:opacity-50",
77
+ "data-[state=checked]:border-foreground/15 data-[state=checked]:from-primary-fill-from data-[state=checked]:to-primary-fill-to data-[state=checked]:text-primary-foreground",
78
+ className
79
+ ),
80
+ ...props,
81
+ children: /* @__PURE__ */ jsx2(
82
+ CheckboxPrimitive.Indicator,
83
+ {
84
+ "data-slot": "checkbox-indicator",
85
+ className: "flex items-center justify-center text-current animate-checkbox-pop",
86
+ children: /* @__PURE__ */ jsx2(CheckIcon2, { className: "size-2.5 stroke-[3.5px]" })
87
+ }
88
+ )
89
+ }
90
+ );
91
+ }
92
+
93
+ // src/ui/popover.tsx
94
+ import { Popover as PopoverPrimitive } from "radix-ui";
95
+ import { jsx as jsx3 } from "react/jsx-runtime";
96
+ function Popover({
97
+ ...props
98
+ }) {
99
+ return /* @__PURE__ */ jsx3(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
100
+ }
101
+ function PopoverTrigger({
102
+ ...props
103
+ }) {
104
+ return /* @__PURE__ */ jsx3(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
105
+ }
106
+ function PopoverAnchor({
107
+ ...props
108
+ }) {
109
+ return /* @__PURE__ */ jsx3(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
110
+ }
111
+ function PopoverContent({
112
+ className,
113
+ align = "center",
114
+ sideOffset = 4,
115
+ variant = "default",
116
+ ...props
117
+ }) {
118
+ return /* @__PURE__ */ jsx3(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx3(
119
+ PopoverPrimitive.Content,
120
+ {
121
+ "data-slot": "popover-content",
122
+ "data-variant": variant,
123
+ align,
124
+ sideOffset,
125
+ className: cn(
126
+ variant === "list" ? cn(
127
+ overlayListPanelClass,
128
+ "min-w-[8rem] origin-[var(--radix-popover-content-transform-origin)]"
129
+ ) : cn(
130
+ overlaySurfaceClass,
131
+ "w-72 origin-[var(--radix-popover-content-transform-origin)] rounded-xl p-4 outline-hidden"
132
+ ),
133
+ className
134
+ ),
135
+ ...props
136
+ }
137
+ ) });
138
+ }
139
+
140
+ // src/ui/skeleton.tsx
141
+ import { jsx as jsx4 } from "react/jsx-runtime";
142
+ function Skeleton({ className, ...props }) {
143
+ return /* @__PURE__ */ jsx4(
144
+ "div",
145
+ {
146
+ "data-slot": "skeleton",
147
+ className: cn("animate-pulse rounded-lg bg-muted", className),
148
+ ...props
149
+ }
150
+ );
151
+ }
152
+
153
+ // src/ui/untitled-button.tsx
154
+ import { cva } from "class-variance-authority";
155
+ import { Slot } from "radix-ui";
156
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
157
+ var SOLID_SKEUOMORPHIC_SHADOW = "shadow-skeuomorphic-solid";
158
+ var BORDERED_SKEUOMORPHIC_SHADOW = "shadow-skeuomorphic-bordered";
159
+ var untitledButtonVariants = cva(
160
+ cn(
161
+ "relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap font-medium",
162
+ "transition-all duration-300 ease-in-out outline-none border",
163
+ "focus-visible:ring-4 focus-visible:ring-primary/20 focus-visible:ring-offset-0",
164
+ "disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
165
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5",
166
+ // Overflow hidden can clip out-of-bounds shadow, so we use precise overflow management and rounded-[inherit] on pseudo overlays
167
+ "after:absolute after:inset-0 after:rounded-[inherit] after:pointer-events-none after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0"
168
+ ),
169
+ {
170
+ variants: {
171
+ color: {
172
+ primary: cn(
173
+ // Exact Premium Untitled UI primary BLACK / dark charcoal: gradient + borders
174
+ "bg-gradient-to-b from-[#344054] to-[#0F1117] text-white border-[#1A1E29]",
175
+ "hover:border-[#181C26]",
176
+ "active:border-[#0A0D14]",
177
+ SOLID_SKEUOMORPHIC_SHADOW,
178
+ // Hover/active overlays for beautiful animation (the gradient is static, the overlay opacity is transitioned)
179
+ "after:bg-white/[0.08] hover:after:opacity-100 active:after:bg-black/[0.12]",
180
+ // Premium Dark Mode inversion: Primary becomes white, popping out elegantly
181
+ "dark:bg-gradient-to-b dark:from-white dark:to-[#F9FAFB] dark:text-[#10121C] dark:border-white",
182
+ "dark:hover:border-[#D0D5DD] dark:hover:text-[#10121C]",
183
+ "dark:shadow-skeuomorphic-bordered",
184
+ "dark:after:bg-black/[0.04] dark:active:after:bg-black/[0.08]"
185
+ ),
186
+ secondary: cn(
187
+ // Exact Untitled UI secondary: premium white/gray gradient + borders
188
+ "bg-gradient-to-b from-white to-[#F9FAFB] text-[#344054] border-[#D0D5DD]",
189
+ "hover:text-[#1D2939] hover:border-[#D0D5DD]",
190
+ BORDERED_SKEUOMORPHIC_SHADOW,
191
+ // Hover/active overlays for white/gray gradient
192
+ "after:bg-black/[0.03] hover:after:opacity-100 active:after:bg-black/[0.08]",
193
+ // Premium Dark Mode inversion: Secondary becomes dark charcoal/gray, merging into the background
194
+ "dark:bg-gradient-to-b dark:from-[#1F242F] dark:to-[#10121C] dark:text-[#D1D5DB] dark:border-[#344054]",
195
+ "dark:hover:border-[#475467] dark:hover:text-white",
196
+ "dark:shadow-skeuomorphic-solid",
197
+ "dark:after:bg-white/[0.06] dark:active:after:bg-black/[0.15]"
198
+ ),
199
+ tertiary: cn(
200
+ "bg-transparent text-[#475467] border-transparent",
201
+ "hover:bg-[#F9FAFB] hover:text-[#344054]",
202
+ "active:bg-[#F2F4F7] active:text-[#1D2939]",
203
+ "after:hidden",
204
+ // No overlay needed for transparent surfaces
205
+ // Dark Mode
206
+ "dark:text-[#9CA3AF] dark:hover:bg-[#1F242F] dark:hover:text-white dark:active:bg-[#11131A]"
207
+ ),
208
+ link: cn(
209
+ "h-auto! bg-transparent p-0! border-transparent text-[#1F242F] hover:text-[#10121C]",
210
+ "hover:underline",
211
+ "after:hidden",
212
+ // Dark Mode
213
+ "dark:text-[#9CA3AF] dark:hover:text-white"
214
+ ),
215
+ "primary-destructive": cn(
216
+ // Exact Untitled UI primary destructive: premium red gradient + borders (vibrant and subtle, not too dark)
217
+ "bg-gradient-to-b from-[#D92D20] to-[#B42318] text-white border-[#B42318]",
218
+ "hover:border-[#9E1B12]",
219
+ "active:border-[#84140D]",
220
+ SOLID_SKEUOMORPHIC_SHADOW,
221
+ // Destructive red hover/active overlays
222
+ "after:bg-white/[0.12] hover:after:opacity-100 active:after:bg-black/[0.15]"
223
+ ),
224
+ "secondary-destructive": cn(
225
+ // Exact Untitled UI secondary destructive: soft red bordered
226
+ "bg-gradient-to-b from-white to-[#F9FAFB] text-[#B42318] border-[#FDA29B]",
227
+ "hover:text-[#9E1B12] hover:border-[#FDA29B]",
228
+ BORDERED_SKEUOMORPHIC_SHADOW,
229
+ // Hover overlay
230
+ "after:bg-red-500/[0.04] hover:after:opacity-100 active:after:bg-red-950/[0.08]",
231
+ // Dark Mode Secondary Destructive: Charcoal fill with red borders and label
232
+ "dark:bg-gradient-to-b dark:from-[#1F242F] dark:to-[#10121C] dark:text-[#F87171] dark:border-[#9E1B12]/50",
233
+ "dark:hover:border-[#F87171]/40",
234
+ "dark:shadow-skeuomorphic-solid",
235
+ "dark:after:bg-white/[0.06] dark:active:after:bg-black/[0.15]"
236
+ )
237
+ },
238
+ size: {
239
+ sm: "h-9 gap-1.5 rounded-lg px-3 text-sm",
240
+ md: "h-10 gap-1.5 rounded-lg px-3.5 text-sm",
241
+ lg: "h-11 gap-2 rounded-lg px-4 text-base",
242
+ xl: "h-12 gap-2 rounded-lg px-5 text-base"
243
+ }
244
+ },
245
+ defaultVariants: {
246
+ color: "primary",
247
+ size: "md"
248
+ }
249
+ }
250
+ );
251
+ function UntitledButton({
252
+ className,
253
+ color,
254
+ size,
255
+ iconLeading,
256
+ iconTrailing,
257
+ isLoading = false,
258
+ asChild = false,
259
+ disabled,
260
+ type = "button",
261
+ children,
262
+ ...props
263
+ }) {
264
+ const isDisabled = disabled || isLoading;
265
+ const classes = cn(untitledButtonVariants({ color, size }), className);
266
+ if (asChild) {
267
+ return /* @__PURE__ */ jsx5(
268
+ Slot.Root,
269
+ {
270
+ className: classes,
271
+ "aria-disabled": isDisabled ? true : void 0,
272
+ "data-slot": "untitled-button",
273
+ ...props,
274
+ children
275
+ }
276
+ );
277
+ }
278
+ return /* @__PURE__ */ jsxs2(
279
+ "button",
280
+ {
281
+ type,
282
+ disabled: isDisabled,
283
+ "data-slot": "untitled-button",
284
+ className: classes,
285
+ ...props,
286
+ children: [
287
+ isLoading ? /* @__PURE__ */ jsx5(
288
+ "span",
289
+ {
290
+ "aria-hidden": true,
291
+ className: "size-4 animate-spin rounded-full border-2 border-current border-t-transparent"
292
+ }
293
+ ) : iconLeading,
294
+ children,
295
+ !isLoading ? iconTrailing : null
296
+ ]
297
+ }
298
+ );
299
+ }
300
+
301
+ // src/ui/banner.tsx
302
+ import { XIcon } from "lucide-react";
303
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
304
+ var bannerSoftClass = {
305
+ default: "border-border/50 bg-muted/30 text-foreground/90 dark:bg-muted/15",
306
+ primary: "border-primary/15 bg-primary/5 text-primary-800 dark:text-primary-200 [&_[data-banner-icon]]:text-primary",
307
+ success: "border-emerald-500/15 bg-emerald-500/5 text-emerald-800 dark:text-emerald-300 [&_[data-banner-icon]]:text-emerald-600 dark:[&_[data-banner-icon]]:text-emerald-400",
308
+ warn: "border-amber-500/15 bg-amber-500/5 text-amber-800 dark:text-amber-300 [&_[data-banner-icon]]:text-amber-600 dark:[&_[data-banner-icon]]:text-amber-400",
309
+ danger: "border-destructive/15 bg-destructive/5 text-destructive dark:text-red-300 [&_[data-banner-icon]]:text-destructive"
310
+ };
311
+ var bannerSolidClass = {
312
+ default: "border-transparent bg-foreground text-background shadow-sm",
313
+ primary: "border-transparent bg-gradient-to-r from-primary to-primary/95 text-primary-foreground shadow-sm shadow-primary/5",
314
+ success: "border-transparent bg-gradient-to-r from-emerald-600 to-emerald-500 text-white shadow-sm shadow-emerald-500/5 dark:from-emerald-500 dark:to-emerald-400",
315
+ warn: "border-transparent bg-gradient-to-r from-amber-500 to-amber-400 text-white shadow-sm shadow-amber-500/5 dark:from-amber-500 dark:to-amber-400",
316
+ danger: "border-transparent bg-gradient-to-r from-destructive to-destructive/95 text-destructive-foreground shadow-sm shadow-destructive/5"
317
+ };
318
+ var bannerOutlineClass = {
319
+ default: "border-border/80 bg-background/50 text-foreground/90 backdrop-blur-sm shadow-sm",
320
+ primary: "border-primary/30 bg-primary/[0.02] text-foreground [&_[data-banner-icon]]:text-primary shadow-sm",
321
+ success: "border-emerald-500/30 bg-emerald-500/[0.02] text-foreground [&_[data-banner-icon]]:text-emerald-600 dark:[&_[data-banner-icon]]:text-emerald-400 shadow-sm",
322
+ warn: "border-amber-500/30 bg-amber-500/[0.02] text-foreground [&_[data-banner-icon]]:text-amber-600 dark:[&_[data-banner-icon]]:text-amber-400 shadow-sm",
323
+ danger: "border-destructive/30 bg-destructive/[0.02] text-foreground [&_[data-banner-icon]]:text-destructive shadow-sm"
324
+ };
325
+ var bannerVariantClass = {
326
+ soft: bannerSoftClass,
327
+ solid: bannerSolidClass,
328
+ outline: bannerOutlineClass
329
+ };
330
+ var bannerSizeClass = {
331
+ sm: "gap-2.5 px-3.5 py-2 text-xs",
332
+ default: "gap-3 px-4 py-3 text-sm"
333
+ };
334
+ function Banner({
335
+ tone = "default",
336
+ variant = "soft",
337
+ size = "default",
338
+ icon,
339
+ title,
340
+ actions,
341
+ onDismiss,
342
+ className,
343
+ children,
344
+ ...props
345
+ }) {
346
+ const isSolid = variant === "solid";
347
+ const isSingleLine = !title;
348
+ return /* @__PURE__ */ jsxs3(
349
+ "div",
350
+ {
351
+ "data-slot": "banner",
352
+ "data-variant": variant,
353
+ role: "status",
354
+ className: cn(
355
+ "flex w-full rounded-xl border transition-all duration-200",
356
+ isSingleLine ? "items-center" : "items-start",
357
+ bannerSizeClass[size],
358
+ bannerVariantClass[variant][tone],
359
+ className
360
+ ),
361
+ ...props,
362
+ children: [
363
+ icon ? /* @__PURE__ */ jsx6(
364
+ "span",
365
+ {
366
+ "data-banner-icon": true,
367
+ className: cn(
368
+ "shrink-0 [&_svg]:size-4",
369
+ isSingleLine ? "self-center" : "mt-0.5"
370
+ ),
371
+ children: icon
372
+ }
373
+ ) : null,
374
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
375
+ title ? /* @__PURE__ */ jsx6("p", { className: "font-medium tracking-tight", children: title }) : null,
376
+ children ? /* @__PURE__ */ jsx6(
377
+ "div",
378
+ {
379
+ className: cn(
380
+ isSolid ? "opacity-90" : "text-muted-foreground",
381
+ title && "mt-0.5"
382
+ ),
383
+ children
384
+ }
385
+ ) : null
386
+ ] }),
387
+ actions ? /* @__PURE__ */ jsx6("div", { className: "flex shrink-0 items-center gap-2", children: actions }) : null,
388
+ onDismiss ? /* @__PURE__ */ jsx6(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ "aria-label": "Dismiss",
393
+ onClick: onDismiss,
394
+ className: cn(
395
+ "-mr-1 inline-flex size-7 shrink-0 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15",
396
+ isSingleLine ? "self-center" : "-mt-0.5",
397
+ isSolid ? "opacity-80 hover:bg-background/15 hover:opacity-100" : "text-muted-foreground hover:bg-foreground/10 hover:text-foreground"
398
+ ),
399
+ children: /* @__PURE__ */ jsx6(XIcon, { className: "size-4", "aria-hidden": true })
400
+ }
401
+ ) : null
402
+ ]
403
+ }
404
+ );
405
+ }
406
+
407
+ // src/ui/timeline.tsx
408
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
409
+ var timelineRowGap = {
410
+ sm: "pb-4",
411
+ default: "pb-6"
412
+ };
413
+ var toneStyles = {
414
+ default: {
415
+ border: "border-neutral-300 dark:border-neutral-700",
416
+ dot: "bg-neutral-400 dark:bg-neutral-500",
417
+ icon: "text-neutral-500 dark:text-neutral-400"
418
+ },
419
+ primary: {
420
+ border: "border-primary/50 dark:border-primary/40",
421
+ dot: "bg-primary",
422
+ icon: "text-primary"
423
+ },
424
+ success: {
425
+ border: "border-emerald-500/40 dark:border-emerald-500/30",
426
+ dot: "bg-emerald-500",
427
+ icon: "text-emerald-500"
428
+ },
429
+ warn: {
430
+ border: "border-amber-500/40 dark:border-amber-500/30",
431
+ dot: "bg-amber-500",
432
+ icon: "text-amber-500"
433
+ },
434
+ danger: {
435
+ border: "border-destructive/40 dark:border-destructive/30",
436
+ dot: "bg-destructive",
437
+ icon: "text-destructive"
438
+ }
439
+ };
440
+ function Timeline({ items, size = "default", className, ...props }) {
441
+ return /* @__PURE__ */ jsx7("ol", { "data-slot": "timeline", className: cn("flex flex-col", className), ...props, children: items.map((item, index) => {
442
+ const last = index === items.length - 1;
443
+ const tone = item.tone ?? "default";
444
+ const styles = toneStyles[tone];
445
+ return /* @__PURE__ */ jsxs4(
446
+ "li",
447
+ {
448
+ className: cn("relative flex gap-3.5 last:pb-0", timelineRowGap[size]),
449
+ children: [
450
+ !last ? /* @__PURE__ */ jsx7(
451
+ "span",
452
+ {
453
+ "aria-hidden": true,
454
+ className: cn(
455
+ "absolute left-2.5 bottom-0 w-[1.5px] -translate-x-1/2 bg-neutral-200 dark:bg-neutral-800",
456
+ item.icon ? "top-6" : "top-3"
457
+ )
458
+ }
459
+ ) : null,
460
+ /* @__PURE__ */ jsx7("div", { className: "relative flex w-5 shrink-0 justify-center pt-0.5", children: /* @__PURE__ */ jsx7(
461
+ "span",
462
+ {
463
+ className: cn(
464
+ "relative z-10 flex shrink-0 items-center justify-center rounded-full border-[1.5px] bg-background transition-colors",
465
+ styles.border,
466
+ item.icon ? "size-6" : "size-4"
467
+ ),
468
+ children: item.icon ? /* @__PURE__ */ jsx7("div", { className: cn("text-xs", styles.icon), children: item.icon }) : /* @__PURE__ */ jsx7(
469
+ "span",
470
+ {
471
+ className: cn(
472
+ "size-1.5 rounded-full",
473
+ styles.dot
474
+ )
475
+ }
476
+ )
477
+ }
478
+ ) }),
479
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1 pb-0.5", children: [
480
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-2", children: [
481
+ /* @__PURE__ */ jsx7("p", { className: "text-sm font-normal text-foreground", children: item.title }),
482
+ item.meta ? /* @__PURE__ */ jsx7("span", { className: "shrink-0 text-xs text-muted-foreground tabular-nums", children: item.meta }) : null
483
+ ] }),
484
+ item.description ? /* @__PURE__ */ jsx7("p", { className: "mt-0.5 text-[13px] leading-relaxed text-muted-foreground", children: item.description }) : null
485
+ ] })
486
+ ]
487
+ },
488
+ item.id
489
+ );
490
+ }) });
491
+ }
492
+
493
+ // src/ui/kanban.tsx
494
+ import * as React2 from "react";
495
+ import {
496
+ DndContext,
497
+ DragOverlay,
498
+ KeyboardSensor,
499
+ PointerSensor,
500
+ closestCorners,
501
+ useDroppable,
502
+ useSensor,
503
+ useSensors
504
+ } from "@dnd-kit/core";
505
+ import {
506
+ SortableContext,
507
+ arrayMove,
508
+ sortableKeyboardCoordinates,
509
+ useSortable,
510
+ verticalListSortingStrategy
511
+ } from "@dnd-kit/sortable";
512
+ import { CSS } from "@dnd-kit/utilities";
513
+ import { GripVerticalIcon } from "lucide-react";
514
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
515
+ var columnTitleToneClass = {
516
+ default: "text-foreground",
517
+ primary: "text-blue-600 dark:text-blue-400",
518
+ success: "text-emerald-600 dark:text-emerald-400",
519
+ warn: "text-amber-600 dark:text-amber-400",
520
+ danger: "text-rose-600 dark:text-rose-400"
521
+ };
522
+ var cardVariantClass = {
523
+ default: "border border-border/70 bg-card shadow-sm hover:border-border hover:shadow-md",
524
+ outline: "border border-border bg-card hover:border-foreground/25",
525
+ muted: "border border-transparent bg-muted/60 hover:bg-muted"
526
+ };
527
+ var cardToneClass = {
528
+ default: "bg-card shadow-sm hover:shadow-md",
529
+ primary: "bg-card shadow-sm hover:shadow-md",
530
+ success: "bg-card shadow-sm hover:shadow-md",
531
+ warn: "bg-card shadow-sm hover:shadow-md",
532
+ danger: "bg-card shadow-sm hover:shadow-md"
533
+ };
534
+ function cardSurfaceClass(variant, tone) {
535
+ return variant === "tonal" ? cardToneClass[tone] : cardVariantClass[variant];
536
+ }
537
+ var densityColumnClass = {
538
+ default: "w-72 gap-2.5",
539
+ compact: "w-64 gap-2"
540
+ };
541
+ var densityListClass = {
542
+ default: "gap-2.5",
543
+ compact: "gap-2"
544
+ };
545
+ var densityCardClass = {
546
+ default: "rounded-lg p-2",
547
+ compact: "rounded-lg p-1.5"
548
+ };
549
+ function defaultGetCardId(card) {
550
+ return card.id;
551
+ }
552
+ function SortableCard({
553
+ card,
554
+ cardId,
555
+ column,
556
+ density,
557
+ variant,
558
+ disabled,
559
+ dragHandle,
560
+ className,
561
+ renderCard
562
+ }) {
563
+ const {
564
+ attributes,
565
+ listeners,
566
+ setNodeRef,
567
+ transform,
568
+ transition,
569
+ isDragging
570
+ } = useSortable({ id: cardId, disabled });
571
+ const style = {
572
+ transform: CSS.Translate.toString(transform),
573
+ transition
574
+ };
575
+ const dragHandleProps = disabled ? void 0 : { ...attributes, ...listeners };
576
+ return /* @__PURE__ */ jsxs5(
577
+ "div",
578
+ {
579
+ ref: setNodeRef,
580
+ style,
581
+ "data-slot": "kanban-card",
582
+ "data-dragging": isDragging ? "" : void 0,
583
+ className: cn(
584
+ "group/kanban-card relative text-sm text-foreground transition",
585
+ densityCardClass[density],
586
+ cardSurfaceClass(variant, column.tone ?? "default"),
587
+ isDragging && "opacity-40",
588
+ className
589
+ ),
590
+ children: [
591
+ !disabled && dragHandle === "auto" ? /* @__PURE__ */ jsx8(
592
+ "button",
593
+ {
594
+ type: "button",
595
+ "aria-label": "Drag card",
596
+ className: "absolute right-1.5 top-1.5 z-10 grid size-6 cursor-grab touch-none place-items-center rounded-md text-muted-foreground/40 opacity-0 transition hover:bg-foreground/5 hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 group-hover/kanban-card:opacity-100 active:cursor-grabbing",
597
+ ...attributes,
598
+ ...listeners,
599
+ children: /* @__PURE__ */ jsx8(GripVerticalIcon, { className: "size-4", "aria-hidden": true })
600
+ }
601
+ ) : null,
602
+ renderCard(card, { column, isDragging, isOverlay: false, dragHandleProps })
603
+ ]
604
+ }
605
+ );
606
+ }
607
+ function KanbanColumnView({
608
+ column,
609
+ cardIds,
610
+ density,
611
+ cardVariant,
612
+ disabled,
613
+ dragHandle,
614
+ emptyColumnLabel,
615
+ className,
616
+ cardClassName,
617
+ getCardId,
618
+ renderColumnHeader,
619
+ renderCard
620
+ }) {
621
+ const tone = column.tone ?? "default";
622
+ const { setNodeRef, isOver } = useDroppable({ id: column.id, disabled });
623
+ return /* @__PURE__ */ jsxs5(
624
+ "div",
625
+ {
626
+ "data-slot": "kanban-column",
627
+ className: cn(
628
+ "flex shrink-0 flex-col",
629
+ densityColumnClass[density],
630
+ className
631
+ ),
632
+ children: [
633
+ renderColumnHeader ? renderColumnHeader(column) : /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-0.5 px-1 pb-0.5", children: [
634
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
635
+ /* @__PURE__ */ jsx8(
636
+ "h3",
637
+ {
638
+ className: cn(
639
+ "min-w-0 flex-1 truncate text-xs font-medium",
640
+ columnTitleToneClass[tone]
641
+ ),
642
+ children: column.title
643
+ }
644
+ ),
645
+ /* @__PURE__ */ jsx8("span", { className: "shrink-0 text-xs font-normal tabular-nums text-muted-foreground/60", children: column.cards.length }),
646
+ column.action ? /* @__PURE__ */ jsx8("div", { className: "shrink-0", children: column.action }) : null
647
+ ] }),
648
+ column.description ? /* @__PURE__ */ jsx8("p", { className: "truncate text-xs text-muted-foreground", children: column.description }) : null
649
+ ] }),
650
+ /* @__PURE__ */ jsx8(SortableContext, { items: cardIds, strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx8(
651
+ "div",
652
+ {
653
+ ref: setNodeRef,
654
+ "data-slot": "kanban-column-body",
655
+ className: cn(
656
+ "flex min-h-16 flex-1 flex-col rounded-xl transition-colors",
657
+ densityListClass[density],
658
+ isOver && "bg-muted/50"
659
+ ),
660
+ children: column.cards.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "flex flex-1 items-center justify-center rounded-xl border border-dashed border-border/60 px-2 py-8 text-center text-xs text-muted-foreground/70", children: emptyColumnLabel ?? "Drop here" }) : column.cards.map((card) => {
661
+ const id = getCardId(card);
662
+ return /* @__PURE__ */ jsx8(
663
+ SortableCard,
664
+ {
665
+ card,
666
+ cardId: id,
667
+ column,
668
+ density,
669
+ variant: cardVariant,
670
+ disabled,
671
+ dragHandle,
672
+ className: cardClassName,
673
+ renderCard
674
+ },
675
+ id
676
+ );
677
+ })
678
+ }
679
+ ) }),
680
+ column.footer ? /* @__PURE__ */ jsx8("div", { className: "px-0.5 pt-0.5", children: column.footer }) : null
681
+ ]
682
+ }
683
+ );
684
+ }
685
+ function cloneColumns(columns) {
686
+ return columns.map((col) => ({ ...col, cards: [...col.cards] }));
687
+ }
688
+ function locateCard(columns, getCardId, cardId) {
689
+ for (const col of columns) {
690
+ const index = col.cards.findIndex((c) => getCardId(c) === cardId);
691
+ if (index !== -1) return { columnId: col.id, index };
692
+ }
693
+ return null;
694
+ }
695
+ function Kanban({
696
+ columns: columnsProp,
697
+ defaultColumns,
698
+ onColumnsChange,
699
+ onMove,
700
+ renderCard,
701
+ renderColumnHeader,
702
+ getCardId = defaultGetCardId,
703
+ emptyColumnLabel,
704
+ density = "default",
705
+ cardVariant = "default",
706
+ dragHandle = "auto",
707
+ disabled = false,
708
+ className,
709
+ columnClassName,
710
+ cardClassName,
711
+ ...rest
712
+ }) {
713
+ const ariaLabel = rest["aria-label"] ?? "Kanban board";
714
+ const isControlled = columnsProp !== void 0;
715
+ const [internal, setInternal] = React2.useState(
716
+ () => cloneColumns(defaultColumns ?? columnsProp ?? [])
717
+ );
718
+ const [activeId, setActiveId] = React2.useState(null);
719
+ const dragOriginRef = React2.useRef(null);
720
+ React2.useEffect(() => {
721
+ if (isControlled && activeId === null) {
722
+ setInternal(cloneColumns(columnsProp));
723
+ }
724
+ }, [columnsProp, isControlled, activeId]);
725
+ const columns = internal;
726
+ const columnIds = React2.useMemo(
727
+ () => new Set(columns.map((c) => c.id)),
728
+ [columns]
729
+ );
730
+ const sensors = useSensors(
731
+ useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
732
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
733
+ );
734
+ const activeCard = React2.useMemo(() => {
735
+ if (!activeId) return null;
736
+ for (const col of columns) {
737
+ const card = col.cards.find((c) => getCardId(c) === activeId);
738
+ if (card) return { card, column: col };
739
+ }
740
+ return null;
741
+ }, [activeId, columns, getCardId]);
742
+ const resolveTargetColumnId = (overId) => {
743
+ if (columnIds.has(overId)) return overId;
744
+ const loc = locateCard(columns, getCardId, overId);
745
+ return loc?.columnId;
746
+ };
747
+ const handleDragStart = (event) => {
748
+ const id = String(event.active.id);
749
+ setActiveId(id);
750
+ dragOriginRef.current = locateCard(columns, getCardId, id);
751
+ };
752
+ const handleDragOver = (event) => {
753
+ const { active, over } = event;
754
+ if (!over) return;
755
+ const activeCardId = String(active.id);
756
+ const overId = String(over.id);
757
+ const from = locateCard(columns, getCardId, activeCardId);
758
+ const toColumnId = resolveTargetColumnId(overId);
759
+ if (!from || !toColumnId || from.columnId === toColumnId) return;
760
+ setInternal((prev) => {
761
+ const next = cloneColumns(prev);
762
+ const fromCol = next.find((c) => c.id === from.columnId);
763
+ const toCol = next.find((c) => c.id === toColumnId);
764
+ if (!fromCol || !toCol) return prev;
765
+ const movingIndex = fromCol.cards.findIndex(
766
+ (c) => getCardId(c) === activeCardId
767
+ );
768
+ if (movingIndex === -1) return prev;
769
+ const [moving] = fromCol.cards.splice(movingIndex, 1);
770
+ const overIsCard = !columnIds.has(overId);
771
+ const overIndex = overIsCard ? toCol.cards.findIndex((c) => getCardId(c) === overId) : toCol.cards.length;
772
+ const insertAt = overIndex === -1 ? toCol.cards.length : overIndex;
773
+ toCol.cards.splice(insertAt, 0, moving);
774
+ return next;
775
+ });
776
+ };
777
+ const finishDrag = (event) => {
778
+ const { active, over } = event;
779
+ const origin = dragOriginRef.current;
780
+ const movedCard = activeCard?.card;
781
+ dragOriginRef.current = null;
782
+ setActiveId(null);
783
+ if (!over || !origin) return;
784
+ const activeCardId = String(active.id);
785
+ const overId = String(over.id);
786
+ let next = columns;
787
+ const current = locateCard(columns, getCardId, activeCardId);
788
+ const toColumnId = resolveTargetColumnId(overId) ?? current?.columnId;
789
+ if (current && toColumnId && current.columnId === toColumnId) {
790
+ const col = columns.find((c) => c.id === toColumnId);
791
+ if (col) {
792
+ const oldIndex = col.cards.findIndex((c) => getCardId(c) === activeCardId);
793
+ const overIsCard = !columnIds.has(overId);
794
+ const overIndex = overIsCard ? col.cards.findIndex((c) => getCardId(c) === overId) : col.cards.length - 1;
795
+ const newIndex = overIndex === -1 ? col.cards.length - 1 : overIndex;
796
+ if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) {
797
+ next = columns.map(
798
+ (c) => c.id === toColumnId ? { ...c, cards: arrayMove(c.cards, oldIndex, newIndex) } : c
799
+ );
800
+ }
801
+ }
802
+ }
803
+ if (next !== columns) setInternal(next);
804
+ const finalLoc = locateCard(next, getCardId, activeCardId);
805
+ const moved = finalLoc && (finalLoc.columnId !== origin.columnId || finalLoc.index !== origin.index);
806
+ if (moved) {
807
+ if (movedCard) {
808
+ onMove?.({
809
+ card: movedCard,
810
+ cardId: activeCardId,
811
+ from: origin,
812
+ to: finalLoc,
813
+ columns: next
814
+ });
815
+ }
816
+ onColumnsChange?.(next);
817
+ }
818
+ };
819
+ return /* @__PURE__ */ jsxs5(
820
+ DndContext,
821
+ {
822
+ sensors,
823
+ collisionDetection: closestCorners,
824
+ onDragStart: handleDragStart,
825
+ onDragOver: handleDragOver,
826
+ onDragEnd: finishDrag,
827
+ onDragCancel: () => {
828
+ dragOriginRef.current = null;
829
+ setActiveId(null);
830
+ if (isControlled) setInternal(cloneColumns(columnsProp));
831
+ },
832
+ children: [
833
+ /* @__PURE__ */ jsx8(
834
+ "div",
835
+ {
836
+ "data-slot": "kanban",
837
+ role: "list",
838
+ "aria-label": ariaLabel,
839
+ className: cn(
840
+ "flex w-full items-start overflow-x-auto p-1.5 pb-6 -m-1.5",
841
+ density === "compact" ? "gap-3" : "gap-4",
842
+ className
843
+ ),
844
+ children: columns.map((column) => /* @__PURE__ */ jsx8(
845
+ KanbanColumnView,
846
+ {
847
+ column,
848
+ cardIds: column.cards.map(getCardId),
849
+ density,
850
+ cardVariant,
851
+ disabled,
852
+ dragHandle,
853
+ emptyColumnLabel,
854
+ className: columnClassName,
855
+ cardClassName,
856
+ getCardId,
857
+ renderColumnHeader,
858
+ renderCard
859
+ },
860
+ column.id
861
+ ))
862
+ }
863
+ ),
864
+ /* @__PURE__ */ jsx8(DragOverlay, { children: activeCard ? /* @__PURE__ */ jsx8(
865
+ "div",
866
+ {
867
+ "data-slot": "kanban-card-overlay",
868
+ className: cn(
869
+ "text-sm text-foreground shadow-xl ring-1 ring-black/5",
870
+ densityCardClass[density],
871
+ cardSurfaceClass(cardVariant, activeCard.column.tone ?? "default"),
872
+ "rotate-2 cursor-grabbing"
873
+ ),
874
+ children: renderCard(activeCard.card, {
875
+ column: activeCard.column,
876
+ isDragging: true,
877
+ isOverlay: true
878
+ })
879
+ }
880
+ ) : null })
881
+ ]
882
+ }
883
+ );
884
+ }
885
+
886
+ export {
887
+ CopyButton,
888
+ Checkbox,
889
+ Popover,
890
+ PopoverTrigger,
891
+ PopoverAnchor,
892
+ PopoverContent,
893
+ Skeleton,
894
+ untitledButtonVariants,
895
+ UntitledButton,
896
+ Banner,
897
+ Timeline,
898
+ Kanban
899
+ };