@burtson-labs/ui 0.14.0 → 0.15.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 (245) 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/agent-run.d.ts +22 -0
  7. package/dist/components/agent-run.js +131 -0
  8. package/dist/components/agent-run.js.map +1 -0
  9. package/dist/components/alert-dialog.d.ts +12 -10
  10. package/dist/components/alert-dialog.js +29 -20
  11. package/dist/components/alert-dialog.js.map +1 -1
  12. package/dist/components/alert.d.ts +5 -3
  13. package/dist/components/alert.js +11 -7
  14. package/dist/components/alert.js.map +1 -1
  15. package/dist/components/app-shell.d.ts +7 -6
  16. package/dist/components/app-shell.js +21 -14
  17. package/dist/components/app-shell.js.map +1 -1
  18. package/dist/components/attachment.d.ts +3 -3
  19. package/dist/components/attachment.js +10 -7
  20. package/dist/components/attachment.js.map +1 -1
  21. package/dist/components/audio-player.d.ts +2 -2
  22. package/dist/components/audio-player.js +8 -6
  23. package/dist/components/audio-player.js.map +1 -1
  24. package/dist/components/avatar-upload.d.ts +19 -0
  25. package/dist/components/avatar-upload.js +359 -0
  26. package/dist/components/avatar-upload.js.map +1 -0
  27. package/dist/components/avatar.d.ts +3 -3
  28. package/dist/components/avatar.js +10 -7
  29. package/dist/components/avatar.js.map +1 -1
  30. package/dist/components/badge.d.ts +1 -1
  31. package/dist/components/badge.js +12 -10
  32. package/dist/components/badge.js.map +1 -1
  33. package/dist/components/breadcrumb.d.ts +8 -8
  34. package/dist/components/breadcrumb.js +24 -17
  35. package/dist/components/breadcrumb.js.map +1 -1
  36. package/dist/components/button.d.ts +8 -1
  37. package/dist/components/button.js +12 -3
  38. package/dist/components/button.js.map +1 -1
  39. package/dist/components/card.d.ts +8 -8
  40. package/dist/components/card.js +32 -23
  41. package/dist/components/card.js.map +1 -1
  42. package/dist/components/chat-history.d.ts +1 -1
  43. package/dist/components/chat-history.js +10 -9
  44. package/dist/components/chat-history.js.map +1 -1
  45. package/dist/components/chat-layout.d.ts +1 -1
  46. package/dist/components/chat-layout.js +7 -3
  47. package/dist/components/chat-layout.js.map +1 -1
  48. package/dist/components/checkbox-card.d.ts +2 -2
  49. package/dist/components/checkbox-card.js +9 -7
  50. package/dist/components/checkbox-card.js.map +1 -1
  51. package/dist/components/checkbox.d.ts +23 -2
  52. package/dist/components/checkbox.js +48 -6
  53. package/dist/components/checkbox.js.map +1 -1
  54. package/dist/components/collapsible.d.ts +2 -2
  55. package/dist/components/collapsible.js +7 -5
  56. package/dist/components/collapsible.js.map +1 -1
  57. package/dist/components/combobox.d.ts +14 -4
  58. package/dist/components/combobox.js +18 -8
  59. package/dist/components/combobox.js.map +1 -1
  60. package/dist/components/command.d.ts +68 -12
  61. package/dist/components/command.js +38 -25
  62. package/dist/components/command.js.map +1 -1
  63. package/dist/components/composer.d.ts +2 -2
  64. package/dist/components/composer.js +9 -7
  65. package/dist/components/composer.js.map +1 -1
  66. package/dist/components/connection-status.d.ts +3 -3
  67. package/dist/components/connection-status.js +18 -14
  68. package/dist/components/connection-status.js.map +1 -1
  69. package/dist/components/context-menu.d.ts +14 -13
  70. package/dist/components/context-menu.js +33 -23
  71. package/dist/components/context-menu.js.map +1 -1
  72. package/dist/components/conversation.d.ts +1 -1
  73. package/dist/components/conversation.js +6 -5
  74. package/dist/components/conversation.js.map +1 -1
  75. package/dist/components/copy-button.d.ts +1 -1
  76. package/dist/components/copy-button.js +3 -2
  77. package/dist/components/copy-button.js.map +1 -1
  78. package/dist/components/data-table.d.ts +7 -2
  79. package/dist/components/data-table.js +16 -42
  80. package/dist/components/data-table.js.map +1 -1
  81. package/dist/components/dialog.d.ts +18 -9
  82. package/dist/components/dialog.js +38 -22
  83. package/dist/components/dialog.js.map +1 -1
  84. package/dist/components/dropdown-menu.d.ts +14 -13
  85. package/dist/components/dropdown-menu.js +33 -23
  86. package/dist/components/dropdown-menu.js.map +1 -1
  87. package/dist/components/editor-tabs.d.ts +1 -1
  88. package/dist/components/editor-tabs.js +6 -5
  89. package/dist/components/editor-tabs.js.map +1 -1
  90. package/dist/components/empty-state.d.ts +5 -5
  91. package/dist/components/empty-state.js +16 -11
  92. package/dist/components/empty-state.js.map +1 -1
  93. package/dist/components/error-summary.d.ts +21 -0
  94. package/dist/components/error-summary.js +62 -0
  95. package/dist/components/error-summary.js.map +1 -0
  96. package/dist/components/field.d.ts +66 -7
  97. package/dist/components/field.js +151 -20
  98. package/dist/components/field.js.map +1 -1
  99. package/dist/components/form-actions.d.ts +18 -0
  100. package/dist/components/form-actions.js +30 -0
  101. package/dist/components/form-actions.js.map +1 -0
  102. package/dist/components/icon-button.d.ts +3 -1
  103. package/dist/components/icon-button.js +5 -3
  104. package/dist/components/icon-button.js.map +1 -1
  105. package/dist/components/input.d.ts +42 -7
  106. package/dist/components/input.js +53 -10
  107. package/dist/components/input.js.map +1 -1
  108. package/dist/components/kbd.d.ts +2 -2
  109. package/dist/components/kbd.js +7 -5
  110. package/dist/components/kbd.js.map +1 -1
  111. package/dist/components/label.d.ts +6 -1
  112. package/dist/components/label.js +10 -4
  113. package/dist/components/label.js.map +1 -1
  114. package/dist/components/markdown.d.ts +4 -3
  115. package/dist/components/markdown.js +7 -6
  116. package/dist/components/markdown.js.map +1 -1
  117. package/dist/components/menubar.d.ts +16 -14
  118. package/dist/components/menubar.js +39 -27
  119. package/dist/components/menubar.js.map +1 -1
  120. package/dist/components/message-actions.d.ts +3 -3
  121. package/dist/components/message-actions.js +15 -12
  122. package/dist/components/message-actions.js.map +1 -1
  123. package/dist/components/message.d.ts +1 -1
  124. package/dist/components/message.js +5 -3
  125. package/dist/components/message.js.map +1 -1
  126. package/dist/components/mobile-nav.d.ts +1 -1
  127. package/dist/components/mobile-nav.js +6 -5
  128. package/dist/components/mobile-nav.js.map +1 -1
  129. package/dist/components/native-select.d.ts +16 -0
  130. package/dist/components/native-select.js +33 -0
  131. package/dist/components/native-select.js.map +1 -0
  132. package/dist/components/navigation-menu.d.ts +12 -11
  133. package/dist/components/navigation-menu.js +32 -23
  134. package/dist/components/navigation-menu.js.map +1 -1
  135. package/dist/components/number-input.d.ts +23 -0
  136. package/dist/components/number-input.js +45 -0
  137. package/dist/components/number-input.js.map +1 -0
  138. package/dist/components/onboarding-checklist.d.ts +1 -1
  139. package/dist/components/onboarding-checklist.js +4 -3
  140. package/dist/components/onboarding-checklist.js.map +1 -1
  141. package/dist/components/page-header.d.ts +7 -6
  142. package/dist/components/page-header.js +22 -15
  143. package/dist/components/page-header.js.map +1 -1
  144. package/dist/components/pagination.d.ts +1 -1
  145. package/dist/components/pagination.js +3 -2
  146. package/dist/components/pagination.js.map +1 -1
  147. package/dist/components/popover.d.ts +13 -10
  148. package/dist/components/popover.js +23 -17
  149. package/dist/components/popover.js.map +1 -1
  150. package/dist/components/progress.d.ts +1 -1
  151. package/dist/components/progress.js +6 -5
  152. package/dist/components/progress.js.map +1 -1
  153. package/dist/components/radio-group.d.ts +22 -3
  154. package/dist/components/radio-group.js +51 -10
  155. package/dist/components/radio-group.js.map +1 -1
  156. package/dist/components/reasoning.d.ts +3 -3
  157. package/dist/components/reasoning.js +9 -7
  158. package/dist/components/reasoning.js.map +1 -1
  159. package/dist/components/scroll-area.d.ts +3 -2
  160. package/dist/components/scroll-area.js +10 -7
  161. package/dist/components/scroll-area.js.map +1 -1
  162. package/dist/components/secret-input.d.ts +3 -2
  163. package/dist/components/secret-input.js +6 -4
  164. package/dist/components/secret-input.js.map +1 -1
  165. package/dist/components/select.d.ts +11 -6
  166. package/dist/components/select.js +24 -18
  167. package/dist/components/select.js.map +1 -1
  168. package/dist/components/separator.d.ts +1 -1
  169. package/dist/components/separator.js +4 -3
  170. package/dist/components/separator.js.map +1 -1
  171. package/dist/components/sheet.d.ts +10 -8
  172. package/dist/components/sheet.js +31 -20
  173. package/dist/components/sheet.js.map +1 -1
  174. package/dist/components/skeleton.d.ts +2 -1
  175. package/dist/components/skeleton.js +6 -4
  176. package/dist/components/skeleton.js.map +1 -1
  177. package/dist/components/slider.d.ts +10 -6
  178. package/dist/components/slider.js +15 -7
  179. package/dist/components/slider.js.map +1 -1
  180. package/dist/components/source.d.ts +2 -2
  181. package/dist/components/source.js +9 -7
  182. package/dist/components/source.js.map +1 -1
  183. package/dist/components/spinner.d.ts +4 -2
  184. package/dist/components/spinner.js +4 -3
  185. package/dist/components/spinner.js.map +1 -1
  186. package/dist/components/stat-card.d.ts +27 -2
  187. package/dist/components/stat-card.js +69 -9
  188. package/dist/components/stat-card.js.map +1 -1
  189. package/dist/components/status.d.ts +7 -6
  190. package/dist/components/status.js +11 -5
  191. package/dist/components/status.js.map +1 -1
  192. package/dist/components/steps.d.ts +1 -1
  193. package/dist/components/steps.js +4 -3
  194. package/dist/components/steps.js.map +1 -1
  195. package/dist/components/switch.d.ts +46 -2
  196. package/dist/components/switch.js +85 -8
  197. package/dist/components/switch.js.map +1 -1
  198. package/dist/components/table.d.ts +29 -12
  199. package/dist/components/table.js +79 -34
  200. package/dist/components/table.js.map +1 -1
  201. package/dist/components/tabs.d.ts +9 -4
  202. package/dist/components/tabs.js +22 -13
  203. package/dist/components/tabs.js.map +1 -1
  204. package/dist/components/textarea.d.ts +6 -1
  205. package/dist/components/textarea.js +3 -3
  206. package/dist/components/textarea.js.map +1 -1
  207. package/dist/components/toast.d.ts +10 -6
  208. package/dist/components/toast.js +27 -18
  209. package/dist/components/toast.js.map +1 -1
  210. package/dist/components/toaster.d.ts +4 -2
  211. package/dist/components/toaster.js +6 -2
  212. package/dist/components/toaster.js.map +1 -1
  213. package/dist/components/tool-call.d.ts +3 -3
  214. package/dist/components/tool-call.js +16 -12
  215. package/dist/components/tool-call.js.map +1 -1
  216. package/dist/components/toolbar.d.ts +9 -4
  217. package/dist/components/toolbar.js +14 -9
  218. package/dist/components/toolbar.js.map +1 -1
  219. package/dist/components/tooltip.d.ts +3 -2
  220. package/dist/components/tooltip.js +7 -4
  221. package/dist/components/tooltip.js.map +1 -1
  222. package/dist/components/tour.js +2 -2
  223. package/dist/components/tour.js.map +1 -1
  224. package/dist/components/tree-view.d.ts +1 -1
  225. package/dist/components/tree-view.js +9 -5
  226. package/dist/components/tree-view.js.map +1 -1
  227. package/dist/components/voice-recorder.d.ts +1 -1
  228. package/dist/components/voice-recorder.js +4 -2
  229. package/dist/components/voice-recorder.js.map +1 -1
  230. package/dist/index.d.ts +8 -1
  231. package/dist/index.js +19 -12
  232. package/dist/lib/media.d.ts +18 -0
  233. package/dist/lib/media.js +34 -0
  234. package/dist/lib/media.js.map +1 -0
  235. package/dist/lib/utils.d.ts +32 -0
  236. package/dist/lib/utils.js +33 -1
  237. package/dist/lib/utils.js.map +1 -1
  238. package/dist/mui/index.js +20 -8
  239. package/dist/mui/index.js.map +1 -1
  240. package/dist/styles.css +1 -1
  241. package/dist/theme.css +36 -17
  242. package/dist/tokens.d.ts +78 -36
  243. package/dist/tokens.js +63 -32
  244. package/dist/tokens.js.map +1 -1
  245. package/package.json +5 -3
