panelui-web 0.10.0 → 0.11.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 (99) hide show
  1. package/dist/components/alert/index.d.ts +15 -8
  2. package/dist/components/alert/index.js +65 -18
  3. package/dist/components/alert-dialog/index.d.ts +9 -3
  4. package/dist/components/alert-dialog/index.js +23 -7
  5. package/dist/components/avatar/index.d.ts +9 -3
  6. package/dist/components/avatar/index.js +34 -10
  7. package/dist/components/badge/index.d.ts +57 -0
  8. package/dist/components/badge/index.js +61 -0
  9. package/dist/components/bar-picker/index.d.ts +8 -1
  10. package/dist/components/bar-picker/index.js +35 -26
  11. package/dist/components/button/index.d.ts +68 -46
  12. package/dist/components/button/index.js +38 -2
  13. package/dist/components/button-group/index.d.ts +11 -5
  14. package/dist/components/button-group/index.js +31 -7
  15. package/dist/components/canvas/index.d.ts +9 -3
  16. package/dist/components/canvas/index.js +53 -25
  17. package/dist/components/card/index.d.ts +13 -7
  18. package/dist/components/card/index.js +45 -17
  19. package/dist/components/checkbox/index.d.ts +73 -0
  20. package/dist/components/checkbox/index.js +100 -0
  21. package/dist/components/chip/index.d.ts +79 -0
  22. package/dist/components/chip/index.js +112 -0
  23. package/dist/components/choice-cards/index.d.ts +11 -5
  24. package/dist/components/choice-cards/index.js +25 -6
  25. package/dist/components/command-dock/index.d.ts +16 -9
  26. package/dist/components/command-dock/index.js +76 -19
  27. package/dist/components/day-grid/index.d.ts +12 -6
  28. package/dist/components/day-grid/index.js +71 -27
  29. package/dist/components/dialog/index.d.ts +19 -12
  30. package/dist/components/dialog/index.js +84 -25
  31. package/dist/components/feedback/index.d.ts +20 -13
  32. package/dist/components/feedback/index.js +83 -27
  33. package/dist/components/field/index.d.ts +85 -0
  34. package/dist/components/field/index.js +90 -0
  35. package/dist/components/flow/index.d.ts +21 -14
  36. package/dist/components/flow/index.js +56 -20
  37. package/dist/components/frame/index.d.ts +17 -11
  38. package/dist/components/frame/index.js +73 -22
  39. package/dist/components/input/index.d.ts +9 -24
  40. package/dist/components/input/index.js +53 -6
  41. package/dist/components/inspector/index.d.ts +69 -63
  42. package/dist/components/inspector/index.js +31 -9
  43. package/dist/components/kpi/index.d.ts +20 -14
  44. package/dist/components/kpi/index.js +111 -36
  45. package/dist/components/label/index.d.ts +10 -4
  46. package/dist/components/label/index.js +39 -9
  47. package/dist/components/lane-board/index.d.ts +15 -9
  48. package/dist/components/lane-board/index.js +97 -57
  49. package/dist/components/menu/index.d.ts +10 -4
  50. package/dist/components/menu/index.js +28 -10
  51. package/dist/components/message/index.d.ts +15 -9
  52. package/dist/components/message/index.js +71 -19
  53. package/dist/components/not-found/index.d.ts +10 -4
  54. package/dist/components/not-found/index.js +48 -12
  55. package/dist/components/planet/index.d.ts +11 -5
  56. package/dist/components/planet/index.js +73 -20
  57. package/dist/components/prompt/index.d.ts +30 -23
  58. package/dist/components/prompt/index.js +148 -52
  59. package/dist/components/radio-group/index.d.ts +124 -0
  60. package/dist/components/radio-group/index.js +216 -0
  61. package/dist/components/range-meter/index.d.ts +9 -3
  62. package/dist/components/range-meter/index.js +61 -27
  63. package/dist/components/rolling-number/index.d.ts +8 -1
  64. package/dist/components/rolling-number/index.js +15 -6
  65. package/dist/components/select/index.d.ts +7 -1
  66. package/dist/components/select/index.js +13 -4
  67. package/dist/components/sheet/index.d.ts +8 -2
  68. package/dist/components/sheet/index.js +27 -15
  69. package/dist/components/slider/index.d.ts +11 -4
  70. package/dist/components/slider/index.js +30 -10
  71. package/dist/components/soundwave/index.d.ts +8 -2
  72. package/dist/components/soundwave/index.js +26 -6
  73. package/dist/components/stat/index.d.ts +13 -7
  74. package/dist/components/stat/index.js +45 -15
  75. package/dist/components/table/index.d.ts +128 -0
  76. package/dist/components/table/index.js +145 -0
  77. package/dist/components/tabs/index.d.ts +7 -1
  78. package/dist/components/tabs/index.js +8 -2
  79. package/dist/components/textarea/index.d.ts +0 -23
  80. package/dist/components/textarea/index.js +22 -5
  81. package/dist/components/tilt-card/index.d.ts +9 -3
  82. package/dist/components/tilt-card/index.js +28 -8
  83. package/dist/components/timeline/index.d.ts +20 -13
  84. package/dist/components/timeline/index.js +166 -59
  85. package/dist/components/toast/dither-text.d.ts +13 -0
  86. package/dist/components/toast/dither-text.js +143 -0
  87. package/dist/components/toast/dither.d.ts +29 -0
  88. package/dist/components/toast/dither.js +153 -0
  89. package/dist/components/toast/index.d.ts +108 -0
  90. package/dist/components/toast/index.js +225 -0
  91. package/dist/components/toolbar/index.d.ts +9 -3
  92. package/dist/components/toolbar/index.js +23 -6
  93. package/dist/components/workbench/index.d.ts +50 -43
  94. package/dist/components/workbench/index.js +77 -20
  95. package/dist/components/xy-pad/index.d.ts +9 -3
  96. package/dist/components/xy-pad/index.js +13 -2
  97. package/dist/index.d.ts +7 -0
  98. package/dist/index.js +7 -0
  99. package/package.json +1 -1
