@burtson-labs/ui 0.6.0 → 0.8.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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/components/alert-dialog.js +1 -1
  3. package/dist/components/alert-dialog.js.map +1 -1
  4. package/dist/components/alert.js +2 -2
  5. package/dist/components/alert.js.map +1 -1
  6. package/dist/components/button.d.ts +1 -1
  7. package/dist/components/button.js +26 -6
  8. package/dist/components/button.js.map +1 -1
  9. package/dist/components/checkbox.js +11 -6
  10. package/dist/components/checkbox.js.map +1 -1
  11. package/dist/components/combobox.d.ts +6 -1
  12. package/dist/components/combobox.js +25 -6
  13. package/dist/components/combobox.js.map +1 -1
  14. package/dist/components/composer.d.ts +5 -3
  15. package/dist/components/composer.js +46 -9
  16. package/dist/components/composer.js.map +1 -1
  17. package/dist/components/conversation.js +2 -2
  18. package/dist/components/conversation.js.map +1 -1
  19. package/dist/components/copy-button.d.ts +17 -0
  20. package/dist/components/copy-button.js +56 -0
  21. package/dist/components/copy-button.js.map +1 -0
  22. package/dist/components/dialog.js +1 -1
  23. package/dist/components/dialog.js.map +1 -1
  24. package/dist/components/input.d.ts +2 -2
  25. package/dist/components/input.js +5 -4
  26. package/dist/components/input.js.map +1 -1
  27. package/dist/components/markdown.js +5 -14
  28. package/dist/components/markdown.js.map +1 -1
  29. package/dist/components/message.js +1 -1
  30. package/dist/components/message.js.map +1 -1
  31. package/dist/components/pagination.d.ts +1 -1
  32. package/dist/components/pagination.js +8 -2
  33. package/dist/components/pagination.js.map +1 -1
  34. package/dist/components/popover.js +1 -1
  35. package/dist/components/popover.js.map +1 -1
  36. package/dist/components/progress.d.ts +1 -1
  37. package/dist/components/progress.js +7 -4
  38. package/dist/components/progress.js.map +1 -1
  39. package/dist/components/secret-input.d.ts +11 -0
  40. package/dist/components/secret-input.js +40 -0
  41. package/dist/components/secret-input.js.map +1 -0
  42. package/dist/components/sheet.js +1 -1
  43. package/dist/components/sheet.js.map +1 -1
  44. package/dist/components/slider.d.ts +15 -0
  45. package/dist/components/slider.js +31 -0
  46. package/dist/components/slider.js.map +1 -0
  47. package/dist/components/steps.js +2 -2
  48. package/dist/components/steps.js.map +1 -1
  49. package/dist/components/textarea.d.ts +1 -1
  50. package/dist/components/textarea.js +4 -3
  51. package/dist/components/textarea.js.map +1 -1
  52. package/dist/components/toaster.d.ts +20 -0
  53. package/dist/components/toaster.js +61 -0
  54. package/dist/components/toaster.js.map +1 -0
  55. package/dist/components/tool-call.d.ts +3 -1
  56. package/dist/components/tool-call.js +20 -2
  57. package/dist/components/tool-call.js.map +1 -1
  58. package/dist/components/toolbar.js +1 -1
  59. package/dist/components/toolbar.js.map +1 -1
  60. package/dist/components/tooltip.js +13 -5
  61. package/dist/components/tooltip.js.map +1 -1
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +7 -3
  64. package/dist/styles.css +1 -1
  65. package/dist/theme.css +16 -1
  66. package/dist/tokens.js +1 -1
  67. package/dist/tokens.js.map +1 -1
  68. package/package.json +1 -1
package/README.md CHANGED
@@ -58,7 +58,7 @@ npx shadcn@latest add https://ui.burtson.ai/r/button.json
58
58
 
59
59
  ## <picture><source media="(prefers-color-scheme: dark)" srcset="https://icons.burtson.ai/svg-white/panel-grid.svg"/><img src="https://icons.burtson.ai/svg-black/panel-grid.svg" align="center" alt=""/></picture> Components
60
60
 
61
- Accordion · Alert · Alert Dialog · App Shell · Avatar · Badge · Breadcrumb · Button · Card · Checkbox · Collapsible · Combobox · Command · Dialog · Dropdown Menu · Empty State · Field · Icon Button · Input · Kbd · Label · Navigation Menu · Page Header · Pagination · Popover · Progress · Radio Group · Reasoning · Scroll Area · Select · Separator · Sheet · Skeleton · Spinner · Stat Card · Status · Steps · Switch · Table · Tabs · Textarea · Toast · Toolbar · Tooltip
61
+ Accordion · Alert · Alert Dialog · App Shell · Avatar · Badge · Breadcrumb · Button · Card · Checkbox · Collapsible · Combobox · Command · Copy Button · Dialog · Dropdown Menu · Empty State · Field · Icon Button · Input · Kbd · Label · Navigation Menu · Page Header · Pagination · Popover · Progress · Radio Group · Reasoning · Scroll Area · Secret Input · Select · Separator · Sheet · Skeleton · Slider · Spinner · Stat Card · Status · Steps · Switch · Table · Tabs · Textarea · Toast · Toaster · Toolbar · Tooltip
62
62
 
63
63
  Live previews and code for each one are at [ui.burtson.ai](https://ui.burtson.ai/docs/components/button).
64
64
 
@@ -27,7 +27,7 @@ function AlertDialogContent({ className, ...props }) {
27
27
  className: "fixed inset-0 z-50 bg-black/55 backdrop-blur-[3px] data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in"
28
28
  }), /* @__PURE__ */ jsx(AlertDialog.Content, {
29
29
  "data-slot": "alert-dialog-content",
30
- className: cn("fixed top-1/2 left-1/2 z-50 grid [--bl-scale:0.95] [--bl-ty:8px] w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 gap-5 rounded-xl border border-border-strong bg-surface-raised p-5 text-foreground shadow-[0_24px_80px_rgb(0_0_0_/_0.28)] outline-none data-[state=closed]:animate-out data-[state=open]:animate-in sm:p-6", className),
30
+ className: cn("fixed top-1/2 left-1/2 z-50 grid [--bl-scale:0.95] [--bl-ty:8px] w-[calc(100%-2rem)] max-w-md max-h-[calc(100dvh-2rem)] overflow-y-auto overscroll-contain -translate-x-1/2 -translate-y-1/2 gap-5 rounded-xl border border-border-strong bg-surface-raised p-5 text-foreground shadow-[0_24px_80px_rgb(0_0_0_/_0.28)] outline-none data-[state=closed]:animate-out data-[state=open]:animate-in sm:p-6", className),
31
31
  ...props
32
32
  })] });
