@timbal-ai/timbal-react 1.2.0 → 1.4.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.
@@ -9,14 +9,13 @@ import {
9
9
  TIMBAL_V2_MODAL_SURFACE,
10
10
  TIMBAL_V2_SWITCH_THUMB,
11
11
  TIMBAL_V2_SWITCH_TRACK_OFF,
12
- buttonVariants,
13
12
  cn,
14
13
  controlClass,
15
14
  controlSurfaceClass,
16
15
  overlayItemClass,
17
16
  overlayListPanelClass,
18
17
  overlaySurfaceClass
19
- } from "./chunk-BMXFXLVV.esm.js";
18
+ } from "./chunk-QU7ET55D.esm.js";
20
19
 
21
20
  // src/ui/dropdown-menu.tsx
22
21
  import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
@@ -1451,8 +1450,11 @@ function CommandShortcut({
1451
1450
 
1452
1451
  // src/ui/calendar.tsx
1453
1452
  import * as React from "react";
1454
- import { ChevronLeftIcon as ChevronLeftIcon2, ChevronRightIcon as ChevronRightIcon5 } from "lucide-react";
1455
- import { DayPicker } from "react-day-picker";
1453
+ import { ChevronDownIcon as ChevronDownIcon3, ChevronLeftIcon as ChevronLeftIcon2, ChevronRightIcon as ChevronRightIcon5 } from "lucide-react";
1454
+ import {
1455
+ DayPicker,
1456
+ getDefaultClassNames
1457
+ } from "react-day-picker";
1456
1458
  import { jsx as jsx17 } from "react/jsx-runtime";
1457
1459
  function Calendar({
1458
1460
  className,
@@ -1460,56 +1462,71 @@ function Calendar({
1460
1462
  showOutsideDays = true,
1461
1463
  ...props
1462
1464
  }) {
1465
+ const defaults = getDefaultClassNames();
1463
1466
  return /* @__PURE__ */ jsx17(
1464
1467
  DayPicker,
1465
1468
  {
1466
1469
  showOutsideDays,
1467
- className: cn("p-3", className),
1470
+ className: cn("p-4", className),
1468
1471
  classNames: {
1469
- months: "flex flex-col gap-4 sm:flex-row sm:gap-4",
1470
- month: "flex flex-col gap-4",
1471
- month_caption: "relative flex items-center justify-center pt-1",
1472
- caption_label: "text-sm font-medium",
1473
- nav: "flex items-center gap-1",
1474
- button_previous: cn(
1475
- buttonVariants({ variant: "outline" }),
1476
- "absolute left-1 size-7 bg-transparent p-0 opacity-70 hover:opacity-100"
1472
+ root: cn("w-fit", defaults.root),
1473
+ months: cn("relative flex flex-col gap-4 sm:flex-row", defaults.months),
1474
+ month: cn("flex w-full flex-col gap-3", defaults.month),
1475
+ nav: cn(
1476
+ "absolute inset-x-0 top-0 flex items-center justify-between",
1477
+ defaults.nav
1478
+ ),
1479
+ button_previous: cn(navButtonClass, defaults.button_previous),
1480
+ button_next: cn(navButtonClass, defaults.button_next),
1481
+ month_caption: cn(
1482
+ "flex h-10 items-center justify-center",
1483
+ defaults.month_caption
1484
+ ),
1485
+ caption_label: cn("text-sm font-semibold", defaults.caption_label),
1486
+ dropdowns: cn(
1487
+ "flex h-10 items-center justify-center gap-1.5 text-sm font-semibold",
1488
+ defaults.dropdowns
1489
+ ),
1490
+ dropdown_root: cn(
1491
+ "relative rounded-md border border-border focus-within:ring-2 focus-within:ring-foreground/10",
1492
+ defaults.dropdown_root
1477
1493
  ),
1478
- button_next: cn(
1479
- buttonVariants({ variant: "outline" }),
1480
- "absolute right-1 size-7 bg-transparent p-0 opacity-70 hover:opacity-100"
1494
+ dropdown: cn("absolute inset-0 bg-popover opacity-0", defaults.dropdown),
1495
+ month_grid: cn("border-separate border-spacing-y-1", defaults.month_grid),
1496
+ weekdays: cn(defaults.weekdays),
1497
+ weekday: cn(
1498
+ "size-10 pb-2 text-xs font-medium text-muted-foreground",
1499
+ defaults.weekday
1500
+ ),
1501
+ week: cn(defaults.week),
1502
+ week_number_header: cn("size-10", defaults.week_number_header),
1503
+ week_number: cn(
1504
+ "text-xs text-muted-foreground",
1505
+ defaults.week_number
1481
1506
  ),
1482
- month_grid: "w-full border-collapse",
1483
- weekdays: "flex",
1484
- weekday: "w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground",
1485
- week: "mt-2 flex w-full",
1486
1507
  day: cn(
1487
- "relative size-9 p-0 text-center text-sm",
1488
- "[&:has([aria-selected].day-range-end)]:rounded-r-md",
1489
- "[&:has([aria-selected].day-outside)]:bg-accent/50",
1490
- "[&:has([aria-selected])]:bg-accent",
1491
- "first:[&:has([aria-selected])]:rounded-l-md",
1492
- "last:[&:has([aria-selected])]:rounded-r-md",
1493
- "focus-within:relative focus-within:z-10"
1508
+ "relative size-10 p-0 text-center text-sm focus-within:relative focus-within:z-10",
1509
+ defaults.day
1510
+ ),
1511
+ range_start: cn("rounded-l-md", defaults.range_start),
1512
+ range_middle: cn("rounded-none", defaults.range_middle),
1513
+ range_end: cn("rounded-r-md", defaults.range_end),
1514
+ today: cn(
1515
+ "[&>button]:font-semibold [&>button:not([data-selected-single=true]):not([data-range-middle=true])]:text-primary",
1516
+ defaults.today
1494
1517
  ),
1495
- day_button: cn(
1496
- buttonVariants({ variant: "ghost" }),
1497
- "size-9 p-0 font-normal aria-selected:opacity-100"
1518
+ outside: cn(
1519
+ "text-muted-foreground/60 aria-selected:text-muted-foreground",
1520
+ defaults.outside
1498
1521
  ),
1499
- range_start: "day-range-start rounded-l-md",
1500
- range_middle: "day-range-middle aria-selected:bg-accent",
1501
- range_end: "day-range-end rounded-r-md",
1502
- selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
1503
- today: "bg-accent text-accent-foreground",
1504
- outside: "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
1505
- disabled: "text-muted-foreground opacity-50",
1506
- hidden: "invisible",
1522
+ disabled: cn("text-muted-foreground opacity-50", defaults.disabled),
1523
+ hidden: cn("invisible", defaults.hidden),
1507
1524
  ...classNames
1508
1525
  },
1509
1526
  components: {
1510
- Chevron: ({ orientation, className: className2, ...chevronProps }) => {
1511
- const Icon = orientation === "left" ? ChevronLeftIcon2 : ChevronRightIcon5;
1512
- return /* @__PURE__ */ jsx17(Icon, { className: cn("size-4", className2), ...chevronProps });
1527
+ Chevron: ({ orientation, className: chevronClass, ...chevronProps }) => {
1528
+ const Icon = orientation === "left" ? ChevronLeftIcon2 : orientation === "right" ? ChevronRightIcon5 : ChevronDownIcon3;
1529
+ return /* @__PURE__ */ jsx17(Icon, { className: cn("size-4", chevronClass), ...chevronProps });
1513
1530
  },
1514
1531
  DayButton: CalendarDayButton
1515
1532
  },
@@ -1517,6 +1534,12 @@ function Calendar({
1517
1534
  }
1518
1535
  );
1519
1536
  }
1537
+ var navButtonClass = cn(
1538
+ "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors",
1539
+ "hover:bg-accent hover:text-accent-foreground",
1540
+ "disabled:pointer-events-none disabled:opacity-40",
1541
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/10"
1542
+ );
1520
1543
  function CalendarDayButton({
1521
1544
  className,
1522
1545
  day,
@@ -1527,25 +1550,26 @@ function CalendarDayButton({
1527
1550
  React.useEffect(() => {
1528
1551
  if (modifiers.focused) ref.current?.focus();
1529
1552
  }, [modifiers.focused]);
1553
+ const isSingle = modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle;
1530
1554
  return /* @__PURE__ */ jsx17(
1531
1555
  "button",
1532
1556
  {
1533
1557
  ref,
1534
1558
  type: "button",
1535
1559
  "data-day": day.date.toLocaleDateString(),
1536
- "data-selected-single": modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle,
1537
- "data-range-start": modifiers.range_start,
1538
- "data-range-end": modifiers.range_end,
1539
- "data-range-middle": modifiers.range_middle,
1560
+ "data-selected-single": isSingle || void 0,
1561
+ "data-range-start": modifiers.range_start || void 0,
1562
+ "data-range-end": modifiers.range_end || void 0,
1563
+ "data-range-middle": modifiers.range_middle || void 0,
1540
1564
  className: cn(
1541
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors",
1565
+ "inline-flex size-full items-center justify-center rounded-md text-sm font-normal transition-colors",
1542
1566
  "hover:bg-accent hover:text-accent-foreground",
1543
- "focus-visible:ring-2 focus-visible:ring-foreground/10 focus-visible:outline-none",
1567
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15",
1544
1568
  "disabled:pointer-events-none disabled:opacity-50",
1545
- "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground",
1546
- "data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground",
1547
- "data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground",
1548
- "data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground",
1569
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[selected-single=true]:hover:bg-primary",
1570
+ "data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground",
1571
+ "data-[range-start=true]:rounded-l-md data-[range-start=true]:rounded-r-none data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground",
1572
+ "data-[range-end=true]:rounded-r-md data-[range-end=true]:rounded-l-none data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground",
1549
1573
  className
1550
1574
  ),
1551
1575
  ...props
@@ -1554,7 +1578,7 @@ function CalendarDayButton({
1554
1578
  }
1555
1579
 
1556
1580
  // src/ui/combobox.tsx
1557
- import { ChevronDownIcon as ChevronDownIcon3 } from "lucide-react";
1581
+ import { ChevronDownIcon as ChevronDownIcon4 } from "lucide-react";
1558
1582
  import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
1559
1583
  function Combobox({
1560
1584
  ...props
@@ -1580,7 +1604,7 @@ function ComboboxTrigger({
1580
1604
  ...props,
1581
1605
  children: [
1582
1606
  /* @__PURE__ */ jsx18("span", { className: "truncate", children }),
1583
- /* @__PURE__ */ jsx18(ChevronDownIcon3, { className: "size-4 shrink-0 opacity-50" })
1607
+ /* @__PURE__ */ jsx18(ChevronDownIcon4, { className: "size-4 shrink-0 opacity-50" })
1584
1608
  ]
1585
1609
  }
1586
1610
  );
@@ -1950,7 +1974,7 @@ function InputGroupText({
1950
1974
 
1951
1975
  // src/ui/accordion.tsx
1952
1976
  import { Accordion as AccordionPrimitive } from "radix-ui";
1953
- import { ChevronDownIcon as ChevronDownIcon4 } from "lucide-react";
1977
+ import { ChevronDownIcon as ChevronDownIcon5 } from "lucide-react";
1954
1978
  import { jsx as jsx24, jsxs as jsxs11 } from "react/jsx-runtime";
1955
1979
  function Accordion({
1956
1980
  ...props
@@ -1988,7 +2012,7 @@ function AccordionTrigger({
1988
2012
  ...props,
1989
2013
  children: [
1990
2014
  children,
1991
- /* @__PURE__ */ jsx24(ChevronDownIcon4, { className: "pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200" })
2015
+ /* @__PURE__ */ jsx24(ChevronDownIcon5, { className: "pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200" })
1992
2016
  ]
1993
2017
  }
1994
2018
  ) });
@@ -3227,6 +3251,673 @@ function Toaster() {
3227
3251
  ] });
3228
3252
  }
3229
3253
 
3254
+ // src/ui/avatar-group.tsx
3255
+ import * as React4 from "react";
3256
+ import { jsxs as jsxs18 } from "react/jsx-runtime";
3257
+ var spacingClass = {
3258
+ sm: "-space-x-2",
3259
+ md: "-space-x-3"
3260
+ };
3261
+ function AvatarGroup({
3262
+ className,
3263
+ children,
3264
+ max,
3265
+ spacing = "sm",
3266
+ ...props
3267
+ }) {
3268
+ const items = React4.Children.toArray(children);
3269
+ const overflow = typeof max === "number" ? items.length - max : 0;
3270
+ const visible = typeof max === "number" ? items.slice(0, max) : items;
3271
+ return /* @__PURE__ */ jsxs18(
3272
+ "div",
3273
+ {
3274
+ "data-slot": "avatar-group",
3275
+ className: cn(
3276
+ "flex items-center",
3277
+ spacingClass[spacing],
3278
+ "[&>*]:rounded-full [&>*]:ring-2 [&>*]:ring-background",
3279
+ className
3280
+ ),
3281
+ ...props,
3282
+ children: [
3283
+ visible,
3284
+ overflow > 0 ? /* @__PURE__ */ jsxs18(
3285
+ "span",
3286
+ {
3287
+ "aria-label": `${overflow} more`,
3288
+ className: "inline-flex size-8 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground ring-2 ring-background",
3289
+ children: [
3290
+ "+",
3291
+ overflow
3292
+ ]
3293
+ }
3294
+ ) : null
3295
+ ]
3296
+ }
3297
+ );
3298
+ }
3299
+
3300
+ // src/ui/stepper.tsx
3301
+ import { CheckIcon as CheckIcon5 } from "lucide-react";
3302
+ import { jsx as jsx42, jsxs as jsxs19 } from "react/jsx-runtime";
3303
+ function Stepper({
3304
+ steps,
3305
+ current,
3306
+ orientation = "horizontal",
3307
+ className,
3308
+ ...props
3309
+ }) {
3310
+ const isVertical = orientation === "vertical";
3311
+ return /* @__PURE__ */ jsx42(
3312
+ "ol",
3313
+ {
3314
+ "data-slot": "stepper",
3315
+ className: cn(
3316
+ "flex",
3317
+ isVertical ? "flex-col gap-0" : "items-start gap-2",
3318
+ className
3319
+ ),
3320
+ ...props,
3321
+ children: steps.map((step, index) => {
3322
+ const complete = index < current;
3323
+ const active = index === current;
3324
+ const last = index === steps.length - 1;
3325
+ const connectorFilled = index < current;
3326
+ return /* @__PURE__ */ jsxs19(
3327
+ "li",
3328
+ {
3329
+ "aria-current": active ? "step" : void 0,
3330
+ className: cn(
3331
+ "flex min-w-0",
3332
+ isVertical ? "gap-3" : "flex-1 flex-col gap-1.5",
3333
+ last && !isVertical && "flex-none"
3334
+ ),
3335
+ children: [
3336
+ /* @__PURE__ */ jsxs19("div", { className: cn("flex items-center gap-2", isVertical && "flex-col"), children: [
3337
+ /* @__PURE__ */ jsx42(
3338
+ "span",
3339
+ {
3340
+ className: cn(
3341
+ "inline-flex size-7 shrink-0 items-center justify-center rounded-full border text-xs font-medium transition-colors",
3342
+ complete && "border-primary bg-primary text-primary-foreground",
3343
+ active && "border-primary text-primary",
3344
+ !complete && !active && "border-border text-muted-foreground"
3345
+ ),
3346
+ children: complete ? /* @__PURE__ */ jsx42(CheckIcon5, { className: "size-3.5", "aria-hidden": true }) : index + 1
3347
+ }
3348
+ ),
3349
+ !last ? /* @__PURE__ */ jsx42(
3350
+ "span",
3351
+ {
3352
+ "aria-hidden": true,
3353
+ className: cn(
3354
+ isVertical ? "w-px flex-1" : "h-px flex-1",
3355
+ connectorFilled ? "bg-primary" : "bg-border",
3356
+ isVertical && "min-h-6"
3357
+ )
3358
+ }
3359
+ ) : null
3360
+ ] }),
3361
+ /* @__PURE__ */ jsxs19("div", { className: cn("min-w-0", isVertical && "pb-4"), children: [
3362
+ /* @__PURE__ */ jsx42(
3363
+ "p",
3364
+ {
3365
+ className: cn(
3366
+ "truncate text-sm font-medium",
3367
+ active ? "text-foreground" : "text-muted-foreground"
3368
+ ),
3369
+ children: step.label
3370
+ }
3371
+ ),
3372
+ step.description ? /* @__PURE__ */ jsx42("p", { className: "truncate text-xs text-muted-foreground", children: step.description }) : null
3373
+ ] })
3374
+ ]
3375
+ },
3376
+ step.id
3377
+ );
3378
+ })
3379
+ }
3380
+ );
3381
+ }
3382
+
3383
+ // src/ui/timeline.tsx
3384
+ import { jsx as jsx43, jsxs as jsxs20 } from "react/jsx-runtime";
3385
+ var dotToneClass = {
3386
+ default: "border-border bg-card",
3387
+ primary: "border-primary bg-primary",
3388
+ success: "border-emerald-500 bg-emerald-500",
3389
+ warn: "border-amber-500 bg-amber-500",
3390
+ danger: "border-destructive bg-destructive"
3391
+ };
3392
+ function Timeline({ items, className, ...props }) {
3393
+ return /* @__PURE__ */ jsx43("ol", { "data-slot": "timeline", className: cn("flex flex-col", className), ...props, children: items.map((item, index) => {
3394
+ const last = index === items.length - 1;
3395
+ const tone = item.tone ?? "default";
3396
+ return /* @__PURE__ */ jsxs20("li", { className: "relative flex gap-3 pb-5 last:pb-0", children: [
3397
+ /* @__PURE__ */ jsxs20("div", { className: "flex flex-col items-center", children: [
3398
+ /* @__PURE__ */ jsx43(
3399
+ "span",
3400
+ {
3401
+ className: cn(
3402
+ "z-[1] mt-0.5 inline-flex size-3 shrink-0 items-center justify-center rounded-full border-2",
3403
+ dotToneClass[tone],
3404
+ item.icon && "size-6"
3405
+ ),
3406
+ children: item.icon
3407
+ }
3408
+ ),
3409
+ !last ? /* @__PURE__ */ jsx43("span", { "aria-hidden": true, className: "w-px flex-1 bg-border" }) : null
3410
+ ] }),
3411
+ /* @__PURE__ */ jsxs20("div", { className: "min-w-0 flex-1 pb-0.5", children: [
3412
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-start justify-between gap-2", children: [
3413
+ /* @__PURE__ */ jsx43("p", { className: "text-sm font-medium text-foreground", children: item.title }),
3414
+ item.meta ? /* @__PURE__ */ jsx43("span", { className: "shrink-0 text-xs text-muted-foreground tabular-nums", children: item.meta }) : null
3415
+ ] }),
3416
+ item.description ? /* @__PURE__ */ jsx43("p", { className: "mt-0.5 text-sm text-muted-foreground", children: item.description }) : null
3417
+ ] })
3418
+ ] }, item.id);
3419
+ }) });
3420
+ }
3421
+
3422
+ // src/ui/rating.tsx
3423
+ import * as React5 from "react";
3424
+ import { StarIcon } from "lucide-react";
3425
+ import { jsx as jsx44 } from "react/jsx-runtime";
3426
+ var sizeClass = { sm: "size-4", md: "size-5", lg: "size-6" };
3427
+ function Rating({
3428
+ value: valueProp,
3429
+ defaultValue = 0,
3430
+ onChange,
3431
+ max = 5,
3432
+ readOnly = false,
3433
+ disabled = false,
3434
+ size = "md",
3435
+ label = "Rating",
3436
+ className
3437
+ }) {
3438
+ const [uncontrolled, setUncontrolled] = React5.useState(defaultValue);
3439
+ const isControlled = valueProp !== void 0;
3440
+ const value = isControlled ? valueProp : uncontrolled;
3441
+ const [hover, setHover] = React5.useState(null);
3442
+ const interactive = !readOnly && !disabled;
3443
+ const shown = hover ?? value;
3444
+ const set = (next) => {
3445
+ if (!interactive) return;
3446
+ if (!isControlled) setUncontrolled(next);
3447
+ onChange?.(next);
3448
+ };
3449
+ if (!interactive) {
3450
+ return /* @__PURE__ */ jsx44(
3451
+ "span",
3452
+ {
3453
+ "data-slot": "rating",
3454
+ role: "img",
3455
+ "aria-label": `${label}: ${value} of ${max}`,
3456
+ className: cn("inline-flex items-center gap-0.5", disabled && "opacity-50", className),
3457
+ children: Array.from({ length: max }, (_, i) => /* @__PURE__ */ jsx44(
3458
+ StarIcon,
3459
+ {
3460
+ "aria-hidden": true,
3461
+ className: cn(
3462
+ sizeClass[size],
3463
+ i < value ? "fill-amber-400 text-amber-400" : "fill-transparent text-muted-foreground/40"
3464
+ )
3465
+ },
3466
+ i
3467
+ ))
3468
+ }
3469
+ );
3470
+ }
3471
+ return /* @__PURE__ */ jsx44(
3472
+ "span",
3473
+ {
3474
+ "data-slot": "rating",
3475
+ role: "radiogroup",
3476
+ "aria-label": label,
3477
+ className: cn("inline-flex items-center gap-0.5", className),
3478
+ onMouseLeave: () => setHover(null),
3479
+ children: Array.from({ length: max }, (_, i) => {
3480
+ const unit = i + 1;
3481
+ const filled = unit <= shown;
3482
+ return /* @__PURE__ */ jsx44(
3483
+ "button",
3484
+ {
3485
+ type: "button",
3486
+ role: "radio",
3487
+ "aria-checked": unit === value,
3488
+ "aria-label": `${unit} ${unit === 1 ? "star" : "stars"}`,
3489
+ onClick: () => set(unit === value ? 0 : unit),
3490
+ onMouseEnter: () => setHover(unit),
3491
+ onFocus: () => setHover(unit),
3492
+ onBlur: () => setHover(null),
3493
+ className: "rounded-sm p-0.5 transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15",
3494
+ children: /* @__PURE__ */ jsx44(
3495
+ StarIcon,
3496
+ {
3497
+ className: cn(
3498
+ sizeClass[size],
3499
+ "transition-colors",
3500
+ filled ? "fill-amber-400 text-amber-400" : "fill-transparent text-muted-foreground/40"
3501
+ )
3502
+ }
3503
+ )
3504
+ },
3505
+ i
3506
+ );
3507
+ })
3508
+ }
3509
+ );
3510
+ }
3511
+
3512
+ // src/ui/number-field.tsx
3513
+ import * as React6 from "react";
3514
+ import { MinusIcon as MinusIcon2, PlusIcon } from "lucide-react";
3515
+ import { jsx as jsx45, jsxs as jsxs21 } from "react/jsx-runtime";
3516
+ var heightClass = { sm: "h-9", default: "h-10" };
3517
+ var stepButtonClass = "inline-flex aspect-square h-full items-center justify-center text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-foreground/15";
3518
+ function clamp(n, min, max) {
3519
+ if (typeof min === "number" && n < min) return min;
3520
+ if (typeof max === "number" && n > max) return max;
3521
+ return n;
3522
+ }
3523
+ function NumberField({
3524
+ value: valueProp,
3525
+ defaultValue = 0,
3526
+ onValueChange,
3527
+ min,
3528
+ max,
3529
+ step = 1,
3530
+ size = "default",
3531
+ disabled,
3532
+ ariaLabel,
3533
+ className,
3534
+ ...inputProps
3535
+ }) {
3536
+ const [uncontrolled, setUncontrolled] = React6.useState(defaultValue);
3537
+ const isControlled = valueProp !== void 0;
3538
+ const value = isControlled ? valueProp : uncontrolled;
3539
+ const commit = (next) => {
3540
+ const clamped = clamp(next, min, max);
3541
+ if (!isControlled) setUncontrolled(clamped);
3542
+ onValueChange?.(clamped);
3543
+ };
3544
+ return /* @__PURE__ */ jsxs21(
3545
+ "div",
3546
+ {
3547
+ "data-slot": "number-field",
3548
+ className: cn(
3549
+ controlSurfaceClass,
3550
+ "inline-flex w-full items-stretch overflow-hidden rounded-lg p-0",
3551
+ heightClass[size],
3552
+ disabled && "opacity-50",
3553
+ className
3554
+ ),
3555
+ children: [
3556
+ /* @__PURE__ */ jsx45(
3557
+ "button",
3558
+ {
3559
+ type: "button",
3560
+ tabIndex: -1,
3561
+ "aria-hidden": true,
3562
+ disabled: disabled || typeof min === "number" && value <= min,
3563
+ onClick: () => commit(value - step),
3564
+ className: cn(stepButtonClass, "border-r border-border"),
3565
+ children: /* @__PURE__ */ jsx45(MinusIcon2, { className: "size-4" })
3566
+ }
3567
+ ),
3568
+ /* @__PURE__ */ jsx45(
3569
+ "input",
3570
+ {
3571
+ type: "number",
3572
+ inputMode: "decimal",
3573
+ role: "spinbutton",
3574
+ "aria-label": ariaLabel,
3575
+ "aria-valuenow": value,
3576
+ "aria-valuemin": min,
3577
+ "aria-valuemax": max,
3578
+ value: Number.isNaN(value) ? "" : value,
3579
+ disabled,
3580
+ onChange: (e) => {
3581
+ const next = e.target.valueAsNumber;
3582
+ if (Number.isNaN(next)) {
3583
+ if (!isControlled) setUncontrolled(Number.NaN);
3584
+ return;
3585
+ }
3586
+ commit(next);
3587
+ },
3588
+ className: "w-full min-w-0 [appearance:textfield] bg-transparent px-2 text-center text-sm text-foreground outline-none disabled:cursor-not-allowed [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
3589
+ ...inputProps
3590
+ }
3591
+ ),
3592
+ /* @__PURE__ */ jsx45(
3593
+ "button",
3594
+ {
3595
+ type: "button",
3596
+ tabIndex: -1,
3597
+ "aria-hidden": true,
3598
+ disabled: disabled || typeof max === "number" && value >= max,
3599
+ onClick: () => commit(value + step),
3600
+ className: cn(stepButtonClass, "border-l border-border"),
3601
+ children: /* @__PURE__ */ jsx45(PlusIcon, { className: "size-4" })
3602
+ }
3603
+ )
3604
+ ]
3605
+ }
3606
+ );
3607
+ }
3608
+
3609
+ // src/ui/tag-input.tsx
3610
+ import * as React7 from "react";
3611
+ import { XIcon as XIcon3 } from "lucide-react";
3612
+ import { jsx as jsx46, jsxs as jsxs22 } from "react/jsx-runtime";
3613
+ function TagInput({
3614
+ value: valueProp,
3615
+ defaultValue = [],
3616
+ onChange,
3617
+ placeholder,
3618
+ separators = ["Enter", ","],
3619
+ dedupe = true,
3620
+ max,
3621
+ disabled,
3622
+ ariaLabel,
3623
+ className,
3624
+ inputClassName
3625
+ }) {
3626
+ const [uncontrolled, setUncontrolled] = React7.useState(defaultValue);
3627
+ const isControlled = valueProp !== void 0;
3628
+ const tags = isControlled ? valueProp : uncontrolled;
3629
+ const [draft, setDraft] = React7.useState("");
3630
+ const setTags = (next) => {
3631
+ if (!isControlled) setUncontrolled(next);
3632
+ onChange?.(next);
3633
+ };
3634
+ const addTag = (raw) => {
3635
+ const tag = raw.trim();
3636
+ if (!tag) return;
3637
+ if (typeof max === "number" && tags.length >= max) return;
3638
+ if (dedupe && tags.some((t) => t.toLowerCase() === tag.toLowerCase())) {
3639
+ setDraft("");
3640
+ return;
3641
+ }
3642
+ setTags([...tags, tag]);
3643
+ setDraft("");
3644
+ };
3645
+ const removeAt = (index) => {
3646
+ setTags(tags.filter((_, i) => i !== index));
3647
+ };
3648
+ const handleKeyDown = (event) => {
3649
+ if (separators.includes(event.key)) {
3650
+ event.preventDefault();
3651
+ addTag(draft);
3652
+ } else if (event.key === "Backspace" && draft === "" && tags.length > 0) {
3653
+ removeAt(tags.length - 1);
3654
+ }
3655
+ };
3656
+ return /* @__PURE__ */ jsxs22(
3657
+ "div",
3658
+ {
3659
+ "data-slot": "tag-input",
3660
+ className: cn(
3661
+ controlSurfaceClass,
3662
+ "flex min-h-10 w-full flex-wrap items-center gap-1.5 rounded-lg px-2 py-1.5",
3663
+ disabled && "pointer-events-none opacity-50",
3664
+ className
3665
+ ),
3666
+ children: [
3667
+ tags.map((tag, index) => /* @__PURE__ */ jsxs22(
3668
+ "span",
3669
+ {
3670
+ className: "inline-flex items-center gap-1 rounded-md bg-muted py-0.5 pl-2 pr-1 text-xs font-medium text-foreground",
3671
+ children: [
3672
+ tag,
3673
+ /* @__PURE__ */ jsx46(
3674
+ "button",
3675
+ {
3676
+ type: "button",
3677
+ "aria-label": `Remove ${tag}`,
3678
+ onClick: () => removeAt(index),
3679
+ className: "inline-flex size-4 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-foreground/10 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/20",
3680
+ children: /* @__PURE__ */ jsx46(XIcon3, { className: "size-3", "aria-hidden": true })
3681
+ }
3682
+ )
3683
+ ]
3684
+ },
3685
+ `${tag}-${index}`
3686
+ )),
3687
+ /* @__PURE__ */ jsx46(
3688
+ "input",
3689
+ {
3690
+ type: "text",
3691
+ "aria-label": ariaLabel ?? placeholder ?? "Add tag",
3692
+ value: draft,
3693
+ disabled,
3694
+ placeholder: tags.length === 0 ? placeholder : void 0,
3695
+ onChange: (e) => setDraft(e.target.value),
3696
+ onKeyDown: handleKeyDown,
3697
+ onBlur: () => addTag(draft),
3698
+ className: cn(
3699
+ "min-w-[6rem] flex-1 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground/70",
3700
+ inputClassName
3701
+ )
3702
+ }
3703
+ )
3704
+ ]
3705
+ }
3706
+ );
3707
+ }
3708
+
3709
+ // src/ui/banner.tsx
3710
+ import { XIcon as XIcon4 } from "lucide-react";
3711
+ import { jsx as jsx47, jsxs as jsxs23 } from "react/jsx-runtime";
3712
+ var bannerToneClass = {
3713
+ default: "border-border bg-muted/50 text-foreground",
3714
+ primary: "border-primary/20 bg-primary/10 text-foreground",
3715
+ success: "border-emerald-500/25 bg-emerald-500/10 text-foreground [&_[data-banner-icon]]:text-emerald-600 dark:[&_[data-banner-icon]]:text-emerald-400",
3716
+ warn: "border-amber-500/25 bg-amber-500/10 text-foreground [&_[data-banner-icon]]:text-amber-600 dark:[&_[data-banner-icon]]:text-amber-400",
3717
+ danger: "border-destructive/25 bg-destructive/10 text-foreground [&_[data-banner-icon]]:text-destructive"
3718
+ };
3719
+ function Banner({
3720
+ tone = "default",
3721
+ icon,
3722
+ title,
3723
+ actions,
3724
+ onDismiss,
3725
+ className,
3726
+ children,
3727
+ ...props
3728
+ }) {
3729
+ return /* @__PURE__ */ jsxs23(
3730
+ "div",
3731
+ {
3732
+ "data-slot": "banner",
3733
+ role: "status",
3734
+ className: cn(
3735
+ "flex w-full items-start gap-3 rounded-lg border px-4 py-3 text-sm",
3736
+ bannerToneClass[tone],
3737
+ className
3738
+ ),
3739
+ ...props,
3740
+ children: [
3741
+ icon ? /* @__PURE__ */ jsx47("span", { "data-banner-icon": true, className: "mt-0.5 shrink-0 [&_svg]:size-4", children: icon }) : null,
3742
+ /* @__PURE__ */ jsxs23("div", { className: "min-w-0 flex-1", children: [
3743
+ title ? /* @__PURE__ */ jsx47("p", { className: "font-medium", children: title }) : null,
3744
+ children ? /* @__PURE__ */ jsx47("div", { className: cn("text-muted-foreground", title && "mt-0.5"), children }) : null
3745
+ ] }),
3746
+ actions ? /* @__PURE__ */ jsx47("div", { className: "flex shrink-0 items-center gap-2", children: actions }) : null,
3747
+ onDismiss ? /* @__PURE__ */ jsx47(
3748
+ "button",
3749
+ {
3750
+ type: "button",
3751
+ "aria-label": "Dismiss",
3752
+ onClick: onDismiss,
3753
+ className: "-mr-1 -mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-foreground/10 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15",
3754
+ children: /* @__PURE__ */ jsx47(XIcon4, { className: "size-4", "aria-hidden": true })
3755
+ }
3756
+ ) : null
3757
+ ]
3758
+ }
3759
+ );
3760
+ }
3761
+
3762
+ // src/ui/copy-button.tsx
3763
+ import * as React8 from "react";
3764
+ import { CheckIcon as CheckIcon6, CopyIcon } from "lucide-react";
3765
+ import { jsx as jsx48, jsxs as jsxs24 } from "react/jsx-runtime";
3766
+ function CopyButton({
3767
+ value,
3768
+ timeout = 1500,
3769
+ onCopied,
3770
+ className,
3771
+ children,
3772
+ onClick,
3773
+ ...props
3774
+ }) {
3775
+ const [copied, setCopied] = React8.useState(false);
3776
+ const timer = React8.useRef(void 0);
3777
+ React8.useEffect(() => () => clearTimeout(timer.current), []);
3778
+ const handleClick = async (event) => {
3779
+ onClick?.(event);
3780
+ if (event.defaultPrevented) return;
3781
+ try {
3782
+ await navigator.clipboard.writeText(value);
3783
+ setCopied(true);
3784
+ onCopied?.(value);
3785
+ clearTimeout(timer.current);
3786
+ timer.current = setTimeout(() => setCopied(false), timeout);
3787
+ } catch {
3788
+ }
3789
+ };
3790
+ const Icon = copied ? CheckIcon6 : CopyIcon;
3791
+ return /* @__PURE__ */ jsxs24(
3792
+ "button",
3793
+ {
3794
+ type: "button",
3795
+ "data-slot": "copy-button",
3796
+ "data-copied": copied || void 0,
3797
+ "aria-label": copied ? "Copied" : "Copy",
3798
+ onClick: handleClick,
3799
+ className: cn(
3800
+ "inline-flex items-center justify-center gap-1.5 rounded-md text-sm font-medium text-muted-foreground transition-colors",
3801
+ "hover:bg-accent hover:text-foreground data-[copied=true]:text-emerald-600 dark:data-[copied=true]:text-emerald-400",
3802
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15",
3803
+ children ? "h-8 px-2" : "size-8",
3804
+ className
3805
+ ),
3806
+ ...props,
3807
+ children: [
3808
+ /* @__PURE__ */ jsx48(Icon, { className: "size-4 shrink-0", "aria-hidden": true }),
3809
+ children
3810
+ ]
3811
+ }
3812
+ );
3813
+ }
3814
+
3815
+ // src/ui/snippet.tsx
3816
+ import { jsx as jsx49, jsxs as jsxs25 } from "react/jsx-runtime";
3817
+ function Snippet({
3818
+ children,
3819
+ symbol,
3820
+ hideCopy = false,
3821
+ className,
3822
+ ...props
3823
+ }) {
3824
+ return /* @__PURE__ */ jsxs25(
3825
+ "div",
3826
+ {
3827
+ "data-slot": "snippet",
3828
+ className: cn(
3829
+ "flex items-center gap-2 rounded-lg border border-border bg-muted/40 py-1.5 pl-3 pr-1.5 font-mono text-sm",
3830
+ className
3831
+ ),
3832
+ ...props,
3833
+ children: [
3834
+ symbol ? /* @__PURE__ */ jsx49("span", { "aria-hidden": true, className: "select-none text-muted-foreground", children: symbol }) : null,
3835
+ /* @__PURE__ */ jsx49("code", { className: "min-w-0 flex-1 truncate text-foreground", children }),
3836
+ hideCopy ? null : /* @__PURE__ */ jsx49(CopyButton, { value: children, className: "size-7 shrink-0" })
3837
+ ]
3838
+ }
3839
+ );
3840
+ }
3841
+
3842
+ // src/ui/circular-progress.tsx
3843
+ import { jsx as jsx50, jsxs as jsxs26 } from "react/jsx-runtime";
3844
+ var toneClass = {
3845
+ primary: "text-primary",
3846
+ success: "text-emerald-500",
3847
+ warn: "text-amber-500",
3848
+ danger: "text-destructive"
3849
+ };
3850
+ function CircularProgress({
3851
+ value = 0,
3852
+ max = 100,
3853
+ size = 40,
3854
+ thickness = 4,
3855
+ showLabel = false,
3856
+ label,
3857
+ tone = "primary",
3858
+ className,
3859
+ ...props
3860
+ }) {
3861
+ const indeterminate = value === null || value === void 0;
3862
+ const radius = (size - thickness) / 2;
3863
+ const circumference = 2 * Math.PI * radius;
3864
+ const pct = indeterminate ? 0.25 : Math.min(Math.max(value / max, 0), 1);
3865
+ const dashOffset = circumference * (1 - pct);
3866
+ return /* @__PURE__ */ jsxs26(
3867
+ "div",
3868
+ {
3869
+ "data-slot": "circular-progress",
3870
+ role: "progressbar",
3871
+ "aria-valuenow": indeterminate ? void 0 : Math.round(pct * 100),
3872
+ "aria-valuemin": 0,
3873
+ "aria-valuemax": 100,
3874
+ className: cn("relative inline-flex shrink-0", className),
3875
+ style: { width: size, height: size },
3876
+ ...props,
3877
+ children: [
3878
+ /* @__PURE__ */ jsxs26(
3879
+ "svg",
3880
+ {
3881
+ width: size,
3882
+ height: size,
3883
+ viewBox: `0 0 ${size} ${size}`,
3884
+ className: cn(toneClass[tone], indeterminate && "animate-spin"),
3885
+ children: [
3886
+ /* @__PURE__ */ jsx50(
3887
+ "circle",
3888
+ {
3889
+ cx: size / 2,
3890
+ cy: size / 2,
3891
+ r: radius,
3892
+ fill: "none",
3893
+ strokeWidth: thickness,
3894
+ className: "stroke-current opacity-15"
3895
+ }
3896
+ ),
3897
+ /* @__PURE__ */ jsx50(
3898
+ "circle",
3899
+ {
3900
+ cx: size / 2,
3901
+ cy: size / 2,
3902
+ r: radius,
3903
+ fill: "none",
3904
+ strokeWidth: thickness,
3905
+ strokeLinecap: "round",
3906
+ strokeDasharray: circumference,
3907
+ strokeDashoffset: dashOffset,
3908
+ transform: `rotate(-90 ${size / 2} ${size / 2})`,
3909
+ className: cn("stroke-current", !indeterminate && "transition-[stroke-dashoffset] duration-500")
3910
+ }
3911
+ )
3912
+ ]
3913
+ }
3914
+ ),
3915
+ showLabel && !indeterminate ? /* @__PURE__ */ jsx50("span", { className: "absolute inset-0 flex items-center justify-center text-[0.7rem] font-medium tabular-nums text-foreground", children: label ?? `${Math.round(pct * 100)}%` }) : null
3916
+ ]
3917
+ }
3918
+ );
3919
+ }
3920
+
3230
3921
  export {
3231
3922
  DropdownMenu,
3232
3923
  DropdownMenuTrigger,
@@ -3435,5 +4126,15 @@ export {
3435
4126
  ToastDescription,
3436
4127
  toast,
3437
4128
  useToast,
3438
- Toaster
4129
+ Toaster,
4130
+ AvatarGroup,
4131
+ Stepper,
4132
+ Timeline,
4133
+ Rating,
4134
+ NumberField,
4135
+ TagInput,
4136
+ Banner,
4137
+ CopyButton,
4138
+ Snippet,
4139
+ CircularProgress
3439
4140
  };