@@ -68,6 +68,8 @@ import { Button } from '../button/index.js';
68
68
  import { Menu } from '../menu/index.js';
69
69
  import { Select } from '../select/index.js';
70
70
  import { Textarea } from '../textarea/index.js';
71
+ import { mergeProps } from '@base-ui/react/merge-props';
72
+ import { useRender } from '@base-ui/react/use-render';
71
73
  const promptVariants = tv({
72
74
  slots: {
73
75
  root: 'isolate flex w-full flex-col',
@@ -120,10 +122,7 @@ const promptVariants = tv({
120
122
  // The plus turns to a cross while its menu is open.
121
123
  menuTrigger: 'data-popup-open:bg-accent [&_svg]:transition-transform [&_svg]:duration-200 data-popup-open:[&_svg]:rotate-45 motion-reduce:[&_svg]:transition-none',
122
124
  modelValue: 'flex min-w-0 items-center gap-2 [&_svg]:size-4',
123
- submit: [
124
- 'relative size-10 shrink-0 overflow-visible rounded-full p-0',
125
- 'disabled:opacity-40',
126
- ],
125
+ submit: ['relative size-10 shrink-0 overflow-visible rounded-full p-0', 'disabled:opacity-40'],
127
126
  submitIcon: 'absolute inset-0 m-auto flex items-center justify-center transition-[opacity,scale,rotate] duration-200 ease-out motion-reduce:transition-none [&_svg]:size-[18px]',
128
127
  submitRing: 'pointer-events-none absolute -inset-[3px] size-[calc(100%+6px)] text-primary transition-opacity duration-200',
129
128
  statusText: 'min-w-0 truncate bg-[linear-gradient(90deg,var(--muted-foreground)_35%,var(--foreground)_50%,var(--muted-foreground)_65%)] bg-[length:250%_100%] bg-clip-text text-transparent',
@@ -298,7 +297,25 @@ function PromptRoot({ value: controlledValue, defaultValue = '', onValueChange,
298
297
  keyHandler,
299
298
  listbox,
300
299
  setListbox,
301
- }), [value, setValue, attachments, addFiles, removeAttachment, openFileDialog, status, mode, busy, disabled, empty, submit, stop, onStop, dragging, fieldId, listbox]);
300
+ }), [
301
+ value,
302
+ setValue,
303
+ attachments,
304
+ addFiles,
305
+ removeAttachment,
306
+ openFileDialog,
307
+ status,
308
+ mode,
309
+ busy,
310
+ disabled,
311
+ empty,
312
+ submit,
313
+ stop,
314
+ onStop,
315
+ dragging,
316
+ fieldId,
317
+ listbox,
318
+ ]);
302
319
  const send = (event) => {
303
320
  event.preventDefault();
304
321
  submit();
@@ -372,55 +389,88 @@ function PromptStatus({ open, mode, icon, spinner = true, children, ...props })
372
389
  }
373
390
  PromptStatus.displayName = 'Prompt.Status';
374
391
  /** A strip under the field, inset from its edges, for where the message is going: a branch, a folder, a ticket. */
375
- function PromptContext({ open, mode, className, ...props }) {
392
+ function PromptContext({ open, mode, className, render, ...props }) {
376
393
  const shown = useBandOpen('Prompt.Context', open, mode, () => true);
377
394
  const slots = promptVariants();
378
395
  const clip = useRef(null);
379
396
  useEffect(() => {
380
397
  clip.current?.toggleAttribute('inert', !shown);
381
398
  }, [shown]);
382
- return (_jsx("div", { "data-slot": "prompt-context", "data-open": shown ? '' : undefined, className: slots.contextFold({ className: shown ? '-mt-5 grid-rows-[1fr] opacity-100' : 'mt-0 grid-rows-[0fr] opacity-0' }), children: _jsx("div", { ref: clip, className: "min-h-0 overflow-hidden", children: _jsx("div", { className: slots.context({ className }), ...props }) }) }));
399
+ const element = useRender({
400
+ defaultTagName: 'div',
401
+ render,
402
+ props: mergeProps({ className: slots.context({ className }) }, props),
403
+ });
404
+ return (_jsx("div", { "data-slot": "prompt-context", "data-open": shown ? '' : undefined, className: slots.contextFold({
405
+ className: shown ? '-mt-5 grid-rows-[1fr] opacity-100' : 'mt-0 grid-rows-[0fr] opacity-0',
406
+ }), children: _jsx("div", { ref: clip, className: "min-h-0 overflow-hidden", children: element }) }));
383
407
  }
384
408
  PromptContext.displayName = 'Prompt.Context';
385
409
  /** One fact in the context strip, with an icon before it. */
386
- function PromptContextItem({ onSelect, className, children, ...props }) {
410
+ function PromptContextItem({ onSelect, className, children, render, ...props }) {
387
411
  const slots = promptVariants();
388
412
  if (onSelect) {
389
413
  return (_jsx("button", { type: "button", "data-slot": "prompt-context-item", onClick: onSelect, className: slots.contextItem({ className: [slots.contextButton(), className] }), children: children }));
390
414
  }
391
- return (_jsx("span", { "data-slot": "prompt-context-item", className: slots.contextItem({ className }), ...props, children: children }));
415
+ return useRender({
416
+ defaultTagName: 'span',
417
+ render,
418
+ props: mergeProps({ 'data-slot': 'prompt-context-item', className: slots.contextItem({ className }), children: _jsx(_Fragment, { children: children }) }, props),
419
+ });
392
420
  }
393
421
  PromptContextItem.displayName = 'Prompt.ContextItem';
394
422
  /** A quiet line under the prompt. Keys written in `<kbd>` are drawn as keys. */
395
- function PromptHint({ className, children, ...props }) {
396
- return (_jsx("p", { "data-slot": "prompt-hint", className: promptVariants().hint({ className }), ...props, children: children ?? (_jsxs(_Fragment, { children: [_jsx("kbd", { children: "Shift" }), " + ", _jsx("kbd", { children: "Enter" }), " for a new line"] })) }));
423
+ function PromptHint({ className, children, render, ...props }) {
424
+ return useRender({
425
+ defaultTagName: 'p',
426
+ render,
427
+ props: mergeProps({
428
+ 'data-slot': 'prompt-hint',
429
+ className: promptVariants().hint({ className }),
430
+ children: (_jsx(_Fragment, { children: children ?? (_jsxs(_Fragment, { children: [_jsx("kbd", { children: "Shift" }), " + ", _jsx("kbd", { children: "Enter" }), " for a new line"] })) })),
431
+ }, props),
432
+ });
397
433
  }
398
434
  PromptHint.displayName = 'Prompt.Hint';
399
435
  /** The card that holds the text, the files and the footer. Files can be dropped on it. */
400
- function PromptField({ layout = 'stacked', className, onDragOver, onDragLeave, onDrop, ...props }) {
436
+ function PromptField({ layout = 'stacked', className, onDragOver, onDragLeave, onDrop, render, ...props }) {
401
437
  const prompt = usePrompt('Prompt.Field');
402
438
  const hasFiles = (event) => [...event.dataTransfer.types].includes('Files');
403
- return (_jsx("div", { ref: prompt.fieldRef, "data-slot": "prompt-field", "data-layout": layout, "data-status": prompt.status, "data-dragging": prompt.dragging ? '' : undefined, className: promptVariants().field({ className }), onDragOver: (event) => {
404
- onDragOver?.(event);
405
- if (prompt.disabled || !hasFiles(event))
406
- return;
407
- event.preventDefault();
408
- event.dataTransfer.dropEffect = 'copy';
409
- if (!prompt.dragging)
410
- prompt.setDragging(true);
411
- }, onDragLeave: (event) => {
412
- onDragLeave?.(event);
413
- // Leaving for a child of the field is not leaving the field.
414
- if (!event.currentTarget.contains(event.relatedTarget))
439
+ return useRender({
440
+ defaultTagName: 'div',
441
+ render,
442
+ props: mergeProps({
443
+ ref: prompt.fieldRef,
444
+ 'data-slot': 'prompt-field',
445
+ 'data-layout': layout,
446
+ 'data-status': prompt.status,
447
+ 'data-dragging': prompt.dragging ? '' : undefined,
448
+ className: promptVariants().field({ className }),
449
+ onDragOver: (event) => {
450
+ onDragOver?.(event);
451
+ if (prompt.disabled || !hasFiles(event))
452
+ return;
453
+ event.preventDefault();
454
+ event.dataTransfer.dropEffect = 'copy';
455
+ if (!prompt.dragging)
456
+ prompt.setDragging(true);
457
+ },
458
+ onDragLeave: (event) => {
459
+ onDragLeave?.(event);
460
+ // Leaving for a child of the field is not leaving the field.
461
+ if (!event.currentTarget.contains(event.relatedTarget))
462
+ prompt.setDragging(false);
463
+ },
464
+ onDrop: (event) => {
465
+ onDrop?.(event);
415
466
  prompt.setDragging(false);
416
- }, onDrop: (event) => {
417
- onDrop?.(event);
418
- prompt.setDragging(false);
419
- if (prompt.disabled || !hasFiles(event))
420
- return;
421
- event.preventDefault();
422
- prompt.addFiles(event.dataTransfer.files);
423
- }, ...props }));
467
+ if (prompt.disabled || !hasFiles(event))
468
+ return;
469
+ event.preventDefault();
470
+ prompt.addFiles(event.dataTransfer.files);
471
+ },
472
+ }, props),
473
+ });
424
474
  }
425
475
  PromptField.displayName = 'Prompt.Field';
426
476
  /** Where the message is written. Enter sends, Shift+Enter starts a new line, and pasted files are attached. */
@@ -462,22 +512,39 @@ function formatSize(bytes) {
462
512
  }
463
513
  const XIcon = () => (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }));
464
514
  /** The files attached so far: images as thumbnails, anything else as a chip. Takes no room while there are none. */
465
- function PromptAttachments({ className, 'aria-label': ariaLabel = 'Attached files', ...props }) {
515
+ function PromptAttachments({ className, 'aria-label': ariaLabel = 'Attached files', render, ...props }) {
466
516
  const prompt = usePrompt('Prompt.Attachments');
467
517
  const slots = promptVariants();
468
518
  if (prompt.attachments.length === 0)
469
519
  return null;
470
- return (_jsx("ul", { "data-slot": "prompt-attachments", "aria-label": ariaLabel, className: slots.attachments({ className }), ...props, children: prompt.attachments.map(({ id, file, preview }) => (_jsxs("li", { className: slots.attachment({ className: preview ? slots.attachmentImage() : slots.attachmentFile() }), children: [preview ? (_jsx("img", { src: preview, alt: file.name, className: "size-full rounded-[inherit] object-cover" })) : (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: slots.attachmentGlyph(), children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" }), _jsx("path", { d: "M14 3v5h5" })] }) }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: slots.attachmentName(), children: file.name }), _jsx("span", { className: slots.attachmentMeta(), children: formatSize(file.size) })] })] })), _jsx("button", { type: "button", "aria-label": `Remove ${file.name}`, className: slots.attachmentRemove(), onClick: () => prompt.removeAttachment(id), children: _jsx(XIcon, {}) })] }, id))) }));
520
+ return useRender({
521
+ defaultTagName: 'ul',
522
+ render,
523
+ props: mergeProps({
524
+ 'data-slot': 'prompt-attachments',
525
+ 'aria-label': ariaLabel,
526
+ className: slots.attachments({ className }),
527
+ children: (_jsx(_Fragment, { children: prompt.attachments.map(({ id, file, preview }) => (_jsxs("li", { className: slots.attachment({ className: preview ? slots.attachmentImage() : slots.attachmentFile() }), children: [preview ? (_jsx("img", { src: preview, alt: file.name, className: "size-full rounded-[inherit] object-cover" })) : (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: slots.attachmentGlyph(), children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" }), _jsx("path", { d: "M14 3v5h5" })] }) }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: slots.attachmentName(), children: file.name }), _jsx("span", { className: slots.attachmentMeta(), children: formatSize(file.size) })] })] })), _jsx("button", { type: "button", "aria-label": `Remove ${file.name}`, className: slots.attachmentRemove(), onClick: () => prompt.removeAttachment(id), children: _jsx(XIcon, {}) })] }, id))) })),
528
+ }, props),
529
+ });
471
530
  }
472
531
  PromptAttachments.displayName = 'Prompt.Attachments';
473
532
  /** The row under the text: tools at the start, the submit button at the end. */
474
- function PromptFooter({ className, ...props }) {
475
- return _jsx("div", { "data-slot": "prompt-footer", className: promptVariants().footer({ className }), ...props });
533
+ function PromptFooter({ className, render, ...props }) {
534
+ return useRender({
535
+ defaultTagName: 'div',
536
+ render,
537
+ props: mergeProps({ 'data-slot': 'prompt-footer', className: promptVariants().footer({ className }) }, props),
538
+ });
476
539
  }
477
540
  PromptFooter.displayName = 'Prompt.Footer';
478
541
  /** The tools at the start of the footer. Wraps onto a second line where it has to. */
479
- function PromptTools({ className, ...props }) {
480
- return _jsx("div", { "data-slot": "prompt-tools", className: promptVariants().tools({ className }), ...props });
542
+ function PromptTools({ className, render, ...props }) {
543
+ return useRender({
544
+ defaultTagName: 'div',
545
+ render,
546
+ props: mergeProps({ 'data-slot': 'prompt-tools', className: promptVariants().tools({ className }) }, props),
547
+ });
481
548
  }
482
549
  PromptTools.displayName = 'Prompt.Tools';
483
550
  /** A round button in the footer, holding an icon. With `pressed`, it switches on and off. */
@@ -521,7 +588,9 @@ const PaperclipIcon = () => (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0
521
588
  */
522
589
  function PromptMenu({ label = 'Add', icon, children, side = 'top', align = 'start', width = 'auto', className, ...props }) {
523
590
  const prompt = usePrompt('Prompt.Menu');
524
- return (_jsxs(Menu, { children: [_jsx(Menu.Trigger, { disabled: prompt.disabled, "aria-label": label, title: label, render: _jsx(Button, { "data-slot": "prompt-menu", variant: "outline", shape: "pill", size: "icon" }), className: promptVariants().menuTrigger(), children: icon ?? (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) }), _jsx(Menu.Content, { side: side, align: align, anchor: width === 'field' ? prompt.fieldRef : undefined, className: [width === 'field' ? 'w-(--anchor-width) rounded-2xl p-1.5' : undefined, className].filter(Boolean).join(' ') || undefined, ...props, children: children })] }));
591
+ return (_jsxs(Menu, { children: [_jsx(Menu.Trigger, { disabled: prompt.disabled, "aria-label": label, title: label, render: _jsx(Button, { "data-slot": "prompt-menu", variant: "outline", shape: "pill", size: "icon" }), className: promptVariants().menuTrigger(), children: icon ?? (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) }), _jsx(Menu.Content, { side: side, align: align, anchor: width === 'field' ? prompt.fieldRef : undefined, className: [width === 'field' ? 'w-(--anchor-width) rounded-2xl p-1.5' : undefined, className]
592
+ .filter(Boolean)
593
+ .join(' ') || undefined, ...props, children: children })] }));
525
594
  }
526
595
  PromptMenu.displayName = 'Prompt.Menu';
527
596
  /** The menu item that opens the file picker. */
@@ -627,16 +696,30 @@ function PromptTasks({ open, mode, title, expanded, defaultExpanded = false, onE
627
696
  }
628
697
  PromptTasks.displayName = 'Prompt.Tasks';
629
698
  /** One task in `Prompt.Tasks`. */
630
- function PromptTask({ status = 'pending', className, children, ...props }) {
699
+ function PromptTask({ status = 'pending', className, children, render, ...props }) {
631
700
  const slots = promptVariants();
632
701
  const text = useShimmer(status === 'active');
633
- return (_jsxs("li", { "data-status": status, className: slots.task({ className }), ...props, children: [_jsx("span", { className: slots.taskMark(), children: status === 'done' ? (_jsx(CheckCircle, { className: slots.taskDone() })) : status === 'active' ? (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.75", strokeLinecap: "round", className: "size-3.5 animate-spin text-foreground motion-reduce:animate-none", children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.22-8.56" }) })) : status === 'error' ? (_jsx("span", { className: slots.taskError(), children: "!" })) : (_jsx("span", { className: slots.taskPending() })) }), _jsx("span", { ref: text, className: status === 'active'
634
- ? slots.statusText({ className: 'whitespace-normal' })
635
- : status === 'done'
636
- ? 'text-muted-foreground line-through decoration-muted-foreground/40'
637
- : status === 'error'
638
- ? 'text-destructive'
639
- : 'text-muted-foreground', children: children }), _jsx("span", { className: "sr-only", children: status === 'done' ? ', done' : status === 'active' ? ', in progress' : status === 'error' ? ', failed' : '' })] }));
702
+ return useRender({
703
+ defaultTagName: 'li',
704
+ render,
705
+ props: mergeProps({
706
+ 'data-status': status,
707
+ className: slots.task({ className }),
708
+ children: (_jsxs(_Fragment, { children: [_jsx("span", { className: slots.taskMark(), children: status === 'done' ? (_jsx(CheckCircle, { className: slots.taskDone() })) : status === 'active' ? (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.75", strokeLinecap: "round", className: "size-3.5 animate-spin text-foreground motion-reduce:animate-none", children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.22-8.56" }) })) : status === 'error' ? (_jsx("span", { className: slots.taskError(), children: "!" })) : (_jsx("span", { className: slots.taskPending() })) }), _jsx("span", { ref: text, className: status === 'active'
709
+ ? slots.statusText({ className: 'whitespace-normal' })
710
+ : status === 'done'
711
+ ? 'text-muted-foreground line-through decoration-muted-foreground/40'
712
+ : status === 'error'
713
+ ? 'text-destructive'
714
+ : 'text-muted-foreground', children: children }), _jsx("span", { className: "sr-only", children: status === 'done'
715
+ ? ', done'
716
+ : status === 'active'
717
+ ? ', in progress'
718
+ : status === 'error'
719
+ ? ', failed'
720
+ : '' })] })),
721
+ }, props),
722
+ });
640
723
  }
641
724
  PromptTask.displayName = 'Prompt.Task';
642
725
  /**
@@ -644,18 +727,23 @@ PromptTask.displayName = 'Prompt.Task';
644
727
  * word, `/sum`. The arrow keys move through it, Enter or Tab chooses, Escape
645
728
  * puts it away.
646
729
  */
647
- function PromptCommands({ items, trigger = '/', onSelect, label = 'Commands', maxHeight = '12rem' }) {
730
+ function PromptCommands({ items, trigger = '/', onSelect, label = 'Commands', maxHeight = '12rem', }) {
648
731
  const prompt = usePrompt('Prompt.Commands');
649
732
  const slots = promptVariants();
650
733
  const listId = useId();
651
734
  const [activeIndex, setActiveIndex] = useState(0);
652
735
  const [dismissed, setDismissed] = useState(null);
653
736
  const list = useRef(null);
654
- const query = prompt.value.startsWith(trigger) && !/\s/.test(prompt.value) ? prompt.value.slice(trigger.length).toLowerCase() : null;
737
+ const query = prompt.value.startsWith(trigger) && !/\s/.test(prompt.value)
738
+ ? prompt.value.slice(trigger.length).toLowerCase()
739
+ : null;
655
740
  const matches = query === null
656
741
  ? []
657
742
  : items
658
- .filter((item) => item.value.toLowerCase().includes(query) || String(item.label ?? '').toLowerCase().includes(query))
743
+ .filter((item) => item.value.toLowerCase().includes(query) ||
744
+ String(item.label ?? '')
745
+ .toLowerCase()
746
+ .includes(query))
659
747
  // Those whose name starts with what was typed come first.
660
748
  .sort((a, b) => Number(!a.value.toLowerCase().startsWith(query)) - Number(!b.value.toLowerCase().startsWith(query)));
661
749
  const open = matches.length > 0 && dismissed !== prompt.value;
@@ -733,7 +821,7 @@ const BAR_PITCH = 5;
733
821
  * has been heard so far, the sound as a wave scrolling to the left, how long
734
822
  * it has been, and buttons to throw it away or keep it.
735
823
  */
736
- function PromptRecorder({ transcript, placeholder = 'Listening…', onCancel, onDone, level, cancelLabel = 'Cancel recording', doneLabel = 'Finish recording', className, ...props }) {
824
+ function PromptRecorder({ transcript, placeholder = 'Listening…', onCancel, onDone, level, cancelLabel = 'Cancel recording', doneLabel = 'Finish recording', className, render, ...props }) {
737
825
  const slots = promptVariants();
738
826
  // Starts on a low murmur rather than flat silence, so the wave is never a row of dots.
739
827
  const [levels, setLevels] = useState(() => Array.from({ length: BARS }, (_, i) => 0.12 + 0.06 * Math.sin(i * 1.7)));
@@ -778,7 +866,15 @@ function PromptRecorder({ transcript, placeholder = 'Listening…', onCancel, on
778
866
  };
779
867
  }, []);
780
868
  const time = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
781
- return (_jsxs("div", { "data-slot": "prompt-recorder", className: slots.recorder({ className }), ...props, children: [_jsx("p", { "aria-live": "polite", className: slots.recorderTranscript(), children: transcript || (_jsx("span", { ref: listening, className: slots.recorderListening(), children: placeholder })) }), _jsxs("div", { className: slots.recorderControls(), children: [_jsx(Button, { variant: "ghost", size: "icon", shape: "pill", "aria-label": cancelLabel, onClick: onCancel, children: _jsx(XIcon, {}) }), _jsx("div", { ref: wave, "aria-hidden": "true", className: slots.recorderWave(), children: levels.slice(-fits).map((value, index) => (_jsx("span", { className: slots.recorderBar(), style: { height: `${3 + value * 33}px` } }, index))) }), _jsxs("span", { role: "timer", "aria-label": `Recording, ${time}`, className: slots.recorderTime(), children: [_jsx("span", { className: slots.recorderDot() }), time] }), _jsx(Button, { shape: "pill", "aria-label": doneLabel, onClick: onDone, className: slots.recorderDone(), children: _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.25", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) })] })] }));
869
+ return useRender({
870
+ defaultTagName: 'div',
871
+ render,
872
+ props: mergeProps({
873
+ 'data-slot': 'prompt-recorder',
874
+ className: slots.recorder({ className }),
875
+ children: (_jsxs(_Fragment, { children: [_jsx("p", { "aria-live": "polite", className: slots.recorderTranscript(), children: transcript || (_jsx("span", { ref: listening, className: slots.recorderListening(), children: placeholder })) }), _jsxs("div", { className: slots.recorderControls(), children: [_jsx(Button, { variant: "ghost", size: "icon", shape: "pill", "aria-label": cancelLabel, onClick: onCancel, children: _jsx(XIcon, {}) }), _jsx("div", { ref: wave, "aria-hidden": "true", className: slots.recorderWave(), children: levels.slice(-fits).map((value, index) => (_jsx("span", { className: slots.recorderBar(), style: { height: `${3 + value * 33}px` } }, index))) }), _jsxs("span", { role: "timer", "aria-label": `Recording, ${time}`, className: slots.recorderTime(), children: [_jsx("span", { className: slots.recorderDot() }), time] }), _jsx(Button, { shape: "pill", "aria-label": doneLabel, onClick: onDone, className: slots.recorderDone(), children: _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.25", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) }) })] })] })),
876
+ }, props),
877
+ });
782
878
  }
783
879
  PromptRecorder.displayName = 'Prompt.Recorder';
784
880
  export const Prompt = Object.assign(PromptRoot, {
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Radio Group — one choice from a short list, all of it visible at once.
3
+ *
4
+ * ```tsx
5
+ * <RadioGroup defaultValue="standard" aria-label="Delivery">
6
+ * <Label><Radio value="standard" /><Label.Text>Standard</Label.Text></Label>
7
+ * <Label><Radio value="express" /><Label.Text>Express</Label.Text></Label>
8
+ * </RadioGroup>
9
+ * ```
10
+ *
11
+ * ## The dot lands
12
+ *
13
+ * The chosen radio's centre grows in with a small overshoot, and the ring
14
+ * thickens to meet it, so the choice reads as something being set down
15
+ * rather than a colour changing. The one it leaves shrinks back at the same
16
+ * time. With reduced motion both happen at once.
17
+ *
18
+ * ## Arrow keys move the choice
19
+ *
20
+ * The group is one stop for Tab. Inside it, the arrow keys move the choice
21
+ * from one radio to the next, as a native radio group does.
22
+ *
23
+ * ## As cards
24
+ *
25
+ * `RadioGroup.Card` puts each option in a card of its own, with a title, a
26
+ * line of description and something at the far end such as a price, and the
27
+ * radio drawn inside it. The whole card is the target. It comes in three
28
+ * versions: `outline` rings the chosen card in its colour, `filled` tints
29
+ * it, and `dither` fills it from the far edge with an ordered dither in its
30
+ * colour, densest at the edge and thinning towards the text, which sweeps in
31
+ * as the card is chosen and back out as it is left.
32
+ *
33
+ * ## When to use something else
34
+ *
35
+ * For two to five options that each need a line of explanation, ChoiceCards
36
+ * lays them out as cards. For more than about seven, a Select keeps the form
37
+ * short. For a choice that takes effect at once, without a form to submit,
38
+ * Tabs or a ButtonGroup read better.
39
+ */
40
+ import { Radio as BaseRadio } from '@base-ui/react/radio';
41
+ import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
42
+ import type { ComponentProps, CSSProperties, ReactNode } from 'react';
43
+ import { useRender } from '@base-ui/react/use-render';
44
+ import type { ElementType as RenderElementType } from 'react';
45
+ /** Draws a part as another element, or composes it with a component: `render={<a href="/" />}`. */
46
+ type RenderProp<Tag extends RenderElementType> = {
47
+ render?: useRender.ComponentProps<Tag>['render'];
48
+ };
49
+ export type RadioSize = 'sm' | 'md' | 'lg';
50
+ export type RadioColor = 'primary' | 'success' | 'info' | 'destructive';
51
+ export interface RadioGroupProps extends Omit<BaseRadioGroup.Props<string>, 'className'> {
52
+ /** Whether the radios stack or sit in a row. Also sets which arrow keys move the choice. Default `vertical`. */
53
+ orientation?: 'vertical' | 'horizontal';
54
+ className?: string;
55
+ }
56
+ /** Holds one value for the radios inside it. Takes `value`/`onValueChange` or `defaultValue`, and `name` for a form. */
57
+ declare function RadioGroupRoot({ orientation, className, ...props }: RadioGroupProps): import("react").JSX.Element;
58
+ declare namespace RadioGroupRoot {
59
+ var displayName: string;
60
+ }
61
+ export interface RadioProps extends Omit<BaseRadio.Root.Props<string>, 'className' | 'color'> {
62
+ /** Default `md`. */
63
+ size?: RadioSize;
64
+ /** The colour of the chosen radio. `primary` by default. */
65
+ color?: RadioColor;
66
+ /** What a screen reader calls it, when there is no visible label tied to it. */
67
+ label?: string;
68
+ className?: string;
69
+ style?: CSSProperties;
70
+ }
71
+ /** One option. `value` is what the group's value becomes when it is chosen. */
72
+ declare function Radio({ size, color, label, className, ...props }: RadioProps): import("react").JSX.Element;
73
+ declare namespace Radio {
74
+ var displayName: string;
75
+ }
76
+ export type RadioCardAppearance = 'outline' | 'filled' | 'dither';
77
+ export interface RadioCardProps extends Omit<BaseRadio.Root.Props<string>, 'className' | 'color' | 'children'> {
78
+ /**
79
+ * How the chosen card is marked: a ring, a tint, or a dither ramp from the
80
+ * far edge. The ramp draws where `RadioGroup.CardAside` would sit, so leave
81
+ * the aside out of a `dither` card. Default `outline`.
82
+ */
83
+ appearance?: RadioCardAppearance;
84
+ /** The colour the chosen card is marked in. `primary` by default. */
85
+ color?: RadioColor;
86
+ className?: string;
87
+ style?: CSSProperties;
88
+ /** `RadioGroup.CardTitle`, `RadioGroup.CardDescription` and `RadioGroup.CardAside`. */
89
+ children?: ReactNode;
90
+ }
91
+ /** One option drawn as a card, with its radio inside. The whole card is pressed to choose it. */
92
+ declare function RadioCard({ appearance, color, className, children, ...props }: RadioCardProps): import("react").JSX.Element;
93
+ declare namespace RadioCard {
94
+ var displayName: string;
95
+ }
96
+ /** Holds a card's title and description, beside the radio. */
97
+ declare function RadioCardBody({ className, render, ...props }: ComponentProps<'span'> & RenderProp<'span'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
98
+ declare namespace RadioCardBody {
99
+ var displayName: string;
100
+ }
101
+ /** The option's name. */
102
+ declare function RadioCardTitle({ className, render, ...props }: ComponentProps<'span'> & RenderProp<'span'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
103
+ declare namespace RadioCardTitle {
104
+ var displayName: string;
105
+ }
106
+ /** A line under the name saying what the option means. */
107
+ declare function RadioCardDescription({ className, render, ...props }: ComponentProps<'span'> & RenderProp<'span'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
108
+ declare namespace RadioCardDescription {
109
+ var displayName: string;
110
+ }
111
+ /** Pushed to the far end of the card: a price, a duration, a badge. */
112
+ declare function RadioCardAside({ className, render, ...props }: ComponentProps<'span'> & RenderProp<'span'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
113
+ declare namespace RadioCardAside {
114
+ var displayName: string;
115
+ }
116
+ export declare const RadioGroup: typeof RadioGroupRoot & {
117
+ Item: typeof Radio;
118
+ Card: typeof RadioCard;
119
+ CardBody: typeof RadioCardBody;
120
+ CardTitle: typeof RadioCardTitle;
121
+ CardDescription: typeof RadioCardDescription;
122
+ CardAside: typeof RadioCardAside;
123
+ };
124
+ export { Radio, Radio as RadioGroupItem, RadioCard, RadioCard as RadioGroupCard, RadioCardBody as RadioGroupCardBody, RadioCardTitle as RadioGroupCardTitle, RadioCardDescription as RadioGroupCardDescription, RadioCardAside as RadioGroupCardAside, };