33
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.js","names":["AlertDialog","AlertDialogPrimitive"],"sources":["../../src/components/alert-dialog.tsx"],"sourcesContent":["import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { buttonVariants } from './button';\n\n/**\n * A confirmation that interrupts: \"Delete 3 keys?\", \"Revoke access?\". Unlike\n * Dialog it has no close button and ignores outside clicks, so the person\n * answers it; Escape still cancels. Focus starts on Cancel.\n */\nfunction AlertDialog(props: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n}\n\nfunction AlertDialogTrigger(props: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />;\n}\n\nfunction AlertDialogContent({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {\n return (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Overlay\n data-slot=\"alert-dialog-overlay\"\n className=\"fixed inset-0 z-50 bg-black/55 backdrop-blur-[3px] data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in\"\n />\n <AlertDialogPrimitive.Content\n data-slot=\"alert-dialog-content\"\n className={cn(\n 'fixed top-1/2 left-1/2 z-50 grid [--bl-scale:0.95] [--bl-ty:8px] w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 gap-5 rounded-xl border border-border-strong bg-surface-raised p-5 text-foreground shadow-[0_24px_80px_rgb(0_0_0_/_0.28)] outline-none data-[state=closed]:animate-out data-[state=open]:animate-in sm:p-6',\n className,\n )}\n {...props}\n />\n </AlertDialogPrimitive.Portal>\n );\n}\n\nfunction AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div data-slot=\"alert-dialog-header\" className={cn('grid gap-1.5', className)} {...props} />\n );\n}\n\nfunction AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}\n {...props}\n />\n );\n}\n\nfunction AlertDialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n return (\n <AlertDialogPrimitive.Title\n data-slot=\"alert-dialog-title\"\n className={cn('text-base font-semibold tracking-[-0.02em]', className)}\n {...props}\n />\n );\n}\n\nfunction AlertDialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n return (\n <AlertDialogPrimitive.Description\n data-slot=\"alert-dialog-description\"\n className={cn('text-sm leading-5 text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\n/** The confirming button. `destructive` paints it as a danger action. */\nfunction AlertDialogAction({\n className,\n destructive = false,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Action> & { destructive?: boolean }) {\n return (\n <AlertDialogPrimitive.Action\n data-slot=\"alert-dialog-action\"\n className={cn(\n buttonVariants({ variant: destructive ? 'destructive' : 'default' }),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogCancel({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {\n return (\n <AlertDialogPrimitive.Cancel\n data-slot=\"alert-dialog-cancel\"\n className={cn(buttonVariants({ variant: 'outline' }), className)}\n {...props}\n />\n );\n}\n\nexport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n};\n"],"mappings":";;;;;;;;;;;AAYA,SAASA,cAAY,OAA+D;CAClF,OAAO,oBAACC,YAAqB,MAAtB;EAA2B,aAAU;EAAe,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,mBAAmB,OAAkE;CAC5F,OAAO,oBAACA,YAAqB,SAAtB;EAA8B,aAAU;EAAuB,GAAI;CAAQ,CAAA;AACpF;AAEA,SAAS,mBAAmB,EAC1B,WACA,GAAG,SACyD;CAC5D,OACE,qBAACA,YAAqB,QAAtB,EAAA,UAAA,CACE,oBAACA,YAAqB,SAAtB;EACE,aAAU;EACV,WAAU;CACX,CAAA,GACD,oBAACA,YAAqB,SAAtB;EACE,aAAU;EACV,WAAW,GACT,8UACA,SACF;EACA,GAAI;CACL,CAAA,CAC0B,EAAA,CAAA;AAEjC;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAsC;CAC/E,OACE,oBAAC,OAAD;EAAK,aAAU;EAAsB,WAAW,GAAG,gBAAgB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAE/F;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAsC;CAC/E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,0DAA0D,SAAS;EACjF,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,GAAG,SACuD;CAC1D,OACE,oBAACA,YAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GAAG,8CAA8C,SAAS;EACrE,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,uBAAuB,EAC9B,WACA,GAAG,SAC6D;CAChE,OACE,oBAACA,YAAqB,aAAtB;EACE,aAAU;EACV,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;CACL,CAAA;AAEL;;AAGA,SAAS,kBAAkB,EACzB,WACA,cAAc,OACd,GAAG,SACoF;CACvF,OACE,oBAACA,YAAqB,QAAtB;EACE,aAAU;EACV,WAAW,GACT,eAAe,EAAE,SAAS,cAAc,gBAAgB,UAAU,CAAC,GACnE,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,kBAAkB,EACzB,WACA,GAAG,SACwD;CAC3D,OACE,oBAACA,YAAqB,QAAtB;EACE,aAAU;EACV,WAAW,GAAG,eAAe,EAAE,SAAS,UAAU,CAAC,GAAG,SAAS;EAC/D,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"alert-dialog.js","names":["AlertDialog","AlertDialogPrimitive"],"sources":["../../src/components/alert-dialog.tsx"],"sourcesContent":["import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { buttonVariants } from './button';\n\n/**\n * A confirmation that interrupts: \"Delete 3 keys?\", \"Revoke access?\". Unlike\n * Dialog it has no close button and ignores outside clicks, so the person\n * answers it; Escape still cancels. Focus starts on Cancel.\n */\nfunction AlertDialog(props: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n}\n\nfunction AlertDialogTrigger(props: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />;\n}\n\nfunction AlertDialogContent({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {\n return (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Overlay\n data-slot=\"alert-dialog-overlay\"\n className=\"fixed inset-0 z-50 bg-black/55 backdrop-blur-[3px] data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in\"\n />\n <AlertDialogPrimitive.Content\n data-slot=\"alert-dialog-content\"\n className={cn(\n 'fixed top-1/2 left-1/2 z-50 grid [--bl-scale:0.95] [--bl-ty:8px] w-[calc(100%-2rem)] max-w-md max-h-[calc(100dvh-2rem)] overflow-y-auto overscroll-contain -translate-x-1/2 -translate-y-1/2 gap-5 rounded-xl border border-border-strong bg-surface-raised p-5 text-foreground shadow-[0_24px_80px_rgb(0_0_0_/_0.28)] outline-none data-[state=closed]:animate-out data-[state=open]:animate-in sm:p-6',\n className,\n )}\n {...props}\n />\n </AlertDialogPrimitive.Portal>\n );\n}\n\nfunction AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div data-slot=\"alert-dialog-header\" className={cn('grid gap-1.5', className)} {...props} />\n );\n}\n\nfunction AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}\n {...props}\n />\n );\n}\n\nfunction AlertDialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n return (\n <AlertDialogPrimitive.Title\n data-slot=\"alert-dialog-title\"\n className={cn('text-base font-semibold tracking-[-0.02em]', className)}\n {...props}\n />\n );\n}\n\nfunction AlertDialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n return (\n <AlertDialogPrimitive.Description\n data-slot=\"alert-dialog-description\"\n className={cn('text-sm leading-5 text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\n/** The confirming button. `destructive` paints it as a danger action. */\nfunction AlertDialogAction({\n className,\n destructive = false,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Action> & { destructive?: boolean }) {\n return (\n <AlertDialogPrimitive.Action\n data-slot=\"alert-dialog-action\"\n className={cn(\n buttonVariants({ variant: destructive ? 'destructive' : 'default' }),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDialogCancel({\n className,\n ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {\n return (\n <AlertDialogPrimitive.Cancel\n data-slot=\"alert-dialog-cancel\"\n className={cn(buttonVariants({ variant: 'outline' }), className)}\n {...props}\n />\n );\n}\n\nexport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n};\n"],"mappings":";;;;;;;;;;;AAYA,SAASA,cAAY,OAA+D;CAClF,OAAO,oBAACC,YAAqB,MAAtB;EAA2B,aAAU;EAAe,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,mBAAmB,OAAkE;CAC5F,OAAO,oBAACA,YAAqB,SAAtB;EAA8B,aAAU;EAAuB,GAAI;CAAQ,CAAA;AACpF;AAEA,SAAS,mBAAmB,EAC1B,WACA,GAAG,SACyD;CAC5D,OACE,qBAACA,YAAqB,QAAtB,EAAA,UAAA,CACE,oBAACA,YAAqB,SAAtB;EACE,aAAU;EACV,WAAU;CACX,CAAA,GACD,oBAACA,YAAqB,SAAtB;EACE,aAAU;EACV,WAAW,GACT,2YACA,SACF;EACA,GAAI;CACL,CAAA,CAC0B,EAAA,CAAA;AAEjC;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAsC;CAC/E,OACE,oBAAC,OAAD;EAAK,aAAU;EAAsB,WAAW,GAAG,gBAAgB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAE/F;AAEA,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAsC;CAC/E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,0DAA0D,SAAS;EACjF,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,GAAG,SACuD;CAC1D,OACE,oBAACA,YAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GAAG,8CAA8C,SAAS;EACrE,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,uBAAuB,EAC9B,WACA,GAAG,SAC6D;CAChE,OACE,oBAACA,YAAqB,aAAtB;EACE,aAAU;EACV,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;CACL,CAAA;AAEL;;AAGA,SAAS,kBAAkB,EACzB,WACA,cAAc,OACd,GAAG,SACoF;CACvF,OACE,oBAACA,YAAqB,QAAtB;EACE,aAAU;EACV,WAAW,GACT,eAAe,EAAE,SAAS,cAAc,gBAAgB,UAAU,CAAC,GACnE,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,kBAAkB,EACzB,WACA,GAAG,SACwD;CAC3D,OACE,oBAACA,YAAqB,QAAtB;EACE,aAAU;EACV,WAAW,GAAG,eAAe,EAAE,SAAS,UAAU,CAAC,GAAG,SAAS;EAC/D,GAAI;CACL,CAAA;AAEL"}
@@ -3,7 +3,7 @@ import "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { cva } from "class-variance-authority";
5
5
  //#region src/components/alert.tsx
6
- var alertVariants = cva("relative grid w-full grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-lg border px-4 py-3.5 text-sm shadow-xs [&>svg]:mt-0.5 [&>svg]:size-4", {
6
+ var alertVariants = cva("group/alert relative grid w-full grid-cols-1 has-[>svg]:grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-lg border px-4 py-3.5 text-sm shadow-xs [&>svg]:mt-0.5 [&>svg]:size-4", {
7
7
  variants: { variant: {
8
8
  default: "border-border bg-surface text-foreground [&>svg]:text-muted-foreground",
9
9
  brand: "border-brand/25 bg-brand-soft/70 text-foreground [&>svg]:text-brand",
@@ -32,7 +32,7 @@ function AlertTitle({ className, ...props }) {
32
32
  function AlertDescription({ className, ...props }) {
33
33
  return /* @__PURE__ */ jsx("div", {
34
34
  "data-slot": "alert-description",
35
- className: cn("col-start-2 text-[13px] leading-5 text-muted-foreground", className),
35
+ className: cn("col-start-1 group-has-[>svg]/alert:col-start-2 text-[13px] leading-5 text-muted-foreground", className),
36
36
  ...props
37
37
  });
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"alert.js","names":[],"sources":["../../src/components/alert.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nconst alertVariants = cva(\n 'relative grid w-full grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-lg border px-4 py-3.5 text-sm shadow-xs [&>svg]:mt-0.5 [&>svg]:size-4',\n {\n variants: {\n variant: {\n default: 'border-border bg-surface text-foreground [&>svg]:text-muted-foreground',\n brand: 'border-brand/25 bg-brand-soft/70 text-foreground [&>svg]:text-brand',\n success: 'border-success/25 bg-success/10 text-foreground [&>svg]:text-success',\n warning: 'border-warning/25 bg-warning/10 text-foreground [&>svg]:text-warning',\n destructive:\n 'border-destructive/25 bg-destructive/10 text-foreground [&>svg]:text-destructive',\n info: 'border-info/25 bg-info/10 text-foreground [&>svg]:text-info',\n },\n },\n defaultVariants: { variant: 'default' },\n },\n);\n\nfunction Alert({\n className,\n variant,\n ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {\n return (\n <div\n data-slot=\"alert\"\n role=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n {...props}\n />\n );\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn('font-semibold tracking-[-0.01em]', className)}\n {...props}\n />\n );\n}\n\nfunction AlertDescription({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn('col-start-2 text-[13px] leading-5 text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nexport { Alert, AlertDescription, AlertTitle, alertVariants };\n"],"mappings":";;;;;AAKA,IAAM,gBAAgB,IACpB,6IACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,OAAO;EACP,SAAS;EACT,SAAS;EACT,aACE;EACF,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AAEA,SAAS,MAAM,EACb,WACA,SACA,GAAG,SACgE;CACnE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EACnD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAsC;CACxE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,oCAAoC,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EAAE,WAAW,GAAG,SAAsC;CAC9E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,2DAA2D,SAAS;EAClF,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"alert.js","names":[],"sources":["../../src/components/alert.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nconst alertVariants = cva(\n 'group/alert relative grid w-full grid-cols-1 has-[>svg]:grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 rounded-lg border px-4 py-3.5 text-sm shadow-xs [&>svg]:mt-0.5 [&>svg]:size-4',\n {\n variants: {\n variant: {\n default: 'border-border bg-surface text-foreground [&>svg]:text-muted-foreground',\n brand: 'border-brand/25 bg-brand-soft/70 text-foreground [&>svg]:text-brand',\n success: 'border-success/25 bg-success/10 text-foreground [&>svg]:text-success',\n warning: 'border-warning/25 bg-warning/10 text-foreground [&>svg]:text-warning',\n destructive:\n 'border-destructive/25 bg-destructive/10 text-foreground [&>svg]:text-destructive',\n info: 'border-info/25 bg-info/10 text-foreground [&>svg]:text-info',\n },\n },\n defaultVariants: { variant: 'default' },\n },\n);\n\nfunction Alert({\n className,\n variant,\n ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {\n return (\n <div\n data-slot=\"alert\"\n role=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n {...props}\n />\n );\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn('font-semibold tracking-[-0.01em]', className)}\n {...props}\n />\n );\n}\n\nfunction AlertDescription({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n 'col-start-1 group-has-[>svg]/alert:col-start-2 text-[13px] leading-5 text-muted-foreground',\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Alert, AlertDescription, AlertTitle, alertVariants };\n"],"mappings":";;;;;AAKA,IAAM,gBAAgB,IACpB,gLACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,OAAO;EACP,SAAS;EACT,SAAS;EACT,aACE;EACF,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AAEA,SAAS,MAAM,EACb,WACA,SACA,GAAG,SACgE;CACnE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EACnD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAsC;CACxE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,oCAAoC,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EAAE,WAAW,GAAG,SAAsC;CAC9E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,8FACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
@@ -8,5 +8,5 @@ export interface ButtonProps extends React.ComponentProps<'button'>, VariantProp
8
8
  asChild?: boolean;
9
9
  loading?: boolean;
10
10
  }
11
- declare function Button({ className, variant, size, asChild, loading, disabled, children, ...props }: ButtonProps): React.JSX.Element;
11
+ declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
12
12
  export { Button, buttonVariants };
@@ -1,14 +1,14 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { Slot } from "radix-ui";
3
- import "react";
3
+ import * as React from "react";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { cva } from "class-variance-authority";
6
6
  //#region src/components/button.tsx
7
- var buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold tracking-[-0.01em] outline-none transition-[background-color,border-color,color,box-shadow,transform] duration-150 select-none focus-visible:ring-[3px] focus-visible:ring-ring/20 active:translate-y-px disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
7
+ var buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold tracking-[-0.01em] outline-none transition-[background-color,border-color,color,box-shadow,transform] duration-150 select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:ring-[3px] focus-visible:ring-ring/20 active:translate-y-px disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
8
8
  variants: {
9
9
  variant: {
10
- default: "border border-primary bg-primary text-primary-foreground shadow-xs hover:border-brand-hover hover:bg-brand-hover",
11
- brand: "border border-brand bg-brand text-white shadow-[0_1px_2px_rgb(68_8_94_/_0.18)] hover:border-brand-hover hover:bg-brand-hover",
10
+ default: "border border-primary bg-primary text-primary-foreground shadow-xs hover:brightness-90",
11
+ brand: "border border-primary bg-primary text-primary-foreground shadow-xs hover:brightness-90",
12
12
  soft: "border border-brand/15 bg-brand-soft text-brand-soft-foreground hover:bg-brand/15",
13
13
  secondary: "border border-border bg-secondary text-secondary-foreground shadow-xs hover:border-border-strong hover:bg-muted",
14
14
  outline: "border border-border-strong bg-surface text-foreground shadow-xs hover:border-brand/35 hover:bg-accent/60 hover:text-accent-foreground",
@@ -31,9 +31,11 @@ var buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap-2
31
31
  size: "default"
32
32
  }
33
33
  });
34
- function Button({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }) {
34
+ var Button = React.forwardRef(function Button({ className, variant, size, asChild = false, loading = false, disabled, children, type, onClickCapture, onKeyDownCapture, ...props }, ref) {
35
35
  const Comp = asChild ? Slot.Root : "button";
36
36
  return /* @__PURE__ */ jsx(Comp, {
37
+ ref,
38
+ type: asChild ? void 0 : type ?? "button",
37
39
  "data-slot": "button",
38
40
  "data-loading": loading ? "" : void 0,
39
41
  "aria-busy": loading || void 0,
@@ -43,12 +45,30 @@ function Button({ className, variant, size, asChild = false, loading = false, di
43
45
  size
44
46
  }), className),
45
47
  ...props,
48
+ "aria-disabled": asChild && (disabled || loading) ? true : props["aria-disabled"],
49
+ tabIndex: asChild && (disabled || loading) ? -1 : props.tabIndex,
50
+ onClickCapture: (event) => {
51
+ if (disabled || loading) {
52
+ event.preventDefault();
53
+ event.stopPropagation();
54
+ return;
55
+ }
56
+ onClickCapture?.(event);
57
+ },
58
+ onKeyDownCapture: (event) => {
59
+ if ((disabled || loading) && (event.key === "Enter" || event.key === " ")) {
60
+ event.preventDefault();
61
+ event.stopPropagation();
62
+ return;
63
+ }
64
+ onKeyDownCapture?.(event);
65
+ },
46
66
  children: asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [loading ? /* @__PURE__ */ jsx("span", {
47
67
  "aria-hidden": "true",
48
68
  className: "size-3.5 animate-spin rounded-full border-2 border-current border-r-transparent opacity-70"
49
69
  }) : null, children] })
50
70
  });
51
- }
71
+ });
52
72
  //#endregion
53
73
  export { Button, buttonVariants };
54
74
 
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":["_Fragment"],"sources":["../../src/components/button.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold tracking-[-0.01em] outline-none transition-[background-color,border-color,color,box-shadow,transform] duration-150 select-none focus-visible:ring-[3px] focus-visible:ring-ring/20 active:translate-y-px disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default:\n 'border border-primary bg-primary text-primary-foreground shadow-xs hover:border-brand-hover hover:bg-brand-hover',\n brand:\n 'border border-brand bg-brand text-white shadow-[0_1px_2px_rgb(68_8_94_/_0.18)] hover:border-brand-hover hover:bg-brand-hover',\n soft: 'border border-brand/15 bg-brand-soft text-brand-soft-foreground hover:bg-brand/15',\n secondary:\n 'border border-border bg-secondary text-secondary-foreground shadow-xs hover:border-border-strong hover:bg-muted',\n outline:\n 'border border-border-strong bg-surface text-foreground shadow-xs hover:border-brand/35 hover:bg-accent/60 hover:text-accent-foreground',\n ghost: 'border border-transparent text-foreground hover:bg-muted',\n destructive:\n 'border border-destructive bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',\n link: 'h-auto rounded-none border-0 p-0 text-brand shadow-none hover:text-brand-hover hover:underline hover:underline-offset-4 active:translate-y-0',\n },\n size: {\n xs: 'h-7 gap-1.5 rounded-sm px-2.5 text-xs [&_svg:not([class*=size-])]:size-3.5',\n sm: 'h-8 px-3 text-[13px]',\n default: 'h-9 px-3.5',\n lg: 'h-10 rounded-lg px-4 text-[15px]',\n icon: 'size-9 px-0',\n 'icon-sm': 'size-8 px-0',\n 'icon-lg': 'size-10 rounded-lg px-0',\n },\n },\n defaultVariants: { variant: 'default', size: 'default' },\n },\n);\n\nexport interface ButtonProps\n extends React.ComponentProps<'button'>, VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n loading?: boolean;\n}\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n disabled,\n children,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : 'button';\n\n return (\n <Comp\n data-slot=\"button\"\n data-loading={loading ? '' : undefined}\n aria-busy={loading || undefined}\n disabled={!asChild ? disabled || loading : undefined}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n >\n {asChild ? (\n // Slot needs exactly one child to merge onto, so no spinner here.\n children\n ) : (\n <>\n {loading ? (\n <span\n aria-hidden=\"true\"\n className=\"size-3.5 animate-spin rounded-full border-2 border-current border-r-transparent opacity-70\"\n />\n ) : null}\n {children}\n </>\n )}\n </Comp>\n );\n}\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;AAMA,IAAM,iBAAiB,IACrB,ybACA;CACE,UAAU;EACR,SAAS;GACP,SACE;GACF,OACE;GACF,MAAM;GACN,WACE;GACF,SACE;GACF,OAAO;GACP,aACE;GACF,MAAM;EACR;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,MAAM;GACN,WAAW;GACX,WAAW;EACb;CACF;CACA,iBAAiB;EAAE,SAAS;EAAW,MAAM;CAAU;AACzD,CACF;AAQA,SAAS,OAAO,EACd,WACA,SACA,MACA,UAAU,OACV,UAAU,OACV,UACA,UACA,GAAG,SACW;CACd,MAAM,OAAO,UAAU,KAAK,OAAO;CAEnC,OACE,oBAAC,MAAD;EACE,aAAU;EACV,gBAAc,UAAU,KAAK,KAAA;EAC7B,aAAW,WAAW,KAAA;EACtB,UAAU,CAAC,UAAU,YAAY,UAAU,KAAA;EAC3C,WAAW,GAAG,eAAe;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAC1D,GAAI;EAEH,UAAA,UAEC,WAEA,qBAAAA,UAAA,EAAA,UAAA,CACG,UACC,oBAAC,QAAD;GACE,eAAY;GACZ,WAAU;EACX,CAAA,IACC,MACH,QACD,EAAA,CAAA;CAEA,CAAA;AAEV"}
1
+ {"version":3,"file":"button.js","names":["_Fragment"],"sources":["../../src/components/button.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-semibold tracking-[-0.01em] outline-none transition-[background-color,border-color,color,box-shadow,transform] duration-150 select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:ring-[3px] focus-visible:ring-ring/20 active:translate-y-px disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default:\n 'border border-primary bg-primary text-primary-foreground shadow-xs hover:brightness-90',\n brand:\n 'border border-primary bg-primary text-primary-foreground shadow-xs hover:brightness-90',\n soft: 'border border-brand/15 bg-brand-soft text-brand-soft-foreground hover:bg-brand/15',\n secondary:\n 'border border-border bg-secondary text-secondary-foreground shadow-xs hover:border-border-strong hover:bg-muted',\n outline:\n 'border border-border-strong bg-surface text-foreground shadow-xs hover:border-brand/35 hover:bg-accent/60 hover:text-accent-foreground',\n ghost: 'border border-transparent text-foreground hover:bg-muted',\n destructive:\n 'border border-destructive bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',\n link: 'h-auto rounded-none border-0 p-0 text-brand shadow-none hover:text-brand-hover hover:underline hover:underline-offset-4 active:translate-y-0',\n },\n size: {\n xs: 'h-7 gap-1.5 rounded-sm px-2.5 text-xs [&_svg:not([class*=size-])]:size-3.5',\n sm: 'h-8 px-3 text-[13px]',\n default: 'h-9 px-3.5',\n lg: 'h-10 rounded-lg px-4 text-[15px]',\n icon: 'size-9 px-0',\n 'icon-sm': 'size-8 px-0',\n 'icon-lg': 'size-10 rounded-lg px-0',\n },\n },\n defaultVariants: { variant: 'default', size: 'default' },\n },\n);\n\nexport interface ButtonProps\n extends React.ComponentProps<'button'>, VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n loading?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n disabled,\n children,\n type,\n onClickCapture,\n onKeyDownCapture,\n ...props\n }: ButtonProps,\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'button';\n\n return (\n <Comp\n ref={ref}\n type={asChild ? undefined : (type ?? 'button')}\n data-slot=\"button\"\n data-loading={loading ? '' : undefined}\n aria-busy={loading || undefined}\n disabled={!asChild ? disabled || loading : undefined}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n aria-disabled={asChild && (disabled || loading) ? true : props['aria-disabled']}\n tabIndex={asChild && (disabled || loading) ? -1 : props.tabIndex}\n onClickCapture={(event) => {\n if (disabled || loading) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n onClickCapture?.(event);\n }}\n onKeyDownCapture={(event) => {\n if ((disabled || loading) && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n onKeyDownCapture?.(event);\n }}\n >\n {asChild ? (\n // Slot needs exactly one child to merge onto, so no spinner here.\n children\n ) : (\n <>\n {loading ? (\n <span\n aria-hidden=\"true\"\n className=\"size-3.5 animate-spin rounded-full border-2 border-current border-r-transparent opacity-70\"\n />\n ) : null}\n {children}\n </>\n )}\n </Comp>\n );\n});\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;AAMA,IAAM,iBAAiB,IACrB,2gBACA;CACE,UAAU;EACR,SAAS;GACP,SACE;GACF,OACE;GACF,MAAM;GACN,WACE;GACF,SACE;GACF,OAAO;GACP,aACE;GACF,MAAM;EACR;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,SAAS;GACT,IAAI;GACJ,MAAM;GACN,WAAW;GACX,WAAW;EACb;CACF;CACA,iBAAiB;EAAE,SAAS;EAAW,MAAM;CAAU;AACzD,CACF;AAQA,IAAM,SAAS,MAAM,WAA2C,SAAS,OACvE,EACE,WACA,SACA,MACA,UAAU,OACV,UAAU,OACV,UACA,UACA,MACA,gBACA,kBACA,GAAG,SAEL,KACA;CACA,MAAM,OAAO,UAAU,KAAK,OAAO;CAEnC,OACE,oBAAC,MAAD;EACO;EACL,MAAM,UAAU,KAAA,IAAa,QAAQ;EACrC,aAAU;EACV,gBAAc,UAAU,KAAK,KAAA;EAC7B,aAAW,WAAW,KAAA;EACtB,UAAU,CAAC,UAAU,YAAY,UAAU,KAAA;EAC3C,WAAW,GAAG,eAAe;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAC1D,GAAI;EACJ,iBAAe,YAAY,YAAY,WAAW,OAAO,MAAM;EAC/D,UAAU,YAAY,YAAY,WAAW,KAAK,MAAM;EACxD,iBAAiB,UAAU;GACzB,IAAI,YAAY,SAAS;IACvB,MAAM,eAAe;IACrB,MAAM,gBAAgB;IACtB;GACF;GACA,iBAAiB,KAAK;EACxB;EACA,mBAAmB,UAAU;GAC3B,KAAK,YAAY,aAAa,MAAM,QAAQ,WAAW,MAAM,QAAQ,MAAM;IACzE,MAAM,eAAe;IACrB,MAAM,gBAAgB;IACtB;GACF;GACA,mBAAmB,KAAK;EAC1B;EAEC,UAAA,UAEC,WAEA,qBAAAA,UAAA,EAAA,UAAA,CACG,UACC,oBAAC,QAAD;GACE,eAAY;GACZ,WAAU;EACX,CAAA,IACC,MACH,QACD,EAAA,CAAA;CAEA,CAAA;AAEV,CAAC"}
@@ -1,22 +1,27 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { Checkbox } from "radix-ui";
3
3
  import "react";
4
- import { jsx } from "react/jsx-runtime";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import Check from "@burtson-labs/icons/react/check";
6
+ import Minus from "@burtson-labs/icons/react/minus";
6
7
  //#region src/components/checkbox.tsx
7
8
  function Checkbox$1({ className, ...props }) {
8
9
  return /* @__PURE__ */ jsx(Checkbox.Root, {
9
10
  "data-slot": "checkbox",
10
11
  className: cn("peer size-4 shrink-0 rounded-xs border border-border-strong bg-surface shadow-xs transition-[border-color,box-shadow] outline-none hover:border-brand/50 focus-visible:ring-[3px] focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", className),
11
12
  ...props,
12
- children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
13
+ children: /* @__PURE__ */ jsxs(Checkbox.Indicator, {
13
14
  "data-slot": "checkbox-indicator",
14
- className: "grid place-content-center text-current",
15
- children: /* @__PURE__ */ jsx(Check, {
16
- className: "size-3.5",
15
+ className: "group/indicator grid place-content-center text-current",
16
+ children: [/* @__PURE__ */ jsx(Check, {
17
+ className: "size-3.5 group-data-[state=indeterminate]/indicator:hidden",
17
18
  strokeWidth: 2.5,
18
19
  "aria-hidden": true
19
- })
20
+ }), /* @__PURE__ */ jsx(Minus, {
21
+ className: "hidden size-3.5 group-data-[state=indeterminate]/indicator:block",
22
+ strokeWidth: 2.5,
23
+ "aria-hidden": true
24
+ })]
20
25
  })
21
26
  });
22
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.js","names":["Checkbox","CheckboxPrimitive"],"sources":["../../src/components/checkbox.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport { Checkbox as CheckboxPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nfunction Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n return (\n <CheckboxPrimitive.Root\n data-slot=\"checkbox\"\n className={cn(\n 'peer size-4 shrink-0 rounded-xs border border-border-strong bg-surface shadow-xs transition-[border-color,box-shadow] outline-none hover:border-brand/50 focus-visible:ring-[3px] focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"grid place-content-center text-current\"\n >\n <Check className=\"size-3.5\" strokeWidth={2.5} aria-hidden />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n );\n}\n\nexport { Checkbox };\n"],"mappings":";;;;;;AAMA,SAASA,WAAS,EAAE,WAAW,GAAG,SAA8D;CAC9F,OACE,oBAACC,SAAkB,MAAnB;EACE,aAAU;EACV,WAAW,GACT,iZACA,SACF;EACA,GAAI;EAEJ,UAAA,oBAACA,SAAkB,WAAnB;GACE,aAAU;GACV,WAAU;GAEV,UAAA,oBAAC,OAAD;IAAO,WAAU;IAAW,aAAa;IAAK,eAAA;GAAa,CAAA;EAChC,CAAA;CACP,CAAA;AAE5B"}
1
+ {"version":3,"file":"checkbox.js","names":["Checkbox","CheckboxPrimitive"],"sources":["../../src/components/checkbox.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport Minus from '@burtson-labs/icons/react/minus';\nimport { Checkbox as CheckboxPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nfunction Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {\n return (\n <CheckboxPrimitive.Root\n data-slot=\"checkbox\"\n className={cn(\n 'peer size-4 shrink-0 rounded-xs border border-border-strong bg-surface shadow-xs transition-[border-color,box-shadow] outline-none hover:border-brand/50 focus-visible:ring-[3px] focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n data-slot=\"checkbox-indicator\"\n className=\"group/indicator grid place-content-center text-current\"\n >\n <Check\n className=\"size-3.5 group-data-[state=indeterminate]/indicator:hidden\"\n strokeWidth={2.5}\n aria-hidden\n />\n <Minus\n className=\"hidden size-3.5 group-data-[state=indeterminate]/indicator:block\"\n strokeWidth={2.5}\n aria-hidden\n />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n );\n}\n\nexport { Checkbox };\n"],"mappings":";;;;;;;AAOA,SAASA,WAAS,EAAE,WAAW,GAAG,SAA8D;CAC9F,OACE,oBAACC,SAAkB,MAAnB;EACE,aAAU;EACV,WAAW,GACT,iZACA,SACF;EACA,GAAI;EAEJ,UAAA,qBAACA,SAAkB,WAAnB;GACE,aAAU;GACV,WAAU;GAFZ,UAAA,CAIE,oBAAC,OAAD;IACE,WAAU;IACV,aAAa;IACb,eAAA;GACD,CAAA,GACD,oBAAC,OAAD;IACE,WAAU;IACV,aAAa;IACb,eAAA;GACD,CAAA,CAC0B;;CACP,CAAA;AAE5B"}
@@ -26,7 +26,12 @@ export interface ComboboxProps {
26
26
  id?: string;
27
27
  className?: string;
28
28
  'aria-invalid'?: boolean;
29
+ 'aria-label'?: string;
30
+ 'aria-labelledby'?: string;
31
+ 'aria-describedby'?: string;
32
+ name?: string;
33
+ required?: boolean;
29
34
  }
30
35
  /** Pick one value from a long or remote list by typing. */
31
- declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, loading, onSearchChange, clearable, id, className, ...aria }: ComboboxProps): React.JSX.Element;
36
+ declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, loading, onSearchChange, clearable, id, className, name, required, ...aria }: ComboboxProps): React.JSX.Element;
32
37
  export { Combobox };
@@ -4,26 +4,43 @@ import { fieldClasses } from "./input.js";
4
4
  import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
5
5
  import { Spinner } from "./spinner.js";
6
6
  import * as React from "react";
7
- import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import Check from "@burtson-labs/icons/react/check";
9
9
  import ChevronsUpDown from "@burtson-labs/icons/react/chevrons-up-down";
10
10
  //#region src/components/combobox.tsx
11
11
  /** Pick one value from a long or remote list by typing. */
12
- function Combobox({ options, value, onValueChange, placeholder = "Select…", searchPlaceholder = "Search…", emptyText = "No matches.", disabled, loading, onSearchChange, clearable = true, id, className, ...aria }) {
12
+ function Combobox({ options, value, onValueChange, placeholder = "Select…", searchPlaceholder = "Search…", emptyText = "No matches.", disabled, loading, onSearchChange, clearable = true, id, className, name, required, ...aria }) {
13
13
  const [open, setOpen] = React.useState(false);
14
14
  const listId = React.useId();
15
15
  const selected = options.find((o) => o.value === value);
16
- return /* @__PURE__ */ jsxs(Popover, {
16
+ return /* @__PURE__ */ jsxs(Fragment, { children: [name && /* @__PURE__ */ jsx("input", {
17
+ type: "hidden",
18
+ name,
19
+ value: value ?? "",
20
+ disabled
21
+ }), /* @__PURE__ */ jsxs(Popover, {
17
22
  open,
18
23
  onOpenChange: setOpen,
19
24
  children: [/* @__PURE__ */ jsx(PopoverTrigger, {
20
25
  asChild: true,
21
26
  children: /* @__PURE__ */ jsxs("button", {
22
27
  type: "button",
23
- id,
24
28
  role: "combobox",
29
+ "aria-required": required || void 0,
30
+ onKeyDown: (event) => {
31
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
32
+ event.preventDefault();
33
+ setOpen(true);
34
+ }
35
+ },
36
+ id,
25
37
  "aria-expanded": open,
26
- "aria-controls": listId,
38
+ "aria-haspopup": "dialog",
39
+ "aria-controls": open ? listId : void 0,
40
+ "aria-label": aria["aria-label"] ?? (aria["aria-labelledby"] || id ? void 0 : placeholder),
41
+ "aria-labelledby": aria["aria-labelledby"],
42
+ "aria-describedby": aria["aria-describedby"],
43
+ "data-required": required || void 0,
27
44
  "aria-invalid": aria["aria-invalid"],
28
45
  disabled,
29
46
  "data-slot": "combobox-trigger",
@@ -38,11 +55,13 @@ function Combobox({ options, value, onValueChange, placeholder = "Select…", se
38
55
  })
39
56
  }), /* @__PURE__ */ jsx(PopoverContent, {
40
57
  id: listId,
58
+ "aria-label": aria["aria-label"] ?? placeholder,
41
59
  className: "w-(--radix-popover-trigger-width) min-w-64 p-0",
42
60
  align: "start",
43
61
  children: /* @__PURE__ */ jsxs(Command, {
44
62
  shouldFilter: !onSearchChange,
45
63
  children: [/* @__PURE__ */ jsx(CommandInput, {
64
+ "aria-label": searchPlaceholder,
46
65
  placeholder: searchPlaceholder,
47
66
  onValueChange: onSearchChange
48
67
  }), /* @__PURE__ */ jsxs(CommandList, { children: [loading ? /* @__PURE__ */ jsxs("div", {
@@ -71,7 +90,7 @@ function Combobox({ options, value, onValueChange, placeholder = "Select…", se
71
90
  }, o.value)) })] })]
72
91
  })
73
92
  })]
74
- });
93
+ })] });
75
94
  }
76
95
  //#endregion
77
96
  export { Combobox };
@@ -1 +1 @@
1
- {"version":3,"file":"combobox.js","names":[],"sources":["../../src/components/combobox.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronsUpDown from '@burtson-labs/icons/react/chevrons-up-down';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from './command';\nimport { fieldClasses } from './input';\nimport { Popover, PopoverContent, PopoverTrigger } from './popover';\nimport { Spinner } from './spinner';\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n /** Second line, e.g. an email under a name. Also searched. */\n description?: string;\n disabled?: boolean;\n}\n\nexport interface ComboboxProps {\n options: ComboboxOption[];\n value: string | null;\n onValueChange: (value: string | null) => void;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyText?: string;\n disabled?: boolean;\n /** Show a spinner in the list, e.g. while `onSearchChange` fetches. */\n loading?: boolean;\n /**\n * Called as the person types. Pass it to search on a server; the options\n * you pass back are shown as-is (no client filtering).\n */\n onSearchChange?: (search: string) => void;\n /** Let the person clear the selection by picking it again. */\n clearable?: boolean;\n id?: string;\n className?: string;\n 'aria-invalid'?: boolean;\n}\n\n/** Pick one value from a long or remote list by typing. */\nfunction Combobox({\n options,\n value,\n onValueChange,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyText = 'No matches.',\n disabled,\n loading,\n onSearchChange,\n clearable = true,\n id,\n className,\n ...aria\n}: ComboboxProps) {\n const [open, setOpen] = React.useState(false);\n const listId = React.useId();\n const selected = options.find((o) => o.value === value);\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={listId}\n aria-invalid={aria['aria-invalid']}\n disabled={disabled}\n data-slot=\"combobox-trigger\"\n className={cn(\n fieldClasses,\n 'flex h-9 items-center justify-between gap-2 text-left',\n !selected && 'text-muted-foreground',\n className,\n )}\n >\n <span className=\"truncate\">{selected?.label ?? value ?? placeholder}</span>\n <ChevronsUpDown className=\"size-4 shrink-0 opacity-60\" aria-hidden />\n </button>\n </PopoverTrigger>\n <PopoverContent\n id={listId}\n className=\"w-(--radix-popover-trigger-width) min-w-64 p-0\"\n align=\"start\"\n >\n <Command shouldFilter={!onSearchChange}>\n <CommandInput placeholder={searchPlaceholder} onValueChange={onSearchChange} />\n <CommandList>\n {loading ? (\n <div className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\">\n <Spinner /> Searching…\n </div>\n ) : (\n <CommandEmpty>{emptyText}</CommandEmpty>\n )}\n {!loading && (\n <CommandGroup>\n {options.map((o) => (\n <CommandItem\n key={o.value}\n value={`${o.label} ${o.description ?? ''} ${o.value}`}\n disabled={o.disabled}\n onSelect={() => {\n onValueChange(clearable && o.value === value ? null : o.value);\n setOpen(false);\n }}\n >\n <span className=\"grid min-w-0 flex-1\">\n <span className=\"truncate\">{o.label}</span>\n {o.description && (\n <span className=\"truncate text-xs text-muted-foreground\">\n {o.description}\n </span>\n )}\n </span>\n <Check\n aria-hidden\n className={cn(\n 'ml-auto size-4 text-brand',\n o.value === value ? 'opacity-100' : 'opacity-0',\n )}\n />\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport { Combobox };\n"],"mappings":";;;;;;;;;;;AAiDA,SAAS,SAAS,EAChB,SACA,OACA,eACA,cAAc,WACd,oBAAoB,WACpB,YAAY,eACZ,UACA,SACA,gBACA,YAAY,MACZ,IACA,WACA,GAAG,QACa;CAChB,MAAM,CAAC,MAAM,WAAW,MAAM,SAAS,KAAK;CAC5C,MAAM,SAAS,MAAM,MAAM;CAC3B,MAAM,WAAW,QAAQ,MAAM,MAAM,EAAE,UAAU,KAAK;CACtD,OACE,qBAAC,SAAD;EAAe;EAAM,cAAc;EAAnC,UAAA,CACE,oBAAC,gBAAD;GAAgB,SAAA;GACd,UAAA,qBAAC,UAAD;IACE,MAAK;IACD;IACJ,MAAK;IACL,iBAAe;IACf,iBAAe;IACf,gBAAc,KAAK;IACT;IACV,aAAU;IACV,WAAW,GACT,cACA,yDACA,CAAC,YAAY,yBACb,SACF;IAdF,UAAA,CAgBE,oBAAC,QAAD;KAAM,WAAU;KAAY,UAAA,UAAU,SAAS,SAAS;IAAkB,CAAA,GAC1E,oBAAC,gBAAD;KAAgB,WAAU;KAA6B,eAAA;IAAa,CAAA,CAC9D;;EACM,CAAA,GAChB,oBAAC,gBAAD;GACE,IAAI;GACJ,WAAU;GACV,OAAM;GAEN,UAAA,qBAAC,SAAD;IAAS,cAAc,CAAC;IAAxB,UAAA,CACE,oBAAC,cAAD;KAAc,aAAa;KAAmB,eAAe;IAAiB,CAAA,GAC9E,qBAAC,aAAD,EAAA,UAAA,CACG,UACC,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,SAAD,CAAU,CAAA,GAAC,aACR;IAEL,CAAA,IAAA,oBAAC,cAAD,EAAA,UAAe,UAAwB,CAAA,GAExC,CAAC,WACA,oBAAC,cAAD,EAAA,UACG,QAAQ,KAAK,MACZ,qBAAC,aAAD;KAEE,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,eAAe,GAAG,GAAG,EAAE;KAC9C,UAAU,EAAE;KACZ,gBAAgB;MACd,cAAc,aAAa,EAAE,UAAU,QAAQ,OAAO,EAAE,KAAK;MAC7D,QAAQ,KAAK;KACf;KAPF,UAAA,CASE,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,oBAAC,QAAD;OAAM,WAAU;OAAY,UAAA,EAAE;MAAY,CAAA,GACzC,EAAE,eACD,oBAAC,QAAD;OAAM,WAAU;OACb,UAAA,EAAE;MACC,CAAA,CAEJ;KACN,CAAA,GAAA,oBAAC,OAAD;MACE,eAAA;MACA,WAAW,GACT,6BACA,EAAE,UAAU,QAAQ,gBAAgB,WACtC;KACD,CAAA,CACU;IAvBN,GAAA,EAAE,KAuBI,CACd,EACW,CAAA,CAEL,EAAA,CAAA,CACN;;EACK,CAAA,CACT;;AAEb"}
1
+ {"version":3,"file":"combobox.js","names":["_Fragment"],"sources":["../../src/components/combobox.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronsUpDown from '@burtson-labs/icons/react/chevrons-up-down';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from './command';\nimport { fieldClasses } from './input';\nimport { Popover, PopoverContent, PopoverTrigger } from './popover';\nimport { Spinner } from './spinner';\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n /** Second line, e.g. an email under a name. Also searched. */\n description?: string;\n disabled?: boolean;\n}\n\nexport interface ComboboxProps {\n options: ComboboxOption[];\n value: string | null;\n onValueChange: (value: string | null) => void;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyText?: string;\n disabled?: boolean;\n /** Show a spinner in the list, e.g. while `onSearchChange` fetches. */\n loading?: boolean;\n /**\n * Called as the person types. Pass it to search on a server; the options\n * you pass back are shown as-is (no client filtering).\n */\n onSearchChange?: (search: string) => void;\n /** Let the person clear the selection by picking it again. */\n clearable?: boolean;\n id?: string;\n className?: string;\n 'aria-invalid'?: boolean;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n name?: string;\n required?: boolean;\n}\n\n/** Pick one value from a long or remote list by typing. */\nfunction Combobox({\n options,\n value,\n onValueChange,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyText = 'No matches.',\n disabled,\n loading,\n onSearchChange,\n clearable = true,\n id,\n className,\n name,\n required,\n ...aria\n}: ComboboxProps) {\n const [open, setOpen] = React.useState(false);\n const listId = React.useId();\n const selected = options.find((o) => o.value === value);\n return (\n <>\n {name && <input type=\"hidden\" name={name} value={value ?? ''} disabled={disabled} />}\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n role=\"combobox\"\n aria-required={required || undefined}\n onKeyDown={(event) => {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n setOpen(true);\n }\n }}\n id={id}\n aria-expanded={open}\n aria-haspopup=\"dialog\"\n aria-controls={open ? listId : undefined}\n aria-label={\n aria['aria-label'] ?? (aria['aria-labelledby'] || id ? undefined : placeholder)\n }\n aria-labelledby={aria['aria-labelledby']}\n aria-describedby={aria['aria-describedby']}\n data-required={required || undefined}\n aria-invalid={aria['aria-invalid']}\n disabled={disabled}\n data-slot=\"combobox-trigger\"\n className={cn(\n fieldClasses,\n 'flex h-9 items-center justify-between gap-2 text-left',\n !selected && 'text-muted-foreground',\n className,\n )}\n >\n <span className=\"truncate\">{selected?.label ?? value ?? placeholder}</span>\n <ChevronsUpDown className=\"size-4 shrink-0 opacity-60\" aria-hidden />\n </button>\n </PopoverTrigger>\n <PopoverContent\n id={listId}\n aria-label={aria['aria-label'] ?? placeholder}\n className=\"w-(--radix-popover-trigger-width) min-w-64 p-0\"\n align=\"start\"\n >\n <Command shouldFilter={!onSearchChange}>\n <CommandInput\n aria-label={searchPlaceholder}\n placeholder={searchPlaceholder}\n onValueChange={onSearchChange}\n />\n <CommandList>\n {loading ? (\n <div className=\"flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground\">\n <Spinner /> Searching…\n </div>\n ) : (\n <CommandEmpty>{emptyText}</CommandEmpty>\n )}\n {!loading && (\n <CommandGroup>\n {options.map((o) => (\n <CommandItem\n key={o.value}\n value={`${o.label} ${o.description ?? ''} ${o.value}`}\n disabled={o.disabled}\n onSelect={() => {\n onValueChange(clearable && o.value === value ? null : o.value);\n setOpen(false);\n }}\n >\n <span className=\"grid min-w-0 flex-1\">\n <span className=\"truncate\">{o.label}</span>\n {o.description && (\n <span className=\"truncate text-xs text-muted-foreground\">\n {o.description}\n </span>\n )}\n </span>\n <Check\n aria-hidden\n className={cn(\n 'ml-auto size-4 text-brand',\n o.value === value ? 'opacity-100' : 'opacity-0',\n )}\n />\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n </>\n );\n}\n\nexport { Combobox };\n"],"mappings":";;;;;;;;;;;AAsDA,SAAS,SAAS,EAChB,SACA,OACA,eACA,cAAc,WACd,oBAAoB,WACpB,YAAY,eACZ,UACA,SACA,gBACA,YAAY,MACZ,IACA,WACA,MACA,UACA,GAAG,QACa;CAChB,MAAM,CAAC,MAAM,WAAW,MAAM,SAAS,KAAK;CAC5C,MAAM,SAAS,MAAM,MAAM;CAC3B,MAAM,WAAW,QAAQ,MAAM,MAAM,EAAE,UAAU,KAAK;CACtD,OACE,qBAAAA,UAAA,EAAA,UAAA,CACG,QAAQ,oBAAC,SAAD;EAAO,MAAK;EAAe;EAAM,OAAO,SAAS;EAAc;CAAW,CAAA,GACnF,qBAAC,SAAD;EAAe;EAAM,cAAc;EAAnC,UAAA,CACE,oBAAC,gBAAD;GAAgB,SAAA;GACd,UAAA,qBAAC,UAAD;IACE,MAAK;IACL,MAAK;IACL,iBAAe,YAAY,KAAA;IAC3B,YAAY,UAAU;KACpB,IAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,WAAW;MACxD,MAAM,eAAe;MACrB,QAAQ,IAAI;KACd;IACF;IACI;IACJ,iBAAe;IACf,iBAAc;IACd,iBAAe,OAAO,SAAS,KAAA;IAC/B,cACE,KAAK,kBAAkB,KAAK,sBAAsB,KAAK,KAAA,IAAY;IAErE,mBAAiB,KAAK;IACtB,oBAAkB,KAAK;IACvB,iBAAe,YAAY,KAAA;IAC3B,gBAAc,KAAK;IACT;IACV,aAAU;IACV,WAAW,GACT,cACA,yDACA,CAAC,YAAY,yBACb,SACF;IA5BF,UAAA,CA8BE,oBAAC,QAAD;KAAM,WAAU;KAAY,UAAA,UAAU,SAAS,SAAS;IAAkB,CAAA,GAC1E,oBAAC,gBAAD;KAAgB,WAAU;KAA6B,eAAA;IAAa,CAAA,CAC9D;;EACM,CAAA,GAChB,oBAAC,gBAAD;GACE,IAAI;GACJ,cAAY,KAAK,iBAAiB;GAClC,WAAU;GACV,OAAM;GAEN,UAAA,qBAAC,SAAD;IAAS,cAAc,CAAC;IAAxB,UAAA,CACE,oBAAC,cAAD;KACE,cAAY;KACZ,aAAa;KACb,eAAe;IAChB,CAAA,GACD,qBAAC,aAAD,EAAA,UAAA,CACG,UACC,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,SAAD,CAAU,CAAA,GAAC,aACR;IAEL,CAAA,IAAA,oBAAC,cAAD,EAAA,UAAe,UAAwB,CAAA,GAExC,CAAC,WACA,oBAAC,cAAD,EAAA,UACG,QAAQ,KAAK,MACZ,qBAAC,aAAD;KAEE,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,eAAe,GAAG,GAAG,EAAE;KAC9C,UAAU,EAAE;KACZ,gBAAgB;MACd,cAAc,aAAa,EAAE,UAAU,QAAQ,OAAO,EAAE,KAAK;MAC7D,QAAQ,KAAK;KACf;KAPF,UAAA,CASE,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,oBAAC,QAAD;OAAM,WAAU;OAAY,UAAA,EAAE;MAAY,CAAA,GACzC,EAAE,eACD,oBAAC,QAAD;OAAM,WAAU;OACb,UAAA,EAAE;MACC,CAAA,CAEJ;KACN,CAAA,GAAA,oBAAC,OAAD;MACE,eAAA;MACA,WAAW,GACT,6BACA,EAAE,UAAU,QAAQ,gBAAgB,WACtC;KACD,CAAA,CACU;IAvBN,GAAA,EAAE,KAuBI,CACd,EACW,CAAA,CAEL,EAAA,CAAA,CACN;;EACK,CAAA,CACT;CACT,CAAA,CAAA,EAAA,CAAA;AAEN"}
@@ -1,7 +1,9 @@
1
1
  import * as React from 'react';
2
2
  export interface ComposerProps extends Omit<React.ComponentProps<'form'>, 'onSubmit' | 'onChange'> {
3
- /** Called with the trimmed text; the composer clears itself afterwards. */
4
- onSubmit: (text: string) => void;
3
+ /** Clears after success. Reject a returned promise to retain the draft. */
4
+ onSubmit: (text: string) => void | Promise<void>;
5
+ onSubmitError?: (error: unknown) => void;
6
+ submitErrorText?: string;
5
7
  /** Controlled value; leave out to let the composer keep its own. */
6
8
  value?: string;
7
9
  onValueChange?: (value: string) => void;
@@ -20,7 +22,7 @@ export interface ComposerProps extends Omit<React.ComponentProps<'form'>, 'onSub
20
22
  * The message box: grows with its text, Enter sends, Shift+Enter adds a
21
23
  * line, and it never sends mid-composition (IME input).
22
24
  */
23
- declare function Composer({ onSubmit, value: controlled, onValueChange, streaming, onStop, disabled, placeholder, attachments, actions, label, className, ...props }: ComposerProps): React.JSX.Element;
25
+ declare function Composer({ onSubmit, onSubmitError, submitErrorText, value: controlled, onValueChange, streaming, onStop, disabled, placeholder, attachments, actions, label, className, ...props }: ComposerProps): React.JSX.Element;
24
26
  export interface SuggestionsProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {
25
27
  items: string[];
26
28
  onSelect: (item: string) => void;
@@ -9,18 +9,45 @@ import Square from "@burtson-labs/icons/react/square";
9
9
  * The message box: grows with its text, Enter sends, Shift+Enter adds a
10
10
  * line, and it never sends mid-composition (IME input).
11
11
  */
12
- function Composer({ onSubmit, value: controlled, onValueChange, streaming = false, onStop, disabled = false, placeholder = "Ask anything…", attachments, actions, label = "Message", className, ...props }) {
12
+ function Composer({ onSubmit, onSubmitError, submitErrorText = "Message could not be sent. Your draft is saved here. Try again.", value: controlled, onValueChange, streaming = false, onStop, disabled = false, placeholder = "Ask anything…", attachments, actions, label = "Message", className, ...props }) {
13
13
  const [own, setOwn] = React.useState("");
14
14
  const value = controlled ?? own;
15
+ const [pending, setPending] = React.useState(false);
16
+ const [failed, setFailed] = React.useState(false);
17
+ const sending = React.useRef(false);
18
+ const latest = React.useRef(value);
19
+ const mounted = React.useRef(true);
20
+ const errorId = React.useId();
21
+ React.useEffect(() => {
22
+ latest.current = value;
23
+ }, [value]);
24
+ React.useEffect(() => {
25
+ mounted.current = true;
26
+ return () => {
27
+ mounted.current = false;
28
+ };
29
+ }, []);
15
30
  const setValue = (v) => {
16
31
  if (controlled === void 0) setOwn(v);
17
32
  onValueChange?.(v);
18
33
  };
19
- const canSend = !disabled && !streaming && value.trim().length > 0;
20
- const send = () => {
21
- if (!canSend) return;
22
- onSubmit(value.trim());
23
- setValue("");
34
+ const canSend = !disabled && !streaming && !pending && value.trim().length > 0;
35
+ const send = async () => {
36
+ if (!canSend || sending.current) return;
37
+ const draft = value;
38
+ sending.current = true;
39
+ setFailed(false);
40
+ setPending(true);
41
+ try {
42
+ await onSubmit(draft.trim());
43
+ if (mounted.current && latest.current === draft) setValue("");
44
+ } catch (error) {
45
+ if (mounted.current) setFailed(true);
46
+ onSubmitError?.(error);
47
+ } finally {
48
+ sending.current = false;
49
+ if (mounted.current) setPending(false);
50
+ }
24
51
  };
25
52
  return /* @__PURE__ */ jsxs("form", {
26
53
  "data-slot": "composer",
@@ -35,16 +62,24 @@ function Composer({ onSubmit, value: controlled, onValueChange, streaming = fals
35
62
  "aria-label": label,
36
63
  rows: 1,
37
64
  value,
38
- disabled,
65
+ disabled: disabled || pending,
66
+ "aria-describedby": failed ? errorId : void 0,
67
+ "aria-invalid": failed || void 0,
39
68
  placeholder,
40
69
  onChange: (e) => setValue(e.target.value),
41
70
  onKeyDown: (e) => {
42
- if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
71
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && e.nativeEvent.keyCode !== 229) {
43
72
  e.preventDefault();
44
73
  send();
45
74
  }
46
75
  },
47
- className: "field-sizing-content max-h-48 min-h-9 w-full resize-none bg-transparent px-2 py-1.5 text-sm leading-6 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
76
+ className: "field-sizing-content max-h-48 min-h-9 w-full resize-none bg-transparent px-2 py-1.5 text-base sm:text-sm leading-6 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
77
+ }),
78
+ failed && /* @__PURE__ */ jsx("p", {
79
+ id: errorId,
80
+ role: "alert",
81
+ className: "px-2 text-sm text-destructive",
82
+ children: submitErrorText
48
83
  }),
49
84
  attachments && /* @__PURE__ */ jsx("div", {
50
85
  className: "flex flex-wrap gap-1.5 px-1",
@@ -60,11 +95,13 @@ function Composer({ onSubmit, value: controlled, onValueChange, streaming = fals
60
95
  variant: "secondary",
61
96
  "aria-label": "Stop",
62
97
  onClick: onStop,
98
+ disabled: !onStop || disabled,
63
99
  children: /* @__PURE__ */ jsx(Square, { className: "fill-current" })
64
100
  }) : /* @__PURE__ */ jsx(Button, {
65
101
  type: "submit",
66
102
  size: "icon-sm",
67
103
  "aria-label": "Send",
104
+ loading: pending,
68
105
  disabled: !canSend,
69
106
  children: /* @__PURE__ */ jsx(ArrowUp, {})
70
107
  })
@@ -1 +1 @@
1
- {"version":3,"file":"composer.js","names":[],"sources":["../../src/components/composer.tsx"],"sourcesContent":["import ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport Square from '@burtson-labs/icons/react/square';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\nexport interface ComposerProps extends Omit<React.ComponentProps<'form'>, 'onSubmit' | 'onChange'> {\n /** Called with the trimmed text; the composer clears itself afterwards. */\n onSubmit: (text: string) => void;\n /** Controlled value; leave out to let the composer keep its own. */\n value?: string;\n onValueChange?: (value: string) => void;\n /** While a reply streams, the send button becomes Stop. */\n streaming?: boolean;\n onStop?: () => void;\n disabled?: boolean;\n placeholder?: string;\n /** Attachment chips or a file button, shown under the text. */\n attachments?: React.ReactNode;\n /** Extra controls left of the send button (model picker, tools). */\n actions?: React.ReactNode;\n label?: string;\n}\n\n/**\n * The message box: grows with its text, Enter sends, Shift+Enter adds a\n * line, and it never sends mid-composition (IME input).\n */\nfunction Composer({\n onSubmit,\n value: controlled,\n onValueChange,\n streaming = false,\n onStop,\n disabled = false,\n placeholder = 'Ask anything…',\n attachments,\n actions,\n label = 'Message',\n className,\n ...props\n}: ComposerProps) {\n const [own, setOwn] = React.useState('');\n const value = controlled ?? own;\n const setValue = (v: string) => {\n if (controlled === undefined) setOwn(v);\n onValueChange?.(v);\n };\n const canSend = !disabled && !streaming && value.trim().length > 0;\n const send = () => {\n if (!canSend) return;\n onSubmit(value.trim());\n setValue('');\n };\n\n return (\n <form\n data-slot=\"composer\"\n onSubmit={(e) => {\n e.preventDefault();\n send();\n }}\n className={cn(\n 'grid gap-2 rounded-lg border border-input bg-surface p-2 shadow-xs transition-[border-color,box-shadow] focus-within:border-brand focus-within:ring-[3px] focus-within:ring-ring/15 dark:bg-surface-raised',\n disabled && 'opacity-60',\n className,\n )}\n {...props}\n >\n <textarea\n aria-label={label}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault();\n send();\n }\n }}\n className=\"field-sizing-content max-h-48 min-h-9 w-full resize-none bg-transparent px-2 py-1.5 text-sm leading-6 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n />\n {attachments && <div className=\"flex flex-wrap gap-1.5 px-1\">{attachments}</div>}\n <div className=\"flex items-center gap-1\">\n {actions}\n <div className=\"ml-auto\">\n {streaming ? (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"secondary\"\n aria-label=\"Stop\"\n onClick={onStop}\n >\n <Square className=\"fill-current\" />\n </Button>\n ) : (\n <Button type=\"submit\" size=\"icon-sm\" aria-label=\"Send\" disabled={!canSend}>\n <ArrowUp />\n </Button>\n )}\n </div>\n </div>\n </form>\n );\n}\n\nexport interface SuggestionsProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {\n items: string[];\n onSelect: (item: string) => void;\n}\n\n/** Starter prompts as chips. */\nfunction Suggestions({ items, onSelect, className, ...props }: SuggestionsProps) {\n return (\n <div\n data-slot=\"suggestions\"\n role=\"group\"\n aria-label=\"Suggestions\"\n className={cn('flex flex-wrap gap-2', className)}\n {...props}\n >\n {items.map((item) => (\n <button\n key={item}\n type=\"button\"\n onClick={() => onSelect(item)}\n className=\"animate-in rounded-full border border-border-strong bg-surface px-3 py-1.5 text-[13px] text-foreground outline-none transition-colors hover:border-brand/40 hover:bg-brand-soft hover:text-brand-soft-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20\"\n >\n {item}\n </button>\n ))}\n </div>\n );\n}\n\nexport { Composer, Suggestions };\n"],"mappings":";;;;;;;;;;;AA8BA,SAAS,SAAS,EAChB,UACA,OAAO,YACP,eACA,YAAY,OACZ,QACA,WAAW,OACX,cAAc,iBACd,aACA,SACA,QAAQ,WACR,WACA,GAAG,SACa;CAChB,MAAM,CAAC,KAAK,UAAU,MAAM,SAAS,EAAE;CACvC,MAAM,QAAQ,cAAc;CAC5B,MAAM,YAAY,MAAc;EAC9B,IAAI,eAAe,KAAA,GAAW,OAAO,CAAC;EACtC,gBAAgB,CAAC;CACnB;CACA,MAAM,UAAU,CAAC,YAAY,CAAC,aAAa,MAAM,KAAK,CAAC,CAAC,SAAS;CACjE,MAAM,aAAa;EACjB,IAAI,CAAC,SAAS;EACd,SAAS,MAAM,KAAK,CAAC;EACrB,SAAS,EAAE;CACb;CAEA,OACE,qBAAC,QAAD;EACE,aAAU;EACV,WAAW,MAAM;GACf,EAAE,eAAe;GACjB,KAAK;EACP;EACA,WAAW,GACT,8MACA,YAAY,cACZ,SACF;EACA,GAAI;EAXN,UAAA;GAaE,oBAAC,YAAD;IACE,cAAY;IACZ,MAAM;IACC;IACG;IACG;IACb,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;IACxC,YAAY,MAAM;KAChB,IAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,YAAY,CAAC,EAAE,YAAY,aAAa;MAClE,EAAE,eAAe;MACjB,KAAK;KACP;IACF;IACA,WAAU;GACX,CAAA;GACA,eAAe,oBAAC,OAAD;IAAK,WAAU;IAA+B,UAAA;GAAiB,CAAA;GAC/E,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,SACD,oBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,YACC,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,cAAW;MACX,SAAS;MAET,UAAA,oBAAC,QAAD,EAAQ,WAAU,eAAgB,CAAA;KAC5B,CAAA,IAER,oBAAC,QAAD;MAAQ,MAAK;MAAS,MAAK;MAAU,cAAW;MAAO,UAAU,CAAC;MAChE,UAAA,oBAAC,SAAD,CAAU,CAAA;KACJ,CAAA;IAEP,CAAA,CACF;;EACD;;AAEV;;AAQA,SAAS,YAAY,EAAE,OAAO,UAAU,WAAW,GAAG,SAA2B;CAC/E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,cAAW;EACX,WAAW,GAAG,wBAAwB,SAAS;EAC/C,GAAI;EAEH,UAAA,MAAM,KAAK,SACV,oBAAC,UAAD;GAEE,MAAK;GACL,eAAe,SAAS,IAAI;GAC5B,WAAU;GAET,UAAA;EACK,GAND,IAMC,CACT;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"composer.js","names":[],"sources":["../../src/components/composer.tsx"],"sourcesContent":["import ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport Square from '@burtson-labs/icons/react/square';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\nexport interface ComposerProps extends Omit<React.ComponentProps<'form'>, 'onSubmit' | 'onChange'> {\n /** Clears after success. Reject a returned promise to retain the draft. */\n onSubmit: (text: string) => void | Promise<void>;\n onSubmitError?: (error: unknown) => void;\n submitErrorText?: string;\n /** Controlled value; leave out to let the composer keep its own. */\n value?: string;\n onValueChange?: (value: string) => void;\n /** While a reply streams, the send button becomes Stop. */\n streaming?: boolean;\n onStop?: () => void;\n disabled?: boolean;\n placeholder?: string;\n /** Attachment chips or a file button, shown under the text. */\n attachments?: React.ReactNode;\n /** Extra controls left of the send button (model picker, tools). */\n actions?: React.ReactNode;\n label?: string;\n}\n\n/**\n * The message box: grows with its text, Enter sends, Shift+Enter adds a\n * line, and it never sends mid-composition (IME input).\n */\nfunction Composer({\n onSubmit,\n onSubmitError,\n submitErrorText = 'Message could not be sent. Your draft is saved here. Try again.',\n value: controlled,\n onValueChange,\n streaming = false,\n onStop,\n disabled = false,\n placeholder = 'Ask anything…',\n attachments,\n actions,\n label = 'Message',\n className,\n ...props\n}: ComposerProps) {\n const [own, setOwn] = React.useState('');\n const value = controlled ?? own;\n const [pending, setPending] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const sending = React.useRef(false);\n const latest = React.useRef(value);\n const mounted = React.useRef(true);\n const errorId = React.useId();\n React.useEffect(() => {\n latest.current = value;\n }, [value]);\n React.useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const setValue = (v: string) => {\n if (controlled === undefined) setOwn(v);\n onValueChange?.(v);\n };\n const canSend = !disabled && !streaming && !pending && value.trim().length > 0;\n const send = async () => {\n if (!canSend || sending.current) return;\n const draft = value;\n sending.current = true;\n setFailed(false);\n setPending(true);\n try {\n await onSubmit(draft.trim());\n if (mounted.current && latest.current === draft) setValue('');\n } catch (error) {\n if (mounted.current) setFailed(true);\n onSubmitError?.(error);\n } finally {\n sending.current = false;\n if (mounted.current) setPending(false);\n }\n };\n\n return (\n <form\n data-slot=\"composer\"\n onSubmit={(e) => {\n e.preventDefault();\n void send();\n }}\n className={cn(\n 'grid gap-2 rounded-lg border border-input bg-surface p-2 shadow-xs transition-[border-color,box-shadow] focus-within:border-brand focus-within:ring-[3px] focus-within:ring-ring/15 dark:bg-surface-raised',\n disabled && 'opacity-60',\n className,\n )}\n {...props}\n >\n <textarea\n aria-label={label}\n rows={1}\n value={value}\n disabled={disabled || pending}\n aria-describedby={failed ? errorId : undefined}\n aria-invalid={failed || undefined}\n placeholder={placeholder}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (\n e.key === 'Enter' &&\n !e.shiftKey &&\n !e.nativeEvent.isComposing &&\n e.nativeEvent.keyCode !== 229\n ) {\n e.preventDefault();\n void send();\n }\n }}\n className=\"field-sizing-content max-h-48 min-h-9 w-full resize-none bg-transparent px-2 py-1.5 text-base sm:text-sm leading-6 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\"\n />\n {failed && (\n <p id={errorId} role=\"alert\" className=\"px-2 text-sm text-destructive\">\n {submitErrorText}\n </p>\n )}\n {attachments && <div className=\"flex flex-wrap gap-1.5 px-1\">{attachments}</div>}\n <div className=\"flex items-center gap-1\">\n {actions}\n <div className=\"ml-auto\">\n {streaming ? (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"secondary\"\n aria-label=\"Stop\"\n onClick={onStop}\n disabled={!onStop || disabled}\n >\n <Square className=\"fill-current\" />\n </Button>\n ) : (\n <Button\n type=\"submit\"\n size=\"icon-sm\"\n aria-label=\"Send\"\n loading={pending}\n disabled={!canSend}\n >\n <ArrowUp />\n </Button>\n )}\n </div>\n </div>\n </form>\n );\n}\n\nexport interface SuggestionsProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {\n items: string[];\n onSelect: (item: string) => void;\n}\n\n/** Starter prompts as chips. */\nfunction Suggestions({ items, onSelect, className, ...props }: SuggestionsProps) {\n return (\n <div\n data-slot=\"suggestions\"\n role=\"group\"\n aria-label=\"Suggestions\"\n className={cn('flex flex-wrap gap-2', className)}\n {...props}\n >\n {items.map((item) => (\n <button\n key={item}\n type=\"button\"\n onClick={() => onSelect(item)}\n className=\"animate-in rounded-full border border-border-strong bg-surface px-3 py-1.5 text-[13px] text-foreground outline-none transition-colors hover:border-brand/40 hover:bg-brand-soft hover:text-brand-soft-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20\"\n >\n {item}\n </button>\n ))}\n </div>\n );\n}\n\nexport { Composer, Suggestions };\n"],"mappings":";;;;;;;;;;;AAgCA,SAAS,SAAS,EAChB,UACA,eACA,kBAAkB,mEAClB,OAAO,YACP,eACA,YAAY,OACZ,QACA,WAAW,OACX,cAAc,iBACd,aACA,SACA,QAAQ,WACR,WACA,GAAG,SACa;CAChB,MAAM,CAAC,KAAK,UAAU,MAAM,SAAS,EAAE;CACvC,MAAM,QAAQ,cAAc;CAC5B,MAAM,CAAC,SAAS,cAAc,MAAM,SAAS,KAAK;CAClD,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,KAAK;CAChD,MAAM,UAAU,MAAM,OAAO,KAAK;CAClC,MAAM,SAAS,MAAM,OAAO,KAAK;CACjC,MAAM,UAAU,MAAM,OAAO,IAAI;CACjC,MAAM,UAAU,MAAM,MAAM;CAC5B,MAAM,gBAAgB;EACpB,OAAO,UAAU;CACnB,GAAG,CAAC,KAAK,CAAC;CACV,MAAM,gBAAgB;EACpB,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;EACpB;CACF,GAAG,CAAC,CAAC;CACL,MAAM,YAAY,MAAc;EAC9B,IAAI,eAAe,KAAA,GAAW,OAAO,CAAC;EACtC,gBAAgB,CAAC;CACnB;CACA,MAAM,UAAU,CAAC,YAAY,CAAC,aAAa,CAAC,WAAW,MAAM,KAAK,CAAC,CAAC,SAAS;CAC7E,MAAM,OAAO,YAAY;EACvB,IAAI,CAAC,WAAW,QAAQ,SAAS;EACjC,MAAM,QAAQ;EACd,QAAQ,UAAU;EAClB,UAAU,KAAK;EACf,WAAW,IAAI;EACf,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,CAAC;GAC3B,IAAI,QAAQ,WAAW,OAAO,YAAY,OAAO,SAAS,EAAE;EAC9D,SAAS,OAAO;GACd,IAAI,QAAQ,SAAS,UAAU,IAAI;GACnC,gBAAgB,KAAK;EACvB,UAAU;GACR,QAAQ,UAAU;GAClB,IAAI,QAAQ,SAAS,WAAW,KAAK;EACvC;CACF;CAEA,OACE,qBAAC,QAAD;EACE,aAAU;EACV,WAAW,MAAM;GACf,EAAE,eAAe;GACjB,KAAU;EACZ;EACA,WAAW,GACT,8MACA,YAAY,cACZ,SACF;EACA,GAAI;EAXN,UAAA;GAaE,oBAAC,YAAD;IACE,cAAY;IACZ,MAAM;IACC;IACP,UAAU,YAAY;IACtB,oBAAkB,SAAS,UAAU,KAAA;IACrC,gBAAc,UAAU,KAAA;IACX;IACb,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;IACxC,YAAY,MAAM;KAChB,IACE,EAAE,QAAQ,WACV,CAAC,EAAE,YACH,CAAC,EAAE,YAAY,eACf,EAAE,YAAY,YAAY,KAC1B;MACA,EAAE,eAAe;MACjB,KAAU;KACZ;IACF;IACA,WAAU;GACX,CAAA;GACA,UACC,oBAAC,KAAD;IAAG,IAAI;IAAS,MAAK;IAAQ,WAAU;IACpC,UAAA;GACA,CAAA;GAEJ,eAAe,oBAAC,OAAD;IAAK,WAAU;IAA+B,UAAA;GAAiB,CAAA;GAC/E,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,SACD,oBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,YACC,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,cAAW;MACX,SAAS;MACT,UAAU,CAAC,UAAU;MAErB,UAAA,oBAAC,QAAD,EAAQ,WAAU,eAAgB,CAAA;KAC5B,CAAA,IAER,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,cAAW;MACX,SAAS;MACT,UAAU,CAAC;MAEX,UAAA,oBAAC,SAAD,CAAU,CAAA;KACJ,CAAA;IAEP,CAAA,CACF;;EACD;;AAEV;;AAQA,SAAS,YAAY,EAAE,OAAO,UAAU,WAAW,GAAG,SAA2B;CAC/E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,cAAW;EACX,WAAW,GAAG,wBAAwB,SAAS;EAC/C,GAAI;EAEH,UAAA,MAAM,KAAK,SACV,oBAAC,UAAD;GAEE,MAAK;GACL,eAAe,SAAS,IAAI;GAC5B,WAAU;GAET,UAAA;EACK,GAND,IAMC,CACT;CACE,CAAA;AAET"}
@@ -25,7 +25,7 @@ function Conversation({ children, empty, label = "Conversation", className, ...p
25
25
  }, []);
26
26
  React.useEffect(() => {
27
27
  const el = content.current;
28
- if (!el) return;
28
+ if (!el || typeof ResizeObserver === "undefined") return;
29
29
  const ro = new ResizeObserver(() => {
30
30
  if (pinned.current) scrollToBottom();
31
31
  });
@@ -65,7 +65,7 @@ function Conversation({ children, empty, label = "Conversation", className, ...p
65
65
  onClick: () => {
66
66
  pinned.current = true;
67
67
  setAtBottom(true);
68
- scrollToBottom("smooth");
68
+ scrollToBottom(typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth");
69
69
  },
70
70
  className: "absolute bottom-3 left-1/2 inline-flex -translate-x-1/2 animate-in items-center gap-1.5 rounded-full border border-border-strong bg-surface-raised px-3 py-1.5 text-xs font-semibold shadow-md outline-none hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/20 [&_svg]:size-3.5",
71
71
  children: [/* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": true }), " Jump to latest"]
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.js","names":[],"sources":["../../src/components/conversation.tsx"],"sourcesContent":["import ArrowDown from '@burtson-labs/icons/react/arrow-down';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nexport interface ConversationProps extends React.ComponentProps<'div'> {\n /** Shown instead of the messages when there are none. */\n empty?: React.ReactNode;\n /** Accessible name for the message log. */\n label?: string;\n}\n\n/**\n * A scrolling message log that stays pinned to the newest message while it\n * streams, unless the reader has scrolled up; then a \"Jump to latest\" button\n * appears instead of yanking them back down.\n */\nfunction Conversation({\n children,\n empty,\n label = 'Conversation',\n className,\n ...props\n}: ConversationProps) {\n const scroller = React.useRef<HTMLDivElement>(null);\n const content = React.useRef<HTMLDivElement>(null);\n const pinned = React.useRef(true);\n const [atBottom, setAtBottom] = React.useState(true);\n const hasMessages = React.Children.toArray(children).length > 0;\n\n const scrollToBottom = React.useCallback((behavior: ScrollBehavior = 'auto') => {\n const el = scroller.current;\n if (!el) return;\n if (typeof el.scrollTo === 'function') el.scrollTo({ top: el.scrollHeight, behavior });\n else el.scrollTop = el.scrollHeight;\n }, []);\n\n React.useEffect(() => {\n const el = content.current;\n if (!el) return;\n // Content grows while a reply streams; follow it only if pinned.\n const ro = new ResizeObserver(() => {\n if (pinned.current) scrollToBottom();\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [scrollToBottom]);\n\n React.useEffect(() => {\n if (pinned.current) scrollToBottom();\n }, [children, scrollToBottom]);\n\n const onScroll = () => {\n const el = scroller.current;\n if (!el) return;\n const bottom = el.scrollHeight - el.scrollTop - el.clientHeight < 32;\n pinned.current = bottom;\n setAtBottom(bottom);\n };\n\n return (\n <div data-slot=\"conversation\" className={cn('relative min-h-0', className)} {...props}>\n <div\n ref={scroller}\n role=\"log\"\n aria-label={label}\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n // A scrollable region must be reachable by keyboard (WCAG 2.1.1).\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n onScroll={onScroll}\n className=\"size-full overflow-y-auto outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20\"\n >\n <div ref={content} className=\"grid gap-5 p-4\">\n {hasMessages ? children : empty}\n </div>\n </div>\n {!atBottom && (\n <button\n type=\"button\"\n onClick={() => {\n pinned.current = true;\n setAtBottom(true);\n scrollToBottom('smooth');\n }}\n className=\"absolute bottom-3 left-1/2 inline-flex -translate-x-1/2 animate-in items-center gap-1.5 rounded-full border border-border-strong bg-surface-raised px-3 py-1.5 text-xs font-semibold shadow-md outline-none hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/20 [&_svg]:size-3.5\"\n >\n <ArrowDown aria-hidden /> Jump to latest\n </button>\n )}\n </div>\n );\n}\n\nexport { Conversation };\n"],"mappings":";;;;;;;;;;AAiBA,SAAS,aAAa,EACpB,UACA,OACA,QAAQ,gBACR,WACA,GAAG,SACiB;CACpB,MAAM,WAAW,MAAM,OAAuB,IAAI;CAClD,MAAM,UAAU,MAAM,OAAuB,IAAI;CACjD,MAAM,SAAS,MAAM,OAAO,IAAI;CAChC,MAAM,CAAC,UAAU,eAAe,MAAM,SAAS,IAAI;CACnD,MAAM,cAAc,MAAM,SAAS,QAAQ,QAAQ,CAAC,CAAC,SAAS;CAE9D,MAAM,iBAAiB,MAAM,aAAa,WAA2B,WAAW;EAC9E,MAAM,KAAK,SAAS;EACpB,IAAI,CAAC,IAAI;EACT,IAAI,OAAO,GAAG,aAAa,YAAY,GAAG,SAAS;GAAE,KAAK,GAAG;GAAc;EAAS,CAAC;OAChF,GAAG,YAAY,GAAG;CACzB,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB;EACpB,MAAM,KAAK,QAAQ;EACnB,IAAI,CAAC,IAAI;EAET,MAAM,KAAK,IAAI,qBAAqB;GAClC,IAAI,OAAO,SAAS,eAAe;EACrC,CAAC;EACD,GAAG,QAAQ,EAAE;EACb,aAAa,GAAG,WAAW;CAC7B,GAAG,CAAC,cAAc,CAAC;CAEnB,MAAM,gBAAgB;EACpB,IAAI,OAAO,SAAS,eAAe;CACrC,GAAG,CAAC,UAAU,cAAc,CAAC;CAE7B,MAAM,iBAAiB;EACrB,MAAM,KAAK,SAAS;EACpB,IAAI,CAAC,IAAI;EACT,MAAM,SAAS,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;EAClE,OAAO,UAAU;EACjB,YAAY,MAAM;CACpB;CAEA,OACE,qBAAC,OAAD;EAAK,aAAU;EAAe,WAAW,GAAG,oBAAoB,SAAS;EAAG,GAAI;EAAhF,UAAA,CACE,oBAAC,OAAD;GACE,KAAK;GACL,MAAK;GACL,cAAY;GACZ,aAAU;GACV,iBAAc;GAGd,UAAU;GACA;GACV,WAAU;GAEV,UAAA,oBAAC,OAAD;IAAK,KAAK;IAAS,WAAU;IAC1B,UAAA,cAAc,WAAW;GACvB,CAAA;EACF,CAAA,GACJ,CAAC,YACA,qBAAC,UAAD;GACE,MAAK;GACL,eAAe;IACb,OAAO,UAAU;IACjB,YAAY,IAAI;IAChB,eAAe,QAAQ;GACzB;GACA,WAAU;GAPZ,UAAA,CASE,oBAAC,WAAD,EAAW,eAAA,KAAa,CAAA,GAAC,iBACnB;EAEP,CAAA,CAAA;;AAET"}
1
+ {"version":3,"file":"conversation.js","names":[],"sources":["../../src/components/conversation.tsx"],"sourcesContent":["import ArrowDown from '@burtson-labs/icons/react/arrow-down';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nexport interface ConversationProps extends React.ComponentProps<'div'> {\n /** Shown instead of the messages when there are none. */\n empty?: React.ReactNode;\n /** Accessible name for the message log. */\n label?: string;\n}\n\n/**\n * A scrolling message log that stays pinned to the newest message while it\n * streams, unless the reader has scrolled up; then a \"Jump to latest\" button\n * appears instead of yanking them back down.\n */\nfunction Conversation({\n children,\n empty,\n label = 'Conversation',\n className,\n ...props\n}: ConversationProps) {\n const scroller = React.useRef<HTMLDivElement>(null);\n const content = React.useRef<HTMLDivElement>(null);\n const pinned = React.useRef(true);\n const [atBottom, setAtBottom] = React.useState(true);\n const hasMessages = React.Children.toArray(children).length > 0;\n\n const scrollToBottom = React.useCallback((behavior: ScrollBehavior = 'auto') => {\n const el = scroller.current;\n if (!el) return;\n if (typeof el.scrollTo === 'function') el.scrollTo({ top: el.scrollHeight, behavior });\n else el.scrollTop = el.scrollHeight;\n }, []);\n\n React.useEffect(() => {\n const el = content.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Content grows while a reply streams; follow it only if pinned.\n const ro = new ResizeObserver(() => {\n if (pinned.current) scrollToBottom();\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [scrollToBottom]);\n\n React.useEffect(() => {\n if (pinned.current) scrollToBottom();\n }, [children, scrollToBottom]);\n\n const onScroll = () => {\n const el = scroller.current;\n if (!el) return;\n const bottom = el.scrollHeight - el.scrollTop - el.clientHeight < 32;\n pinned.current = bottom;\n setAtBottom(bottom);\n };\n\n return (\n <div data-slot=\"conversation\" className={cn('relative min-h-0', className)} {...props}>\n <div\n ref={scroller}\n role=\"log\"\n aria-label={label}\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n // A scrollable region must be reachable by keyboard (WCAG 2.1.1).\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n onScroll={onScroll}\n className=\"size-full overflow-y-auto outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20\"\n >\n <div ref={content} className=\"grid gap-5 p-4\">\n {hasMessages ? children : empty}\n </div>\n </div>\n {!atBottom && (\n <button\n type=\"button\"\n onClick={() => {\n pinned.current = true;\n setAtBottom(true);\n scrollToBottom(\n typeof matchMedia === 'function' &&\n matchMedia('(prefers-reduced-motion: reduce)').matches\n ? 'auto'\n : 'smooth',\n );\n }}\n className=\"absolute bottom-3 left-1/2 inline-flex -translate-x-1/2 animate-in items-center gap-1.5 rounded-full border border-border-strong bg-surface-raised px-3 py-1.5 text-xs font-semibold shadow-md outline-none hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/20 [&_svg]:size-3.5\"\n >\n <ArrowDown aria-hidden /> Jump to latest\n </button>\n )}\n </div>\n );\n}\n\nexport { Conversation };\n"],"mappings":";;;;;;;;;;AAiBA,SAAS,aAAa,EACpB,UACA,OACA,QAAQ,gBACR,WACA,GAAG,SACiB;CACpB,MAAM,WAAW,MAAM,OAAuB,IAAI;CAClD,MAAM,UAAU,MAAM,OAAuB,IAAI;CACjD,MAAM,SAAS,MAAM,OAAO,IAAI;CAChC,MAAM,CAAC,UAAU,eAAe,MAAM,SAAS,IAAI;CACnD,MAAM,cAAc,MAAM,SAAS,QAAQ,QAAQ,CAAC,CAAC,SAAS;CAE9D,MAAM,iBAAiB,MAAM,aAAa,WAA2B,WAAW;EAC9E,MAAM,KAAK,SAAS;EACpB,IAAI,CAAC,IAAI;EACT,IAAI,OAAO,GAAG,aAAa,YAAY,GAAG,SAAS;GAAE,KAAK,GAAG;GAAc;EAAS,CAAC;OAChF,GAAG,YAAY,GAAG;CACzB,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB;EACpB,MAAM,KAAK,QAAQ;EACnB,IAAI,CAAC,MAAM,OAAO,mBAAmB,aAAa;EAElD,MAAM,KAAK,IAAI,qBAAqB;GAClC,IAAI,OAAO,SAAS,eAAe;EACrC,CAAC;EACD,GAAG,QAAQ,EAAE;EACb,aAAa,GAAG,WAAW;CAC7B,GAAG,CAAC,cAAc,CAAC;CAEnB,MAAM,gBAAgB;EACpB,IAAI,OAAO,SAAS,eAAe;CACrC,GAAG,CAAC,UAAU,cAAc,CAAC;CAE7B,MAAM,iBAAiB;EACrB,MAAM,KAAK,SAAS;EACpB,IAAI,CAAC,IAAI;EACT,MAAM,SAAS,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;EAClE,OAAO,UAAU;EACjB,YAAY,MAAM;CACpB;CAEA,OACE,qBAAC,OAAD;EAAK,aAAU;EAAe,WAAW,GAAG,oBAAoB,SAAS;EAAG,GAAI;EAAhF,UAAA,CACE,oBAAC,OAAD;GACE,KAAK;GACL,MAAK;GACL,cAAY;GACZ,aAAU;GACV,iBAAc;GAGd,UAAU;GACA;GACV,WAAU;GAEV,UAAA,oBAAC,OAAD;IAAK,KAAK;IAAS,WAAU;IAC1B,UAAA,cAAc,WAAW;GACvB,CAAA;EACF,CAAA,GACJ,CAAC,YACA,qBAAC,UAAD;GACE,MAAK;GACL,eAAe;IACb,OAAO,UAAU;IACjB,YAAY,IAAI;IAChB,eACE,OAAO,eAAe,cACpB,WAAW,kCAAkC,CAAC,CAAC,UAC7C,SACA,QACN;GACF;GACA,WAAU;GAZZ,UAAA,CAcE,oBAAC,WAAD,EAAW,eAAA,KAAa,CAAA,GAAC,iBACnB;EAEP,CAAA,CAAA;;AAET"}
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+ import { type IconButtonProps } from './icon-button';
3
+ export interface CopyButtonProps extends Omit<IconButtonProps, 'label' | 'children' | 'onClick'> {
4
+ /** The text to put on the clipboard. */
5
+ value: string;
6
+ /** Accessible name, e.g. "Copy API key". */
7
+ label?: string;
8
+ onCopySuccess?: () => void;
9
+ onCopyError?: (error: unknown) => void;
10
+ }
11
+ /**
12
+ * Icon button that copies `value` and shows a check for a moment. When the
13
+ * browser blocks the clipboard it raises a toast (needs a mounted Toaster)
14
+ * asking the person to copy by hand.
15
+ */
16
+ declare function CopyButton({ value, label, variant, size, onCopySuccess, onCopyError, ...props }: CopyButtonProps): React.JSX.Element;
17
+ export { CopyButton };