@burtson-labs/ui 0.14.0 → 0.14.1

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 (239) hide show
  1. package/LICENSES/DEPENDENCIES.txt +1 -1
  2. package/README.md +15 -11
  3. package/dist/components/accordion.d.ts +3 -3
  4. package/dist/components/accordion.js +13 -10
  5. package/dist/components/accordion.js.map +1 -1
  6. package/dist/components/alert-dialog.d.ts +12 -10
  7. package/dist/components/alert-dialog.js +29 -20
  8. package/dist/components/alert-dialog.js.map +1 -1
  9. package/dist/components/alert.d.ts +5 -3
  10. package/dist/components/alert.js +11 -7
  11. package/dist/components/alert.js.map +1 -1
  12. package/dist/components/app-shell.d.ts +7 -6
  13. package/dist/components/app-shell.js +21 -14
  14. package/dist/components/app-shell.js.map +1 -1
  15. package/dist/components/attachment.d.ts +3 -3
  16. package/dist/components/attachment.js +10 -7
  17. package/dist/components/attachment.js.map +1 -1
  18. package/dist/components/audio-player.d.ts +2 -2
  19. package/dist/components/audio-player.js +8 -6
  20. package/dist/components/audio-player.js.map +1 -1
  21. package/dist/components/avatar.d.ts +3 -3
  22. package/dist/components/avatar.js +10 -7
  23. package/dist/components/avatar.js.map +1 -1
  24. package/dist/components/badge.d.ts +1 -1
  25. package/dist/components/badge.js +12 -10
  26. package/dist/components/badge.js.map +1 -1
  27. package/dist/components/breadcrumb.d.ts +8 -8
  28. package/dist/components/breadcrumb.js +24 -17
  29. package/dist/components/breadcrumb.js.map +1 -1
  30. package/dist/components/button.d.ts +8 -1
  31. package/dist/components/button.js +12 -3
  32. package/dist/components/button.js.map +1 -1
  33. package/dist/components/card.d.ts +8 -8
  34. package/dist/components/card.js +32 -23
  35. package/dist/components/card.js.map +1 -1
  36. package/dist/components/chat-history.d.ts +1 -1
  37. package/dist/components/chat-history.js +10 -9
  38. package/dist/components/chat-history.js.map +1 -1
  39. package/dist/components/chat-layout.d.ts +1 -1
  40. package/dist/components/chat-layout.js +7 -3
  41. package/dist/components/chat-layout.js.map +1 -1
  42. package/dist/components/checkbox-card.d.ts +2 -2
  43. package/dist/components/checkbox-card.js +9 -7
  44. package/dist/components/checkbox-card.js.map +1 -1
  45. package/dist/components/checkbox.d.ts +23 -2
  46. package/dist/components/checkbox.js +48 -6
  47. package/dist/components/checkbox.js.map +1 -1
  48. package/dist/components/collapsible.d.ts +2 -2
  49. package/dist/components/collapsible.js +7 -5
  50. package/dist/components/collapsible.js.map +1 -1
  51. package/dist/components/combobox.d.ts +14 -4
  52. package/dist/components/combobox.js +18 -8
  53. package/dist/components/combobox.js.map +1 -1
  54. package/dist/components/command.d.ts +68 -12
  55. package/dist/components/command.js +38 -25
  56. package/dist/components/command.js.map +1 -1
  57. package/dist/components/composer.d.ts +2 -2
  58. package/dist/components/composer.js +9 -7
  59. package/dist/components/composer.js.map +1 -1
  60. package/dist/components/connection-status.d.ts +3 -3
  61. package/dist/components/connection-status.js +18 -14
  62. package/dist/components/connection-status.js.map +1 -1
  63. package/dist/components/context-menu.d.ts +14 -13
  64. package/dist/components/context-menu.js +33 -23
  65. package/dist/components/context-menu.js.map +1 -1
  66. package/dist/components/conversation.d.ts +1 -1
  67. package/dist/components/conversation.js +6 -5
  68. package/dist/components/conversation.js.map +1 -1
  69. package/dist/components/copy-button.d.ts +1 -1
  70. package/dist/components/copy-button.js +3 -2
  71. package/dist/components/copy-button.js.map +1 -1
  72. package/dist/components/data-table.d.ts +7 -2
  73. package/dist/components/data-table.js +16 -42
  74. package/dist/components/data-table.js.map +1 -1
  75. package/dist/components/dialog.d.ts +18 -9
  76. package/dist/components/dialog.js +38 -22
  77. package/dist/components/dialog.js.map +1 -1
  78. package/dist/components/dropdown-menu.d.ts +14 -13
  79. package/dist/components/dropdown-menu.js +33 -23
  80. package/dist/components/dropdown-menu.js.map +1 -1
  81. package/dist/components/editor-tabs.d.ts +1 -1
  82. package/dist/components/editor-tabs.js +6 -5
  83. package/dist/components/editor-tabs.js.map +1 -1
  84. package/dist/components/empty-state.d.ts +5 -5
  85. package/dist/components/empty-state.js +16 -11
  86. package/dist/components/empty-state.js.map +1 -1
  87. package/dist/components/error-summary.d.ts +21 -0
  88. package/dist/components/error-summary.js +62 -0
  89. package/dist/components/error-summary.js.map +1 -0
  90. package/dist/components/field.d.ts +66 -7
  91. package/dist/components/field.js +151 -20
  92. package/dist/components/field.js.map +1 -1
  93. package/dist/components/form-actions.d.ts +18 -0
  94. package/dist/components/form-actions.js +30 -0
  95. package/dist/components/form-actions.js.map +1 -0
  96. package/dist/components/icon-button.d.ts +3 -1
  97. package/dist/components/icon-button.js +5 -3
  98. package/dist/components/icon-button.js.map +1 -1
  99. package/dist/components/input.d.ts +42 -7
  100. package/dist/components/input.js +53 -10
  101. package/dist/components/input.js.map +1 -1
  102. package/dist/components/kbd.d.ts +2 -2
  103. package/dist/components/kbd.js +7 -5
  104. package/dist/components/kbd.js.map +1 -1
  105. package/dist/components/label.d.ts +6 -1
  106. package/dist/components/label.js +10 -4
  107. package/dist/components/label.js.map +1 -1
  108. package/dist/components/markdown.d.ts +4 -3
  109. package/dist/components/markdown.js +7 -6
  110. package/dist/components/markdown.js.map +1 -1
  111. package/dist/components/menubar.d.ts +16 -14
  112. package/dist/components/menubar.js +39 -27
  113. package/dist/components/menubar.js.map +1 -1
  114. package/dist/components/message-actions.d.ts +3 -3
  115. package/dist/components/message-actions.js +15 -12
  116. package/dist/components/message-actions.js.map +1 -1
  117. package/dist/components/message.d.ts +1 -1
  118. package/dist/components/message.js +5 -3
  119. package/dist/components/message.js.map +1 -1
  120. package/dist/components/mobile-nav.d.ts +1 -1
  121. package/dist/components/mobile-nav.js +6 -5
  122. package/dist/components/mobile-nav.js.map +1 -1
  123. package/dist/components/native-select.d.ts +16 -0
  124. package/dist/components/native-select.js +33 -0
  125. package/dist/components/native-select.js.map +1 -0
  126. package/dist/components/navigation-menu.d.ts +12 -11
  127. package/dist/components/navigation-menu.js +32 -23
  128. package/dist/components/navigation-menu.js.map +1 -1
  129. package/dist/components/number-input.d.ts +23 -0
  130. package/dist/components/number-input.js +45 -0
  131. package/dist/components/number-input.js.map +1 -0
  132. package/dist/components/onboarding-checklist.d.ts +1 -1
  133. package/dist/components/onboarding-checklist.js +4 -3
  134. package/dist/components/onboarding-checklist.js.map +1 -1
  135. package/dist/components/page-header.d.ts +7 -6
  136. package/dist/components/page-header.js +22 -15
  137. package/dist/components/page-header.js.map +1 -1
  138. package/dist/components/pagination.d.ts +1 -1
  139. package/dist/components/pagination.js +3 -2
  140. package/dist/components/pagination.js.map +1 -1
  141. package/dist/components/popover.d.ts +13 -10
  142. package/dist/components/popover.js +23 -17
  143. package/dist/components/popover.js.map +1 -1
  144. package/dist/components/progress.d.ts +1 -1
  145. package/dist/components/progress.js +6 -5
  146. package/dist/components/progress.js.map +1 -1
  147. package/dist/components/radio-group.d.ts +22 -3
  148. package/dist/components/radio-group.js +51 -10
  149. package/dist/components/radio-group.js.map +1 -1
  150. package/dist/components/reasoning.d.ts +3 -3
  151. package/dist/components/reasoning.js +9 -7
  152. package/dist/components/reasoning.js.map +1 -1
  153. package/dist/components/scroll-area.d.ts +3 -2
  154. package/dist/components/scroll-area.js +10 -7
  155. package/dist/components/scroll-area.js.map +1 -1
  156. package/dist/components/secret-input.d.ts +3 -2
  157. package/dist/components/secret-input.js +6 -4
  158. package/dist/components/secret-input.js.map +1 -1
  159. package/dist/components/select.d.ts +11 -6
  160. package/dist/components/select.js +24 -18
  161. package/dist/components/select.js.map +1 -1
  162. package/dist/components/separator.d.ts +1 -1
  163. package/dist/components/separator.js +4 -3
  164. package/dist/components/separator.js.map +1 -1
  165. package/dist/components/sheet.d.ts +10 -8
  166. package/dist/components/sheet.js +31 -20
  167. package/dist/components/sheet.js.map +1 -1
  168. package/dist/components/skeleton.d.ts +2 -1
  169. package/dist/components/skeleton.js +6 -4
  170. package/dist/components/skeleton.js.map +1 -1
  171. package/dist/components/slider.d.ts +10 -6
  172. package/dist/components/slider.js +15 -7
  173. package/dist/components/slider.js.map +1 -1
  174. package/dist/components/source.d.ts +2 -2
  175. package/dist/components/source.js +9 -7
  176. package/dist/components/source.js.map +1 -1
  177. package/dist/components/spinner.d.ts +4 -2
  178. package/dist/components/spinner.js +4 -3
  179. package/dist/components/spinner.js.map +1 -1
  180. package/dist/components/stat-card.d.ts +27 -2
  181. package/dist/components/stat-card.js +69 -9
  182. package/dist/components/stat-card.js.map +1 -1
  183. package/dist/components/status.d.ts +7 -6
  184. package/dist/components/status.js +11 -5
  185. package/dist/components/status.js.map +1 -1
  186. package/dist/components/steps.d.ts +1 -1
  187. package/dist/components/steps.js +4 -3
  188. package/dist/components/steps.js.map +1 -1
  189. package/dist/components/switch.d.ts +46 -2
  190. package/dist/components/switch.js +85 -8
  191. package/dist/components/switch.js.map +1 -1
  192. package/dist/components/table.d.ts +29 -12
  193. package/dist/components/table.js +79 -34
  194. package/dist/components/table.js.map +1 -1
  195. package/dist/components/tabs.d.ts +9 -4
  196. package/dist/components/tabs.js +22 -13
  197. package/dist/components/tabs.js.map +1 -1
  198. package/dist/components/textarea.d.ts +6 -1
  199. package/dist/components/textarea.js +3 -3
  200. package/dist/components/textarea.js.map +1 -1
  201. package/dist/components/toast.d.ts +10 -6
  202. package/dist/components/toast.js +27 -18
  203. package/dist/components/toast.js.map +1 -1
  204. package/dist/components/toaster.d.ts +4 -2
  205. package/dist/components/toaster.js +6 -2
  206. package/dist/components/toaster.js.map +1 -1
  207. package/dist/components/tool-call.d.ts +2 -2
  208. package/dist/components/tool-call.js +10 -8
  209. package/dist/components/tool-call.js.map +1 -1
  210. package/dist/components/toolbar.d.ts +9 -4
  211. package/dist/components/toolbar.js +14 -9
  212. package/dist/components/toolbar.js.map +1 -1
  213. package/dist/components/tooltip.d.ts +3 -2
  214. package/dist/components/tooltip.js +7 -4
  215. package/dist/components/tooltip.js.map +1 -1
  216. package/dist/components/tour.js +2 -2
  217. package/dist/components/tour.js.map +1 -1
  218. package/dist/components/tree-view.d.ts +1 -1
  219. package/dist/components/tree-view.js +9 -5
  220. package/dist/components/tree-view.js.map +1 -1
  221. package/dist/components/voice-recorder.d.ts +1 -1
  222. package/dist/components/voice-recorder.js +4 -2
  223. package/dist/components/voice-recorder.js.map +1 -1
  224. package/dist/index.d.ts +6 -1
  225. package/dist/index.js +17 -12
  226. package/dist/lib/media.d.ts +18 -0
  227. package/dist/lib/media.js +34 -0
  228. package/dist/lib/media.js.map +1 -0
  229. package/dist/lib/utils.d.ts +32 -0
  230. package/dist/lib/utils.js +33 -1
  231. package/dist/lib/utils.js.map +1 -1
  232. package/dist/mui/index.js +20 -8
  233. package/dist/mui/index.js.map +1 -1
  234. package/dist/styles.css +1 -1
  235. package/dist/theme.css +36 -17
  236. package/dist/tokens.d.ts +78 -36
  237. package/dist/tokens.js +63 -32
  238. package/dist/tokens.js.map +1 -1
  239. package/package.json +5 -3
