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
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * Dialog — a task taken out of the page and done in a box over it: renaming
5
5
  * something, inviting people, filling in a short form.
@@ -56,6 +56,8 @@ import { Dialog as Primitive } from '@base-ui/react/dialog';
56
56
  import { ScrollArea } from '@base-ui/react/scroll-area';
57
57
  import { Children, createContext, isValidElement, useContext, useEffect, useId, useMemo, useRef, } from 'react';
58
58
  import { tv } from 'tailwind-variants';
59
+ import { mergeProps } from '@base-ui/react/merge-props';
60
+ import { useRender } from '@base-ui/react/use-render';
59
61
  const dialogVariants = tv({
60
62
  slots: {
61
63
  backdrop: 'fixed inset-0 z-50 bg-black/40 transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
@@ -82,7 +84,14 @@ const dialogVariants = tv({
82
84
  bodyContent: 'flex flex-col gap-4',
83
85
  scrollbar: 'my-2 me-1 flex w-1.5 opacity-0 transition-opacity delay-300 data-hovering:opacity-100 data-hovering:delay-0 data-scrolling:opacity-100 data-scrolling:delay-0',
84
86
  thumb: 'relative flex-1 rounded-full bg-foreground/20',
85
- footer: 'flex shrink-0 items-center',
87
+ footer: [
88
+ 'flex shrink-0 items-center',
89
+ // The footer sits on a tinted surface, and the secondary fill is a tint
90
+ // too, so the button all but disappears into it. There it takes the
91
+ // panel's own surface instead, which keeps it lighter than the band.
92
+ '*:data-[variant=secondary]:bg-popover *:data-[variant=secondary]:shadow-xs',
93
+ '*:data-[variant=secondary]:hover:not-disabled:bg-[color-mix(in_oklab,var(--popover),var(--foreground)_5%)]',
94
+ ],
86
95
  close: [
87
96
  'absolute end-4 top-4 z-10 inline-flex size-8 cursor-pointer items-center justify-center rounded-full',
88
97
  'bg-muted text-muted-foreground outline-none transition-[background-color,color,scale] duration-150',
@@ -144,7 +153,7 @@ const dialogVariants = tv({
144
153
  defaultVariants: { variant: 'inset', size: 'md' },
145
154
  });
146
155
  const DialogContext = createContext({ inline: false, showClose: true, variant: 'inset' });
147
- function DialogRoot({ children, open, defaultOpen, onOpenChange, dismissible = true, }) {
156
+ function DialogRoot({ children, open, defaultOpen, onOpenChange, dismissible = true }) {
148
157
  return (_jsx(Primitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange ? (next) => onOpenChange(next) : undefined, disablePointerDismissal: !dismissible, children: children }));
149
158
  }
150
159
  DialogRoot.displayName = 'Dialog';
@@ -170,10 +179,18 @@ function DialogContent({ variant = 'inset', size = 'md', showClose = true, blur
170
179
  }
171
180
  DialogContent.displayName = 'Dialog.Content';
172
181
  /** The heading block. It leaves room for the close button beside the title. */
173
- function DialogHeader({ title, description, className, children, ...props }) {
182
+ function DialogHeader({ title, description, className, children, render, ...props }) {
174
183
  const { showClose, variant } = useContext(DialogContext);
175
184
  const slots = dialogVariants({ variant });
176
- return (_jsxs("div", { "data-slot": "dialog-header", className: slots.header({ className: [showClose && 'pe-14', className] }), ...props, children: [title !== undefined ? _jsx(DialogTitle, { children: title }) : null, description !== undefined ? _jsx(DialogDescription, { children: description }) : null, children] }));
185
+ return useRender({
186
+ defaultTagName: 'div',
187
+ render,
188
+ props: mergeProps({
189
+ 'data-slot': 'dialog-header',
190
+ className: slots.header({ className: [showClose && 'pe-14', className] }),
191
+ children: (_jsxs(_Fragment, { children: [title !== undefined ? _jsx(DialogTitle, { children: title }) : null, description !== undefined ? _jsx(DialogDescription, { children: description }) : null, children] })),
192
+ }, props),
193
+ });
177
194
  }
178
195
  DialogHeader.displayName = 'Dialog.Header';
179
196
  /** The dialog's name, announced when it opens. */
@@ -191,7 +208,7 @@ function DialogDescription({ className, ...props }) {
191
208
  const slots = dialogVariants();
192
209
  if (inline)
193
210
  return _jsx("p", { "data-slot": "dialog-description", className: slots.description({ className }), ...props });
194
- return _jsx(Primitive.Description, { "data-slot": "dialog-description", className: slots.description({ className }), ...props });
211
+ return (_jsx(Primitive.Description, { "data-slot": "dialog-description", className: slots.description({ className }), ...props }));
195
212
  }
196
213
  DialogDescription.displayName = 'Dialog.Description';
197
214
  /**
@@ -209,9 +226,13 @@ DialogBody.displayName = 'Dialog.Body';
209
226
  * equal pills; in `plain` they sit at the end of a ruled strip. Keep it a
210
227
  * direct child of `Dialog.Content`, so the band can find it.
211
228
  */
212
- function DialogFooter({ className, ...props }) {
229
+ function DialogFooter({ className, render, ...props }) {
213
230
  const { variant } = useContext(DialogContext);
214
- return _jsx("div", { "data-slot": "dialog-footer", className: dialogVariants({ variant }).footer({ className }), ...props });
231
+ return useRender({
232
+ defaultTagName: 'div',
233
+ render,
234
+ props: mergeProps({ 'data-slot': 'dialog-footer', className: dialogVariants({ variant }).footer({ className }) }, props),
235
+ });
215
236
  }
216
237
  DialogFooter.displayName = 'Dialog.Footer';
217
238
  /** Closes the dialog. */
@@ -228,9 +249,11 @@ DialogClose.displayName = 'Dialog.Close';
228
249
  * A list of one-line choices in the body. The arrow keys, Home and End move
229
250
  * between its items, and it opens scrolled to the selected one.
230
251
  */
231
- function DialogList({ className, onKeyDown, children, ...props }) {
252
+ function DialogList({ className, onKeyDown, children, render, ...props }) {
232
253
  const list = useRef(null);
233
- const items = () => [...(list.current?.querySelectorAll('[data-slot=dialog-list-item]:not([aria-disabled=true])') ?? [])];
254
+ const items = () => [
255
+ ...(list.current?.querySelectorAll('[data-slot=dialog-list-item]:not([aria-disabled=true])') ?? []),
256
+ ];
234
257
  useEffect(() => {
235
258
  const all = items();
236
259
  const selected = all.find((item) => item.dataset.selected !== undefined);
@@ -257,30 +280,66 @@ function DialogList({ className, onKeyDown, children, ...props }) {
257
280
  event.preventDefault();
258
281
  next.focus();
259
282
  };
260
- return (_jsx("div", { ref: list, role: "listbox", "data-slot": "dialog-list", className: dialogVariants().list({ className }), onKeyDown: move, ...props, children: children }));
283
+ return useRender({
284
+ defaultTagName: 'div',
285
+ render,
286
+ props: mergeProps({
287
+ ref: list,
288
+ role: 'listbox',
289
+ 'data-slot': 'dialog-list',
290
+ className: dialogVariants().list({ className }),
291
+ onKeyDown: move,
292
+ children: _jsx(_Fragment, { children: children }),
293
+ }, props),
294
+ });
261
295
  }
262
296
  DialogList.displayName = 'Dialog.List';
263
297
  /** A run of items in `Dialog.List` under a heading of their own. */
264
- function DialogListGroup({ label, className, children, ...props }) {
298
+ function DialogListGroup({ label, className, children, render, ...props }) {
265
299
  const slots = dialogVariants();
266
300
  const id = useId();
267
- return (_jsxs("div", { role: "group", "aria-labelledby": id, "data-slot": "dialog-list-group", className: slots.listGroup({ className }), ...props, children: [_jsx("div", { id: id, className: slots.listGroupLabel(), children: label }), children] }));
301
+ return useRender({
302
+ defaultTagName: 'div',
303
+ render,
304
+ props: mergeProps({
305
+ role: 'group',
306
+ 'aria-labelledby': id,
307
+ 'data-slot': 'dialog-list-group',
308
+ className: slots.listGroup({ className }),
309
+ children: (_jsxs(_Fragment, { children: [_jsx("div", { id: id, className: slots.listGroupLabel(), children: label }), children] })),
310
+ }, props),
311
+ });
268
312
  }
269
313
  DialogListGroup.displayName = 'Dialog.ListGroup';
270
314
  /** One choice in `Dialog.List`, on one line. */
271
- function DialogListItem({ selected = false, onSelect, meta, disabled = false, className, onClick, onKeyDown, children, ...props }) {
315
+ function DialogListItem({ selected = false, onSelect, meta, disabled = false, className, onClick, onKeyDown, children, render, ...props }) {
272
316
  const slots = dialogVariants();
273
- return (_jsxs("div", { role: "option", "aria-selected": selected, "aria-disabled": disabled || undefined, tabIndex: selected ? 0 : -1, "data-slot": "dialog-list-item", "data-selected": selected ? '' : undefined, className: slots.listItem({ className }), onClick: (event) => {
274
- onClick?.(event);
275
- if (!disabled)
276
- onSelect?.();
277
- }, onKeyDown: (event) => {
278
- onKeyDown?.(event);
279
- if ((event.key === 'Enter' || event.key === ' ') && !disabled) {
280
- event.preventDefault();
281
- onSelect?.();
282
- }
283
- }, ...props, children: [_jsx("span", { className: slots.listItemLabel(), children: children }), meta !== undefined ? _jsx("span", { className: slots.listItemMeta(), children: meta }) : null, _jsx("span", { "aria-hidden": "true", className: slots.listItemCheck(), children: selected ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5" }) })) : null })] }));
317
+ return useRender({
318
+ defaultTagName: 'div',
319
+ render,
320
+ props: mergeProps({
321
+ role: 'option',
322
+ 'aria-selected': selected,
323
+ 'aria-disabled': disabled || undefined,
324
+ tabIndex: selected ? 0 : -1,
325
+ 'data-slot': 'dialog-list-item',
326
+ 'data-selected': selected ? '' : undefined,
327
+ className: slots.listItem({ className }),
328
+ onClick: (event) => {
329
+ onClick?.(event);
330
+ if (!disabled)
331
+ onSelect?.();
332
+ },
333
+ onKeyDown: (event) => {
334
+ onKeyDown?.(event);
335
+ if ((event.key === 'Enter' || event.key === ' ') && !disabled) {
336
+ event.preventDefault();
337
+ onSelect?.();
338
+ }
339
+ },
340
+ children: (_jsxs(_Fragment, { children: [_jsx("span", { className: slots.listItemLabel(), children: children }), meta !== undefined ? _jsx("span", { className: slots.listItemMeta(), children: meta }) : null, _jsx("span", { "aria-hidden": "true", className: slots.listItemCheck(), children: selected ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5" }) })) : null })] })),
341
+ }, props),
342
+ });
284
343
  }
285
344
  DialogListItem.displayName = 'Dialog.ListItem';
286
345
  export const Dialog = Object.assign(DialogRoot, {
@@ -1,4 +1,11 @@
1
1
  import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
2
+ import { useRender } from '@base-ui/react/use-render';
3
+ import type * as ReactTypes from 'react';
4
+ import type { ElementType as RenderElementType } from 'react';
5
+ /** Draws a part as another element, or composes it with a component: `render={<a href="/" />}`. */
6
+ type RenderProp<Tag extends RenderElementType> = {
7
+ render?: useRender.ComponentProps<Tag>['render'];
8
+ };
2
9
  export interface FeedbackProps {
3
10
  children?: ReactNode;
4
11
  /** Whether the dialog is open, when the caller holds that. */
@@ -20,7 +27,7 @@ export interface FeedbackProps {
20
27
  */
21
28
  dismissible?: boolean;
22
29
  }
23
- declare function FeedbackRoot({ children, open, onOpenChange, defaultOpen, value, defaultValue, onValueChange, dismissible, }: FeedbackProps): import("react").JSX.Element;
30
+ declare function FeedbackRoot({ children, open, onOpenChange, defaultOpen, value, defaultValue, onValueChange, dismissible, }: FeedbackProps): ReactTypes.JSX.Element;
24
31
  declare namespace FeedbackRoot {
25
32
  var displayName: string;
26
33
  }
@@ -33,7 +40,7 @@ export interface FeedbackTriggerProps extends Omit<ComponentProps<'button'>, 'ch
33
40
  render?: ReactElement;
34
41
  }
35
42
  /** What opens the dialog. A plain button unless `render` supplies one. */
36
- declare function FeedbackTrigger({ render, children, ...props }: FeedbackTriggerProps): import("react").JSX.Element;
43
+ declare function FeedbackTrigger({ render, children, ...props }: FeedbackTriggerProps): ReactTypes.JSX.Element;
37
44
  declare namespace FeedbackTrigger {
38
45
  var displayName: string;
39
46
  }
@@ -50,21 +57,21 @@ export interface FeedbackContentProps extends Omit<ComponentProps<'div'>, 'child
50
57
  blur?: boolean;
51
58
  }
52
59
  /** The shell: the darker band, and everything set in it. */
53
- declare function FeedbackContent({ inline, blur, className, children, ...props }: FeedbackContentProps): import("react").JSX.Element;
60
+ declare function FeedbackContent({ inline, blur, className, children, ...props }: FeedbackContentProps): ReactTypes.JSX.Element;
54
61
  declare namespace FeedbackContent {
55
62
  var displayName: string;
56
63
  }
57
64
  export interface FeedbackPanelProps extends ComponentProps<'div'> {
58
65
  }
59
66
  /** The well set into the shell: the title, the close button and the field. */
60
- declare function FeedbackPanel({ className, ...props }: FeedbackPanelProps): import("react").JSX.Element;
67
+ declare function FeedbackPanel({ className, render, ...props }: FeedbackPanelProps & RenderProp<'div'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
61
68
  declare namespace FeedbackPanel {
62
69
  var displayName: string;
63
70
  }
64
71
  export interface FeedbackTitleProps extends ComponentProps<'h2'> {
65
72
  }
66
73
  /** The question. In a dialog it is also what the dialog is announced as. */
67
- declare function FeedbackTitle({ className, ...props }: FeedbackTitleProps): import("react").JSX.Element;
74
+ declare function FeedbackTitle({ className, ...props }: FeedbackTitleProps): ReactTypes.JSX.Element;
68
75
  declare namespace FeedbackTitle {
69
76
  var displayName: string;
70
77
  }
@@ -73,7 +80,7 @@ export interface FeedbackCloseProps extends Omit<ComponentProps<'button'>, 'chil
73
80
  label?: string;
74
81
  }
75
82
  /** The ✕ in the panel's corner. */
76
- declare function FeedbackClose({ label, className, onClick, ...props }: FeedbackCloseProps): import("react").JSX.Element;
83
+ declare function FeedbackClose({ label, className, onClick, render, ...props }: FeedbackCloseProps & RenderProp<'button'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
77
84
  declare namespace FeedbackClose {
78
85
  var displayName: string;
79
86
  }
@@ -91,7 +98,7 @@ export interface FeedbackFieldProps extends Omit<ComponentProps<'textarea'>, 'va
91
98
  * What is being written. No border and no background of its own: it is
92
99
  * already inside a well.
93
100
  */
94
- declare function FeedbackField({ value, onValueChange, minHeight, maxHeight, className, style, onKeyDown, ref, 'aria-label': ariaLabel, ...props }: FeedbackFieldProps): import("react").JSX.Element;
101
+ declare function FeedbackField({ value, onValueChange, minHeight, maxHeight, className, style, onKeyDown, ref, 'aria-label': ariaLabel, ...props }: FeedbackFieldProps): ReactTypes.JSX.Element;
95
102
  declare namespace FeedbackField {
96
103
  var displayName: string;
97
104
  }
@@ -101,7 +108,7 @@ export interface FeedbackDescriptionProps extends ComponentProps<'p'> {
101
108
  * Body text in the panel, in place of the field: a thank-you once the message
102
109
  * has gone. In a dialog it is also what the dialog is described as.
103
110
  */
104
- declare function FeedbackDescription({ className, ...props }: FeedbackDescriptionProps): import("react").JSX.Element;
111
+ declare function FeedbackDescription({ className, ...props }: FeedbackDescriptionProps): ReactTypes.JSX.Element;
105
112
  declare namespace FeedbackDescription {
106
113
  var displayName: string;
107
114
  }
@@ -114,7 +121,7 @@ export interface FeedbackMessageProps extends ComponentProps<'p'> {
114
121
  tone?: 'muted' | 'destructive';
115
122
  }
116
123
  /** A short line under the field. Figures in it keep their width as they change. */
117
- declare function FeedbackMessage({ tone, className, ...props }: FeedbackMessageProps): import("react").JSX.Element;
124
+ declare function FeedbackMessage({ tone, className, render, ...props }: FeedbackMessageProps & RenderProp<'p'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
118
125
  declare namespace FeedbackMessage {
119
126
  var displayName: string;
120
127
  }
@@ -137,21 +144,21 @@ export interface FeedbackRatingProps extends Omit<ComponentProps<'div'>, 'childr
137
144
  * A row of round buttons, one per score, with a word under each end. Use it
138
145
  * as the first step of a dialog, and ask for the words once a score is in.
139
146
  */
140
- declare function FeedbackRating({ max, onValueChange, startLabel, endLabel, minHeight, className, style, ...props }: FeedbackRatingProps): import("react").JSX.Element;
147
+ declare function FeedbackRating({ max, onValueChange, startLabel, endLabel, minHeight, className, style, render, ...props }: FeedbackRatingProps & RenderProp<'div'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
141
148
  declare namespace FeedbackRating {
142
149
  var displayName: string;
143
150
  }
144
151
  export interface FeedbackFooterProps extends ComponentProps<'div'> {
145
152
  }
146
153
  /** The action row, in the band under the panel. */
147
- declare function FeedbackFooter({ className, ...props }: FeedbackFooterProps): import("react").JSX.Element;
154
+ declare function FeedbackFooter({ className, render, ...props }: FeedbackFooterProps & RenderProp<'div'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
148
155
  declare namespace FeedbackFooter {
149
156
  var displayName: string;
150
157
  }
151
158
  export interface FeedbackActionProps extends ComponentProps<'button'> {
152
159
  }
153
160
  /** Discards and closes. Call `preventDefault` in `onClick` to keep it open. */
154
- declare function FeedbackCancel({ children, className, disabled, onClick, ...props }: FeedbackActionProps): import("react").JSX.Element;
161
+ declare function FeedbackCancel({ children, className, disabled, onClick, render, ...props }: FeedbackActionProps & RenderProp<'button'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
155
162
  declare namespace FeedbackCancel {
156
163
  var displayName: string;
157
164
  }
@@ -163,7 +170,7 @@ export interface FeedbackSubmitProps extends Omit<FeedbackActionProps, 'onSubmit
163
170
  onSubmit?: (value: string) => void;
164
171
  }
165
172
  /** Sends. Inert while the field is empty, unless `disabled` says otherwise. */
166
- declare function FeedbackSubmit({ children, className, disabled, onClick, onSubmit, ...props }: FeedbackSubmitProps): import("react").JSX.Element;
173
+ declare function FeedbackSubmit({ children, className, disabled, onClick, onSubmit, render, ...props }: FeedbackSubmitProps & RenderProp<'button'>): ReactElement<unknown, string | ReactTypes.JSXElementConstructor<any>>;
167
174
  declare namespace FeedbackSubmit {
168
175
  var displayName: string;
169
176
  }
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * Feedback — a dialog whose body is something to write in, and whose actions
5
5
  * sit in a band around it.
@@ -47,6 +47,8 @@ import { Dialog } from '@base-ui/react/dialog';
47
47
  import { ScrollArea } from '@base-ui/react/scroll-area';
48
48
  import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useRef, useState, } from 'react';
49
49
  import { tv } from 'tailwind-variants';
50
+ import { mergeProps } from '@base-ui/react/merge-props';
51
+ import { useRender } from '@base-ui/react/use-render';
50
52
  const feedbackVariants = tv({
51
53
  slots: {
52
54
  backdrop: 'fixed inset-0 z-50 bg-black/40 transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0',
@@ -178,9 +180,13 @@ function FeedbackContent({ inline = false, blur = false, className, children, ..
178
180
  }
179
181
  FeedbackContent.displayName = 'Feedback.Content';
180
182
  /** The well set into the shell: the title, the close button and the field. */
181
- function FeedbackPanel({ className, ...props }) {
183
+ function FeedbackPanel({ className, render, ...props }) {
182
184
  const slots = feedbackVariants();
183
- return _jsx("div", { className: slots.panel({ className }), ...props });
185
+ return useRender({
186
+ defaultTagName: 'div',
187
+ render,
188
+ props: mergeProps({ className: slots.panel({ className }) }, props),
189
+ });
184
190
  }
185
191
  FeedbackPanel.displayName = 'Feedback.Panel';
186
192
  /** The question. In a dialog it is also what the dialog is announced as. */
@@ -193,14 +199,24 @@ function FeedbackTitle({ className, ...props }) {
193
199
  }
194
200
  FeedbackTitle.displayName = 'Feedback.Title';
195
201
  /** The ✕ in the panel's corner. */
196
- function FeedbackClose({ label = 'Close', className, onClick, ...props }) {
202
+ function FeedbackClose({ label = 'Close', className, onClick, render, ...props }) {
197
203
  const { setOpen } = useFeedback('Feedback.Close');
198
204
  const slots = feedbackVariants();
199
- return (_jsx("button", { type: "button", "aria-label": label, className: slots.close({ className }), onClick: (event) => {
200
- onClick?.(event);
201
- if (!event.defaultPrevented)
202
- setOpen(false);
203
- }, ...props, children: _jsx("svg", { viewBox: "0 0 10 10", "aria-hidden": "true", className: "size-2.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", children: _jsx("path", { d: "M2 2l6 6M8 2l-6 6" }) }) }));
205
+ return useRender({
206
+ defaultTagName: 'button',
207
+ render,
208
+ props: mergeProps({
209
+ type: 'button',
210
+ 'aria-label': label,
211
+ className: slots.close({ className }),
212
+ onClick: (event) => {
213
+ onClick?.(event);
214
+ if (!event.defaultPrevented)
215
+ setOpen(false);
216
+ },
217
+ children: (_jsx(_Fragment, { children: _jsx("svg", { viewBox: "0 0 10 10", "aria-hidden": "true", className: "size-2.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", children: _jsx("path", { d: "M2 2l6 6M8 2l-6 6" }) }) })),
218
+ }, props),
219
+ });
204
220
  }
205
221
  FeedbackClose.displayName = 'Feedback.Close';
206
222
  /**
@@ -258,39 +274,69 @@ function FeedbackDescription({ className, ...props }) {
258
274
  }
259
275
  FeedbackDescription.displayName = 'Feedback.Description';
260
276
  /** A short line under the field. Figures in it keep their width as they change. */
261
- function FeedbackMessage({ tone = 'muted', className, ...props }) {
277
+ function FeedbackMessage({ tone = 'muted', className, render, ...props }) {
262
278
  const slots = feedbackVariants({ messageTone: tone });
263
- return (_jsx("p", { role: tone === 'destructive' ? 'alert' : undefined, "aria-live": tone === 'destructive' ? undefined : 'polite', className: slots.message({ className }), ...props }));
279
+ return useRender({
280
+ defaultTagName: 'p',
281
+ render,
282
+ props: mergeProps({
283
+ role: tone === 'destructive' ? 'alert' : undefined,
284
+ 'aria-live': tone === 'destructive' ? undefined : 'polite',
285
+ className: slots.message({ className }),
286
+ }, props),
287
+ });
264
288
  }
265
289
  FeedbackMessage.displayName = 'Feedback.Message';
266
290
  /**
267
291
  * A row of round buttons, one per score, with a word under each end. Use it
268
292
  * as the first step of a dialog, and ask for the words once a score is in.
269
293
  */
270
- function FeedbackRating({ max = 5, onValueChange, startLabel, endLabel, minHeight, className, style, ...props }) {
294
+ function FeedbackRating({ max = 5, onValueChange, startLabel, endLabel, minHeight, className, style, render, ...props }) {
271
295
  const slots = feedbackVariants();
272
- return (_jsxs("div", { className: slots.rating({ className }), style: { minHeight, ...style }, ...props, children: [_jsx("div", { className: slots.ratingScores(), children: Array.from({ length: max }, (_, index) => index + 1).map((value) => (_jsx("button", { type: "button", "aria-label": `${value} out of ${max}`, onClick: () => onValueChange?.(value), className: slots.ratingScore(), children: value }, value))) }), startLabel !== undefined || endLabel !== undefined ? (_jsxs("div", { className: slots.ratingEnds(), children: [_jsx("span", { children: startLabel }), _jsx("span", { children: endLabel })] })) : null] }));
296
+ return useRender({
297
+ defaultTagName: 'div',
298
+ render,
299
+ props: mergeProps({
300
+ className: slots.rating({ className }),
301
+ style: { minHeight, ...style },
302
+ children: (_jsxs(_Fragment, { children: [_jsx("div", { className: slots.ratingScores(), children: Array.from({ length: max }, (_, index) => index + 1).map((value) => (_jsx("button", { type: "button", "aria-label": `${value} out of ${max}`, onClick: () => onValueChange?.(value), className: slots.ratingScore(), children: value }, value))) }), startLabel !== undefined || endLabel !== undefined ? (_jsxs("div", { className: slots.ratingEnds(), children: [_jsx("span", { children: startLabel }), _jsx("span", { children: endLabel })] })) : null] })),
303
+ }, props),
304
+ });
273
305
  }
274
306
  FeedbackRating.displayName = 'Feedback.Rating';
275
307
  /** The action row, in the band under the panel. */
276
- function FeedbackFooter({ className, ...props }) {
308
+ function FeedbackFooter({ className, render, ...props }) {
277
309
  const slots = feedbackVariants();
278
- return _jsx("div", { className: slots.footer({ className }), ...props });
310
+ return useRender({
311
+ defaultTagName: 'div',
312
+ render,
313
+ props: mergeProps({ className: slots.footer({ className }) }, props),
314
+ });
279
315
  }
280
316
  FeedbackFooter.displayName = 'Feedback.Footer';
281
317
  /** Discards and closes. Call `preventDefault` in `onClick` to keep it open. */
282
- function FeedbackCancel({ children = 'Cancel', className, disabled, onClick, ...props }) {
318
+ function FeedbackCancel({ children = 'Cancel', className, disabled, onClick, render, ...props }) {
283
319
  const { setOpen } = useFeedback('Feedback.Cancel');
284
320
  const slots = feedbackVariants({ tone: 'cancel', disabled: !!disabled });
285
- return (_jsx("button", { type: "button", disabled: disabled, className: slots.action({ className }), onClick: (event) => {
286
- onClick?.(event);
287
- if (!event.defaultPrevented)
288
- setOpen(false);
289
- }, ...props, children: children }));
321
+ return useRender({
322
+ defaultTagName: 'button',
323
+ render,
324
+ props: mergeProps({
325
+ type: 'button',
326
+ disabled: disabled,
327
+ className: slots.action({ className }),
328
+ onClick: (event) => {
329
+ onClick?.(event);
330
+ if (!event.defaultPrevented)
331
+ setOpen(false);
332
+ },
333
+ children: _jsx(_Fragment, { children: children }),
334
+ }, props),
335
+ });
290
336
  }
291
337
  FeedbackCancel.displayName = 'Feedback.Cancel';
292
338
  /** Sends. Inert while the field is empty, unless `disabled` says otherwise. */
293
- function FeedbackSubmit({ children = 'Submit', className, disabled, onClick, onSubmit, ...props }) {
339
+ function FeedbackSubmit({ children = 'Submit', className, disabled, onClick, onSubmit, render, ...props }) {
294
340
  const feedback = useFeedback('Feedback.Submit');
295
341
  // Empty feedback is worse than none: it is sent by somebody who believes
296
342
  // they said something.
@@ -299,11 +345,21 @@ function FeedbackSubmit({ children = 'Submit', className, disabled, onClick, onS
299
345
  // What the field's shortcut calls. Kept current on every render, so it
300
346
  // sends what is in the field now and not what was there when it mounted.
301
347
  feedback.submit.current = inert ? null : () => onSubmit?.(feedback.value);
302
- return (_jsx("button", { type: "button", disabled: inert, className: slots.action({ className }), onClick: (event) => {
303
- onClick?.(event);
304
- if (!event.defaultPrevented)
305
- onSubmit?.(feedback.value);
306
- }, ...props, children: children }));
348
+ return useRender({
349
+ defaultTagName: 'button',
350
+ render,
351
+ props: mergeProps({
352
+ type: 'button',
353
+ disabled: inert,
354
+ className: slots.action({ className }),
355
+ onClick: (event) => {
356
+ onClick?.(event);
357
+ if (!event.defaultPrevented)
358
+ onSubmit?.(feedback.value);
359
+ },
360
+ children: _jsx(_Fragment, { children: children }),
361
+ }, props),
362
+ });
307
363
  }
308
364
  FeedbackSubmit.displayName = 'Feedback.Submit';
309
365
  export const Feedback = Object.assign(FeedbackRoot, {
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Field — a form control with its label, a line of help, and the reason it was refused.
3
+ *
4
+ * ```tsx
5
+ * <Field name="email">
6
+ * <Field.Label>Work email</Field.Label>
7
+ * <Input type="email" required placeholder="you@company.com" />
8
+ * <Field.Description>We send the receipt here.</Field.Description>
9
+ * <Field.Error match="valueMissing">Enter an email address.</Field.Error>
10
+ * </Field>
11
+ * ```
12
+ *
13
+ * ## The ids are wired for you
14
+ *
15
+ * A label has to point at its control, and the control has to point at the
16
+ * help and the error, or a screen reader reads the field with no name and no
17
+ * reason. Field does that wiring: `Field.Label` is tied to the control inside
18
+ * the field, and the control is described by `Field.Description` and the
19
+ * error that is showing. Input, Textarea, Select, Checkbox, Radio Group and
20
+ * Switch all take part, with nothing passed.
21
+ *
22
+ * ## The label is a Label
23
+ *
24
+ * `Field.Label` is the Label component, so `required`, `optional`,
25
+ * `Label.Hint` and `Label.Action` work in it as they do anywhere else. It
26
+ * turns to the destructive colour and fades with the field, without
27
+ * `invalid` or `disabled` passed to it.
28
+ *
29
+ * ## Errors wait for the reader
30
+ *
31
+ * The browser's own checks — `required`, `type="email"`, `minLength` — run
32
+ * when the control is left, not on every key, so a field is not red while it
33
+ * is still being typed. `Field.Error` with `match` shows a message for one of
34
+ * those checks; with no `match`, it shows whenever the field is invalid,
35
+ * which is the one to use with `invalid` set from a server's answer. An
36
+ * error that appears slides in under the control, and the description it
37
+ * replaces steps aside.
38
+ */
39
+ import { Field as BaseField } from '@base-ui/react/field';
40
+ import { type LabelProps } from '../label/index.js';
41
+ export interface FieldProps extends Omit<BaseField.Root.Props, 'className'> {
42
+ className?: string;
43
+ }
44
+ /**
45
+ * Groups a control with its label, help and error. Takes `name`, `invalid`
46
+ * (for an error from outside, such as a server), `disabled`, and `validate`
47
+ * for a check of your own.
48
+ */
49
+ declare function FieldRoot({ className, ...props }: FieldProps): import("react").JSX.Element;
50
+ declare namespace FieldRoot {
51
+ var displayName: string;
52
+ }
53
+ export interface FieldLabelProps extends Omit<LabelProps, 'invalid' | 'disabled' | 'htmlFor'> {
54
+ }
55
+ /** The control's name, tied to it. Takes `required` and `optional` as Label does, and its parts. */
56
+ declare function FieldLabel({ required, optional, className, children, ...props }: FieldLabelProps): import("react").JSX.Element;
57
+ declare namespace FieldLabel {
58
+ var displayName: string;
59
+ }
60
+ export interface FieldDescriptionProps extends Omit<BaseField.Description.Props, 'className'> {
61
+ className?: string;
62
+ }
63
+ /** A line of help under the control. Hidden while an error is showing. */
64
+ declare function FieldDescription({ className, ...props }: FieldDescriptionProps): import("react").JSX.Element;
65
+ declare namespace FieldDescription {
66
+ var displayName: string;
67
+ }
68
+ export interface FieldErrorProps extends Omit<BaseField.Error.Props, 'className'> {
69
+ className?: string;
70
+ }
71
+ /**
72
+ * Why the value was refused. `match` names the check it is for, such as
73
+ * `valueMissing` or `typeMismatch`; with none, it shows whenever the field is
74
+ * invalid. With no children it shows the browser's own message.
75
+ */
76
+ declare function FieldError({ className, ...props }: FieldErrorProps): import("react").JSX.Element;
77
+ declare namespace FieldError {
78
+ var displayName: string;
79
+ }
80
+ export declare const Field: typeof FieldRoot & {
81
+ Label: typeof FieldLabel;
82
+ Description: typeof FieldDescription;
83
+ Error: typeof FieldError;
84
+ };
85
+ export { FieldLabel, FieldDescription, FieldError };