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
@@ -0,0 +1,216 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Radio Group — one choice from a short list, all of it visible at once.
5
+ *
6
+ * ```tsx
7
+ * <RadioGroup defaultValue="standard" aria-label="Delivery">
8
+ * <Label><Radio value="standard" /><Label.Text>Standard</Label.Text></Label>
9
+ * <Label><Radio value="express" /><Label.Text>Express</Label.Text></Label>
10
+ * </RadioGroup>
11
+ * ```
12
+ *
13
+ * ## The dot lands
14
+ *
15
+ * The chosen radio's centre grows in with a small overshoot, and the ring
16
+ * thickens to meet it, so the choice reads as something being set down
17
+ * rather than a colour changing. The one it leaves shrinks back at the same
18
+ * time. With reduced motion both happen at once.
19
+ *
20
+ * ## Arrow keys move the choice
21
+ *
22
+ * The group is one stop for Tab. Inside it, the arrow keys move the choice
23
+ * from one radio to the next, as a native radio group does.
24
+ *
25
+ * ## As cards
26
+ *
27
+ * `RadioGroup.Card` puts each option in a card of its own, with a title, a
28
+ * line of description and something at the far end such as a price, and the
29
+ * radio drawn inside it. The whole card is the target. It comes in three
30
+ * versions: `outline` rings the chosen card in its colour, `filled` tints
31
+ * it, and `dither` fills it from the far edge with an ordered dither in its
32
+ * colour, densest at the edge and thinning towards the text, which sweeps in
33
+ * as the card is chosen and back out as it is left.
34
+ *
35
+ * ## When to use something else
36
+ *
37
+ * For two to five options that each need a line of explanation, ChoiceCards
38
+ * lays them out as cards. For more than about seven, a Select keeps the form
39
+ * short. For a choice that takes effect at once, without a form to submit,
40
+ * Tabs or a ButtonGroup read better.
41
+ */
42
+ import { Radio as BaseRadio } from '@base-ui/react/radio';
43
+ import { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';
44
+ import { tv } from 'tailwind-variants';
45
+ import { mergeProps } from '@base-ui/react/merge-props';
46
+ import { useRender } from '@base-ui/react/use-render';
47
+ const radioVariants = tv({
48
+ slots: {
49
+ group: 'flex gap-3 data-[orientation=vertical]:flex-col data-[orientation=horizontal]:flex-row data-[orientation=horizontal]:flex-wrap',
50
+ root: [
51
+ 'group/radio relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full border outline-none',
52
+ 'border-input bg-background shadow-xs dark:bg-input/30',
53
+ 'transition-[border-color,border-width,box-shadow] duration-200',
54
+ 'hover:not-data-disabled:border-[color-mix(in_oklab,var(--input),var(--foreground)_25%)]',
55
+ 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
56
+ 'data-checked:border-(--radio-color)',
57
+ 'aria-invalid:border-destructive',
58
+ 'data-disabled:cursor-not-allowed data-disabled:opacity-50',
59
+ "after:absolute after:-inset-2 after:content-['']",
60
+ ],
61
+ indicator: [
62
+ 'pointer-events-none size-[46%] rounded-full bg-(--radio-color)',
63
+ 'scale-0 transition-transform duration-300 ease-[cubic-bezier(0.3,1.6,0.5,1)]',
64
+ 'data-checked:scale-100 motion-reduce:transition-none',
65
+ ],
66
+ },
67
+ variants: {
68
+ size: {
69
+ sm: { root: 'size-3.5' },
70
+ md: { root: 'size-4 data-checked:border-[1.5px]' },
71
+ lg: { root: 'size-5 data-checked:border-2' },
72
+ },
73
+ color: {
74
+ primary: { root: '[--radio-color:var(--primary)]' },
75
+ success: { root: '[--radio-color:var(--success)]' },
76
+ info: { root: '[--radio-color:var(--info)]' },
77
+ destructive: { root: '[--radio-color:var(--destructive)]' },
78
+ },
79
+ },
80
+ defaultVariants: { size: 'md', color: 'primary' },
81
+ });
82
+ /** Holds one value for the radios inside it. Takes `value`/`onValueChange` or `defaultValue`, and `name` for a form. */
83
+ function RadioGroupRoot({ orientation = 'vertical', className, ...props }) {
84
+ return (_jsx(BaseRadioGroup, { "data-slot": "radio-group", "data-orientation": orientation, "aria-orientation": orientation, className: radioVariants().group({ className }), ...props }));
85
+ }
86
+ RadioGroupRoot.displayName = 'RadioGroup';
87
+ /** One option. `value` is what the group's value becomes when it is chosen. */
88
+ function Radio({ size = 'md', color, label, className, ...props }) {
89
+ const slots = radioVariants({ size, color });
90
+ return (_jsx(BaseRadio.Root, { "data-slot": "radio", "data-size": size, "aria-label": label, className: slots.root({ className }), ...props, children: _jsx(BaseRadio.Indicator, { "data-slot": "radio-indicator", keepMounted: true, className: slots.indicator() }) }));
91
+ }
92
+ Radio.displayName = 'RadioGroup.Item';
93
+ /** The 4×4 Bayer matrix, as thresholds 0–15. */
94
+ const BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];
95
+ /** Masks an element to an even ordered dither at `level` of 16, in 2px cells. */
96
+ function ditherMask(level) {
97
+ let rects = '';
98
+ BAYER.forEach((threshold, index) => {
99
+ if (threshold < level)
100
+ rects += `<rect x='${index % 4}' y='${Math.floor(index / 4)}' width='1' height='1'/>`;
101
+ });
102
+ const image = `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4' shape-rendering='crispEdges'>${rects}</svg>`)}")`;
103
+ return { maskImage: image, WebkitMaskImage: image, maskSize: '8px 8px', WebkitMaskSize: '8px 8px' };
104
+ }
105
+ /** The dither ramp's bands, from the far edge in: how wide each reaches, and how dense it is. */
106
+ const RAMP = [
107
+ { reach: '9%', level: 12 },
108
+ { reach: '17%', level: 7 },
109
+ { reach: '25%', level: 3 },
110
+ { reach: '32%', level: 1 },
111
+ ].map((band) => ({ ...band, style: ditherMask(band.level) }));
112
+ const cardVariants = tv({
113
+ slots: {
114
+ card: [
115
+ 'group/card relative flex w-full min-w-0 cursor-pointer items-start gap-3 overflow-hidden rounded-xl border p-3.5 text-start outline-none select-none',
116
+ 'border-border bg-background shadow-xs transition-[border-color,background-color,box-shadow] duration-200 dark:bg-input/20',
117
+ 'hover:not-data-disabled:border-[color-mix(in_oklab,var(--border),var(--foreground)_18%)]',
118
+ 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
119
+ 'data-disabled:cursor-not-allowed data-disabled:opacity-50',
120
+ ],
121
+ dot: [
122
+ 'relative z-10 mt-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-full border border-input bg-background',
123
+ 'transition-[border-color,border-width] duration-200 group-data-checked/card:border-[1.5px] group-data-checked/card:border-(--radio-color)',
124
+ ],
125
+ dotFill: [
126
+ 'size-[46%] rounded-full bg-(--radio-color) scale-0 transition-transform duration-300 ease-[cubic-bezier(0.3,1.6,0.5,1)]',
127
+ 'group-data-checked/card:scale-100 motion-reduce:transition-none',
128
+ ],
129
+ body: 'relative z-10 flex min-w-0 flex-1 flex-col gap-0.5',
130
+ title: 'text-sm font-medium leading-5 text-foreground',
131
+ description: 'text-[0.8125rem] leading-5 text-muted-foreground',
132
+ aside: 'relative z-10 ms-auto shrink-0 text-sm font-medium tabular-nums text-foreground',
133
+ ramp: 'pointer-events-none absolute inset-0',
134
+ band: [
135
+ 'absolute inset-y-0 end-0 bg-(--radio-color)',
136
+ // Hidden by sliding its clip to the far edge, so the sweep runs from there inwards.
137
+ '[clip-path:inset(0_0_0_100%)] rtl:[clip-path:inset(0_100%_0_0)] transition-[clip-path] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]',
138
+ 'group-data-checked/card:[clip-path:inset(0_0_0_0)] motion-reduce:transition-none',
139
+ ],
140
+ cards: 'grid gap-2',
141
+ },
142
+ variants: {
143
+ appearance: {
144
+ outline: {
145
+ card: 'data-checked:border-(--radio-color) data-checked:shadow-[0_0_0_1px_var(--radio-color)]',
146
+ },
147
+ filled: {
148
+ card: [
149
+ 'data-checked:border-[color-mix(in_oklab,var(--radio-color)_45%,var(--background))]',
150
+ 'data-checked:bg-[color-mix(in_oklab,var(--radio-color)_9%,var(--background))] dark:data-checked:bg-[color-mix(in_oklab,var(--radio-color)_16%,var(--background))]',
151
+ ],
152
+ },
153
+ dither: {
154
+ card: 'data-checked:border-[color-mix(in_oklab,var(--radio-color)_55%,var(--background))]',
155
+ },
156
+ },
157
+ color: {
158
+ primary: { card: '[--radio-color:var(--primary)]' },
159
+ success: { card: '[--radio-color:var(--success)]' },
160
+ info: { card: '[--radio-color:var(--info)]' },
161
+ destructive: { card: '[--radio-color:var(--destructive)]' },
162
+ },
163
+ },
164
+ defaultVariants: { appearance: 'outline', color: 'primary' },
165
+ });
166
+ /** One option drawn as a card, with its radio inside. The whole card is pressed to choose it. */
167
+ function RadioCard({ appearance = 'outline', color, className, children, ...props }) {
168
+ const slots = cardVariants({ appearance, color });
169
+ return (_jsxs(BaseRadio.Root, { "data-slot": "radio-card", "data-appearance": appearance, className: slots.card({ className }), ...props, children: [appearance === 'dither' ? (_jsx("span", { "aria-hidden": "true", className: slots.ramp(), children: RAMP.map((band) => (_jsx("span", { className: slots.band(), style: { width: band.reach, ...band.style } }, band.level))) })) : null, _jsx("span", { "data-slot": "radio-card-indicator", "aria-hidden": "true", className: slots.dot(), children: _jsx("span", { className: slots.dotFill() }) }), children] }));
170
+ }
171
+ RadioCard.displayName = 'RadioGroup.Card';
172
+ /** Holds a card's title and description, beside the radio. */
173
+ function RadioCardBody({ className, render, ...props }) {
174
+ return useRender({
175
+ defaultTagName: 'span',
176
+ render,
177
+ props: mergeProps({ 'data-slot': 'radio-card-body', className: cardVariants().body({ className }) }, props),
178
+ });
179
+ }
180
+ RadioCardBody.displayName = 'RadioGroup.CardBody';
181
+ /** The option's name. */
182
+ function RadioCardTitle({ className, render, ...props }) {
183
+ return useRender({
184
+ defaultTagName: 'span',
185
+ render,
186
+ props: mergeProps({ 'data-slot': 'radio-card-title', className: cardVariants().title({ className }) }, props),
187
+ });
188
+ }
189
+ RadioCardTitle.displayName = 'RadioGroup.CardTitle';
190
+ /** A line under the name saying what the option means. */
191
+ function RadioCardDescription({ className, render, ...props }) {
192
+ return useRender({
193
+ defaultTagName: 'span',
194
+ render,
195
+ props: mergeProps({ 'data-slot': 'radio-card-description', className: cardVariants().description({ className }) }, props),
196
+ });
197
+ }
198
+ RadioCardDescription.displayName = 'RadioGroup.CardDescription';
199
+ /** Pushed to the far end of the card: a price, a duration, a badge. */
200
+ function RadioCardAside({ className, render, ...props }) {
201
+ return useRender({
202
+ defaultTagName: 'span',
203
+ render,
204
+ props: mergeProps({ 'data-slot': 'radio-card-aside', className: cardVariants().aside({ className }) }, props),
205
+ });
206
+ }
207
+ RadioCardAside.displayName = 'RadioGroup.CardAside';
208
+ export const RadioGroup = Object.assign(RadioGroupRoot, {
209
+ Item: Radio,
210
+ Card: RadioCard,
211
+ CardBody: RadioCardBody,
212
+ CardTitle: RadioCardTitle,
213
+ CardDescription: RadioCardDescription,
214
+ CardAside: RadioCardAside,
215
+ });
216
+ export { Radio, Radio as RadioGroupItem, RadioCard, RadioCard as RadioGroupCard, RadioCardBody as RadioGroupCardBody, RadioCardTitle as RadioGroupCardTitle, RadioCardDescription as RadioGroupCardDescription, RadioCardAside as RadioGroupCardAside, };
@@ -35,6 +35,12 @@
35
35
  * threshold is how a meter says the reading has gone from fine to not.