@@ -38,5 +38,5 @@ export interface ChatLayoutProps extends Omit<React.ComponentProps<'div'>, 'titl
38
38
  * details column, and a Full screen mode. Put a Conversation and a Composer
39
39
  * in `children`; the layout stacks them and keeps the composer at the bottom.
40
40
  */
41
- declare function ChatLayout({ sidebar, sidebarLabel, details, detailsLabel, title, actions, sidebarOpen: controlledOpen, defaultSidebarOpen, onSidebarOpenChange, allowFullscreen, onFullscreenChange, sidebarSize, children, className, ...props }: ChatLayoutProps): React.JSX.Element;
41
+ declare const ChatLayout: React.ForwardRefExoticComponent<Omit<ChatLayoutProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
42
42
  export { ChatLayout };
@@ -89,7 +89,7 @@ function useDesktop(query = "(min-width: 768px)") {
89
89
  * details column, and a Full screen mode. Put a Conversation and a Composer
90
90
  * in `children`; the layout stacks them and keeps the composer at the bottom.
91
91
  */
92
- function ChatLayout({ sidebar, sidebarLabel = "Chats", details, detailsLabel = "Details", title, actions, sidebarOpen: controlledOpen, defaultSidebarOpen = true, onSidebarOpenChange, allowFullscreen = true, onFullscreenChange, sidebarSize = "24%", children, className, ...props }) {
92
+ var ChatLayout = React.forwardRef(function ChatLayout({ sidebar, sidebarLabel = "Chats", details, detailsLabel = "Details", title, actions, sidebarOpen: controlledOpen, defaultSidebarOpen = true, onSidebarOpenChange, allowFullscreen = true, onFullscreenChange, sidebarSize = "24%", children, className, ...props }, ref) {
93
93
  const root = React.useRef(null);
94
94
  const desktop = useDesktop();
95
95
  const [ownOpen, setOwnOpen] = React.useState(defaultSidebarOpen);
@@ -122,7 +122,11 @@ function ChatLayout({ sidebar, sidebarLabel = "Chats", details, detailsLabel = "
122
122
  children
123
123
  });
124
124
  return /* @__PURE__ */ jsxs("div", {
125
- ref: root,
125
+ ref: (node) => {
126
+ root.current = node;
127
+ if (typeof ref === "function") ref(node);
128
+ else if (ref) ref.current = node;
129
+ },
126
130
  "data-slot": "chat-layout",
127
131
  "data-fullscreen": fullscreen || void 0,
128
132
  className: cn("flex h-full min-h-0 flex-col overflow-hidden bg-background text-foreground", "data-[fullscreen]:fixed data-[fullscreen]:inset-0 data-[fullscreen]:z-50 data-[fullscreen]:h-dvh data-[fullscreen]:w-screen", className),
@@ -211,7 +215,7 @@ function ChatLayout({ sidebar, sidebarLabel = "Chats", details, detailsLabel = "
211
215
  })
212
216
  ]
213
217
  });
214
- }
218
+ });
215
219
  //#endregion
216
220
  export { ChatLayout, useFullscreen };
217
221
 