@@ -5,10 +5,10 @@ import { Progress } from "./progress.js";
5
5
  import { Spinner } from "./spinner.js";
6
6
  import * as React from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
+ import X from "@burtson-labs/icons/react/x";
8
9
  import AlertCircle from "@burtson-labs/icons/react/alert-circle";
9
10
  import FileText from "@burtson-labs/icons/react/file-text";
10
11
  import RotateCw from "@burtson-labs/icons/react/rotate-cw";
11
- import X from "@burtson-labs/icons/react/x";
12
12
  //#region src/components/attachment.tsx
13
13
  var stateText = {
14
14
  queued: "Waiting",
@@ -40,12 +40,13 @@ var InTray = React.createContext(false);
40
40
  * One attached file: name, size, state, progress, and remove/retry. State
41
41
  * changes are announced politely; a failure says why.
42
42
  */
43
- function AttachmentItem({ name, size, state = "ready", progress, error, preview, icon, onRemove, onRetry, layout = "chip", className, ...props }) {
43
+ var AttachmentItem = React.forwardRef(function AttachmentItem({ name, size, state = "ready", progress, error, preview, icon, onRemove, onRetry, layout = "chip", className, ...props }, ref) {
44
44
  const inTray = React.useContext(InTray);
45
45
  const busy = state === "uploading" || state === "parsing";
46
46
  const failed = state === "failed";
47
47
  const meta = [size !== void 0 ? formatBytes(size) : null, stateText[state]].filter(Boolean).join(" · ");
48
48
  return /* @__PURE__ */ jsxs("div", {
49
+ ref,
49
50
  "data-slot": "attachment-item",
50
51
  "data-state": state,
51
52
  "data-layout": layout,
@@ -112,12 +113,13 @@ function AttachmentItem({ name, size, state = "ready", progress, error, preview,
112
113
  })
113
114
  ]
114
115
  });
115
- }
116
+ });
116
117
  /** Attachments waiting to send, in a row that scrolls sideways when full. */
117
- function AttachmentTray({ label = "Attachments", className, children, ...props }) {
118
+ var AttachmentTray = React.forwardRef(function AttachmentTray({ label = "Attachments", className, children, ...props }, ref) {
118
119
  return /* @__PURE__ */ jsx(InTray.Provider, {
119
120
  value: true,
120
121
  children: /* @__PURE__ */ jsx("div", {
122
+ ref,
121
123
  "data-slot": "attachment-tray",
122
124
  role: "list",
123
125
  "aria-label": label,
@@ -126,14 +128,15 @@ function AttachmentTray({ label = "Attachments", className, children, ...props }
126
128
  children
127
129
  })
128
130
  });
129
- }
131
+ });
130
132
  /** A list of uploads with a summary: "2 of 4 ready, 1 failed". */
131
- function UploadQueue({ files, title = "Uploads", onRemove, onRetry, empty, className, ...props }) {
133
+ var UploadQueue = React.forwardRef(function UploadQueue({ files, title = "Uploads", onRemove, onRetry, empty, className, ...props }, ref) {
132
134
  const headingId = React.useId();
133
135
  const ready = files.filter((f) => f.state === "ready").length;
134
136
  const failed = files.filter((f) => f.state === "failed").length;
135
137
  const summary = files.length ? `${ready} of ${files.length} ready${failed ? `, ${failed} failed` : ""}` : "";
136
138
  return /* @__PURE__ */ jsxs("section", {
139
+ ref,
137
140
  "data-slot": "upload-queue",
138
141
  "aria-labelledby": headingId,
139
142
  className: cn("grid gap-2", className),
@@ -170,7 +173,7 @@ function UploadQueue({ files, title = "Uploads", onRemove, onRetry, empty, class
170
173
  })
171
174
  })]
172
175
  });
173
- }
176
+ });
174
177
  //#endregion
175
178
  export { AttachmentItem, AttachmentTray, UploadQueue, formatBytes };
176
179
 