36
36
  */
37
37
  import { type ComponentProps, type ReactNode } from 'react';
38
+ import { useRender } from '@base-ui/react/use-render';
39
+ import type { ElementType as RenderElementType } from 'react';
40
+ /** Draws a part as another element, or composes it with a component: `render={<a href="/" />}`. */
41
+ type RenderProp<Tag extends RenderElementType> = {
42
+ render?: useRender.ComponentProps<Tag>['render'];
43
+ };
38
44
  /** Which of the theme's roles the lit ticks and the tag are drawn in. */
39
45
  export type RangeMeterColor = 'primary' | 'info' | 'success' | 'warning' | 'destructive';
40
46
  export interface RangeMeterProps extends Omit<ComponentProps<'div'>, 'onChange' | 'color'> {
@@ -66,7 +72,7 @@ export interface RangeMeterProps extends Omit<ComponentProps<'div'>, 'onChange'
66
72
  /** Drawn in the tag before the number: an icon. */
67
73
  children?: ReactNode;
68
74
  }
69
- declare function RangeMeterRoot({ min, max, value, onValueChange, step, marker, ticks, color, formatValue, label, valueText, className, children, ...props }: RangeMeterProps): import("react").JSX.Element;
75
+ declare function RangeMeterRoot({ min, max, value, onValueChange, step, marker, ticks, color, formatValue, label, valueText, className, children, render, ...props }: RangeMeterProps & RenderProp<'div'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
70
76
  declare namespace RangeMeterRoot {
71
77
  var displayName: string;
72
78
  }
@@ -76,7 +82,7 @@ export interface RangeMeterLabelProps extends ComponentProps<'p'> {
76
82
  * The meter's name, written over it. The meter's own `label` is what a screen
77
83
  * reader says; this is what everyone else reads.
78
84
  */
79
- declare function RangeMeterLabel({ className, ...props }: RangeMeterLabelProps): import("react").JSX.Element;
85
+ declare function RangeMeterLabel({ className, render, ...props }: RangeMeterLabelProps & RenderProp<'p'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
80
86
  declare namespace RangeMeterLabel {
81
87
  var displayName: string;
82
88
  }
@@ -87,7 +93,7 @@ export interface RangeMeterScaleProps extends Omit<ComponentProps<'div'>, 'child
87
93
  end: ReactNode;
88
94
  }
89
95
  /** What the two ends of the ruler stand for, written under it at either side. */
90
- declare function RangeMeterScale({ start, end, className, ...props }: RangeMeterScaleProps): import("react").JSX.Element;
96
+ declare function RangeMeterScale({ start, end, className, render, ...props }: RangeMeterScaleProps & RenderProp<'div'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
91
97
  declare namespace RangeMeterScale {
92
98
  var displayName: string;
93
99
  }
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * RangeMeter — where a reading sits between its lowest and its highest, on a
5
5
  * ruler of ticks that rises under the value.
@@ -38,6 +38,8 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
38
38
  */
39
39
  import { useCallback, useRef, } from 'react';
40
40
  import { tv } from 'tailwind-variants';
41
+ import { mergeProps } from '@base-ui/react/merge-props';
42
+ import { useRender } from '@base-ui/react/use-render';
41
43
  const rangeMeterVariants = tv({
42
44
  slots: {
43
45
  // Room above the ruler for the tag, so the tag is inside the control's
@@ -90,7 +92,7 @@ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
90
92
  const SPREAD = 2.6;
91
93
  /** A resting tick's height, as a share of the ruler's. */
92
94
  const REST = 0.34;
93
- function RangeMeterRoot({ min = 0, max = 100, value, onValueChange, step = 1, marker, ticks = 41, color = 'primary', formatValue, label = 'Reading', valueText, className, children, ...props }) {
95
+ function RangeMeterRoot({ min = 0, max = 100, value, onValueChange, step = 1, marker, ticks = 41, color = 'primary', formatValue, label = 'Reading', valueText, className, children, render, ...props }) {
94
96
  const interactive = onValueChange !== undefined;
95
97
  const slots = rangeMeterVariants({ color, interactive, marked: marker !== undefined });
96
98
  const ruler = useRef(null);
@@ -150,40 +152,72 @@ function RangeMeterRoot({ min = 0, max = 100, value, onValueChange, step = 1, ma
150
152
  event.preventDefault();
151
153
  onValueChange(clamp(Number(next.toFixed(places)), min, max));
152
154
  };
153
- return (_jsxs("div", { role: interactive ? 'slider' : 'meter', "aria-label": label, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": value, "aria-valuetext": valueText, "aria-orientation": interactive ? 'horizontal' : undefined, tabIndex: interactive ? 0 : undefined, className: slots.root({ className }), onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: endDrag, onPointerCancel: endDrag, onKeyDown: onKeyDown, ...props, children: [_jsxs("span", { "data-tag": "", className: slots.tag(), style: {
154
- insetInlineStart: `${(share * 100).toFixed(2)}%`,
155
- // Hung by the same share of its own width as the point is along the
156
- // ruler, which keeps it over the point and inside the ends.
157
- translate: `calc(${share.toFixed(4)} * -100% * var(--pu-dir, 1))`,
158
- }, children: [children, formatValue ? formatValue(value) : value] }), _jsxs("div", { ref: ruler, "aria-hidden": "true", className: slots.ruler(), children: [Array.from({ length: count }, (_, index) => {
159
- const distance = (index - at) / SPREAD;
160
- // A bell: full height at the reading, resting height a few ticks out.
161
- const rise = Math.exp(-distance * distance);
162
- const lit = index <= at + 0.001;
163
- const part = rangeMeterVariants({ lit, crest: index === crest });
164
- return (_jsx("span", { className: part.tick(), style: {
165
- // Rounded, so the server and the browser write the same
166
- // string for it and the page hydrates without a mismatch.
167
- height: `${((REST + (1 - REST) * rise) * 100).toFixed(1)}%`,
168
- // Lit ticks brighten towards the reading, like a tail behind it.
169
- opacity: lit
170
- ? Number((0.35 + 0.65 * (crest === 0 ? 1 : Math.min(1, index / crest))).toFixed(3))
171
- : undefined,
172
- } }, index));
173
- }), marker !== undefined ? (_jsx("span", { className: slots.marker(), style: { insetInlineStart: `${(clamp((marker - min) / span, 0, 1) * 100).toFixed(2)}%` } })) : null] })] }));
155
+ return useRender({
156
+ defaultTagName: 'div',
157
+ render,
158
+ props: mergeProps({
159
+ role: interactive ? 'slider' : 'meter',
160
+ 'aria-label': label,
161
+ 'aria-valuemin': min,
162
+ 'aria-valuemax': max,
163
+ 'aria-valuenow': value,
164
+ 'aria-valuetext': valueText,
165
+ 'aria-orientation': interactive ? 'horizontal' : undefined,
166
+ tabIndex: interactive ? 0 : undefined,
167
+ className: slots.root({ className }),
168
+ onPointerDown: onPointerDown,
169
+ onPointerMove: onPointerMove,
170
+ onPointerUp: endDrag,
171
+ onPointerCancel: endDrag,
172
+ onKeyDown: onKeyDown,
173
+ children: (_jsxs(_Fragment, { children: [_jsxs("span", { "data-tag": "", className: slots.tag(), style: {
174
+ insetInlineStart: `${(share * 100).toFixed(2)}%`,
175
+ // Hung by the same share of its own width as the point is along the
176
+ // ruler, which keeps it over the point and inside the ends.
177
+ translate: `calc(${share.toFixed(4)} * -100% * var(--pu-dir, 1))`,
178
+ }, children: [children, formatValue ? formatValue(value) : value] }), _jsxs("div", { ref: ruler, "aria-hidden": "true", className: slots.ruler(), children: [Array.from({ length: count }, (_, index) => {
179
+ const distance = (index - at) / SPREAD;
180
+ // A bell: full height at the reading, resting height a few ticks out.
181
+ const rise = Math.exp(-distance * distance);
182
+ const lit = index <= at + 0.001;
183
+ const part = rangeMeterVariants({ lit, crest: index === crest });
184
+ return (_jsx("span", { className: part.tick(), style: {
185
+ // Rounded, so the server and the browser write the same
186
+ // string for it and the page hydrates without a mismatch.
187
+ height: `${((REST + (1 - REST) * rise) * 100).toFixed(1)}%`,
188
+ // Lit ticks brighten towards the reading, like a tail behind it.
189
+ opacity: lit
190
+ ? Number((0.35 + 0.65 * (crest === 0 ? 1 : Math.min(1, index / crest))).toFixed(3))
191
+ : undefined,
192
+ } }, index));
193
+ }), marker !== undefined ? (_jsx("span", { className: slots.marker(), style: { insetInlineStart: `${(clamp((marker - min) / span, 0, 1) * 100).toFixed(2)}%` } })) : null] })] })),
194
+ }, props),
195
+ });
174
196
  }
175
197
  RangeMeterRoot.displayName = 'RangeMeter';
176
198
  /**
177
199
  * The meter's name, written over it. The meter's own `label` is what a screen
178
200
  * reader says; this is what everyone else reads.
179
201
  */
180
- function RangeMeterLabel({ className, ...props }) {
181
- return _jsx("p", { "data-slot": "range-meter-label", className: rangeMeterVariants().label({ className }), ...props });
202
+ function RangeMeterLabel({ className, render, ...props }) {
203
+ return useRender({
204
+ defaultTagName: 'p',
205
+ render,
206
+ props: mergeProps({ 'data-slot': 'range-meter-label', className: rangeMeterVariants().label({ className }) }, props),
207
+ });
182
208
  }
183
209
  RangeMeterLabel.displayName = 'RangeMeter.Label';
184
210
  /** What the two ends of the ruler stand for, written under it at either side. */
185
- function RangeMeterScale({ start, end, className, ...props }) {
186
- return (_jsxs("div", { "data-slot": "range-meter-scale", className: rangeMeterVariants().scale({ className }), ...props, children: [_jsx("span", { children: start }), _jsx("span", { children: end })] }));
211
+ function RangeMeterScale({ start, end, className, render, ...props }) {
212
+ return useRender({
213
+ defaultTagName: 'div',
214
+ render,
215
+ props: mergeProps({
216
+ 'data-slot': 'range-meter-scale',
217
+ className: rangeMeterVariants().scale({ className }),
218
+ children: (_jsxs(_Fragment, { children: [_jsx("span", { children: start }), _jsx("span", { children: end })] })),
219
+ }, props),
220
+ });
187
221
  }
188
222
  RangeMeterScale.displayName = 'RangeMeter.Scale';
189
223
  export const RangeMeter = Object.assign(RangeMeterRoot, {
@@ -23,6 +23,12 @@
23
23
  * the nines roll to zeroes instead of being replaced by them.
24
24
  */
25
25
  import { type ComponentProps } from 'react';
26
+ import { useRender } from '@base-ui/react/use-render';
27
+ import type { ElementType as RenderElementType } from 'react';
28
+ /** Draws a part as another element, or composes it with a component: `render={<a href="/" />}`. */
29
+ type RenderProp<Tag extends RenderElementType> = {
30
+ render?: useRender.ComponentProps<Tag>['render'];
31
+ };
26
32
  export interface RollingNumberProps extends Omit<ComponentProps<'span'>, 'children'> {
27
33
  /** The number to show. Each digit rolls to its new value on its own. */
28
34
  value: number;
@@ -40,7 +46,8 @@ export interface RollingNumberProps extends Omit<ComponentProps<'span'>, 'childr
40
46
  /** How long a digit takes to roll, in milliseconds. */
41
47
  duration?: number;
42
48
  }
43
- export declare function RollingNumber({ value, format, locales, duration, className, ...props }: RollingNumberProps): import("react").JSX.Element;
49
+ export declare function RollingNumber({ value, format, locales, duration, className, render, ...props }: RollingNumberProps & RenderProp<'span'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
44
50
  export declare namespace RollingNumber {
45
51
  var displayName: string;
46
52
  }
53
+ export {};
@@ -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
  * RollingNumber — a number whose digits roll to their new values.
5
5
  *
@@ -26,6 +26,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
26
  */
27
27
  import { useMemo } from 'react';
28
28
  import { tv } from 'tailwind-variants';
29
+ import { mergeProps } from '@base-ui/react/merge-props';
30
+ import { useRender } from '@base-ui/react/use-render';
29
31
  const rollingNumberVariants = tv({
30
32
  slots: {
31
33
  // `tabular-nums` so a 1 is as wide as an 8 and the number does not change
@@ -61,7 +63,7 @@ function piecesOf(parts) {
61
63
  });
62
64
  return pieces;
63
65
  }
64
- export function RollingNumber({ value, format, locales = 'en-US', duration = 600, className, ...props }) {
66
+ export function RollingNumber({ value, format, locales = 'en-US', duration = 600, className, render, ...props }) {
65
67
  const slots = rollingNumberVariants();
66
68
  // The options object is usually written inline, so it is new on every
67
69
  // render. Its contents are what the formatter depends on.
@@ -73,9 +75,16 @@ export function RollingNumber({ value, format, locales = 'en-US', duration = 600
73
75
  const parts = formatter.formatToParts(value);
74
76
  return { label: parts.map((part) => part.value).join(''), pieces: piecesOf(parts) };
75
77
  }, [formatter, value]);
76
- return (_jsxs("span", { className: slots.root({ className }), ...props, children: [_jsx("span", { className: "sr-only", children: label }), pieces.map((piece) => piece.digit === null ? (_jsx("span", { "aria-hidden": "true", children: piece.text }, piece.key)) : (_jsxs("span", { "aria-hidden": "true", className: slots.column(), children: [_jsx("span", { className: "invisible", children: "0" }), _jsx("span", { className: slots.window(), children: _jsx("span", { className: slots.strip(), style: {
77
- transform: `translateY(${-piece.digit * 10}%)`,
78
- transitionDuration: `${duration}ms`,
79
- }, children: DIGITS.map((digit) => (_jsx("span", { className: slots.digit(), children: digit }, digit))) }) })] }, piece.key)))] }));
78
+ return useRender({
79
+ defaultTagName: 'span',
80
+ render,
81
+ props: mergeProps({
82
+ className: slots.root({ className }),
83
+ children: (_jsxs(_Fragment, { children: [_jsx("span", { className: "sr-only", children: label }), pieces.map((piece) => piece.digit === null ? (_jsx("span", { "aria-hidden": "true", children: piece.text }, piece.key)) : (_jsxs("span", { "aria-hidden": "true", className: slots.column(), children: [_jsx("span", { className: "invisible", children: "0" }), _jsx("span", { className: slots.window(), children: _jsx("span", { className: slots.strip(), style: {
84
+ transform: `translateY(${-piece.digit * 10}%)`,
85
+ transitionDuration: `${duration}ms`,
86
+ }, children: DIGITS.map((digit) => (_jsx("span", { className: slots.digit(), children: digit }, digit))) }) })] }, piece.key)))] })),
87
+ }, props),
88
+ });
80
89
  }
81
90
  RollingNumber.displayName = 'RollingNumber';
@@ -39,6 +39,12 @@
39
39
  */
40
40
  import { Select as BaseSelect } from '@base-ui/react/select';
41
41
  import type { ComponentProps, ReactNode } from 'react';
42
+ import { useRender } from '@base-ui/react/use-render';
43
+ import type { ElementType as RenderElementType } from 'react';
44
+ /** Draws a part as another element, or composes it with a component: `render={<a href="/" />}`. */
45
+ type RenderProp<Tag extends RenderElementType> = {
46
+ render?: useRender.ComponentProps<Tag>['render'];
47
+ };
42
48
  export type SelectProps<Value = string, Multiple extends boolean | undefined = false> = BaseSelect.Root.Props<Value, Multiple>;
43
49
  /** Holds the chosen value. Takes `value`/`onValueChange`, `defaultValue`, `items`, `name`, `disabled` and `multiple`. */
44
50
  declare function SelectRoot<Value = string, Multiple extends boolean | undefined = false>(props: SelectProps<Value, Multiple>): import("react").JSX.Element;
@@ -118,7 +124,7 @@ declare namespace SelectGroupLabel {
118
124
  export interface SelectSeparatorProps extends ComponentProps<'div'> {
119
125
  }
120
126
  /** A rule between groups of options. */
121
- declare function SelectSeparator({ className, ...props }: SelectSeparatorProps): import("react").JSX.Element;
127
+ declare function SelectSeparator({ className, render, ...props }: SelectSeparatorProps & RenderProp<'div'>): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
122
128
  declare namespace SelectSeparator {
123
129
  var displayName: string;
124
130
  }
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  /**
3
4
  * Select — a button that opens a list of options, one of which is chosen.
@@ -40,6 +41,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
40
41
  */
41
42
  import { Select as BaseSelect } from '@base-ui/react/select';
42
43
  import { tv } from 'tailwind-variants';
44
+ import { mergeProps } from '@base-ui/react/merge-props';
45
+ import { useRender } from '@base-ui/react/use-render';
43
46
  const selectVariants = tv({
44
47
  slots: {
45
48
  trigger: [
@@ -112,12 +115,14 @@ SelectRoot.displayName = 'Select';
112
115
  function Chevron({ variant }) {
113
116
  // A field shows both directions, since its list can open either way over it.
114
117
  // A chip shows one, and turns over while its list is open.
115
- return (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: variant === 'default' || variant === undefined ? _jsx("path", { d: "m7 15 5 5 5-5M7 9l5-5 5 5" }) : _jsx("path", { d: "m6 9 6 6 6-6" }) }));
118
+ return (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: variant === 'default' || variant === undefined ? (_jsx("path", { d: "m7 15 5 5 5-5M7 9l5-5 5 5" })) : (_jsx("path", { d: "m6 9 6 6 6-6" })) }));
116
119
  }
117
120
  /** The button that opens the list. Put a `Select.Value` in it. */
118
121
  function SelectTrigger({ variant, size, icon, className, children, ...props }) {
119
122
  const slots = selectVariants({ variant, size });
120
- return (_jsxs(BaseSelect.Trigger, { "data-slot": "select-trigger", className: slots.trigger({ className }), ...props, children: [children, icon === null ? null : (_jsx(BaseSelect.Icon, { "data-slot": "select-icon", className: slots.icon({ className: variant === 'default' || variant === undefined ? 'rotate-0!' : undefined }), children: icon ?? _jsx(Chevron, { variant: variant }) }))] }));
123
+ return (_jsxs(BaseSelect.Trigger, { "data-slot": "select-trigger", className: slots.trigger({ className }), ...props, children: [children, icon === null ? null : (_jsx(BaseSelect.Icon, { "data-slot": "select-icon", className: slots.icon({
124
+ className: variant === 'default' || variant === undefined ? 'rotate-0!' : undefined,
125
+ }), children: icon ?? _jsx(Chevron, { variant: variant }) }))] }));
121
126
  }
122
127
  SelectTrigger.displayName = 'Select.Trigger';
123
128
  /** The chosen option's label, or `placeholder` while there is none. */
@@ -148,8 +153,12 @@ function SelectGroupLabel({ className, ...props }) {
148
153
  }
149
154
  SelectGroupLabel.displayName = 'Select.GroupLabel';
150
155
  /** A rule between groups of options. */
151
- function SelectSeparator({ className, ...props }) {
152
- return _jsx("div", { role: "separator", "data-slot": "select-separator", className: selectVariants().separator({ className }), ...props });
156
+ function SelectSeparator({ className, render, ...props }) {
157
+ return useRender({
158
+ defaultTagName: 'div',
159
+ render,
160
+ props: mergeProps({ role: 'separator', 'data-slot': 'select-separator', className: selectVariants().separator({ className }) }, props),
161
+ });
153
162
  }
154
163
  SelectSeparator.displayName = 'Select.Separator';
155
164
  export const Select = Object.assign(SelectRoot, {
@@ -1,4 +1,10 @@
1
1
  import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
2
+ import { useRender } from '@base-ui/react/use-render';
3
+ import type { ElementType as RenderElementType } from 'react';
4
+ /** Draws a part as another element, or composes it with a component: `render={<a href="/" />}`. */
5
+ type RenderProp<Tag extends RenderElementType> = {
6
+ render?: useRender.ComponentProps<Tag>['render'];
7
+ };
2
8
  /** Where the sheet rises from, which is also the direction it leaves in. */
3
9
  export type SheetSide = 'bottom' | 'top' | 'left' | 'right';
4
10
  export interface SheetProps {
@@ -119,7 +125,7 @@ export interface SheetHeaderProps extends Omit<ComponentProps<'div'>, 'title'> {
119
125
  * The heading block. It leaves room for the close button beside the title,
120
126
  * so the two share the top line instead of stacking.
121
127
  */
122
- declare function SheetHeader({ title, description, className, children, ...props }: SheetHeaderProps): import("react").JSX.Element;
128
+ declare function SheetHeader({ title, description, className, children, render, ...props }: SheetHeaderProps & RenderProp<'div'>): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
123
129
  declare namespace SheetHeader {
124
130
  var displayName: string;
125
131
  }
@@ -212,7 +218,7 @@ export interface SheetSlideProps extends ComponentProps<'div'> {
212
218
  aspect?: string;
213
219
  }
214
220
  /** One slide: a rounded frame that fills with an image, a video or anything drawn. */
215
- declare function SheetSlide({ label, aspect, className, style, children, ...props }: SheetSlideProps): import("react").JSX.Element;
221
+ declare function SheetSlide({ label, aspect, className, style, children, render, ...props }: SheetSlideProps & RenderProp<'div'>): import("react").JSX.Element;
216
222
  declare namespace SheetSlide {
217
223
  var displayName: string;
218
224
  }