@@ -1 +1 @@
1
- {"version":3,"file":"chat-layout.js","names":["_Fragment"],"sources":["../../src/components/chat-layout.tsx"],"sourcesContent":["import Maximize from '@burtson-labs/icons/react/maximize';\nimport Minimize from '@burtson-labs/icons/react/minimize';\nimport PanelLeft from '@burtson-labs/icons/react/panel-left';\nimport PanelLeftClose from '@burtson-labs/icons/react/panel-left-close';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { IconButton } from './icon-button';\nimport { ResizablePanel, ResizablePanelGroup, ResizeHandle } from './resizable';\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from './sheet';\n\n/**\n * Full screen for one element: the Fullscreen API where the browser allows\n * it, otherwise the element is pinned over the page with CSS. Escape leaves\n * either way. Nothing remounts, so focus and scroll stay where they were.\n */\nexport function useFullscreen(\n ref: React.RefObject<HTMLElement | null>,\n onChange?: (fullscreen: boolean) => void,\n) {\n const [native, setNative] = React.useState(false);\n const [fallback, setFallback] = React.useState(false);\n const active = native || fallback;\n const changed = React.useRef(onChange);\n React.useEffect(() => {\n changed.current = onChange;\n }, [onChange]);\n\n React.useEffect(() => {\n const sync = () =>\n setNative(Boolean(ref.current && document.fullscreenElement === ref.current));\n document.addEventListener('fullscreenchange', sync);\n return () => document.removeEventListener('fullscreenchange', sync);\n }, [ref]);\n\n React.useEffect(() => {\n if (!fallback) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && !e.defaultPrevented) setFallback(false);\n };\n const root = document.documentElement;\n const previous = root.style.overflow;\n root.style.overflow = 'hidden';\n document.addEventListener('keydown', onKey);\n return () => {\n root.style.overflow = previous;\n document.removeEventListener('keydown', onKey);\n };\n }, [fallback]);\n\n const first = React.useRef(true);\n React.useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n changed.current?.(active);\n }, [active]);\n\n const enter = React.useCallback(async () => {\n const el = ref.current;\n if (!el) return;\n const canNative = document.fullscreenEnabled && typeof el.requestFullscreen === 'function';\n // Denied (iframe without allowfullscreen, iOS) resolves false: use the CSS fallback.\n const ok = canNative\n ? await el.requestFullscreen().then(\n () => true,\n () => false,\n )\n : false;\n if (!ok) setFallback(true);\n }, [ref]);\n\n const exit = React.useCallback(async () => {\n setFallback(false);\n if (document.fullscreenElement && typeof document.exitFullscreen === 'function') {\n await document.exitFullscreen().catch(() => undefined);\n }\n }, []);\n\n const toggle = React.useCallback(() => (active ? exit() : enter()), [active, enter, exit]);\n return { fullscreen: active, native, enter, exit, toggle };\n}\n\nfunction useDesktop(query = '(min-width: 768px)') {\n const get = () => (typeof matchMedia === 'function' ? matchMedia(query).matches : true);\n const [desktop, setDesktop] = React.useState(get);\n React.useEffect(() => {\n if (typeof matchMedia !== 'function') return;\n const mq = matchMedia(query);\n const on = () => setDesktop(mq.matches);\n mq.addEventListener?.('change', on);\n return () => mq.removeEventListener?.('change', on);\n }, [query]);\n return desktop;\n}\n\nexport interface ChatLayoutProps extends Omit<React.ComponentProps<'div'>, 'title'> {\n /** The history column, usually a ChatHistory. A sheet from the left on phones. */\n sidebar?: React.ReactNode;\n sidebarLabel?: string;\n /** An optional right column: sources, files, run details. Hidden on phones. */\n details?: React.ReactNode;\n detailsLabel?: string;\n /** Shown in the header: the conversation title. */\n title?: React.ReactNode;\n /** Header controls right of the title (model picker, share). */\n actions?: React.ReactNode;\n /** Controlled sidebar state; leave out to let the layout keep its own. */\n sidebarOpen?: boolean;\n defaultSidebarOpen?: boolean;\n onSidebarOpenChange?: (open: boolean) => void;\n /** Show the Full screen button. Default true. */\n allowFullscreen?: boolean;\n onFullscreenChange?: (fullscreen: boolean) => void;\n /** Sidebar width, e.g. \"22%\" or 280 (px), as ResizablePanel sizes. */\n sidebarSize?: number | string;\n}\n\n/**\n * The frame for a chat app: header, a resizable history sidebar (a sheet on\n * phones that closes when a chat is picked), the conversation, an optional\n * details column, and a Full screen mode. Put a Conversation and a Composer\n * in `children`; the layout stacks them and keeps the composer at the bottom.\n */\nfunction ChatLayout({\n sidebar,\n sidebarLabel = 'Chats',\n details,\n detailsLabel = 'Details',\n title,\n actions,\n sidebarOpen: controlledOpen,\n defaultSidebarOpen = true,\n onSidebarOpenChange,\n allowFullscreen = true,\n onFullscreenChange,\n sidebarSize = '24%',\n children,\n className,\n ...props\n}: ChatLayoutProps) {\n const root = React.useRef<HTMLDivElement>(null);\n const desktop = useDesktop();\n const [ownOpen, setOwnOpen] = React.useState(defaultSidebarOpen);\n const [sheetOpen, setSheetOpen] = React.useState(false);\n const open = controlledOpen ?? ownOpen;\n const setOpen = (v: boolean) => {\n if (controlledOpen === undefined) setOwnOpen(v);\n onSidebarOpenChange?.(v);\n };\n const { fullscreen, toggle } = useFullscreen(root, onFullscreenChange);\n\n const sidebarToggle = sidebar ? (\n desktop ? (\n <IconButton\n variant=\"ghost\"\n size=\"icon-sm\"\n label={open ? `Hide ${sidebarLabel.toLowerCase()}` : `Show ${sidebarLabel.toLowerCase()}`}\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n {open ? <PanelLeftClose /> : <PanelLeft />}\n </IconButton>\n ) : (\n <IconButton\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-coarse:size-11\"\n label={`Open ${sidebarLabel.toLowerCase()}`}\n aria-expanded={sheetOpen}\n onClick={() => setSheetOpen(true)}\n >\n <PanelLeft />\n </IconButton>\n )\n ) : null;\n\n const main = (\n <div data-slot=\"chat-layout-main\" className=\"flex h-full min-h-0 min-w-0 flex-col\">\n {children}\n </div>\n );\n\n return (\n <div\n ref={root}\n data-slot=\"chat-layout\"\n data-fullscreen={fullscreen || undefined}\n className={cn(\n 'flex h-full min-h-0 flex-col overflow-hidden bg-background text-foreground',\n 'data-[fullscreen]:fixed data-[fullscreen]:inset-0 data-[fullscreen]:z-50 data-[fullscreen]:h-dvh data-[fullscreen]:w-screen',\n className,\n )}\n {...props}\n >\n <header className=\"flex h-12 shrink-0 items-center gap-1 border-b px-2\">\n {sidebarToggle}\n <div className=\"min-w-0 flex-1 truncate px-1 text-sm font-medium\">{title}</div>\n {actions}\n {allowFullscreen && (\n <IconButton\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-coarse:size-11\"\n label={fullscreen ? 'Exit full screen' : 'Full screen'}\n aria-pressed={fullscreen}\n onClick={() => void toggle()}\n >\n {fullscreen ? <Minimize /> : <Maximize />}\n </IconButton>\n )}\n </header>\n <div className=\"min-h-0 flex-1\">\n {desktop && (sidebar || details) ? (\n <ResizablePanelGroup orientation=\"horizontal\">\n {sidebar && open && (\n <>\n <ResizablePanel\n id=\"chat-sidebar\"\n defaultSize={sidebarSize}\n minSize={180}\n maxSize=\"45%\"\n className=\"bg-surface-muted/50\"\n >\n <aside aria-label={sidebarLabel} className=\"h-full\">\n {sidebar}\n </aside>\n </ResizablePanel>\n <ResizeHandle aria-label={`Resize ${sidebarLabel.toLowerCase()}`} />\n </>\n )}\n <ResizablePanel id=\"chat-main\" minSize=\"30%\">\n {main}\n </ResizablePanel>\n {details && (\n <>\n <ResizeHandle aria-label={`Resize ${detailsLabel.toLowerCase()}`} />\n <ResizablePanel id=\"chat-details\" defaultSize=\"26%\" minSize={200} maxSize=\"45%\">\n <aside aria-label={detailsLabel} className=\"h-full overflow-y-auto\">\n {details}\n </aside>\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n ) : (\n main\n )}\n </div>\n {sidebar && !desktop && (\n <Sheet open={sheetOpen} onOpenChange={setSheetOpen}>\n <SheetContent side=\"left\" className=\"w-[min(20rem,88vw)] gap-0 p-0\">\n <SheetTitle className=\"border-b px-4 py-3.5 text-sm\">{sidebarLabel}</SheetTitle>\n <SheetDescription className=\"sr-only\">Pick a conversation.</SheetDescription>\n <div\n className=\"min-h-0 flex-1\"\n // Picking a chat (or New chat) closes the sheet, as on a phone it should.\n onClickCapture={(e) => {\n const t = e.target as HTMLElement;\n if (t.closest('[data-slot=\"chat-history-row\"], [data-close-sheet]'))\n setSheetOpen(false);\n }}\n >\n {sidebar}\n </div>\n </SheetContent>\n </Sheet>\n )}\n </div>\n );\n}\n\nexport { ChatLayout };\n"],"mappings":"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"}
1
+ {"version":3,"file":"chat-layout.js","names":["_Fragment"],"sources":["../../src/components/chat-layout.tsx"],"sourcesContent":["import Maximize from '@burtson-labs/icons/react/maximize';\nimport Minimize from '@burtson-labs/icons/react/minimize';\nimport PanelLeft from '@burtson-labs/icons/react/panel-left';\nimport PanelLeftClose from '@burtson-labs/icons/react/panel-left-close';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { IconButton } from './icon-button';\nimport { ResizablePanel, ResizablePanelGroup, ResizeHandle } from './resizable';\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from './sheet';\n\n/**\n * Full screen for one element: the Fullscreen API where the browser allows\n * it, otherwise the element is pinned over the page with CSS. Escape leaves\n * either way. Nothing remounts, so focus and scroll stay where they were.\n */\nexport function useFullscreen(\n ref: React.RefObject<HTMLElement | null>,\n onChange?: (fullscreen: boolean) => void,\n) {\n const [native, setNative] = React.useState(false);\n const [fallback, setFallback] = React.useState(false);\n const active = native || fallback;\n const changed = React.useRef(onChange);\n React.useEffect(() => {\n changed.current = onChange;\n }, [onChange]);\n\n React.useEffect(() => {\n const sync = () =>\n setNative(Boolean(ref.current && document.fullscreenElement === ref.current));\n document.addEventListener('fullscreenchange', sync);\n return () => document.removeEventListener('fullscreenchange', sync);\n }, [ref]);\n\n React.useEffect(() => {\n if (!fallback) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && !e.defaultPrevented) setFallback(false);\n };\n const root = document.documentElement;\n const previous = root.style.overflow;\n root.style.overflow = 'hidden';\n document.addEventListener('keydown', onKey);\n return () => {\n root.style.overflow = previous;\n document.removeEventListener('keydown', onKey);\n };\n }, [fallback]);\n\n const first = React.useRef(true);\n React.useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n changed.current?.(active);\n }, [active]);\n\n const enter = React.useCallback(async () => {\n const el = ref.current;\n if (!el) return;\n const canNative = document.fullscreenEnabled && typeof el.requestFullscreen === 'function';\n // Denied (iframe without allowfullscreen, iOS) resolves false: use the CSS fallback.\n const ok = canNative\n ? await el.requestFullscreen().then(\n () => true,\n () => false,\n )\n : false;\n if (!ok) setFallback(true);\n }, [ref]);\n\n const exit = React.useCallback(async () => {\n setFallback(false);\n if (document.fullscreenElement && typeof document.exitFullscreen === 'function') {\n await document.exitFullscreen().catch(() => undefined);\n }\n }, []);\n\n const toggle = React.useCallback(() => (active ? exit() : enter()), [active, enter, exit]);\n return { fullscreen: active, native, enter, exit, toggle };\n}\n\nfunction useDesktop(query = '(min-width: 768px)') {\n const get = () => (typeof matchMedia === 'function' ? matchMedia(query).matches : true);\n const [desktop, setDesktop] = React.useState(get);\n React.useEffect(() => {\n if (typeof matchMedia !== 'function') return;\n const mq = matchMedia(query);\n const on = () => setDesktop(mq.matches);\n mq.addEventListener?.('change', on);\n return () => mq.removeEventListener?.('change', on);\n }, [query]);\n return desktop;\n}\n\nexport interface ChatLayoutProps extends Omit<React.ComponentProps<'div'>, 'title'> {\n /** The history column, usually a ChatHistory. A sheet from the left on phones. */\n sidebar?: React.ReactNode;\n sidebarLabel?: string;\n /** An optional right column: sources, files, run details. Hidden on phones. */\n details?: React.ReactNode;\n detailsLabel?: string;\n /** Shown in the header: the conversation title. */\n title?: React.ReactNode;\n /** Header controls right of the title (model picker, share). */\n actions?: React.ReactNode;\n /** Controlled sidebar state; leave out to let the layout keep its own. */\n sidebarOpen?: boolean;\n defaultSidebarOpen?: boolean;\n onSidebarOpenChange?: (open: boolean) => void;\n /** Show the Full screen button. Default true. */\n allowFullscreen?: boolean;\n onFullscreenChange?: (fullscreen: boolean) => void;\n /** Sidebar width, e.g. \"22%\" or 280 (px), as ResizablePanel sizes. */\n sidebarSize?: number | string;\n}\n\n/**\n * The frame for a chat app: header, a resizable history sidebar (a sheet on\n * phones that closes when a chat is picked), the conversation, an optional\n * details column, and a Full screen mode. Put a Conversation and a Composer\n * in `children`; the layout stacks them and keeps the composer at the bottom.\n */\nconst ChatLayout = React.forwardRef<HTMLDivElement, ChatLayoutProps>(function ChatLayout(\n {\n sidebar,\n sidebarLabel = 'Chats',\n details,\n detailsLabel = 'Details',\n title,\n actions,\n sidebarOpen: controlledOpen,\n defaultSidebarOpen = true,\n onSidebarOpenChange,\n allowFullscreen = true,\n onFullscreenChange,\n sidebarSize = '24%',\n children,\n className,\n ...props\n },\n ref,\n) {\n const root = React.useRef<HTMLDivElement>(null);\n const desktop = useDesktop();\n const [ownOpen, setOwnOpen] = React.useState(defaultSidebarOpen);\n const [sheetOpen, setSheetOpen] = React.useState(false);\n const open = controlledOpen ?? ownOpen;\n const setOpen = (v: boolean) => {\n if (controlledOpen === undefined) setOwnOpen(v);\n onSidebarOpenChange?.(v);\n };\n const { fullscreen, toggle } = useFullscreen(root, onFullscreenChange);\n\n const sidebarToggle = sidebar ? (\n desktop ? (\n <IconButton\n variant=\"ghost\"\n size=\"icon-sm\"\n label={open ? `Hide ${sidebarLabel.toLowerCase()}` : `Show ${sidebarLabel.toLowerCase()}`}\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n {open ? <PanelLeftClose /> : <PanelLeft />}\n </IconButton>\n ) : (\n <IconButton\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-coarse:size-11\"\n label={`Open ${sidebarLabel.toLowerCase()}`}\n aria-expanded={sheetOpen}\n onClick={() => setSheetOpen(true)}\n >\n <PanelLeft />\n </IconButton>\n )\n ) : null;\n\n const main = (\n <div data-slot=\"chat-layout-main\" className=\"flex h-full min-h-0 min-w-0 flex-col\">\n {children}\n </div>\n );\n\n return (\n <div\n ref={(node) => {\n root.current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n }}\n data-slot=\"chat-layout\"\n data-fullscreen={fullscreen || undefined}\n className={cn(\n 'flex h-full min-h-0 flex-col overflow-hidden bg-background text-foreground',\n 'data-[fullscreen]:fixed data-[fullscreen]:inset-0 data-[fullscreen]:z-50 data-[fullscreen]:h-dvh data-[fullscreen]:w-screen',\n className,\n )}\n {...props}\n >\n <header className=\"flex h-12 shrink-0 items-center gap-1 border-b px-2\">\n {sidebarToggle}\n <div className=\"min-w-0 flex-1 truncate px-1 text-sm font-medium\">{title}</div>\n {actions}\n {allowFullscreen && (\n <IconButton\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"pointer-coarse:size-11\"\n label={fullscreen ? 'Exit full screen' : 'Full screen'}\n aria-pressed={fullscreen}\n onClick={() => void toggle()}\n >\n {fullscreen ? <Minimize /> : <Maximize />}\n </IconButton>\n )}\n </header>\n <div className=\"min-h-0 flex-1\">\n {desktop && (sidebar || details) ? (\n <ResizablePanelGroup orientation=\"horizontal\">\n {sidebar && open && (\n <>\n <ResizablePanel\n id=\"chat-sidebar\"\n defaultSize={sidebarSize}\n minSize={180}\n maxSize=\"45%\"\n className=\"bg-surface-muted/50\"\n >\n <aside aria-label={sidebarLabel} className=\"h-full\">\n {sidebar}\n </aside>\n </ResizablePanel>\n <ResizeHandle aria-label={`Resize ${sidebarLabel.toLowerCase()}`} />\n </>\n )}\n <ResizablePanel id=\"chat-main\" minSize=\"30%\">\n {main}\n </ResizablePanel>\n {details && (\n <>\n <ResizeHandle aria-label={`Resize ${detailsLabel.toLowerCase()}`} />\n <ResizablePanel id=\"chat-details\" defaultSize=\"26%\" minSize={200} maxSize=\"45%\">\n <aside aria-label={detailsLabel} className=\"h-full overflow-y-auto\">\n {details}\n </aside>\n </ResizablePanel>\n </>\n )}\n </ResizablePanelGroup>\n ) : (\n main\n )}\n </div>\n {sidebar && !desktop && (\n <Sheet open={sheetOpen} onOpenChange={setSheetOpen}>\n <SheetContent side=\"left\" className=\"w-[min(20rem,88vw)] gap-0 p-0\">\n <SheetTitle className=\"border-b px-4 py-3.5 text-sm\">{sidebarLabel}</SheetTitle>\n <SheetDescription className=\"sr-only\">Pick a conversation.</SheetDescription>\n <div\n className=\"min-h-0 flex-1\"\n // Picking a chat (or New chat) closes the sheet, as on a phone it should.\n onClickCapture={(e) => {\n const t = e.target as HTMLElement;\n if (t.closest('[data-slot=\"chat-history-row\"], [data-close-sheet]'))\n setSheetOpen(false);\n }}\n >\n {sidebar}\n </div>\n </SheetContent>\n </Sheet>\n )}\n </div>\n );\n});\n\nexport { ChatLayout };\n"],"mappings":"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"}
@@ -13,7 +13,7 @@ export interface CheckboxCardProps extends Omit<React.ComponentProps<typeof Chec
13
13
  * area (44px or taller). For choices that need explaining: roles,