@@ -1 +1 @@
1
- {"version":3,"file":"attachment.js","names":[],"sources":["../../src/components/attachment.tsx"],"sourcesContent":["import AlertCircle from '@burtson-labs/icons/react/alert-circle';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport RotateCw from '@burtson-labs/icons/react/rotate-cw';\nimport X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Progress } from './progress';\nimport { Spinner } from './spinner';\n\n/**\n * Where a file is on its way into a message. The app owns the upload and the\n * parsing; these components only show the state it reports.\n */\nexport type AttachmentState = 'queued' | 'uploading' | 'parsing' | 'ready' | 'failed';\n\nconst stateText: Record<AttachmentState, string> = {\n queued: 'Waiting',\n uploading: 'Uploading',\n parsing: 'Reading',\n ready: 'Ready',\n failed: 'Failed',\n};\n\n/** 1536 → \"1.5 KB\". */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\nconst InTray = React.createContext(false);\n\nexport interface AttachmentItemProps extends Omit<React.ComponentProps<'div'>, 'children'> {\n name: string;\n /** Bytes. */\n size?: number;\n state?: AttachmentState;\n /** 0–100 while uploading or reading. Leave out for an indeterminate bar. */\n progress?: number;\n /** Why it failed, in words the person can act on. */\n error?: string;\n /** A thumbnail URL for images. */\n preview?: string;\n /** Replaces the file icon. */\n icon?: React.ReactNode;\n onRemove?: () => void;\n onRetry?: () => void;\n /** `chip` sits in a composer; `row` fills an upload list. */\n layout?: 'chip' | 'row';\n}\n\n/**\n * One attached file: name, size, state, progress, and remove/retry. State\n * changes are announced politely; a failure says why.\n */\nfunction AttachmentItem({\n name,\n size,\n state = 'ready',\n progress,\n error,\n preview,\n icon,\n onRemove,\n onRetry,\n layout = 'chip',\n className,\n ...props\n}: AttachmentItemProps) {\n const inTray = React.useContext(InTray);\n const busy = state === 'uploading' || state === 'parsing';\n const failed = state === 'failed';\n const meta = [size !== undefined ? formatBytes(size) : null, stateText[state]]\n .filter(Boolean)\n .join(' · ');\n return (\n <div\n data-slot=\"attachment-item\"\n data-state={state}\n data-layout={layout}\n role={inTray ? 'listitem' : undefined}\n aria-busy={busy || undefined}\n className={cn(\n 'group/attachment relative flex min-w-0 items-center gap-2.5 rounded-md border bg-surface text-sm',\n layout === 'chip' ? 'w-60 max-w-full shrink-0 p-1.5 pr-1' : 'w-full p-2.5',\n failed && 'border-destructive/40 bg-destructive/5',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'grid shrink-0 place-items-center overflow-hidden rounded-sm bg-muted text-muted-foreground [&_svg]:size-4',\n layout === 'chip' ? 'size-9' : 'size-10',\n )}\n >\n {preview ? (\n <img src={preview} alt=\"\" className=\"size-full object-cover\" />\n ) : failed ? (\n <AlertCircle className=\"text-destructive\" aria-hidden />\n ) : busy ? (\n <Spinner className=\"size-4\" role=\"presentation\" aria-hidden />\n ) : (\n (icon ?? <FileText aria-hidden />)\n )}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate font-medium text-foreground\" title={name}>\n {name}\n </span>\n <span\n aria-live=\"polite\"\n className={cn('truncate text-xs', failed ? 'text-destructive' : 'text-muted-foreground')}\n >\n <span className=\"sr-only\">{name}: </span>\n {failed && error ? error : meta}\n </span>\n {busy && (\n <Progress value={progress} aria-label={`${stateText[state]} ${name}`} className=\"h-1\" />\n )}\n </div>\n {failed && onRetry && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Retry ${name}`}\n title=\"Retry\"\n onClick={onRetry}\n >\n <RotateCw />\n </Button>\n )}\n {onRemove && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Remove ${name}`}\n title=\"Remove\"\n onClick={onRemove}\n >\n <X />\n </Button>\n )}\n </div>\n );\n}\n\nexport interface AttachmentTrayProps extends React.ComponentProps<'div'> {\n /** Accessible name for the list. */\n label?: string;\n}\n\n/** Attachments waiting to send, in a row that scrolls sideways when full. */\nfunction AttachmentTray({\n label = 'Attachments',\n className,\n children,\n ...props\n}: AttachmentTrayProps) {\n return (\n <InTray.Provider value={true}>\n <div\n data-slot=\"attachment-tray\"\n role=\"list\"\n aria-label={label}\n className={cn('flex max-w-full gap-1.5 overflow-x-auto pb-0.5', className)}\n {...props}\n >\n {children}\n </div>\n </InTray.Provider>\n );\n}\n\nexport interface UploadQueueFile {\n id: string;\n name: string;\n size?: number;\n state: AttachmentState;\n progress?: number;\n error?: string;\n}\n\nexport interface UploadQueueProps extends Omit<React.ComponentProps<'section'>, 'title'> {\n files: UploadQueueFile[];\n title?: React.ReactNode;\n onRemove?: (id: string) => void;\n onRetry?: (id: string) => void;\n /** Shown when there are no files. */\n empty?: React.ReactNode;\n}\n\n/** A list of uploads with a summary: \"2 of 4 ready, 1 failed\". */\nfunction UploadQueue({\n files,\n title = 'Uploads',\n onRemove,\n onRetry,\n empty,\n className,\n ...props\n}: UploadQueueProps) {\n const headingId = React.useId();\n const ready = files.filter((f) => f.state === 'ready').length;\n const failed = files.filter((f) => f.state === 'failed').length;\n const summary = files.length\n ? `${ready} of ${files.length} ready${failed ? `, ${failed} failed` : ''}`\n : '';\n return (\n <section\n data-slot=\"upload-queue\"\n aria-labelledby={headingId}\n className={cn('grid gap-2', className)}\n {...props}\n >\n <div className=\"flex items-baseline justify-between gap-3\">\n <h3 id={headingId} className=\"text-sm font-semibold\">\n {title}\n </h3>\n <span className=\"text-xs text-muted-foreground tabular-nums\">{summary}</span>\n </div>\n {files.length === 0 ? (\n empty ? (\n <div className=\"text-sm text-muted-foreground\">{empty}</div>\n ) : null\n ) : (\n <InTray.Provider value={true}>\n <div role=\"list\" aria-labelledby={headingId} className=\"grid gap-1.5\">\n {files.map((f) => (\n <AttachmentItem\n key={f.id}\n layout=\"row\"\n name={f.name}\n size={f.size}\n state={f.state}\n progress={f.progress}\n error={f.error}\n onRemove={onRemove ? () => onRemove(f.id) : undefined}\n onRetry={onRetry ? () => onRetry(f.id) : undefined}\n />\n ))}\n </div>\n </InTray.Provider>\n )}\n </section>\n );\n}\n\nexport { AttachmentItem, AttachmentTray, UploadQueue };\n"],"mappings":"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"}
1
+ {"version":3,"file":"attachment.js","names":[],"sources":["../../src/components/attachment.tsx"],"sourcesContent":["import AlertCircle from '@burtson-labs/icons/react/alert-circle';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport RotateCw from '@burtson-labs/icons/react/rotate-cw';\nimport X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Progress } from './progress';\nimport { Spinner } from './spinner';\n\n/**\n * Where a file is on its way into a message. The app owns the upload and the\n * parsing; these components only show the state it reports.\n */\nexport type AttachmentState = 'queued' | 'uploading' | 'parsing' | 'ready' | 'failed';\n\nconst stateText: Record<AttachmentState, string> = {\n queued: 'Waiting',\n uploading: 'Uploading',\n parsing: 'Reading',\n ready: 'Ready',\n failed: 'Failed',\n};\n\n/** 1536 → \"1.5 KB\". */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n const units = ['KB', 'MB', 'GB', 'TB'];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\nconst InTray = React.createContext(false);\n\nexport interface AttachmentItemProps extends Omit<React.ComponentProps<'div'>, 'children'> {\n name: string;\n /** Bytes. */\n size?: number;\n state?: AttachmentState;\n /** 0–100 while uploading or reading. Leave out for an indeterminate bar. */\n progress?: number;\n /** Why it failed, in words the person can act on. */\n error?: string;\n /** A thumbnail URL for images. */\n preview?: string;\n /** Replaces the file icon. */\n icon?: React.ReactNode;\n onRemove?: () => void;\n onRetry?: () => void;\n /** `chip` sits in a composer; `row` fills an upload list. */\n layout?: 'chip' | 'row';\n}\n\n/**\n * One attached file: name, size, state, progress, and remove/retry. State\n * changes are announced politely; a failure says why.\n */\nconst AttachmentItem = React.forwardRef<HTMLDivElement, AttachmentItemProps>(\n function AttachmentItem(\n {\n name,\n size,\n state = 'ready',\n progress,\n error,\n preview,\n icon,\n onRemove,\n onRetry,\n layout = 'chip',\n className,\n ...props\n },\n ref,\n ) {\n const inTray = React.useContext(InTray);\n const busy = state === 'uploading' || state === 'parsing';\n const failed = state === 'failed';\n const meta = [size !== undefined ? formatBytes(size) : null, stateText[state]]\n .filter(Boolean)\n .join(' · ');\n return (\n <div\n ref={ref}\n data-slot=\"attachment-item\"\n data-state={state}\n data-layout={layout}\n role={inTray ? 'listitem' : undefined}\n aria-busy={busy || undefined}\n className={cn(\n 'group/attachment relative flex min-w-0 items-center gap-2.5 rounded-md border bg-surface text-sm',\n layout === 'chip' ? 'w-60 max-w-full shrink-0 p-1.5 pr-1' : 'w-full p-2.5',\n failed && 'border-destructive/40 bg-destructive/5',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'grid shrink-0 place-items-center overflow-hidden rounded-sm bg-muted text-muted-foreground [&_svg]:size-4',\n layout === 'chip' ? 'size-9' : 'size-10',\n )}\n >\n {preview ? (\n <img src={preview} alt=\"\" className=\"size-full object-cover\" />\n ) : failed ? (\n <AlertCircle className=\"text-destructive\" aria-hidden />\n ) : busy ? (\n <Spinner className=\"size-4\" role=\"presentation\" aria-hidden />\n ) : (\n (icon ?? <FileText aria-hidden />)\n )}\n </div>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate font-medium text-foreground\" title={name}>\n {name}\n </span>\n <span\n aria-live=\"polite\"\n className={cn(\n 'truncate text-xs',\n failed ? 'text-destructive' : 'text-muted-foreground',\n )}\n >\n <span className=\"sr-only\">{name}: </span>\n {failed && error ? error : meta}\n </span>\n {busy && (\n <Progress value={progress} aria-label={`${stateText[state]} ${name}`} className=\"h-1\" />\n )}\n </div>\n {failed && onRetry && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Retry ${name}`}\n title=\"Retry\"\n onClick={onRetry}\n >\n <RotateCw />\n </Button>\n )}\n {onRemove && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Remove ${name}`}\n title=\"Remove\"\n onClick={onRemove}\n >\n <X />\n </Button>\n )}\n </div>\n );\n },\n);\n\nexport interface AttachmentTrayProps extends React.ComponentProps<'div'> {\n /** Accessible name for the list. */\n label?: string;\n}\n\n/** Attachments waiting to send, in a row that scrolls sideways when full. */\nconst AttachmentTray = React.forwardRef<HTMLDivElement, AttachmentTrayProps>(\n function AttachmentTray({ label = 'Attachments', className, children, ...props }, ref) {\n return (\n <InTray.Provider value={true}>\n <div\n ref={ref}\n data-slot=\"attachment-tray\"\n role=\"list\"\n aria-label={label}\n className={cn('flex max-w-full gap-1.5 overflow-x-auto pb-0.5', className)}\n {...props}\n >\n {children}\n </div>\n </InTray.Provider>\n );\n },\n);\n\nexport interface UploadQueueFile {\n id: string;\n name: string;\n size?: number;\n state: AttachmentState;\n progress?: number;\n error?: string;\n}\n\nexport interface UploadQueueProps extends Omit<React.ComponentProps<'section'>, 'title'> {\n files: UploadQueueFile[];\n title?: React.ReactNode;\n onRemove?: (id: string) => void;\n onRetry?: (id: string) => void;\n /** Shown when there are no files. */\n empty?: React.ReactNode;\n}\n\n/** A list of uploads with a summary: \"2 of 4 ready, 1 failed\". */\nconst UploadQueue = React.forwardRef<HTMLElement, UploadQueueProps>(function UploadQueue(\n { files, title = 'Uploads', onRemove, onRetry, empty, className, ...props },\n ref,\n) {\n const headingId = React.useId();\n const ready = files.filter((f) => f.state === 'ready').length;\n const failed = files.filter((f) => f.state === 'failed').length;\n const summary = files.length\n ? `${ready} of ${files.length} ready${failed ? `, ${failed} failed` : ''}`\n : '';\n return (\n <section\n ref={ref}\n data-slot=\"upload-queue\"\n aria-labelledby={headingId}\n className={cn('grid gap-2', className)}\n {...props}\n >\n <div className=\"flex items-baseline justify-between gap-3\">\n <h3 id={headingId} className=\"text-sm font-semibold\">\n {title}\n </h3>\n <span className=\"text-xs text-muted-foreground tabular-nums\">{summary}</span>\n </div>\n {files.length === 0 ? (\n empty ? (\n <div className=\"text-sm text-muted-foreground\">{empty}</div>\n ) : null\n ) : (\n <InTray.Provider value={true}>\n <div role=\"list\" aria-labelledby={headingId} className=\"grid gap-1.5\">\n {files.map((f) => (\n <AttachmentItem\n key={f.id}\n layout=\"row\"\n name={f.name}\n size={f.size}\n state={f.state}\n progress={f.progress}\n error={f.error}\n onRemove={onRemove ? () => onRemove(f.id) : undefined}\n onRetry={onRetry ? () => onRetry(f.id) : undefined}\n />\n ))}\n </div>\n </InTray.Provider>\n )}\n </section>\n );\n});\n\nexport { AttachmentItem, AttachmentTray, UploadQueue };\n"],"mappings":"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"}
@@ -33,7 +33,7 @@ export interface AudioPlayerProps extends Omit<React.ComponentProps<'div'>, 'tit
33
33
  * time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a
34
34
  * slider for screen readers.
35
35
  */
36
- declare function AudioPlayer({ src, peaks, title, duration: knownDuration, transcript, downloadName, variant, onPlayChange, className, ...props }: AudioPlayerProps): React.JSX.Element;
36
+ declare const AudioPlayer: React.ForwardRefExoticComponent<Omit<AudioPlayerProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
37
37
  /** A voice note inside a message: the compact AudioPlayer. */
38
- declare function VoiceMessage(props: Omit<AudioPlayerProps, 'variant'>): React.JSX.Element;
38
+ declare const VoiceMessage: React.ForwardRefExoticComponent<Omit<Omit<AudioPlayerProps, "variant">, "ref"> & React.RefAttributes<HTMLDivElement>>;
39
39
  export { AudioPlayer, VoiceMessage };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { cn } from "../lib/utils.js";
2
+ import { cn, focusRingClasses } from "../lib/utils.js";
3
3
  import { Button } from "./button.js";
4
4
  import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -63,7 +63,7 @@ var SPEEDS = [
63
63
  * time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a
64
64
  * slider for screen readers.
65
65
  */
66
- function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, transcript, downloadName, variant = "default", onPlayChange, className, ...props }) {
66
+ var AudioPlayer = React.forwardRef(function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, transcript, downloadName, variant = "default", onPlayChange, className, ...props }, ref) {
67
67
  const audio = React.useRef(null);
68
68
  const [playing, setPlaying] = React.useState(false);
69
69
  const [current, setCurrent] = React.useState(0);
@@ -117,6 +117,7 @@ function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, tra
117
117
  };
118
118
  const bars = peaks && peaks.length ? peaks : null;
119
119
  return /* @__PURE__ */ jsxs("div", {
120
+ ref,
120
121
  "data-slot": "audio-player",
121
122
  "data-variant": variant,
122
123
  className: cn("grid min-w-0 gap-2 rounded-lg border bg-surface text-foreground", compact ? "w-full max-w-sm p-2" : "p-3", className),
@@ -169,7 +170,7 @@ function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, tra
169
170
  if (!rect.width || !total) return;
170
171
  seek((e.clientX - rect.left) / rect.width * total);
171
172
  },
172
- className: cn("relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20", compact ? "h-7" : "h-9"),
173
+ className: cn("relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm", focusRingClasses, compact ? "h-7" : "h-9", "pointer-coarse:h-11"),
173
174
  children: bars ? /* @__PURE__ */ jsx("div", {
174
175
  "aria-hidden": true,
175
176
  className: "flex h-full w-full items-center gap-[2px]",
@@ -236,15 +237,16 @@ function AudioPlayer({ src, peaks, title = "Audio", duration: knownDuration, tra
236
237
  })
237
238
  ]
238
239
  });
239
- }
240
+ });
240
241
  /** A voice note inside a message: the compact AudioPlayer. */