14
14
  * permissions, notification types. One focus ring, on the card.
15
15
  */
16
- declare function CheckboxCard({ title, description, id: idProp, disabled, className, cardClassName, ...props }: CheckboxCardProps): React.JSX.Element;
16
+ declare const CheckboxCard: React.ForwardRefExoticComponent<Omit<CheckboxCardProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
17
17
  export interface CheckboxCardGroupProps extends React.ComponentProps<'fieldset'> {
18
18
  /** The group's name, shown as its legend. */
19
19
  label: React.ReactNode;
@@ -21,5 +21,5 @@ export interface CheckboxCardGroupProps extends React.ComponentProps<'fieldset'>
21
21
  description?: React.ReactNode;
22
22
  }
23
23
  /** A fieldset of CheckboxCards with a visible legend. `disabled` disables every card. */
24
- declare function CheckboxCardGroup({ label, description, className, children, ...props }: CheckboxCardGroupProps): React.JSX.Element;
24
+ declare const CheckboxCardGroup: React.ForwardRefExoticComponent<Omit<CheckboxCardGroupProps, "ref"> & React.RefAttributes<HTMLFieldSetElement>>;
25
25
  export { CheckboxCard, CheckboxCardGroup };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { cn } from "../lib/utils.js";
2
+ import { cn, noOutlineClasses } from "../lib/utils.js";
3
3
  import { Checkbox } from "./checkbox.js";
4
4
  import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -9,7 +9,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
9
9
  * area (44px or taller). For choices that need explaining: roles,
10
10
  * permissions, notification types. One focus ring, on the card.
11
11
  */
12
- function CheckboxCard({ title, description, id: idProp, disabled, className, cardClassName, ...props }) {
12
+ var CheckboxCard = React.forwardRef(function CheckboxCard({ title, description, id: idProp, disabled, className, cardClassName, ...props }, ref) {
13
13
  const auto = React.useId();
14
14
  const id = idProp ?? auto;
15
15
  return /* @__PURE__ */ jsxs("label", {
@@ -17,11 +17,12 @@ function CheckboxCard({ title, description, id: idProp, disabled, className, car
17
17
  "data-slot": "checkbox-card",
18
18
  className: cn("flex min-h-11 cursor-pointer items-start gap-3 rounded-md border border-border bg-surface px-3 py-2.5 transition-colors hover:border-border-strong", "has-[[data-state=checked]]:border-brand/40 has-[[data-state=checked]]:bg-brand-soft/50", "has-[:focus-visible]:border-ring has-[:focus-visible]:inset-ring-1 has-[:focus-visible]:inset-ring-ring", "has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-60 has-[:disabled]:hover:border-border", cardClassName),
19
19
  children: [/* @__PURE__ */ jsx(Checkbox, {
20
+ ref,
20
21
  id,
21
22
  disabled,
22
23
  "aria-labelledby": `${id}-title`,
23
24
  "aria-describedby": description ? `${id}-description` : void 0,
24
- className: cn("mt-0.5 outline-hidden! focus-visible:ring-0 focus-visible:outline-none", className),
25
+ className: cn("mt-0.5 focus-visible:outline-none", noOutlineClasses, className),
25
26
  ...props
26
27
  }), /* @__PURE__ */ jsxs("span", {
27
28
  className: "grid min-w-0 gap-0.5",
@@ -36,18 +37,19 @@ function CheckboxCard({ title, description, id: idProp, disabled, className, car
36
37
  })]
37
38
  })]
38
39
  });
39
- }
40
+ });
40
41
  /** A fieldset of CheckboxCards with a visible legend. `disabled` disables every card. */
41
- function CheckboxCardGroup({ label, description, className, children, ...props }) {
42
+ var CheckboxCardGroup = React.forwardRef(function CheckboxCardGroup({ label, description, className, children, ...props }, ref) {
42
43
  const id = React.useId();
43
44
  return /* @__PURE__ */ jsxs("fieldset", {
45
+ ref,
44
46
  "data-slot": "checkbox-card-group",
45
47
  "aria-describedby": description ? `${id}-description` : void 0,
46
48
  className: cn("grid min-w-0 gap-2", className),
47
49
  ...props,
48
50
  children: [
49
51
  /* @__PURE__ */ jsx("legend", {
50
- className: "mb-1.5 text-sm font-medium",
52
+ className: "mb-1.5 text-sm font-semibold",
51
53
  children: label
52
54
  }),
53
55
  description && /* @__PURE__ */ jsx("p", {
@@ -58,7 +60,7 @@ function CheckboxCardGroup({ label, description, className, children, ...props }
58
60
  children
59
61
  ]
60
62
  });
61
- }
63
+ });
62
64
  //#endregion
63
65
  export { CheckboxCard, CheckboxCardGroup };
64
66
 
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox-card.js","names":[],"sources":["../../src/components/checkbox-card.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as CheckboxPrimitive from '../primitives/vendor/radix/react-checkbox';\n\nimport { Checkbox } from './checkbox';\n\nexport interface CheckboxCardProps extends Omit<\n React.ComponentProps<typeof CheckboxPrimitive.Root>,\n 'title' | 'children'\n> {\n /** The option's name. */\n title: React.ReactNode;\n /** A sentence under the name: what choosing it means. */\n description?: React.ReactNode;\n /** Extra classes for the card (the label); className goes to the checkbox. */\n cardClassName?: string;\n}\n\n/**\n * A checkbox with a title and a sentence, where the whole card is the hit\n * area (44px or taller). For choices that need explaining: roles,\n * permissions, notification types. One focus ring, on the card.\n */\nfunction CheckboxCard({\n title,\n description,\n id: idProp,\n disabled,\n className,\n cardClassName,\n ...props\n}: CheckboxCardProps) {\n const auto = React.useId();\n const id = idProp ?? auto;\n return (\n <label\n htmlFor={id}\n data-slot=\"checkbox-card\"\n className={cn(\n 'flex min-h-11 cursor-pointer items-start gap-3 rounded-md border border-border bg-surface px-3 py-2.5 transition-colors hover:border-border-strong',\n 'has-[[data-state=checked]]:border-brand/40 has-[[data-state=checked]]:bg-brand-soft/50',\n 'has-[:focus-visible]:border-ring has-[:focus-visible]:inset-ring-1 has-[:focus-visible]:inset-ring-ring',\n // :disabled also catches a disabled fieldset around the card.\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-60 has-[:disabled]:hover:border-border',\n cardClassName,\n )}\n >\n <Checkbox\n id={id}\n disabled={disabled}\n aria-labelledby={`${id}-title`}\n aria-describedby={description ? `${id}-description` : undefined}\n className={cn(\n 'mt-0.5 outline-hidden! focus-visible:ring-0 focus-visible:outline-none',\n className,\n )}\n {...props}\n />\n <span className=\"grid min-w-0 gap-0.5\">\n <span id={`${id}-title`} className=\"text-sm leading-5 font-medium\">\n {title}\n </span>\n {description && (\n <span id={`${id}-description`} className=\"text-[13px] leading-5 text-muted-foreground\">\n {description}\n </span>\n )}\n </span>\n </label>\n );\n}\n\nexport interface CheckboxCardGroupProps extends React.ComponentProps<'fieldset'> {\n /** The group's name, shown as its legend. */\n label: React.ReactNode;\n /** Help under the legend. */\n description?: React.ReactNode;\n}\n\n/** A fieldset of CheckboxCards with a visible legend. `disabled` disables every card. */\nfunction CheckboxCardGroup({\n label,\n description,\n className,\n children,\n ...props\n}: CheckboxCardGroupProps) {\n const id = React.useId();\n return (\n <fieldset\n data-slot=\"checkbox-card-group\"\n aria-describedby={description ? `${id}-description` : undefined}\n className={cn('grid min-w-0 gap-2', className)}\n {...props}\n >\n <legend className=\"mb-1.5 text-sm font-medium\">{label}</legend>\n {description && (\n <p id={`${id}-description`} className=\"-mt-1.5 text-[13px] text-muted-foreground\">\n {description}\n </p>\n )}\n {children}\n </fieldset>\n );\n}\n\nexport { CheckboxCard, CheckboxCardGroup };\n"],"mappings":";;;;;;;;;;;AAwBA,SAAS,aAAa,EACpB,OACA,aACA,IAAI,QACJ,UACA,WACA,eACA,GAAG,SACiB;CACpB,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,KAAK,UAAU;CACrB,OACE,qBAAC,SAAD;EACE,SAAS;EACT,aAAU;EACV,WAAW,GACT,sJACA,0FACA,2GAEA,qGACA,aACF;EAVF,UAAA,CAYE,oBAAC,UAAD;GACM;GACM;GACV,mBAAiB,GAAG,GAAG;GACvB,oBAAkB,cAAc,GAAG,GAAG,gBAAgB,KAAA;GACtD,WAAW,GACT,0EACA,SACF;GACA,GAAI;EACL,CAAA,GACD,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAS,WAAU;IAChC,UAAA;GACG,CAAA,GACL,eACC,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAe,WAAU;IACtC,UAAA;GACG,CAAA,CAEJ;EACD,CAAA,CAAA;;AAEX;;AAUA,SAAS,kBAAkB,EACzB,OACA,aACA,WACA,UACA,GAAG,SACsB;CACzB,MAAM,KAAK,MAAM,MAAM;CACvB,OACE,qBAAC,YAAD;EACE,aAAU;EACV,oBAAkB,cAAc,GAAG,GAAG,gBAAgB,KAAA;EACtD,WAAW,GAAG,sBAAsB,SAAS;EAC7C,GAAI;EAJN,UAAA;GAME,oBAAC,UAAD;IAAQ,WAAU;IAA8B,UAAA;GAAc,CAAA;GAC7D,eACC,oBAAC,KAAD;IAAG,IAAI,GAAG,GAAG;IAAe,WAAU;IACnC,UAAA;GACA,CAAA;GAEJ;EACO;;AAEd"}
1
+ {"version":3,"file":"checkbox-card.js","names":[],"sources":["../../src/components/checkbox-card.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn, noOutlineClasses } from '../lib/utils';\nimport * as CheckboxPrimitive from '../primitives/vendor/radix/react-checkbox';\n\nimport { Checkbox } from './checkbox';\n\nexport interface CheckboxCardProps extends Omit<\n React.ComponentProps<typeof CheckboxPrimitive.Root>,\n 'title' | 'children'\n> {\n /** The option's name. */\n title: React.ReactNode;\n /** A sentence under the name: what choosing it means. */\n description?: React.ReactNode;\n /** Extra classes for the card (the label); className goes to the checkbox. */\n cardClassName?: string;\n}\n\n/**\n * A checkbox with a title and a sentence, where the whole card is the hit\n * area (44px or taller). For choices that need explaining: roles,\n * permissions, notification types. One focus ring, on the card.\n */\nconst CheckboxCard = React.forwardRef<HTMLButtonElement, CheckboxCardProps>(function CheckboxCard(\n { title, description, id: idProp, disabled, className, cardClassName, ...props },\n ref,\n) {\n const auto = React.useId();\n const id = idProp ?? auto;\n return (\n <label\n htmlFor={id}\n data-slot=\"checkbox-card\"\n className={cn(\n 'flex min-h-11 cursor-pointer items-start gap-3 rounded-md border border-border bg-surface px-3 py-2.5 transition-colors hover:border-border-strong',\n 'has-[[data-state=checked]]:border-brand/40 has-[[data-state=checked]]:bg-brand-soft/50',\n 'has-[:focus-visible]:border-ring has-[:focus-visible]:inset-ring-1 has-[:focus-visible]:inset-ring-ring',\n // :disabled also catches a disabled fieldset around the card.\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-60 has-[:disabled]:hover:border-border',\n cardClassName,\n )}\n >\n <Checkbox\n ref={ref}\n id={id}\n disabled={disabled}\n aria-labelledby={`${id}-title`}\n aria-describedby={description ? `${id}-description` : undefined}\n // The card draws the focus ring; the box keeps a system outline in forced colors.\n className={cn('mt-0.5 focus-visible:outline-none', noOutlineClasses, className)}\n {...props}\n />\n <span className=\"grid min-w-0 gap-0.5\">\n <span id={`${id}-title`} className=\"text-sm leading-5 font-medium\">\n {title}\n </span>\n {description && (\n <span id={`${id}-description`} className=\"text-[13px] leading-5 text-muted-foreground\">\n {description}\n </span>\n )}\n </span>\n </label>\n );\n});\n\nexport interface CheckboxCardGroupProps extends React.ComponentProps<'fieldset'> {\n /** The group's name, shown as its legend. */\n label: React.ReactNode;\n /** Help under the legend. */\n description?: React.ReactNode;\n}\n\n/** A fieldset of CheckboxCards with a visible legend. `disabled` disables every card. */\nconst CheckboxCardGroup = React.forwardRef<HTMLFieldSetElement, CheckboxCardGroupProps>(\n function CheckboxCardGroup({ label, description, className, children, ...props }, ref) {\n const id = React.useId();\n return (\n <fieldset\n ref={ref}\n data-slot=\"checkbox-card-group\"\n aria-describedby={description ? `${id}-description` : undefined}\n className={cn('grid min-w-0 gap-2', className)}\n {...props}\n >\n <legend className=\"mb-1.5 text-sm font-semibold\">{label}</legend>\n {description && (\n <p id={`${id}-description`} className=\"-mt-1.5 text-[13px] text-muted-foreground\">\n {description}\n </p>\n )}\n {children}\n </fieldset>\n );\n },\n);\n\nexport { CheckboxCard, CheckboxCardGroup };\n"],"mappings":";;;;;;;;;;;AAwBA,IAAM,eAAe,MAAM,WAAiD,SAAS,aACnF,EAAE,OAAO,aAAa,IAAI,QAAQ,UAAU,WAAW,eAAe,GAAG,SACzE,KACA;CACA,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,KAAK,UAAU;CACrB,OACE,qBAAC,SAAD;EACE,SAAS;EACT,aAAU;EACV,WAAW,GACT,sJACA,0FACA,2GAEA,qGACA,aACF;EAVF,UAAA,CAYE,oBAAC,UAAD;GACO;GACD;GACM;GACV,mBAAiB,GAAG,GAAG;GACvB,oBAAkB,cAAc,GAAG,GAAG,gBAAgB,KAAA;GAEtD,WAAW,GAAG,qCAAqC,kBAAkB,SAAS;GAC9E,GAAI;EACL,CAAA,GACD,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAS,WAAU;IAChC,UAAA;GACG,CAAA,GACL,eACC,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAe,WAAU;IACtC,UAAA;GACG,CAAA,CAEJ;EACD,CAAA,CAAA;;AAEX,CAAC;;AAUD,IAAM,oBAAoB,MAAM,WAC9B,SAAS,kBAAkB,EAAE,OAAO,aAAa,WAAW,UAAU,GAAG,SAAS,KAAK;CACrF,MAAM,KAAK,MAAM,MAAM;CACvB,OACE,qBAAC,YAAD;EACO;EACL,aAAU;EACV,oBAAkB,cAAc,GAAG,GAAG,gBAAgB,KAAA;EACtD,WAAW,GAAG,sBAAsB,SAAS;EAC7C,GAAI;EALN,UAAA;GAOE,oBAAC,UAAD;IAAQ,WAAU;IAAgC,UAAA;GAAc,CAAA;GAC/D,eACC,oBAAC,KAAD;IAAG,IAAI,GAAG,GAAG;IAAe,WAAU;IACnC,UAAA;GACA,CAAA;GAEJ;EACO;;AAEd,CACF"}
@@ -1,4 +1,25 @@
1
1
  import * as React from 'react';
2
2
  import * as CheckboxPrimitive from '../primitives/vendor/radix/react-checkbox';
3
- declare function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>): React.JSX.Element;
4
- export { Checkbox };
3
+ /**
4
+ * A 16px box with a 44px hit area on touch screens. Its unchecked border is
5
+ * the field border tone (`input`, 3:1 on the page in both modes); checked it
6
+ * fills with primary in light mode and with brand (the ring colour, 4.5:1 on
7
+ * the page, where primary reaches 2.8:1) in dark mode. Focus is the shared
8
+ * 2px outline. Wrap it in a Label, or use CheckboxRow or CheckboxCard.
9
+ */
10
+ declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
11
+ export interface CheckboxRowProps extends Omit<React.ComponentProps<typeof CheckboxPrimitive.Root>, 'children'> {
12
+ /** The option's name. */
13
+ label: React.ReactNode;
14
+ /** A line under the name. */
15
+ description?: React.ReactNode;
16
+ /** Extra classes for the row (the label); className goes to the checkbox. */
17
+ rowClassName?: string;
18
+ }
19
+ /**
20
+ * A checkbox with its label to the right and an optional line under it, the
21
+ * whole row clickable. For a bordered option with a sentence of explanation
22
+ * use CheckboxCard.
23
+ */
24
+ declare const CheckboxRow: React.ForwardRefExoticComponent<Omit<CheckboxRowProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
25
+ export { Checkbox, CheckboxRow };
@@ -1,15 +1,23 @@
1
1
  "use client";
2
- import { cn } from "../lib/utils.js";
2
+ import { cn, focusRingClasses, touchTargetClasses } from "../lib/utils.js";
3
3
  import { Checkbox as Checkbox$1, CheckboxIndicator } from "../primitives/vendor/radix/react-checkbox/checkbox.js";
4
- import "react";
4
+ import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import Check from "@burtson-labs/icons/react/check";
7
7
  import Minus from "@burtson-labs/icons/react/minus";
8
8
  //#region src/components/checkbox.tsx
9
- function Checkbox({ className, ...props }) {
9
+ /**
10
+ * A 16px box with a 44px hit area on touch screens. Its unchecked border is
11
+ * the field border tone (`input`, 3:1 on the page in both modes); checked it
12
+ * fills with primary in light mode and with brand (the ring colour, 4.5:1 on
13
+ * the page, where primary reaches 2.8:1) in dark mode. Focus is the shared
14
+ * 2px outline. Wrap it in a Label, or use CheckboxRow or CheckboxCard.
15
+ */
16
+ var Checkbox = React.forwardRef(function Checkbox({ className, ...props }, ref) {
10
17
  return /* @__PURE__ */ jsx(Checkbox$1, {
18
+ ref,
11
19
  "data-slot": "checkbox",
12
- 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),
20
+ className: cn("peer size-4 shrink-0 rounded-xs border border-input bg-surface shadow-xs transition-[border-color,box-shadow,background-color] hover:border-brand/50 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 data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground dark:data-[state=checked]:border-brand dark:data-[state=checked]:bg-brand dark:data-[state=checked]:text-background dark:data-[state=indeterminate]:border-brand dark:data-[state=indeterminate]:bg-brand dark:data-[state=indeterminate]:text-background", focusRingClasses, touchTargetClasses, className),
13
21
  ...props,
14
22
  children: /* @__PURE__ */ jsxs(CheckboxIndicator, {
15
23
  "data-slot": "checkbox-indicator",
@@ -25,8 +33,42 @@ function Checkbox({ className, ...props }) {
25
33
  })]
26
34
  })
27
35
  });
28
- }
36
+ });
37
+ /**
38
+ * A checkbox with its label to the right and an optional line under it, the
39
+ * whole row clickable. For a bordered option with a sentence of explanation
40
+ * use CheckboxCard.
41
+ */
42
+ var CheckboxRow = React.forwardRef(function CheckboxRow({ label, description, id: idProp, className, rowClassName, disabled, ...props }, ref) {
43
+ const auto = React.useId();
44
+ const id = idProp ?? auto;
45
+ return /* @__PURE__ */ jsxs("label", {
46
+ htmlFor: id,
47
+ "data-slot": "checkbox-row",
48
+ className: cn("flex min-h-5 cursor-pointer items-start gap-2.5 text-sm leading-5 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-60", rowClassName),
49
+ children: [/* @__PURE__ */ jsx(Checkbox, {
50
+ ref,
51
+ id,
52
+ disabled,
53
+ "aria-labelledby": `${id}-label`,
54
+ "aria-describedby": description ? `${id}-description` : void 0,
55
+ className: cn("mt-0.5", className),
56
+ ...props
57
+ }), /* @__PURE__ */ jsxs("span", {
58
+ className: "grid min-w-0 gap-0.5",
59
+ children: [/* @__PURE__ */ jsx("span", {
60
+ id: `${id}-label`,
61
+ className: "font-medium",
62
+ children: label
63
+ }), description && /* @__PURE__ */ jsx("span", {
64
+ id: `${id}-description`,
65
+ className: "text-xs leading-4 text-muted-foreground",
66
+ children: description
67
+ })]
68
+ })]
69
+ });
70
+ });
29
71
  //#endregion