241
- function VoiceMessage(props) {
242
+ var VoiceMessage = React.forwardRef(function VoiceMessage(props, ref) {
242
243
  return /* @__PURE__ */ jsx(AudioPlayer, {
244
+ ref,
243
245
  title: "Voice message",
244
246
  ...props,
245
247
  variant: "compact"
246
248
  });
247
- }
249
+ });
248
250
  //#endregion
249
251
  export { AudioPlayer, VoiceMessage, computePeaks, formatDuration, peaksFromChannelData };
250
252
 
@@ -1 +1 @@
1
- {"version":3,"file":"audio-player.js","names":[],"sources":["../../src/components/audio-player.tsx"],"sourcesContent":["import Download from '@burtson-labs/icons/react/download';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\n\n/** 83.4 → \"1:23\"; 3723 → \"1:02:03\". */\nexport function formatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00';\n const s = Math.floor(seconds);\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const sec = String(s % 60).padStart(2, '0');\n return h ? `${h}:${String(m).padStart(2, '0')}:${sec}` : `${m}:${sec}`;\n}\n\n/**\n * Reduce decoded audio to `bars` peak heights between 0 and 1 (the loudest\n * sample in each slice, normalised to the loudest slice).\n */\nexport function peaksFromChannelData(data: Float32Array, bars = 48): number[] {\n if (!data.length || bars < 1) return [];\n const size = Math.max(1, Math.floor(data.length / bars));\n const peaks: number[] = [];\n for (let b = 0; b < bars; b++) {\n let max = 0;\n const end = Math.min(data.length, (b + 1) * size);\n for (let i = b * size; i < end; i++) {\n const v = Math.abs(data[i] ?? 0);\n if (v > max) max = v;\n }\n peaks.push(max);\n }\n const top = Math.max(...peaks) || 1;\n return peaks.map((p) => p / top);\n}\n\n/**\n * Decode an audio file or URL with Web Audio and return its peaks for\n * AudioPlayer. Browser only; for long files compute peaks on the server.\n */\nexport async function computePeaks(source: Blob | string, bars = 48): Promise<number[]> {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio is not available in this browser.');\n const buf =\n typeof source === 'string'\n ? await (await fetch(source)).arrayBuffer()\n : await source.arrayBuffer();\n const ctx = new Ctx();\n try {\n const audio = await ctx.decodeAudioData(buf);\n return peaksFromChannelData(audio.getChannelData(0), bars);\n } finally {\n void ctx.close();\n }\n}\n\nconst SPEEDS = [1, 1.5, 2] as const;\n\nexport interface AudioPlayerProps extends Omit<React.ComponentProps<'div'>, 'title'> {\n src: string;\n /** Waveform heights 0–1 (see computePeaks). Leave out for a plain progress bar. */\n peaks?: number[];\n /** Name read out with the controls, e.g. \"Voice note from Dana\". */\n title?: string;\n /** Known length in seconds, shown before the file has loaded. */\n duration?: number;\n /** Text of the recording; adds a Transcript toggle. */\n transcript?: React.ReactNode;\n /** Adds a download link with this file name. */\n downloadName?: string;\n /** `compact` fits inside a message bubble. */\n variant?: 'default' | 'compact';\n onPlayChange?: (playing: boolean) => void;\n}\n\n/**\n * Plays one recording: play and pause, a waveform you can click or drive\n * with the arrow keys (5 seconds a step, Home and End), elapsed and total\n * time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a\n * slider for screen readers.\n */\nfunction AudioPlayer({\n src,\n peaks,\n title = 'Audio',\n duration: knownDuration,\n transcript,\n downloadName,\n variant = 'default',\n onPlayChange,\n className,\n ...props\n}: AudioPlayerProps) {\n const audio = React.useRef<HTMLAudioElement>(null);\n const [playing, setPlaying] = React.useState(false);\n const [current, setCurrent] = React.useState(0);\n const [duration, setDuration] = React.useState(knownDuration ?? 0);\n const [speed, setSpeed] = React.useState<(typeof SPEEDS)[number]>(1);\n const [showTranscript, setShowTranscript] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const transcriptId = React.useId();\n const compact = variant === 'compact';\n const total = duration || knownDuration || 0;\n const ratio = total ? Math.min(1, current / total) : 0;\n\n React.useEffect(() => {\n if (audio.current) audio.current.playbackRate = speed;\n }, [speed]);\n\n const seek = (t: number) => {\n const el = audio.current;\n const clamped = Math.max(0, Math.min(total || t, t));\n if (el) el.currentTime = clamped;\n setCurrent(clamped);\n };\n\n const togglePlay = async () => {\n const el = audio.current;\n if (!el) return;\n if (el.paused) {\n try {\n await el.play();\n } catch {\n setFailed(true);\n }\n } else el.pause();\n };\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n const step = { ArrowRight: 5, ArrowUp: 5, ArrowLeft: -5, ArrowDown: -5 }[e.key];\n if (step !== undefined) {\n e.preventDefault();\n seek(current + step);\n } else if (e.key === 'Home') {\n e.preventDefault();\n seek(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n seek(total);\n } else if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n void togglePlay();\n }\n };\n\n const bars = peaks && peaks.length ? peaks : null;\n\n return (\n <div\n data-slot=\"audio-player\"\n data-variant={variant}\n className={cn(\n 'grid min-w-0 gap-2 rounded-lg border bg-surface text-foreground',\n compact ? 'w-full max-w-sm p-2' : 'p-3',\n className,\n )}\n {...props}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption -- the transcript prop is the text alternative */}\n <audio\n ref={audio}\n src={src}\n preload=\"metadata\"\n onLoadedMetadata={(e) => {\n const d = e.currentTarget.duration;\n if (Number.isFinite(d)) setDuration(d);\n }}\n onTimeUpdate={(e) => setCurrent(e.currentTarget.currentTime)}\n onPlay={() => {\n setPlaying(true);\n onPlayChange?.(true);\n }}\n onPause={() => {\n setPlaying(false);\n onPlayChange?.(false);\n }}\n onEnded={() => setCurrent(0)}\n onError={() => setFailed(true)}\n />\n <div className=\"flex min-w-0 items-center gap-2\">\n <Button\n type=\"button\"\n size={compact ? 'icon-sm' : 'icon'}\n variant={playing ? 'secondary' : 'default'}\n aria-label={playing ? `Pause ${title}` : `Play ${title}`}\n onClick={() => void togglePlay()}\n disabled={failed}\n className=\"shrink-0 rounded-full pointer-coarse:size-11\"\n >\n {playing ? <Pause className=\"fill-current\" /> : <Play className=\"fill-current\" />}\n </Button>\n <div\n role=\"slider\"\n tabIndex={failed ? -1 : 0}\n aria-label={`Seek ${title}`}\n aria-valuemin={0}\n aria-valuemax={Math.round(total)}\n aria-valuenow={Math.round(current)}\n aria-valuetext={`${formatDuration(current)} of ${formatDuration(total)}`}\n onKeyDown={onKeyDown}\n onClick={(e) => {\n const rect = e.currentTarget.getBoundingClientRect();\n if (!rect.width || !total) return;\n seek(((e.clientX - rect.left) / rect.width) * total);\n }}\n className={cn(\n 'relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20',\n compact ? 'h-7' : 'h-9',\n )}\n >\n {bars ? (\n <div aria-hidden className=\"flex h-full w-full items-center gap-[2px]\">\n {bars.map((p, i) => (\n <span\n key={i}\n className={cn(\n 'min-w-[2px] flex-1 rounded-full transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)]',\n (i + 0.5) / bars.length <= ratio ? 'bg-brand' : 'bg-border-strong',\n )}\n style={{ height: `${Math.max(12, Math.round(p * 100))}%` }}\n />\n ))}\n </div>\n ) : (\n <div aria-hidden className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full rounded-full bg-brand\" style={{ width: `${ratio * 100}%` }} />\n </div>\n )}\n </div>\n <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums\">\n {playing || current ? formatDuration(current) : formatDuration(total)}\n </span>\n </div>\n {failed ? (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n This recording could not be played.\n </p>\n ) : (\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={`Playback speed ${speed}×`}\n onClick={() => setSpeed(SPEEDS[(SPEEDS.indexOf(speed) + 1) % SPEEDS.length] ?? 1)}\n className=\"font-mono tabular-nums\"\n >\n {speed}×\n </Button>\n {transcript && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-expanded={showTranscript}\n aria-controls={transcriptId}\n onClick={() => setShowTranscript((v) => !v)}\n >\n <FileText /> Transcript\n </Button>\n )}\n {downloadName && (\n <Button variant=\"ghost\" size=\"xs\" asChild>\n <a href={src} download={downloadName}>\n <Download /> Download\n </a>\n </Button>\n )}\n </div>\n )}\n {transcript && showTranscript && (\n <div\n id={transcriptId}\n className=\"animate-in rounded-md bg-muted/60 p-2.5 text-sm leading-6 text-muted-foreground\"\n >\n {transcript}\n </div>\n )}\n </div>\n );\n}\n\n/** A voice note inside a message: the compact AudioPlayer. */\nfunction VoiceMessage(props: Omit<AudioPlayerProps, 'variant'>) {\n return <AudioPlayer title=\"Voice message\" {...props} variant=\"compact\" />;\n}\n\nexport { AudioPlayer, VoiceMessage };\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,eAAe,SAAyB;CACtD,IAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG,OAAO;CACrD,MAAM,IAAI,KAAK,MAAM,OAAO;CAC5B,MAAM,IAAI,KAAK,MAAM,IAAI,IAAI;CAC7B,MAAM,IAAI,KAAK,MAAO,IAAI,OAAQ,EAAE;CACpC,MAAM,MAAM,OAAO,IAAI,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CAC1C,OAAO,IAAI,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,QAAQ,GAAG,EAAE,GAAG;AACnE;;;;;AAMA,SAAgB,qBAAqB,MAAoB,OAAO,IAAc;CAC5E,IAAI,CAAC,KAAK,UAAU,OAAO,GAAG,OAAO,CAAC;CACtC,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,SAAS,IAAI,CAAC;CACvD,MAAM,QAAkB,CAAC;CACzB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK;EAC7B,IAAI,MAAM;EACV,MAAM,MAAM,KAAK,IAAI,KAAK,SAAS,IAAI,KAAK,IAAI;EAChD,KAAK,IAAI,IAAI,IAAI,MAAM,IAAI,KAAK,KAAK;GACnC,MAAM,IAAI,KAAK,IAAI,KAAK,MAAM,CAAC;GAC/B,IAAI,IAAI,KAAK,MAAM;EACrB;EACA,MAAM,KAAK,GAAG;CAChB;CACA,MAAM,MAAM,KAAK,IAAI,GAAG,KAAK,KAAK;CAClC,OAAO,MAAM,KAAK,MAAM,IAAI,GAAG;AACjC;;;;;AAMA,eAAsB,aAAa,QAAuB,OAAO,IAAuB;CACtF,MAAM,MACJ,WAAW,gBACV,WAAuE;CAC1E,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,6CAA6C;CACvE,MAAM,MACJ,OAAO,WAAW,WACd,OAAO,MAAM,MAAM,MAAM,EAAA,CAAG,YAAY,IACxC,MAAM,OAAO,YAAY;CAC/B,MAAM,MAAM,IAAI,IAAI;CACpB,IAAI;EAEF,OAAO,sBAAqB,MADR,IAAI,gBAAgB,GAAG,EAAA,CACT,eAAe,CAAC,GAAG,IAAI;CAC3D,UAAU;EACR,IAAS,MAAM;CACjB;AACF;AAEA,IAAM,SAAS;CAAC;CAAG;CAAK;AAAC;;;;;;;AAyBzB,SAAS,YAAY,EACnB,KACA,OACA,QAAQ,SACR,UAAU,eACV,YACA,cACA,UAAU,WACV,cACA,WACA,GAAG,SACgB;CACnB,MAAM,QAAQ,MAAM,OAAyB,IAAI;CACjD,MAAM,CAAC,SAAS,cAAc,MAAM,SAAS,KAAK;CAClD,MAAM,CAAC,SAAS,cAAc,MAAM,SAAS,CAAC;CAC9C,MAAM,CAAC,UAAU,eAAe,MAAM,SAAS,iBAAiB,CAAC;CACjE,MAAM,CAAC,OAAO,YAAY,MAAM,SAAkC,CAAC;CACnE,MAAM,CAAC,gBAAgB,qBAAqB,MAAM,SAAS,KAAK;CAChE,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,KAAK;CAChD,MAAM,eAAe,MAAM,MAAM;CACjC,MAAM,UAAU,YAAY;CAC5B,MAAM,QAAQ,YAAY,iBAAiB;CAC3C,MAAM,QAAQ,QAAQ,KAAK,IAAI,GAAG,UAAU,KAAK,IAAI;CAErD,MAAM,gBAAgB;EACpB,IAAI,MAAM,SAAS,MAAM,QAAQ,eAAe;CAClD,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,QAAQ,MAAc;EAC1B,MAAM,KAAK,MAAM;EACjB,MAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,GAAG,CAAC,CAAC;EACnD,IAAI,IAAI,GAAG,cAAc;EACzB,WAAW,OAAO;CACpB;CAEA,MAAM,aAAa,YAAY;EAC7B,MAAM,KAAK,MAAM;EACjB,IAAI,CAAC,IAAI;EACT,IAAI,GAAG,QACL,IAAI;GACF,MAAM,GAAG,KAAK;EAChB,QAAQ;GACN,UAAU,IAAI;EAChB;OACK,GAAG,MAAM;CAClB;CAEA,MAAM,aAAa,MAA2B;EAC5C,MAAM,OAAO;GAAE,YAAY;GAAG,SAAS;GAAG,WAAW;GAAI,WAAW;EAAG,EAAE,EAAE;EAC3E,IAAI,SAAS,KAAA,GAAW;GACtB,EAAE,eAAe;GACjB,KAAK,UAAU,IAAI;EACrB,OAAO,IAAI,EAAE,QAAQ,QAAQ;GAC3B,EAAE,eAAe;GACjB,KAAK,CAAC;EACR,OAAO,IAAI,EAAE,QAAQ,OAAO;GAC1B,EAAE,eAAe;GACjB,KAAK,KAAK;EACZ,OAAO,IAAI,EAAE,QAAQ,OAAO,EAAE,QAAQ,SAAS;GAC7C,EAAE,eAAe;GACjB,WAAgB;EAClB;CACF;CAEA,MAAM,OAAO,SAAS,MAAM,SAAS,QAAQ;CAE7C,OACE,qBAAC,OAAD;EACE,aAAU;EACV,gBAAc;EACd,WAAW,GACT,mEACA,UAAU,wBAAwB,OAClC,SACF;EACA,GAAI;EARN,UAAA;GAWE,oBAAC,SAAD;IACE,KAAK;IACA;IACL,SAAQ;IACR,mBAAmB,MAAM;KACvB,MAAM,IAAI,EAAE,cAAc;KAC1B,IAAI,OAAO,SAAS,CAAC,GAAG,YAAY,CAAC;IACvC;IACA,eAAe,MAAM,WAAW,EAAE,cAAc,WAAW;IAC3D,cAAc;KACZ,WAAW,IAAI;KACf,eAAe,IAAI;IACrB;IACA,eAAe;KACb,WAAW,KAAK;KAChB,eAAe,KAAK;IACtB;IACA,eAAe,WAAW,CAAC;IAC3B,eAAe,UAAU,IAAI;GAC9B,CAAA;GACD,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,QAAD;MACE,MAAK;MACL,MAAM,UAAU,YAAY;MAC5B,SAAS,UAAU,cAAc;MACjC,cAAY,UAAU,SAAS,UAAU,QAAQ;MACjD,eAAe,KAAK,WAAW;MAC/B,UAAU;MACV,WAAU;MAET,UAAA,UAAU,oBAAC,OAAD,EAAO,WAAU,eAAgB,CAAA,IAAI,oBAAC,MAAD,EAAM,WAAU,eAAgB,CAAA;KAC1E,CAAA;KACR,oBAAC,OAAD;MACE,MAAK;MACL,UAAU,SAAS,KAAK;MACxB,cAAY,QAAQ;MACpB,iBAAe;MACf,iBAAe,KAAK,MAAM,KAAK;MAC/B,iBAAe,KAAK,MAAM,OAAO;MACjC,kBAAgB,GAAG,eAAe,OAAO,EAAE,MAAM,eAAe,KAAK;MAC1D;MACX,UAAU,MAAM;OACd,MAAM,OAAO,EAAE,cAAc,sBAAsB;OACnD,IAAI,CAAC,KAAK,SAAS,CAAC,OAAO;OAC3B,MAAO,EAAE,UAAU,KAAK,QAAQ,KAAK,QAAS,KAAK;MACrD;MACA,WAAW,GACT,wIACA,UAAU,QAAQ,KACpB;MAEC,UAAA,OACC,oBAAC,OAAD;OAAK,eAAA;OAAY,WAAU;OACxB,UAAA,KAAK,KAAK,GAAG,MACZ,oBAAC,QAAD;QAEE,WAAW,GACT,kHACC,IAAI,MAAO,KAAK,UAAU,QAAQ,aAAa,kBAClD;QACA,OAAO,EAAE,QAAQ,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,EAAE,GAAG;OAC1D,GANM,CAMN,CACF;MACE,CAAA,IAEL,oBAAC,OAAD;OAAK,eAAA;OAAY,WAAU;OACzB,UAAA,oBAAC,OAAD;QAAK,WAAU;QAA+B,OAAO,EAAE,OAAO,GAAG,QAAQ,IAAI,GAAG;OAAI,CAAA;MACjF,CAAA;KAEJ,CAAA;KACL,oBAAC,QAAD;MAAM,WAAU;MACb,UAAA,WAAW,UAAU,eAAe,OAAO,IAAI,eAAe,KAAK;KAChE,CAAA;IACH;;GACJ,SACC,oBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;IAA2B,UAAA;GAElD,CAAA,IAEH,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,qBAAC,QAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,cAAY,kBAAkB,MAAM;MACpC,eAAe,SAAS,QAAQ,OAAO,QAAQ,KAAK,IAAI,KAAK,OAAO,WAAW,CAAC;MAChF,WAAU;MANZ,UAAA,CAQG,OAAM,GACD;;KACP,cACC,qBAAC,QAAD;MACE,MAAK;MACL,SAAQ;MACR,MAAK;MACL,iBAAe;MACf,iBAAe;MACf,eAAe,mBAAmB,MAAM,CAAC,CAAC;MAN5C,UAAA,CAQE,oBAAC,UAAD,CAAW,CAAA,GAAC,aACN;;KAET,gBACC,oBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,SAAA;MAChC,UAAA,qBAAC,KAAD;OAAG,MAAM;OAAK,UAAU;OAAxB,UAAA,CACE,oBAAC,UAAD,CAAW,CAAA,GAAC,WACX;;KACG,CAAA;IAEP;;GAEN,cAAc,kBACb,oBAAC,OAAD;IACE,IAAI;IACJ,WAAU;IAET,UAAA;GACE,CAAA;EAEJ;;AAET;;AAGA,SAAS,aAAa,OAA0C;CAC9D,OAAO,oBAAC,aAAD;EAAa,OAAM;EAAgB,GAAI;EAAO,SAAQ;CAAW,CAAA;AAC1E"}
1
+ {"version":3,"file":"audio-player.js","names":[],"sources":["../../src/components/audio-player.tsx"],"sourcesContent":["import Download from '@burtson-labs/icons/react/download';\nimport FileText from '@burtson-labs/icons/react/file-text';\nimport Pause from '@burtson-labs/icons/react/pause';\nimport Play from '@burtson-labs/icons/react/play';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses } from '../lib/utils';\n\nimport { Button } from './button';\n\n/** 83.4 → \"1:23\"; 3723 → \"1:02:03\". */\nexport function formatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00';\n const s = Math.floor(seconds);\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const sec = String(s % 60).padStart(2, '0');\n return h ? `${h}:${String(m).padStart(2, '0')}:${sec}` : `${m}:${sec}`;\n}\n\n/**\n * Reduce decoded audio to `bars` peak heights between 0 and 1 (the loudest\n * sample in each slice, normalised to the loudest slice).\n */\nexport function peaksFromChannelData(data: Float32Array, bars = 48): number[] {\n if (!data.length || bars < 1) return [];\n const size = Math.max(1, Math.floor(data.length / bars));\n const peaks: number[] = [];\n for (let b = 0; b < bars; b++) {\n let max = 0;\n const end = Math.min(data.length, (b + 1) * size);\n for (let i = b * size; i < end; i++) {\n const v = Math.abs(data[i] ?? 0);\n if (v > max) max = v;\n }\n peaks.push(max);\n }\n const top = Math.max(...peaks) || 1;\n return peaks.map((p) => p / top);\n}\n\n/**\n * Decode an audio file or URL with Web Audio and return its peaks for\n * AudioPlayer. Browser only; for long files compute peaks on the server.\n */\nexport async function computePeaks(source: Blob | string, bars = 48): Promise<number[]> {\n const Ctx =\n globalThis.AudioContext ??\n (globalThis as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctx) throw new Error('Web Audio is not available in this browser.');\n const buf =\n typeof source === 'string'\n ? await (await fetch(source)).arrayBuffer()\n : await source.arrayBuffer();\n const ctx = new Ctx();\n try {\n const audio = await ctx.decodeAudioData(buf);\n return peaksFromChannelData(audio.getChannelData(0), bars);\n } finally {\n void ctx.close();\n }\n}\n\nconst SPEEDS = [1, 1.5, 2] as const;\n\nexport interface AudioPlayerProps extends Omit<React.ComponentProps<'div'>, 'title'> {\n src: string;\n /** Waveform heights 0–1 (see computePeaks). Leave out for a plain progress bar. */\n peaks?: number[];\n /** Name read out with the controls, e.g. \"Voice note from Dana\". */\n title?: string;\n /** Known length in seconds, shown before the file has loaded. */\n duration?: number;\n /** Text of the recording; adds a Transcript toggle. */\n transcript?: React.ReactNode;\n /** Adds a download link with this file name. */\n downloadName?: string;\n /** `compact` fits inside a message bubble. */\n variant?: 'default' | 'compact';\n onPlayChange?: (playing: boolean) => void;\n}\n\n/**\n * Plays one recording: play and pause, a waveform you can click or drive\n * with the arrow keys (5 seconds a step, Home and End), elapsed and total\n * time, speed (1×, 1.5×, 2×), download and a transcript. The waveform is a\n * slider for screen readers.\n */\nconst AudioPlayer = React.forwardRef<HTMLDivElement, AudioPlayerProps>(function AudioPlayer(\n {\n src,\n peaks,\n title = 'Audio',\n duration: knownDuration,\n transcript,\n downloadName,\n variant = 'default',\n onPlayChange,\n className,\n ...props\n },\n ref,\n) {\n const audio = React.useRef<HTMLAudioElement>(null);\n const [playing, setPlaying] = React.useState(false);\n const [current, setCurrent] = React.useState(0);\n const [duration, setDuration] = React.useState(knownDuration ?? 0);\n const [speed, setSpeed] = React.useState<(typeof SPEEDS)[number]>(1);\n const [showTranscript, setShowTranscript] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const transcriptId = React.useId();\n const compact = variant === 'compact';\n const total = duration || knownDuration || 0;\n const ratio = total ? Math.min(1, current / total) : 0;\n\n React.useEffect(() => {\n if (audio.current) audio.current.playbackRate = speed;\n }, [speed]);\n\n const seek = (t: number) => {\n const el = audio.current;\n const clamped = Math.max(0, Math.min(total || t, t));\n if (el) el.currentTime = clamped;\n setCurrent(clamped);\n };\n\n const togglePlay = async () => {\n const el = audio.current;\n if (!el) return;\n if (el.paused) {\n try {\n await el.play();\n } catch {\n setFailed(true);\n }\n } else el.pause();\n };\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n const step = { ArrowRight: 5, ArrowUp: 5, ArrowLeft: -5, ArrowDown: -5 }[e.key];\n if (step !== undefined) {\n e.preventDefault();\n seek(current + step);\n } else if (e.key === 'Home') {\n e.preventDefault();\n seek(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n seek(total);\n } else if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n void togglePlay();\n }\n };\n\n const bars = peaks && peaks.length ? peaks : null;\n\n return (\n <div\n ref={ref}\n data-slot=\"audio-player\"\n data-variant={variant}\n className={cn(\n 'grid min-w-0 gap-2 rounded-lg border bg-surface text-foreground',\n compact ? 'w-full max-w-sm p-2' : 'p-3',\n className,\n )}\n {...props}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption -- the transcript prop is the text alternative */}\n <audio\n ref={audio}\n src={src}\n preload=\"metadata\"\n onLoadedMetadata={(e) => {\n const d = e.currentTarget.duration;\n if (Number.isFinite(d)) setDuration(d);\n }}\n onTimeUpdate={(e) => setCurrent(e.currentTarget.currentTime)}\n onPlay={() => {\n setPlaying(true);\n onPlayChange?.(true);\n }}\n onPause={() => {\n setPlaying(false);\n onPlayChange?.(false);\n }}\n onEnded={() => setCurrent(0)}\n onError={() => setFailed(true)}\n />\n <div className=\"flex min-w-0 items-center gap-2\">\n <Button\n type=\"button\"\n size={compact ? 'icon-sm' : 'icon'}\n variant={playing ? 'secondary' : 'default'}\n aria-label={playing ? `Pause ${title}` : `Play ${title}`}\n onClick={() => void togglePlay()}\n disabled={failed}\n className=\"shrink-0 rounded-full pointer-coarse:size-11\"\n >\n {playing ? <Pause className=\"fill-current\" /> : <Play className=\"fill-current\" />}\n </Button>\n <div\n role=\"slider\"\n tabIndex={failed ? -1 : 0}\n aria-label={`Seek ${title}`}\n aria-valuemin={0}\n aria-valuemax={Math.round(total)}\n aria-valuenow={Math.round(current)}\n aria-valuetext={`${formatDuration(current)} of ${formatDuration(total)}`}\n onKeyDown={onKeyDown}\n onClick={(e) => {\n const rect = e.currentTarget.getBoundingClientRect();\n if (!rect.width || !total) return;\n seek(((e.clientX - rect.left) / rect.width) * total);\n }}\n className={cn(\n 'relative flex min-w-0 flex-1 cursor-pointer items-center rounded-sm',\n focusRingClasses,\n compact ? 'h-7' : 'h-9',\n 'pointer-coarse:h-11',\n )}\n >\n {bars ? (\n <div aria-hidden className=\"flex h-full w-full items-center gap-[2px]\">\n {bars.map((p, i) => (\n <span\n key={i}\n className={cn(\n 'min-w-[2px] flex-1 rounded-full transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)]',\n (i + 0.5) / bars.length <= ratio ? 'bg-brand' : 'bg-border-strong',\n )}\n style={{ height: `${Math.max(12, Math.round(p * 100))}%` }}\n />\n ))}\n </div>\n ) : (\n <div aria-hidden className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full rounded-full bg-brand\" style={{ width: `${ratio * 100}%` }} />\n </div>\n )}\n </div>\n <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums\">\n {playing || current ? formatDuration(current) : formatDuration(total)}\n </span>\n </div>\n {failed ? (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n This recording could not be played.\n </p>\n ) : (\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={`Playback speed ${speed}×`}\n onClick={() => setSpeed(SPEEDS[(SPEEDS.indexOf(speed) + 1) % SPEEDS.length] ?? 1)}\n className=\"font-mono tabular-nums\"\n >\n {speed}×\n </Button>\n {transcript && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-expanded={showTranscript}\n aria-controls={transcriptId}\n onClick={() => setShowTranscript((v) => !v)}\n >\n <FileText /> Transcript\n </Button>\n )}\n {downloadName && (\n <Button variant=\"ghost\" size=\"xs\" asChild>\n <a href={src} download={downloadName}>\n <Download /> Download\n </a>\n </Button>\n )}\n </div>\n )}\n {transcript && showTranscript && (\n <div\n id={transcriptId}\n className=\"animate-in rounded-md bg-muted/60 p-2.5 text-sm leading-6 text-muted-foreground\"\n >\n {transcript}\n </div>\n )}\n </div>\n );\n});\n\n/** A voice note inside a message: the compact AudioPlayer. */\nconst VoiceMessage = React.forwardRef<HTMLDivElement, Omit<AudioPlayerProps, 'variant'>>(\n function VoiceMessage(props, ref) {\n return <AudioPlayer ref={ref} title=\"Voice message\" {...props} variant=\"compact\" />;\n },\n);\n\nexport { AudioPlayer, VoiceMessage };\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import * as AvatarPrimitive from '../primitives/vendor/radix/react-avatar';
3
- declare function Avatar({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root>): React.JSX.Element;
4
- declare function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element;
5
- declare function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>): React.JSX.Element;
3
+ declare const Avatar: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
4
+ declare const AvatarImage: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarImageProps & React.RefAttributes<HTMLImageElement>, "ref"> & React.RefAttributes<HTMLImageElement>>;
5
+ declare const AvatarFallback: React.ForwardRefExoticComponent<Omit<AvatarPrimitive.AvatarFallbackProps & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
6
6
  export { Avatar, AvatarFallback, AvatarImage };
@@ -1,30 +1,33 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { Avatar as Avatar$1, AvatarFallback as AvatarFallback$1, AvatarImage as AvatarImage$1 } from "../primitives/vendor/radix/react-avatar/avatar.js";
4
- import "react";
4
+ import * as React from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/components/avatar.tsx
7
- function Avatar({ className, ...props }) {
7
+ var Avatar = React.forwardRef(function Avatar({ className, ...props }, ref) {
8
8
  return /* @__PURE__ */ jsx(Avatar$1, {
9
+ ref,
9
10
  "data-slot": "avatar",
10
11
  className: cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className),
11
12
  ...props
12
13
  });
13
- }
14
- function AvatarImage({ className, ...props }) {
14
+ });
15
+ var AvatarImage = React.forwardRef(function AvatarImage({ className, ...props }, ref) {
15
16
  return /* @__PURE__ */ jsx(AvatarImage$1, {
17
+ ref,
16
18
  "data-slot": "avatar-image",
17
19
  className: cn("aspect-square size-full object-cover", className),
18
20
  ...props
19
21
  });
20
- }
21
- function AvatarFallback({ className, ...props }) {
22
+ });
23
+ var AvatarFallback = React.forwardRef(function AvatarFallback({ className, ...props }, ref) {
22
24
  return /* @__PURE__ */ jsx(AvatarFallback$1, {
25
+ ref,
23
26
  "data-slot": "avatar-fallback",
24
27
  className: cn("flex size-full items-center justify-center rounded-full border border-brand/15 bg-brand-soft text-xs font-semibold text-brand-soft-foreground", className),
25
28
  ...props
26
29
  });
27
- }
30
+ });
28
31
  //#endregion
29
32
  export { Avatar, AvatarFallback, AvatarImage };
30
33
 
@@ -1 +1 @@
1
- {"version":3,"file":"avatar.js","names":["AvatarPrimitive.Root","AvatarPrimitive.Image","AvatarPrimitive.Fallback"],"sources":["../../src/components/avatar.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as AvatarPrimitive from '../primitives/vendor/radix/react-avatar';\n\nfunction Avatar({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root>) {\n return (\n <AvatarPrimitive.Root\n data-slot=\"avatar\"\n className={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}\n {...props}\n />\n );\n}\n\nfunction AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) {\n return (\n <AvatarPrimitive.Image\n data-slot=\"avatar-image\"\n className={cn('aspect-square size-full object-cover', className)}\n {...props}\n />\n );\n}\n\nfunction AvatarFallback({\n className,\n ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {\n return (\n <AvatarPrimitive.Fallback\n data-slot=\"avatar-fallback\"\n className={cn(\n 'flex size-full items-center justify-center rounded-full border border-brand/15 bg-brand-soft text-xs font-semibold text-brand-soft-foreground',\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Avatar, AvatarFallback, AvatarImage };\n"],"mappings":";;;;;;AAKA,SAAS,OAAO,EAAE,WAAW,GAAG,SAA4D;CAC1F,OACE,oBAACA,UAAD;EACE,aAAU;EACV,WAAW,GAAG,8DAA8D,SAAS;EACrF,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACC,eAAD;EACE,aAAU;EACV,WAAW,GAAG,wCAAwC,SAAS;EAC/D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EACtB,WACA,GAAG,SACqD;CACxD,OACE,oBAACC,kBAAD;EACE,aAAU;EACV,WAAW,GACT,iJACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"avatar.js","names":["AvatarPrimitive.Root","AvatarPrimitive.Image","AvatarPrimitive.Fallback"],"sources":["../../src/components/avatar.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as AvatarPrimitive from '../primitives/vendor/radix/react-avatar';\n\nconst Avatar = React.forwardRef<HTMLSpanElement, React.ComponentProps<typeof AvatarPrimitive.Root>>(\n function Avatar({ className, ...props }, ref) {\n return (\n <AvatarPrimitive.Root\n ref={ref}\n data-slot=\"avatar\"\n className={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}\n {...props}\n />\n );\n },\n);\n\nconst AvatarImage = React.forwardRef<\n HTMLImageElement,\n React.ComponentProps<typeof AvatarPrimitive.Image>\n>(function AvatarImage({ className, ...props }, ref) {\n return (\n <AvatarPrimitive.Image\n ref={ref}\n data-slot=\"avatar-image\"\n className={cn('aspect-square size-full object-cover', className)}\n {...props}\n />\n );\n});\n\nconst AvatarFallback = React.forwardRef<\n HTMLSpanElement,\n React.ComponentProps<typeof AvatarPrimitive.Fallback>\n>(function AvatarFallback({ className, ...props }, ref) {\n return (\n <AvatarPrimitive.Fallback\n ref={ref}\n data-slot=\"avatar-fallback\"\n className={cn(\n 'flex size-full items-center justify-center rounded-full border border-brand/15 bg-brand-soft text-xs font-semibold text-brand-soft-foreground',\n className,\n )}\n {...props}\n />\n );\n});\n\nexport { Avatar, AvatarFallback, AvatarImage };\n"],"mappings":";;;;;;AAKA,IAAM,SAAS,MAAM,WACnB,SAAS,OAAO,EAAE,WAAW,GAAG,SAAS,KAAK;CAC5C,OACE,oBAACA,UAAD;EACO;EACL,aAAU;EACV,WAAW,GAAG,8DAA8D,SAAS;EACrF,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,cAAc,MAAM,WAGxB,SAAS,YAAY,EAAE,WAAW,GAAG,SAAS,KAAK;CACnD,OACE,oBAACC,eAAD;EACO;EACL,aAAU;EACV,WAAW,GAAG,wCAAwC,SAAS;EAC/D,GAAI;CACL,CAAA;AAEL,CAAC;AAED,IAAM,iBAAiB,MAAM,WAG3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACtD,OACE,oBAACC,kBAAD;EACO;EACL,aAAU;EACV,WAAW,GACT,iJACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CAAC"}
@@ -6,5 +6,5 @@ declare const badgeVariants: (props?: ({
6
6
  export interface BadgeProps extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {
7
7
  asChild?: boolean;
8
8
  }
9
- declare function Badge({ className, variant, asChild, ...props }: BadgeProps): React.JSX.Element;
9
+ declare const Badge: React.ForwardRefExoticComponent<Omit<BadgeProps, "ref"> & React.RefAttributes<HTMLSpanElement>>;
10
10
  export { Badge, badgeVariants };
@@ -1,30 +1,32 @@
1
1
  "use client";
2
- import { cn } from "../lib/utils.js";
2
+ import { cn, focusRingClasses } from "../lib/utils.js";
3
3
  import { Root as Slot } from "../primitives/vendor/radix/react-slot/slot.js";
4
- import "react";
4
+ import * as React from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { cva } from "class-variance-authority";
7
7
  //#region src/components/badge.tsx
8
- var badgeVariants = cva("inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-semibold leading-4 whitespace-nowrap outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/20 [&>svg]:size-3", {
8
+ var badgeVariants = cva(`inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-semibold leading-4 whitespace-nowrap transition-colors ${focusRingClasses} [&>svg]:size-3`, {
9
9
  variants: { variant: {
10
10
  default: "border-border bg-surface-muted text-foreground",
11
11
  secondary: "border-border bg-surface-muted text-foreground",
12
12
  brand: "border-brand/20 bg-brand-soft text-brand-soft-foreground",
13
- success: "border-success/20 bg-success/10 text-success",
14
- warning: "border-warning/20 bg-warning/10 text-warning",
15
- destructive: "border-destructive/20 bg-destructive/10 text-destructive",
16
- info: "border-info/20 bg-info/10 text-info",
13
+ success: "border-success/20 bg-success/10 text-[color-mix(in_srgb,var(--success)_75%,var(--foreground))]",
14
+ warning: "border-warning/20 bg-warning/10 text-[color-mix(in_srgb,var(--warning)_75%,var(--foreground))]",
15
+ destructive: "border-destructive/20 bg-destructive/10 text-[color-mix(in_srgb,var(--destructive)_75%,var(--foreground))]",
16
+ info: "border-info/20 bg-info/10 text-[color-mix(in_srgb,var(--info)_75%,var(--foreground))]",
17
17
  outline: "border-border-strong bg-transparent text-muted-foreground"
18
18
  } },
19
19
  defaultVariants: { variant: "default" }
20
20
  });
21
- function Badge({ className, variant, asChild = false, ...props }) {
21
+ var Badge = React.forwardRef(function Badge({ className, variant, asChild = false, ...props }, ref) {
22
22
  return /* @__PURE__ */ jsx(asChild ? Slot : "span", {
23
+ ref,
23
24
  "data-slot": "badge",
24
- className: cn(badgeVariants({ variant }), className),
25
+ "data-variant": variant ?? "default",
26
+ className: cn(badgeVariants({ variant }), asChild && "relative pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2 pointer-coarse:before:content-['']", className),
25
27
  ...props
26
28
  });
27
- }
29
+ });
28
30
  //#endregion
29
31
  export { Badge, badgeVariants };
30
32
 
@@ -1 +1 @@
1
- {"version":3,"file":"badge.js","names":["Slot.Root"],"sources":["../../src/components/badge.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nconst badgeVariants = cva(\n 'inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-semibold leading-4 whitespace-nowrap outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/20 [&>svg]:size-3',\n {\n variants: {\n variant: {\n default: 'border-border bg-surface-muted text-foreground',\n // 0.x name for the neutral tag; kept so existing apps keep compiling.\n secondary: 'border-border bg-surface-muted text-foreground',\n brand: 'border-brand/20 bg-brand-soft text-brand-soft-foreground',\n success: 'border-success/20 bg-success/10 text-success',\n warning: 'border-warning/20 bg-warning/10 text-warning',\n destructive: 'border-destructive/20 bg-destructive/10 text-destructive',\n info: 'border-info/20 bg-info/10 text-info',\n outline: 'border-border-strong bg-transparent text-muted-foreground',\n },\n },\n defaultVariants: { variant: 'default' },\n },\n);\n\nexport interface BadgeProps\n extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {\n asChild?: boolean;\n}\n\nfunction Badge({ className, variant, asChild = false, ...props }: BadgeProps) {\n const Comp = asChild ? Slot.Root : 'span';\n return (\n <Comp data-slot=\"badge\" className={cn(badgeVariants({ variant }), className)} {...props} />\n );\n}\n\nexport { Badge, badgeVariants };\n"],"mappings":";;;;;;;AAMA,IAAM,gBAAgB,IACpB,2OACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAET,WAAW;EACX,OAAO;EACP,SAAS;EACT,SAAS;EACT,aAAa;EACb,MAAM;EACN,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AAOA,SAAS,MAAM,EAAE,WAAW,SAAS,UAAU,OAAO,GAAG,SAAqB;CAE5E,OACE,oBAFW,UAAUA,OAAY,QAEjC;EAAM,aAAU;EAAQ,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EAAG,GAAI;CAAQ,CAAA;AAE9F"}
1
+ {"version":3,"file":"badge.js","names":["Slot.Root"],"sources":["../../src/components/badge.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses, touchTargetRowClasses } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nconst badgeVariants = cva(\n `inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-semibold leading-4 whitespace-nowrap transition-colors ${focusRingClasses} [&>svg]:size-3`,\n {\n variants: {\n variant: {\n default: 'border-border bg-surface-muted text-foreground',\n // 0.x name for the neutral tag; kept so existing apps keep compiling.\n secondary: 'border-border bg-surface-muted text-foreground',\n brand: 'border-brand/20 bg-brand-soft text-brand-soft-foreground',\n // Status text is the tone mixed a quarter toward the foreground: the\n // plain tone reads at 4.3–4.5:1 on its own tint, the mixed one at 6:1\n // (4.5:1 inside a terminal Card). Mixing toward the foreground darkens\n // it in light mode and lightens it in dark mode and in a terminal Card,\n // which re-points --foreground.\n success:\n 'border-success/20 bg-success/10 text-[color-mix(in_srgb,var(--success)_75%,var(--foreground))]',\n warning:\n 'border-warning/20 bg-warning/10 text-[color-mix(in_srgb,var(--warning)_75%,var(--foreground))]',\n destructive:\n 'border-destructive/20 bg-destructive/10 text-[color-mix(in_srgb,var(--destructive)_75%,var(--foreground))]',\n info: 'border-info/20 bg-info/10 text-[color-mix(in_srgb,var(--info)_75%,var(--foreground))]',\n outline: 'border-border-strong bg-transparent text-muted-foreground',\n },\n },\n defaultVariants: { variant: 'default' },\n },\n);\n\nexport interface BadgeProps\n extends React.ComponentProps<'span'>, VariantProps<typeof badgeVariants> {\n asChild?: boolean;\n}\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { className, variant, asChild = false, ...props },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'span';\n return (\n <Comp\n ref={ref}\n data-slot=\"badge\"\n data-variant={variant ?? 'default'}\n // A badge that is a link or button (asChild) gets a 44px hit area on touch screens.\n className={cn(badgeVariants({ variant }), asChild && touchTargetRowClasses, className)}\n {...props}\n />\n );\n});\n\nexport { Badge, badgeVariants };\n"],"mappings":";;;;;;;AAMA,IAAM,gBAAgB,IACpB,2JAA2J,iBAAiB,kBAC5K;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAET,WAAW;EACX,OAAO;EAMP,SACE;EACF,SACE;EACF,aACE;EACF,MAAM;EACN,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AAOA,IAAM,QAAQ,MAAM,WAAwC,SAAS,MACnE,EAAE,WAAW,SAAS,UAAU,OAAO,GAAG,SAC1C,KACA;CAEA,OACE,oBAFW,UAAUA,OAAY,QAEjC;EACO;EACL,aAAU;EACV,gBAAc,WAAW;EAEzB,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,WAAA,8MAAkC,SAAS;EACrF,GAAI;CACL,CAAA;AAEL,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
- declare function Breadcrumb(props: React.ComponentProps<'nav'>): React.JSX.Element;
3
- declare function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>): React.JSX.Element;
4
- declare function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>): React.JSX.Element;
5
- declare function BreadcrumbLink({ asChild, className, ...props }: React.ComponentProps<'a'> & {
2
+ declare const Breadcrumb: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
3
+ declare const BreadcrumbList: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>, "ref"> & React.RefAttributes<HTMLOListElement>>;
4
+ declare const BreadcrumbItem: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & React.RefAttributes<HTMLLIElement>>;
5
+ declare const BreadcrumbLink: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLAnchorElement> & React.AnchorHTMLAttributes<HTMLAnchorElement> & {
6
6
  asChild?: boolean;
7
- }): React.JSX.Element;
8
- declare function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>): React.JSX.Element;
9
- declare function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>): React.JSX.Element;
10
- declare function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>): React.JSX.Element;
7
+ }, "ref"> & React.RefAttributes<HTMLAnchorElement>>;
8
+ declare const BreadcrumbPage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
9
+ declare const BreadcrumbSeparator: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & React.RefAttributes<HTMLLIElement>>;
10
+ declare const BreadcrumbEllipsis: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
11
11
  export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, };
@@ -1,41 +1,46 @@
1
1
  "use client";
2
- import { cn } from "../lib/utils.js";
2
+ import { cn, focusRingClasses } from "../lib/utils.js";
3
3
  import { Root as Slot } from "../primitives/vendor/radix/react-slot/slot.js";
4
- import "react";
4
+ import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import ChevronRight from "@burtson-labs/icons/react/chevron-right";
7
7
  import MoreHorizontal from "@burtson-labs/icons/react/more-horizontal";
8
8
  //#region src/components/breadcrumb.tsx
9
- function Breadcrumb(props) {
9
+ var Breadcrumb = React.forwardRef(function Breadcrumb(props, ref) {
10
10
  return /* @__PURE__ */ jsx("nav", {
11
+ ref,
11
12
  "aria-label": "Breadcrumb",
12
13
  "data-slot": "breadcrumb",
13
14
  ...props
14
15
  });
15
- }
16
- function BreadcrumbList({ className, ...props }) {
16
+ });
17
+ var BreadcrumbList = React.forwardRef(function BreadcrumbList({ className, ...props }, ref) {
17
18
  return /* @__PURE__ */ jsx("ol", {
19
+ ref,
18
20
  "data-slot": "breadcrumb-list",
19
21
  className: cn("flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground", className),
20
22
  ...props
21
23
  });
22
- }
23
- function BreadcrumbItem({ className, ...props }) {
24
+ });
25
+ var BreadcrumbItem = React.forwardRef(function BreadcrumbItem({ className, ...props }, ref) {
24
26
  return /* @__PURE__ */ jsx("li", {
27
+ ref,
25
28
  "data-slot": "breadcrumb-item",
26
29
  className: cn("inline-flex items-center gap-1.5", className),
27
30
  ...props
28
31
  });
29
- }
30
- function BreadcrumbLink({ asChild, className, ...props }) {
32
+ });
33
+ var BreadcrumbLink = React.forwardRef(function BreadcrumbLink({ asChild, className, ...props }, ref) {
31
34
  return /* @__PURE__ */ jsx(asChild ? Slot : "a", {
35
+ ref,
32
36
  "data-slot": "breadcrumb-link",
33
- className: cn("transition-colors hover:text-foreground", className),
37
+ className: cn("rounded-xs transition-colors hover:text-foreground", focusRingClasses, className),
34
38
  ...props
35
39
  });
36
- }
37
- function BreadcrumbPage({ className, ...props }) {
40
+ });
41
+ var BreadcrumbPage = React.forwardRef(function BreadcrumbPage({ className, ...props }, ref) {
38
42
  return /* @__PURE__ */ jsx("span", {
43
+ ref,
39
44
  "data-slot": "breadcrumb-page",
40
45
  role: "link",
41
46
  "aria-disabled": "true",
@@ -43,9 +48,10 @@ function BreadcrumbPage({ className, ...props }) {
43
48
  className: cn("font-medium text-foreground", className),
44
49
  ...props
45
50
  });
46
- }
47
- function BreadcrumbSeparator({ children, className, ...props }) {
51
+ });
52
+ var BreadcrumbSeparator = React.forwardRef(function BreadcrumbSeparator({ children, className, ...props }, ref) {
48
53
  return /* @__PURE__ */ jsx("li", {
54
+ ref,
49
55
  "data-slot": "breadcrumb-separator",
50
56
  role: "presentation",
51
57
  "aria-hidden": "true",
@@ -53,9 +59,10 @@ function BreadcrumbSeparator({ children, className, ...props }) {
53
59
  ...props,
54
60
  children: children ?? /* @__PURE__ */ jsx(ChevronRight, {})
55
61
  });
56
- }
57
- function BreadcrumbEllipsis({ className, ...props }) {
62
+ });
63
+ var BreadcrumbEllipsis = React.forwardRef(function BreadcrumbEllipsis({ className, ...props }, ref) {
58
64
  return /* @__PURE__ */ jsxs("span", {
65
+ ref,
59
66
  "data-slot": "breadcrumb-ellipsis",
60
67
  role: "presentation",
61
68
  "aria-hidden": "true",
@@ -66,7 +73,7 @@ function BreadcrumbEllipsis({ className, ...props }) {
66
73
  children: "More"
67
74
  })]
68
75
  });
69
- }
76
+ });
70
77
  //#endregion
71
78
  export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator };
72
79
 
@@ -1 +1 @@
1
- {"version":3,"file":"breadcrumb.js","names":["Slot.Root"],"sources":["../../src/components/breadcrumb.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport MoreHorizontal from '@burtson-labs/icons/react/more-horizontal';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nfunction Breadcrumb(props: React.ComponentProps<'nav'>) {\n return <nav aria-label=\"Breadcrumb\" data-slot=\"breadcrumb\" {...props} />;\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {\n return (\n <ol\n data-slot=\"breadcrumb-list\"\n className={cn(\n 'flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {\n return (\n <li\n data-slot=\"breadcrumb-item\"\n className={cn('inline-flex items-center gap-1.5', className)}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbLink({\n asChild,\n className,\n ...props\n}: React.ComponentProps<'a'> & { asChild?: boolean }) {\n const Comp = asChild ? Slot.Root : 'a';\n return (\n <Comp\n data-slot=\"breadcrumb-link\"\n className={cn('transition-colors hover:text-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"breadcrumb-page\"\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('font-medium text-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>) {\n return (\n <li\n data-slot=\"breadcrumb-separator\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('[&>svg]:size-3.5', className)}\n {...props}\n >\n {children ?? <ChevronRight />}\n </li>\n );\n}\n\nfunction BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"breadcrumb-ellipsis\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('flex size-9 items-center justify-center', className)}\n {...props}\n >\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">More</span>\n </span>\n );\n}\n\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n};\n"],"mappings":";;;;;;;;AAOA,SAAS,WAAW,OAAoC;CACtD,OAAO,oBAAC,OAAD;EAAK,cAAW;EAAa,aAAU;EAAa,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,SAAqC;CAC3E,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,iFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,SAAqC;CAC3E,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,GAAG,oCAAoC,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EACtB,SACA,WACA,GAAG,SACiD;CAEpD,OACE,oBAFW,UAAUA,OAAY,KAEjC;EACE,aAAU;EACV,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EAAE,WAAW,GAAG,SAAuC;CAC7E,OACE,oBAAC,QAAD;EACE,aAAU;EACV,MAAK;EACL,iBAAc;EACd,gBAAa;EACb,WAAW,GAAG,+BAA+B,SAAS;EACtD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,oBAAoB,EAAE,UAAU,WAAW,GAAG,SAAqC;CAC1F,OACE,oBAAC,MAAD;EACE,aAAU;EACV,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,oBAAoB,SAAS;EAC3C,GAAI;EAEH,UAAA,YAAY,oBAAC,cAAD,CAAe,CAAA;CAC1B,CAAA;AAER;AAEA,SAAS,mBAAmB,EAAE,WAAW,GAAG,SAAuC;CACjF,OACE,qBAAC,QAAD;EACE,aAAU;EACV,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;EALN,UAAA,CAOE,oBAAC,gBAAD,EAAgB,WAAU,SAAU,CAAA,GACpC,oBAAC,QAAD;GAAM,WAAU;GAAU,UAAA;EAAU,CAAA,CAChC;;AAEV"}
1
+ {"version":3,"file":"breadcrumb.js","names":["Slot.Root"],"sources":["../../src/components/breadcrumb.tsx"],"sourcesContent":["import ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport MoreHorizontal from '@burtson-labs/icons/react/more-horizontal';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses } from '../lib/utils';\nimport * as Slot from '../primitives/vendor/radix/react-slot';\n\nconst Breadcrumb = React.forwardRef<HTMLElement, React.ComponentProps<'nav'>>(\n function Breadcrumb(props, ref) {\n return <nav ref={ref} aria-label=\"Breadcrumb\" data-slot=\"breadcrumb\" {...props} />;\n },\n);\n\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentProps<'ol'>>(\n function BreadcrumbList({ className, ...props }, ref) {\n return (\n <ol\n ref={ref}\n data-slot=\"breadcrumb-list\"\n className={cn(\n 'flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground',\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(\n function BreadcrumbItem({ className, ...props }, ref) {\n return (\n <li\n ref={ref}\n data-slot=\"breadcrumb-item\"\n className={cn('inline-flex items-center gap-1.5', className)}\n {...props}\n />\n );\n },\n);\n\nconst BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<'a'> & { asChild?: boolean }\n>(function BreadcrumbLink({ asChild, className, ...props }, ref) {\n const Comp = asChild ? Slot.Root : 'a';\n return (\n <Comp\n ref={ref}\n data-slot=\"breadcrumb-link\"\n className={cn(\n 'rounded-xs transition-colors hover:text-foreground',\n focusRingClasses,\n className,\n )}\n {...props}\n />\n );\n});\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentProps<'span'>>(\n function BreadcrumbPage({ className, ...props }, ref) {\n return (\n <span\n ref={ref}\n data-slot=\"breadcrumb-page\"\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('font-medium text-foreground', className)}\n {...props}\n />\n );\n },\n);\n\nconst BreadcrumbSeparator = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(\n function BreadcrumbSeparator({ children, className, ...props }, ref) {\n return (\n <li\n ref={ref}\n data-slot=\"breadcrumb-separator\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('[&>svg]:size-3.5', className)}\n {...props}\n >\n {children ?? <ChevronRight />}\n </li>\n );\n },\n);\n\nconst BreadcrumbEllipsis = React.forwardRef<HTMLSpanElement, React.ComponentProps<'span'>>(\n function BreadcrumbEllipsis({ className, ...props }, ref) {\n return (\n <span\n ref={ref}\n data-slot=\"breadcrumb-ellipsis\"\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn('flex size-9 items-center justify-center', className)}\n {...props}\n >\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">More</span>\n </span>\n );\n },\n);\n\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n};\n"],"mappings":";;;;;;;;AAOA,IAAM,aAAa,MAAM,WACvB,SAAS,WAAW,OAAO,KAAK;CAC9B,OAAO,oBAAC,OAAD;EAAU;EAAK,cAAW;EAAa,aAAU;EAAa,GAAI;CAAQ,CAAA;AACnF,CACF;AAEA,IAAM,iBAAiB,MAAM,WAC3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAAC,MAAD;EACO;EACL,aAAU;EACV,WAAW,GACT,iFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,iBAAiB,MAAM,WAC3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAAC,MAAD;EACO;EACL,aAAU;EACV,WAAW,GAAG,oCAAoC,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,iBAAiB,MAAM,WAG3B,SAAS,eAAe,EAAE,SAAS,WAAW,GAAG,SAAS,KAAK;CAE/D,OACE,oBAFW,UAAUA,OAAY,KAEjC;EACO;EACL,aAAU;EACV,WAAW,GACT,sDACA,kBACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CAAC;AAED,IAAM,iBAAiB,MAAM,WAC3B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAAC,QAAD;EACO;EACL,aAAU;EACV,MAAK;EACL,iBAAc;EACd,gBAAa;EACb,WAAW,GAAG,+BAA+B,SAAS;EACtD,GAAI;CACL,CAAA;AAEL,CACF;AAEA,IAAM,sBAAsB,MAAM,WAChC,SAAS,oBAAoB,EAAE,UAAU,WAAW,GAAG,SAAS,KAAK;CACnE,OACE,oBAAC,MAAD;EACO;EACL,aAAU;EACV,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,oBAAoB,SAAS;EAC3C,GAAI;EAEH,UAAA,YAAY,oBAAC,cAAD,CAAe,CAAA;CAC1B,CAAA;AAER,CACF;AAEA,IAAM,qBAAqB,MAAM,WAC/B,SAAS,mBAAmB,EAAE,WAAW,GAAG,SAAS,KAAK;CACxD,OACE,qBAAC,QAAD;EACO;EACL,aAAU;EACV,MAAK;EACL,eAAY;EACZ,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;EANN,UAAA,CAQE,oBAAC,gBAAD,EAAgB,WAAU,SAAU,CAAA,GACpC,oBAAC,QAAD;GAAM,WAAU;GAAU,UAAA;EAAU,CAAA,CAChC;;AAEV,CACF"}
@@ -2,11 +2,18 @@ import { type VariantProps } from 'class-variance-authority';
2
2
  import * as React from 'react';
3
3
  declare const buttonVariants: (props?: ({
4
4
  variant?: "secondary" | "destructive" | "brand" | "link" | "default" | "outline" | "soft" | "ghost" | null | undefined;
5
- size?: "default" | "icon" | "xs" | "sm" | "lg" | "icon-sm" | "icon-lg" | null | undefined;
5
+ size?: "sm" | "lg" | "default" | "icon" | "xs" | "icon-sm" | "icon-lg" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ComponentProps<'button'>, VariantProps<typeof buttonVariants> {
8
8
  asChild?: boolean;
9
9
  loading?: boolean;
10
10
  }
11
+ /**
12
+ * The action primitive. On touch screens every button is at least 44px
13
+ * (`pointer-coarse:min-h-8` opts a button in a dense header out); link-styled
14
+ * buttons stay inline. `data-variant` and `data-size` are set for CSS that
15
+ * needs them. A trigger's `asChild` replaces `data-slot` with its own
16
+ * (`dialog-trigger`), which is why the sizing lives in the classes.
17
+ */
11
18
  declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
12
19
  export { Button, buttonVariants };