30
- export { Checkbox };
72
+ export { Checkbox, CheckboxRow };
31
73
 
32
74
  //# sourceMappingURL=checkbox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.js","names":["CheckboxPrimitive.Root","CheckboxPrimitive.Indicator"],"sources":["../../src/components/checkbox.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport Minus from '@burtson-labs/icons/react/minus';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as CheckboxPrimitive from '../primitives/vendor/radix/react-checkbox';\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,SAAS,SAAS,EAAE,WAAW,GAAG,SAA8D;CAC9F,OACE,oBAACA,YAAD;EACE,aAAU;EACV,WAAW,GACT,iZACA,SACF;EACA,GAAI;EAEJ,UAAA,qBAACC,mBAAD;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"}
1
+ {"version":3,"file":"checkbox.js","names":["CheckboxPrimitive.Root","CheckboxPrimitive.Indicator"],"sources":["../../src/components/checkbox.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport Minus from '@burtson-labs/icons/react/minus';\nimport * as React from 'react';\n\nimport { cn, focusRingClasses, touchTargetClasses } from '../lib/utils';\nimport * as CheckboxPrimitive from '../primitives/vendor/radix/react-checkbox';\n\n/**\n * A 16px box with a 44px hit area on touch screens. Its unchecked border is\n * the field border tone (`input`, 3:1 on the page in both modes); checked it\n * fills with primary in light mode and with brand (the ring colour, 4.5:1 on\n * the page, where primary reaches 2.8:1) in dark mode. Focus is the shared\n * 2px outline. Wrap it in a Label, or use CheckboxRow or CheckboxCard.\n */\nconst Checkbox = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<typeof CheckboxPrimitive.Root>\n>(function Checkbox({ className, ...props }, ref) {\n return (\n <CheckboxPrimitive.Root\n ref={ref}\n data-slot=\"checkbox\"\n className={cn(\n 'peer size-4 shrink-0 rounded-xs border border-input bg-surface shadow-xs transition-[border-color,box-shadow,background-color] hover:border-brand/50 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 data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground dark:data-[state=checked]:border-brand dark:data-[state=checked]:bg-brand dark:data-[state=checked]:text-background dark:data-[state=indeterminate]:border-brand dark:data-[state=indeterminate]:bg-brand dark:data-[state=indeterminate]:text-background',\n focusRingClasses,\n touchTargetClasses,\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 interface CheckboxRowProps extends Omit<\n React.ComponentProps<typeof CheckboxPrimitive.Root>,\n 'children'\n> {\n /** The option's name. */\n label: React.ReactNode;\n /** A line under the name. */\n description?: React.ReactNode;\n /** Extra classes for the row (the label); className goes to the checkbox. */\n rowClassName?: string;\n}\n\n/**\n * A checkbox with its label to the right and an optional line under it, the\n * whole row clickable. For a bordered option with a sentence of explanation\n * use CheckboxCard.\n */\nconst CheckboxRow = React.forwardRef<HTMLButtonElement, CheckboxRowProps>(function CheckboxRow(\n { label, description, id: idProp, className, rowClassName, disabled, ...props },\n ref,\n) {\n const auto = React.useId();\n const id = idProp ?? auto;\n return (\n <label\n htmlFor={id}\n data-slot=\"checkbox-row\"\n className={cn(\n 'flex min-h-5 cursor-pointer items-start gap-2.5 text-sm leading-5 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-60',\n rowClassName,\n )}\n >\n <Checkbox\n ref={ref}\n id={id}\n disabled={disabled}\n aria-labelledby={`${id}-label`}\n aria-describedby={description ? `${id}-description` : undefined}\n className={cn('mt-0.5', className)}\n {...props}\n />\n <span className=\"grid min-w-0 gap-0.5\">\n <span id={`${id}-label`} className=\"font-medium\">\n {label}\n </span>\n {description && (\n <span id={`${id}-description`} className=\"text-xs leading-4 text-muted-foreground\">\n {description}\n </span>\n )}\n </span>\n </label>\n );\n});\n\nexport { Checkbox, CheckboxRow };\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAM,WAAW,MAAM,WAGrB,SAAS,SAAS,EAAE,WAAW,GAAG,SAAS,KAAK;CAChD,OACE,oBAACA,YAAD;EACO;EACL,aAAU;EACV,WAAW,GACT,stBACA,kBACA,oBACA,SACF;EACA,GAAI;EAEJ,UAAA,qBAACC,mBAAD;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,CAAC;;;;;;AAmBD,IAAM,cAAc,MAAM,WAAgD,SAAS,YACjF,EAAE,OAAO,aAAa,IAAI,QAAQ,WAAW,cAAc,UAAU,GAAG,SACxE,KACA;CACA,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,KAAK,UAAU;CACrB,OACE,qBAAC,SAAD;EACE,SAAS;EACT,aAAU;EACV,WAAW,GACT,mIACA,YACF;EANF,UAAA,CAQE,oBAAC,UAAD;GACO;GACD;GACM;GACV,mBAAiB,GAAG,GAAG;GACvB,oBAAkB,cAAc,GAAG,GAAG,gBAAgB,KAAA;GACtD,WAAW,GAAG,UAAU,SAAS;GACjC,GAAI;EACL,CAAA,GACD,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAS,WAAU;IAChC,UAAA;GACG,CAAA,GACL,eACC,oBAAC,QAAD;IAAM,IAAI,GAAG,GAAG;IAAe,WAAU;IACtC,UAAA;GACG,CAAA,CAEJ;EACD,CAAA,CAAA;;AAEX,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import * as CollapsiblePrimitive from '../primitives/vendor/radix/react-collapsible';
3
3
  declare function Collapsible(props: React.ComponentProps<typeof CollapsiblePrimitive.Root>): React.JSX.Element;
4
- declare function CollapsibleTrigger(props: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>): React.JSX.Element;
5
- declare function CollapsibleContent({ className, ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Content>): React.JSX.Element;
4
+ declare const CollapsibleTrigger: React.ForwardRefExoticComponent<Omit<CollapsiblePrimitive.CollapsibleTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
5
+ declare const CollapsibleContent: React.ForwardRefExoticComponent<Omit<CollapsiblePrimitive.CollapsibleContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
6
6
  export { Collapsible, CollapsibleContent, CollapsibleTrigger };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { Content, Root, Trigger } from "../primitives/vendor/radix/react-collapsible/collapsible.js";
4
- import "react";
4
+ import * as React from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/components/collapsible.tsx
7
7
  function Collapsible(props) {
@@ -10,19 +10,21 @@ function Collapsible(props) {
10
10
  ...props
11
11
  });
12
12
  }
13
- function CollapsibleTrigger(props) {
13
+ var CollapsibleTrigger = React.forwardRef(function CollapsibleTrigger(props, ref) {
14
14
  return /* @__PURE__ */ jsx(Trigger, {
15
+ ref,
15
16
  "data-slot": "collapsible-trigger",
16
17
  ...props
17
18
  });
18
- }
19
- function CollapsibleContent({ className, ...props }) {
19
+ });
20
+ var CollapsibleContent = React.forwardRef(function CollapsibleContent({ className, ...props }, ref) {
20
21
  return /* @__PURE__ */ jsx(Content, {
22
+ ref,
21
23
  "data-slot": "collapsible-content",
22
24
  className: cn("overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down", className),
23
25
  ...props
24
26
  });
25
- }
27
+ });
26
28
  //#endregion
27
29
  export { Collapsible, CollapsibleContent, CollapsibleTrigger };
28
30
 
@@ -1 +1 @@
1
- {"version":3,"file":"collapsible.js","names":["CollapsiblePrimitive.Root","CollapsiblePrimitive.Trigger","CollapsiblePrimitive.Content"],"sources":["../../src/components/collapsible.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as CollapsiblePrimitive from '../primitives/vendor/radix/react-collapsible';\n\nfunction Collapsible(props: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger(props: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) {\n return <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />;\n}\n\nfunction CollapsibleContent({\n className,\n ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Content>) {\n return (\n <CollapsiblePrimitive.Content\n data-slot=\"collapsible-content\"\n className={cn(\n 'overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down',\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Collapsible, CollapsibleContent, CollapsibleTrigger };\n"],"mappings":";;;;;;AAKA,SAAS,YAAY,OAA+D;CAClF,OAAO,oBAACA,MAAD;EAA2B,aAAU;EAAc,GAAI;CAAQ,CAAA;AACxE;AAEA,SAAS,mBAAmB,OAAkE;CAC5F,OAAO,oBAACC,SAAD;EAA8B,aAAU;EAAsB,GAAI;CAAQ,CAAA;AACnF;AAEA,SAAS,mBAAmB,EAC1B,WACA,GAAG,SACyD;CAC5D,OACE,oBAACC,SAAD;EACE,aAAU;EACV,WAAW,GACT,yGACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"collapsible.js","names":["CollapsiblePrimitive.Root","CollapsiblePrimitive.Trigger","CollapsiblePrimitive.Content"],"sources":["../../src/components/collapsible.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '../lib/utils';\nimport * as CollapsiblePrimitive from '../primitives/vendor/radix/react-collapsible';\n\nfunction Collapsible(props: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nconst CollapsibleTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<typeof CollapsiblePrimitive.Trigger>\n>(function CollapsibleTrigger(props, ref) {\n return <CollapsiblePrimitive.Trigger ref={ref} data-slot=\"collapsible-trigger\" {...props} />;\n});\n\nconst CollapsibleContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<typeof CollapsiblePrimitive.Content>\n>(function CollapsibleContent({ className, ...props }, ref) {\n return (\n <CollapsiblePrimitive.Content\n ref={ref}\n data-slot=\"collapsible-content\"\n className={cn(\n 'overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down',\n className,\n )}\n {...props}\n />\n );\n});\n\nexport { Collapsible, CollapsibleContent, CollapsibleTrigger };\n"],"mappings":";;;;;;AAKA,SAAS,YAAY,OAA+D;CAClF,OAAO,oBAACA,MAAD;EAA2B,aAAU;EAAc,GAAI;CAAQ,CAAA;AACxE;AAEA,IAAM,qBAAqB,MAAM,WAG/B,SAAS,mBAAmB,OAAO,KAAK;CACxC,OAAO,oBAACC,SAAD;EAAmC;EAAK,aAAU;EAAsB,GAAI;CAAQ,CAAA;AAC7F,CAAC;AAED,IAAM,qBAAqB,MAAM,WAG/B,SAAS,mBAAmB,EAAE,WAAW,GAAG,SAAS,KAAK;CAC1D,OACE,oBAACC,SAAD;EACO;EACL,aAAU;EACV,WAAW,GACT,yGACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type FieldWidth } from './input';
2
3
  export interface ComboboxOption {
3
4
  value: string;
4
5
  label: string;
@@ -8,8 +9,11 @@ export interface ComboboxOption {
8
9
  }
9
10
  export interface ComboboxProps {
10
11
  options: ComboboxOption[];
11
- value: string | null;
12
- onValueChange: (value: string | null) => void;
12
+ /** Controlled: the chosen value, or null. Leave out for an uncontrolled combobox. */
13
+ value?: string | null;
14
+ /** The starting value when uncontrolled. */
15
+ defaultValue?: string | null;
16
+ onValueChange?: (value: string | null) => void;
13
17
  placeholder?: string;
14
18
  searchPlaceholder?: string;
15
19
  emptyText?: string;
@@ -23,15 +27,21 @@ export interface ComboboxProps {
23
27
  onSearchChange?: (search: string) => void;
24
28
  /** Let the person clear the selection by picking it again. */
25
29
  clearable?: boolean;
30
+ /** How wide the trigger is from 640px up; full width on phones. */
31
+ width?: FieldWidth;
26
32
  id?: string;
27
33
  className?: string;
28
34
  'aria-invalid'?: boolean;
29
35
  'aria-label'?: string;
30
36
  'aria-labelledby'?: string;
31
37
  'aria-describedby'?: string;
38
+ 'aria-required'?: boolean;
32
39
  name?: string;
33
40
  required?: boolean;
34
41
  }
35
- /** Pick one value from a long or remote list by typing. */
36
- declare function Combobox({ options, value, onValueChange, placeholder, searchPlaceholder, emptyText, disabled, loading, onSearchChange, clearable, id, className, name, required, ...aria }: ComboboxProps): React.JSX.Element;
42
+ /**
43
+ * Pick one value from a long or remote list by typing. The ref reaches the
44
+ * trigger button, so a form library or an ErrorSummary can focus it.
45
+ */
46
+ declare const Combobox: React.ForwardRefExoticComponent<ComboboxProps & React.RefAttributes<HTMLButtonElement>>;
37
47
  export { Combobox };
@@ -2,16 +2,25 @@
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { Spinner } from "./spinner.js";
4
4
  import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
5
- import { fieldClasses } from "./input.js";
5
+ import { fieldClasses, fieldWidthClasses } from "./input.js";
6
6
  import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./command.js";
7
7
  import * as React from "react";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import Check from "@burtson-labs/icons/react/check";
10
10
  import ChevronsUpDown from "@burtson-labs/icons/react/chevrons-up-down";
11
11
  //#region src/components/combobox.tsx
12
- /** Pick one value from a long or remote list by typing. */
13
- function Combobox({ options, value, onValueChange, placeholder = "Select…", searchPlaceholder = "Search…", emptyText = "No matches.", disabled, loading, onSearchChange, clearable = true, id, className, name, required, ...aria }) {
12
+ /**
13
+ * Pick one value from a long or remote list by typing. The ref reaches the
14
+ * trigger button, so a form library or an ErrorSummary can focus it.
15
+ */
16
+ var Combobox = React.forwardRef(function Combobox({ options, value: valueProp, defaultValue = null, onValueChange, placeholder = "Select…", searchPlaceholder = "Search…", emptyText = "No matches.", disabled, loading, onSearchChange, clearable = true, width, id, className, name, required, ...aria }, ref) {
14
17
  const [open, setOpen] = React.useState(false);
18
+ const [own, setOwn] = React.useState(defaultValue);
19
+ const value = valueProp === void 0 ? own : valueProp;
20
+ const setValue = (next) => {
21
+ if (valueProp === void 0) setOwn(next);
22
+ onValueChange?.(next);
23
+ };
15
24
  const listId = React.useId();
16
25
  const selected = options.find((o) => o.value === value);
17
26
  return /* @__PURE__ */ jsxs(Fragment, { children: [name && /* @__PURE__ */ jsx("input", {
@@ -25,9 +34,10 @@ function Combobox({ options, value, onValueChange, placeholder = "Select…", se
25
34
  children: [/* @__PURE__ */ jsx(PopoverTrigger, {
26
35
  asChild: true,
27
36
  children: /* @__PURE__ */ jsxs("button", {
37
+ ref,
28
38
  type: "button",
29
39
  role: "combobox",
30
- "aria-required": required || void 0,
40
+ "aria-required": aria["aria-required"] ?? (required || void 0),
31
41
  onKeyDown: (event) => {
32
42
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
33
43
  event.preventDefault();
@@ -45,12 +55,12 @@ function Combobox({ options, value, onValueChange, placeholder = "Select…", se
45
55
  "aria-invalid": aria["aria-invalid"],
46
56
  disabled,
47
57
  "data-slot": "combobox-trigger",
48
- className: cn(fieldClasses, "flex h-9 items-center justify-between gap-2 text-left", !selected && "text-muted-foreground", className),
58
+ className: cn(fieldClasses, "flex h-9 items-center justify-between gap-2 text-left", !selected && "text-muted-foreground", width && fieldWidthClasses[width], className),
49
59
  children: [/* @__PURE__ */ jsx("span", {
50
60
  className: "truncate",
51
61
  children: selected?.label ?? value ?? placeholder
52
62
  }), /* @__PURE__ */ jsx(ChevronsUpDown, {
53
- className: "size-4 shrink-0 opacity-60",
63
+ className: "size-4 shrink-0 text-muted-foreground",
54
64
  "aria-hidden": true
55
65
  })]
56
66
  })
@@ -72,7 +82,7 @@ function Combobox({ options, value, onValueChange, placeholder = "Select…", se
72
82
  value: `${o.label} ${o.description ?? ""} ${o.value}`,
73
83
  disabled: o.disabled,
74
84
  onSelect: () => {
75
- onValueChange(clearable && o.value === value ? null : o.value);
85
+ setValue(clearable && o.value === value ? null : o.value);
76
86
  setOpen(false);
77
87
  },
78
88
  children: [/* @__PURE__ */ jsxs("span", {
@@ -92,7 +102,7 @@ function Combobox({ options, value, onValueChange, placeholder = "Select…", se
92
102
  })
93
103
  })]
94
104
  })] });
95
- }
105
+ });
96
106
  //#endregion
97
107
  export { Combobox };
98
108
 
@@ -1 +1 @@
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
+ {"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, type FieldWidth, fieldWidthClasses } 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 /** Controlled: the chosen value, or null. Leave out for an uncontrolled combobox. */\n value?: string | null;\n /** The starting value when uncontrolled. */\n defaultValue?: 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 /** How wide the trigger is from 640px up; full width on phones. */\n width?: FieldWidth;\n id?: string;\n className?: string;\n 'aria-invalid'?: boolean;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-required'?: boolean;\n name?: string;\n required?: boolean;\n}\n\n/**\n * Pick one value from a long or remote list by typing. The ref reaches the\n * trigger button, so a form library or an ErrorSummary can focus it.\n */\nconst Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(function Combobox(\n {\n options,\n value: valueProp,\n defaultValue = null,\n onValueChange,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n emptyText = 'No matches.',\n disabled,\n loading,\n onSearchChange,\n clearable = true,\n width,\n id,\n className,\n name,\n required,\n ...aria\n },\n ref,\n) {\n const [open, setOpen] = React.useState(false);\n const [own, setOwn] = React.useState<string | null>(defaultValue);\n const value = valueProp === undefined ? own : valueProp;\n const setValue = (next: string | null) => {\n if (valueProp === undefined) setOwn(next);\n onValueChange?.(next);\n };\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 ref={ref}\n type=\"button\"\n role=\"combobox\"\n aria-required={aria['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 width && fieldWidthClasses[width],\n className,\n )}\n >\n <span className=\"truncate\">{selected?.label ?? value ?? placeholder}</span>\n <ChevronsUpDown className=\"size-4 shrink-0 text-muted-foreground\" 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 setValue(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":"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"}