@santi020k/lumen-react 0.3.0 → 1.0.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.
@@ -1,15 +1,29 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- /* eslint-disable @eslint-react/no-children-only, @eslint-react/no-clone-element, @eslint-react/no-context-provider, @eslint-react/no-use-context -- Lumen uses React 19 ref props and context provider syntax that remains explicit for readability, and intentionally clones one child for polymorphic composition. */
4
- import { composeClassName, getLumenIcon, lumenCodeTokenClassNames, tokenizeLumenCode } from '@santi020k/lumen-core';
5
3
  import { Children, cloneElement, createContext, createElement, Fragment, isValidElement, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { alignLumenChartSeries, composeClassName, createLumenBarGeometry, createLumenLineGeometry, createLumenPieGeometry, getLumenChartCategories, getLumenChartDomain, getLumenChartTicks, getLumenChartToneClassName, getLumenIcon, getLumenPieChartVariantClassName, hasLumenChartData, hasLumenPieData, lumenCodeTokenClassNames, normalizeLumenFormErrors, resolveLumenChartTone, scaleLumenChartValue, tokenizeLumenCode } from '@santi020k/lumen-core';
6
5
  import { renderSVG } from 'uqr';
7
6
  import { useCalendar, useDialog, useDropdownMenu, useInputOTP, usePopover, useResizable, useSelect, useTabs, useTooltip } from './hooks.js';
7
+ const setRefValue = (ref, value) => {
8
+ if (typeof ref === 'function') {
9
+ ref(value);
10
+ }
11
+ else if (ref) {
12
+ ref.current = value;
13
+ }
14
+ };
8
15
  const emptyOptions = [];
9
16
  const emptyStringOptions = [];
10
17
  const emptyDataTableColumns = [];
11
18
  const emptyDataTableRows = [];
12
- const variantClass = (base, variant, defaultVariant = 'default') => variant === defaultVariant ? false : `${base}--${variant}`;
19
+ const emptyChartSeries = [];
20
+ const emptyPieSeries = {
21
+ data: [],
22
+ id: 'pie',
23
+ label: 'Values'
24
+ };
25
+ const emptyChartValues = [];
26
+ const variantClass = (base, variant, defaultVariant = 'default') => (variant === defaultVariant ? false : `${base}--${variant}`);
13
27
  const iconSizeClass = (size) => size === 'default' ? undefined : `ui-icon--${size}`;
14
28
  const reactSvgAttributeNames = {
15
29
  'clip-rule': 'clipRule',
@@ -22,7 +36,10 @@ const reactSvgAttributeNames = {
22
36
  'stroke-width': 'strokeWidth',
23
37
  tabindex: 'tabIndex'
24
38
  };
25
- const toReactSvgAttributes = (attributes) => Object.fromEntries(Object.entries(attributes).map(([name, value]) => [reactSvgAttributeNames[name] ?? name, value]));
39
+ const toReactSvgAttributes = (attributes) => Object.fromEntries(Object.entries(attributes).map(([name, value]) => [
40
+ reactSvgAttributeNames[name] ?? name,
41
+ value
42
+ ]));
26
43
  const renderIconNode = ([tagName, attributes, children], index) => createElement(tagName, {
27
44
  ...toReactSvgAttributes(attributes),
28
45
  key: attributes.key ?? index
@@ -43,24 +60,34 @@ const getIconAccessibility = ({ ariaHidden, ariaLabel, decorative, label, role }
43
60
  role: role ?? 'img'
44
61
  };
45
62
  };
46
- const renderIconSvg = (icon, className) => (_jsx("svg", { "aria-hidden": "true", className: className, fill: "none", focusable: "false", height: "1em", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", width: "1em", xmlns: "http://www.w3.org/2000/svg", children: icon.node.map(renderIconNode) }));
47
- const renderNamedIcon = (icon) => renderIconSvg(icon, `ui-icon__svg lucide-${icon.name}`);
63
+ const renderIconSvg = (icon, className) => {
64
+ const width = 'size' in icon ? icon.size : icon.width;
65
+ const height = 'size' in icon ? icon.size : icon.height;
66
+ return (_jsx("svg", { "aria-hidden": "true", className: className, fill: icon.style === 'fill' ? 'currentColor' : 'none', focusable: "false", height: "1em", stroke: icon.style === 'fill' ? 'none' : 'currentColor', strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: icon.style === 'fill' ? '0' : '2', viewBox: `0 0 ${width} ${height}`, width: "1em", xmlns: "http://www.w3.org/2000/svg", children: icon.node.map(renderIconNode) }));
67
+ };
68
+ const renderNamedIcon = (icon) => renderIconSvg(icon, `ui-icon__svg ${icon.source ?? 'lucide'}-${icon.name}`);
48
69
  const renderLucideIcon = (name, className) => {
49
70
  const icon = getLumenIcon(name);
50
71
  return icon ? renderIconSvg(icon, className) : null;
51
72
  };
52
- const resolveSurface = (surface, glass) => glass || surface === 'glass' ? 'glass' : surface;
53
- const glassIntensityClass = (glass) => glass === 'subtle' ? 'ui-glass-subtle' : glass === 'strong' && 'ui-glass-strong';
54
- const glassSurfaceClass = (base, surface, glass) => resolveSurface(surface, glass) === 'glass' && composeClassName(`${base}--glass`, glassIntensityClass(glass));
55
- const glassClass = (base, glass) => Boolean(glass) && composeClassName(`${base}--glass`, glassIntensityClass(glass));
73
+ const resolveSurface = (glass) => glass ? 'glass' : 'default';
74
+ const glassIntensityClass = (glass) => glass === 'subtle' ?
75
+ 'ui-glass-subtle' :
76
+ glass === 'strong' && 'ui-glass-strong';
77
+ const glassSurfaceClass = (base, glass) => resolveSurface(glass) === 'glass' &&
78
+ composeClassName(`${base}--glass`, glassIntensityClass(glass));
79
+ const glassClass = (base, glass) => Boolean(glass) &&
80
+ composeClassName(`${base}--glass`, glassIntensityClass(glass));
56
81
  const normalizeOption = (option) => typeof option === 'string' ? { label: option, value: option } : option;
57
82
  const isDataTableCellObject = (cell) => typeof cell === 'object' && cell !== null;
58
83
  const formatDataTableCell = (cell) => {
59
- const value = isDataTableCellObject(cell) ? cell.label ?? cell.value : cell;
84
+ const value = isDataTableCellObject(cell) ? (cell.label ?? cell.value) : cell;
60
85
  return value === undefined || value === null ? '' : String(value);
61
86
  };
62
87
  const getDataTableSortValue = (cell) => {
63
- if (!isDataTableCellObject(cell) || cell.sortValue === undefined || cell.sortValue === null)
88
+ if (!isDataTableCellObject(cell) ||
89
+ cell.sortValue === undefined ||
90
+ cell.sortValue === null)
64
91
  return undefined;
65
92
  return String(cell.sortValue);
66
93
  };
@@ -77,7 +104,7 @@ const composeHandlers = (userHandler, lumenHandler) => (event) => {
77
104
  const Primitive = ({ as, className, uiClassName, ...props }) => {
78
105
  const Tag = as ?? 'div';
79
106
  const safeClassName = typeof className === 'string' ? className : undefined;
80
- return _jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props });
107
+ return (_jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props }));
81
108
  };
82
109
  const DropdownMenuContext = createContext(null);
83
110
  const PopoverContext = createContext(null);
@@ -91,18 +118,18 @@ const requireContext = (context, componentName) => {
91
118
  };
92
119
  export const Accordion = ({ className, variant = 'default', ...props }) => (_jsx("div", { className: composeClassName('ui-accordion', variant === 'flush' && 'ui-accordion--flush', className), "data-variant": variant, ...props }));
93
120
  export const Alert = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-alert', variantClass('ui-alert', variant), glassClass('ui-alert', glass), className), "data-variant": variant, ...props }));
94
- export const AlertDialog = ({ className, defaultOpen, glass = false, onClick, onClose, onOpenChange, open, surface = 'default', ...props }) => {
121
+ export const AlertDialog = ({ className, defaultOpen, glass = false, onClick, onClose, onOpenChange, open, ...props }) => {
95
122
  const dialog = useDialog({ alert: true, defaultOpen, onOpenChange, open });
96
- return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog ui-alert-dialog', glassSurfaceClass('ui-dialog', surface, glass), className), "data-surface": resolveSurface(surface, glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
123
+ return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog ui-alert-dialog', glassSurfaceClass('ui-dialog', glass), className), "data-surface": resolveSurface(glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
97
124
  };
98
125
  export const Agenda = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-agenda', glassClass('ui-agenda', glass), className), ...props }));
99
126
  export const AspectRatio = ({ className, ratio = '16 / 9', style, ...props }) => (_jsx("div", { className: composeClassName('ui-aspect-ratio', className), style: { aspectRatio: ratio, ...style }, ...props }));
100
127
  export const Autocomplete = ({ className, type = 'search', ...props }) => (_jsx("input", { "aria-autocomplete": "list", className: composeClassName('ui-input ui-autocomplete', className), role: "combobox", type: type, ...props }));
101
128
  export const Attachment = ({ className, glass = false, href, ...props }) => {
102
129
  const nextClassName = composeClassName('ui-attachment', glassClass('ui-attachment', glass), className);
103
- return href
104
- ? _jsx("a", { className: nextClassName, href: href, ...props })
105
- : _jsx("article", { className: nextClassName, ...props });
130
+ return href ?
131
+ (_jsx("a", { className: nextClassName, href: href, ...props })) :
132
+ (_jsx("article", { className: nextClassName, ...props }));
106
133
  };
107
134
  export const Avatar = ({ alt = '', children, className, fallback, src, ...props }) => (_jsxs("span", { className: composeClassName('ui-avatar', className), ...props, children: [src ? _jsx("img", { alt: alt, src: src }) : _jsx("span", { children: fallback }), children] }));
108
135
  export const Badge = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-badge', `ui-badge--${variant}`, className), "data-variant": variant, ...props }));
@@ -116,16 +143,10 @@ export const Button = ({ asChild = false, children, className, disabled, loading
116
143
  if (!isValidElement(child)) {
117
144
  throw new TypeError('Button with asChild requires exactly one React element child.');
118
145
  }
119
- return cloneElement(child, {
120
- ...props,
121
- 'aria-busy': loading ? true : undefined,
122
- 'aria-disabled': disabled ? true : undefined,
123
- className: composeClassName(buttonClassName, child.props.className),
124
- ref: ref,
125
- children: (_jsxs(_Fragment, { children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), child.props.children] }))
126
- });
146
+ const ChildComponent = child.type;
147
+ return (_jsxs(ChildComponent, { ...child.props, ...props, "aria-busy": loading ? true : undefined, "aria-disabled": disabled ? true : undefined, className: composeClassName(buttonClassName, child.props.className), "data-slot": "button", ref: ref, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), child.props.children] }));
127
148
  }
128
- return (_jsxs("button", { "aria-busy": loading || undefined, className: buttonClassName, disabled: disabled, ref: ref, type: type, ...props, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), children] }));
149
+ return (_jsxs("button", { "aria-busy": loading || undefined, className: buttonClassName, "data-slot": "button", disabled: disabled, ref: ref, type: type, ...props, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), children] }));
129
150
  };
130
151
  export const ButtonGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-button-group', className), ...props }));
131
152
  export const Calendar = ({ className, defaultValue, disabled = false, glass = false, locale, max, min, month, name, onValueChange, value, ...props }) => {
@@ -142,17 +163,145 @@ export const Calendar = ({ className, defaultValue, disabled = false, glass = fa
142
163
  });
143
164
  return (_jsxs("div", { ...props, ...calendar.rootProps, className: composeClassName(calendar.rootProps.className, glassClass('ui-calendar', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsx("input", { ...calendar.inputProps }), _jsxs("div", { className: "ui-calendar__header", children: [_jsx("button", { ...calendar.previousProps, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "\u2039" }) }), _jsx("strong", { ...calendar.labelProps, children: calendar.label }), _jsx("button", { ...calendar.nextProps, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "\u203A" }) })] }), _jsxs("table", { ...calendar.gridProps, children: [_jsx("thead", { children: _jsx("tr", { role: "row", children: calendar.weekdays.map(weekday => (_jsx("th", { role: "columnheader", scope: "col", children: weekday }, weekday))) }) }), _jsx("tbody", { children: calendar.weeks.map(week => (_jsx("tr", { role: "row", children: week.map(day => (_jsx("td", { ...calendar.getDayProps(day), children: day.day }, day.date))) }, week[0]?.date ?? 'week'))) })] })] }));
144
165
  };
145
- export const Card = ({ as, className, glass = false, variant = 'default', ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant === 'muted' && 'ui-card--muted', variant === 'interactive' && 'ui-card--interactive', variant === 'unstyled' && 'ui-card--unstyled', (glass || variant === 'glass') && composeClassName('ui-card--glass', glassIntensityClass(glass)), className), "data-variant": variant, ...props, uiClassName: "ui-card" }));
166
+ export const Card = ({ as, className, glass = false, variant = 'default', ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant === 'muted' && 'ui-card--muted', variant === 'interactive' && 'ui-card--interactive', variant === 'unstyled' && 'ui-card--unstyled', (glass || variant === 'glass') &&
167
+ composeClassName('ui-card--glass', glassIntensityClass(glass)), className), "data-slot": "card", "data-variant": variant, ...props, uiClassName: "ui-card" }));
168
+ export const CardHeader = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-header", ...props, uiClassName: "ui-card__header" }));
169
+ export const CardTitle = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-title", ...props, uiClassName: "ui-card__title" }));
170
+ export const CardDescription = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-description", ...props, uiClassName: "ui-card__description" }));
171
+ export const CardContent = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-content", ...props, uiClassName: "ui-card__content" }));
172
+ export const CardFooter = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-footer", ...props, uiClassName: "ui-card__footer" }));
146
173
  export const Carousel = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-carousel', glassClass('ui-carousel', glass), className), "data-ui-carousel": true, ...props }));
147
- export const Chart = ({ className, glass = false, ...props }) => (_jsx("figure", { className: composeClassName('ui-chart', glassClass('ui-chart', glass), className), ...props }));
148
- export const Checkbox = ({ className, ...props }) => (_jsx("input", { className: composeClassName('ui-checkbox', className), type: "checkbox", ...props }));
174
+ const ChartHeader = ({ description, heading, value }) => {
175
+ if (!heading && !description && !value)
176
+ return null;
177
+ return (_jsxs("header", { children: [_jsxs("div", { className: "ui-chart__heading", children: [heading && _jsx("h3", { children: heading }), description && _jsx("p", { children: description })] }), value && _jsx("strong", { "data-ui-chart-value": true, children: value })] }));
178
+ };
179
+ export const Chart = ({ caption, children, className, description, glass = false, heading, presentation = 'default', value, ...props }) => (_jsxs("figure", { className: composeClassName('ui-chart', presentation === 'bare' && 'ui-chart--bare', glassClass('ui-chart', glass), className), ...props, children: [_jsx(ChartHeader, { description: description, heading: heading, value: value }), children, caption && _jsx("figcaption", { children: caption })] }));
180
+ const ChartLegend = ({ series }) => (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: series.map((item, index) => (_jsxs("li", { className: getLumenChartToneClassName(item.tone, index), children: [_jsx("span", { "aria-hidden": "true" }), item.label] }, item.id))) }));
181
+ const ChartDataTable = ({ categories, formatCategory = String, formatValue = String, series }) => (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: "View chart data" }), _jsx("div", { children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), series.map(item => (_jsx("th", { scope: "col", children: item.label }, item.id)))] }) }), _jsx("tbody", { children: categories.map(category => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: series
182
+ .flatMap(item => item.data)
183
+ .find(datum => datum.x === category)?.xLabel ??
184
+ formatCategory(category) }), series.map(item => {
185
+ const datum = item.data.find(candidate => candidate.x === category);
186
+ return (_jsx("td", { children: datum?.label ??
187
+ (datum?.y === null || datum === undefined ?
188
+ 'Not available' :
189
+ formatValue(datum.y)) }, item.id));
190
+ })] }, `${typeof category}:${String(category)}`))) })] }) })] }));
191
+ const formatChartPercentage = (percentage) => new Intl.NumberFormat(undefined, {
192
+ maximumFractionDigits: percentage < 0.01 ? 1 : 0,
193
+ style: 'percent'
194
+ }).format(percentage);
195
+ export const Sparkline = ({ area = false, className, label = 'Trend', showEndpoint = true, tone, values = emptyChartValues, ...props }) => {
196
+ const geometry = createLumenLineGeometry(values.map((value, index) => ({ x: index, y: value })), { height: 40, padding: 3, width: 120 });
197
+ const resolvedTone = resolveLumenChartTone(tone);
198
+ const endpoint = geometry.points.at(-1);
199
+ return (_jsxs("span", { "aria-label": label, className: composeClassName('ui-sparkline', `ui-chart-tone--${resolvedTone}`, className), role: "img", ...props, children: [_jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "none", viewBox: "0 0 120 40", children: [area &&
200
+ geometry.areaPaths.map(path => (_jsx("path", { className: "ui-sparkline__area", d: path }, path))), _jsx("path", { className: "ui-sparkline__line", d: geometry.path }), showEndpoint && endpoint && (_jsx("circle", { className: "ui-sparkline__endpoint", cx: endpoint.xCoordinate, cy: endpoint.yCoordinate, r: "2.5" }))] }), _jsx("span", { className: "ui-sr-only", children: label })] }));
201
+ };
202
+ /* eslint-disable complexity -- Chart renderers keep optional semantic and SVG layers colocated. */
203
+ export const BarChart = ({ categoryWidth, className, emptyLabel = 'No chart data available.', formatCategory = String, formatValue = String, layout = 'grouped', orientation = 'vertical', series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
204
+ const geometry = createLumenBarGeometry(series, {
205
+ ...(categoryWidth === undefined ? {} : { categoryWidth }),
206
+ layout,
207
+ orientation
208
+ });
209
+ const hasData = hasLumenChartData(series);
210
+ const ticks = getLumenChartTicks(geometry.domain);
211
+ const categories = geometry.categories.map(category => category.category);
212
+ const margin = orientation === 'horizontal' ?
213
+ {
214
+ bottom: 24,
215
+ left: Math.max(64, Math.min(240, categoryWidth ?? 112)),
216
+ right: 20,
217
+ top: 16
218
+ } :
219
+ { bottom: 52, left: 52, right: 16, top: 16 };
220
+ return (_jsxs(Chart, { className: composeClassName('ui-bar-chart', className), ...props, children: [showLegend && hasData && _jsx(ChartLegend, { series: series }), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: emptyLabel })), _jsx("div", { className: "ui-chart__plot", hidden: !hasData, children: _jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${geometry.width} ${geometry.height}`, children: [_jsx("g", { className: "ui-chart__grid", children: ticks.map(tick => {
221
+ const coordinate = orientation === 'horizontal' ?
222
+ scaleLumenChartValue(tick, geometry.domain, margin.left, geometry.width - margin.right) :
223
+ scaleLumenChartValue(tick, geometry.domain, geometry.height - margin.bottom, margin.top);
224
+ return orientation === 'horizontal' ?
225
+ (_jsx("line", { x1: coordinate, x2: coordinate, y1: margin.top, y2: geometry.height - margin.bottom }, tick)) :
226
+ (_jsx("line", { x1: margin.left, x2: geometry.width - margin.right, y1: coordinate, y2: coordinate }, tick));
227
+ }) }), _jsx("g", { className: "ui-chart__axis-labels", children: geometry.categories.map(category => (_jsx("text", { dominantBaseline: orientation === 'horizontal' ? 'middle' : undefined, textAnchor: orientation === 'horizontal' ? 'end' : 'middle', x: category.x, y: category.y, children: String(category.label) }, `${typeof category.label}:${String(category.label)}`))) }), _jsx("g", { className: "ui-bar-chart__marks", children: geometry.marks.map(mark => (_jsx("rect", { className: getLumenChartToneClassName(mark.tone), height: mark.height, rx: "4", width: mark.width, x: mark.x, y: mark.y, children: _jsx("title", { children: `${series
228
+ .flatMap(item => item.data)
229
+ .find(datum => datum.x === mark.category)?.xLabel ??
230
+ formatCategory(mark.category)} · ${mark.seriesLabel}: ${formatValue(mark.value)}` }) }, `${mark.seriesId}:${String(mark.category)}`))) })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, formatCategory: formatCategory, formatValue: formatValue, series: series }))] }));
231
+ };
232
+ const getLineChartMarkerStep = (markers, categoryCount) => {
233
+ if (markers === false || markers === 'none')
234
+ return Number.POSITIVE_INFINITY;
235
+ if (typeof markers === 'number')
236
+ return Math.max(1, Math.round(markers));
237
+ if (markers === 'auto')
238
+ return Math.max(1, Math.ceil(categoryCount / 24));
239
+ return 1;
240
+ };
241
+ export const LineChart = ({ area = false, className, emptyLabel = 'No chart data available.', formatCategory = String, formatValue = String, markers = 'auto', referenceValue, series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
242
+ const width = 640;
243
+ const height = 320;
244
+ const padding = 44;
245
+ const categories = getLumenChartCategories(series);
246
+ const hasData = hasLumenChartData(series);
247
+ const alignedSeries = series.map(item => alignLumenChartSeries(item, categories));
248
+ const domain = getLumenChartDomain([
249
+ ...alignedSeries.flatMap(item => item.data.map(datum => datum.y)),
250
+ referenceValue ?? null
251
+ ], false);
252
+ const geometries = alignedSeries.map(item => createLumenLineGeometry(item.data, {
253
+ domain,
254
+ height,
255
+ includeZero: false,
256
+ padding,
257
+ width
258
+ }));
259
+ const ticks = getLumenChartTicks(domain);
260
+ const labelStep = Math.max(1, Math.ceil(categories.length / 8));
261
+ const markerStep = getLineChartMarkerStep(markers, categories.length);
262
+ const referenceY = referenceValue === undefined ?
263
+ undefined :
264
+ scaleLumenChartValue(referenceValue, domain, height - padding, padding);
265
+ return (_jsxs(Chart, { className: composeClassName('ui-line-chart', className), ...props, children: [showLegend && hasData && _jsx(ChartLegend, { series: series }), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: emptyLabel })), _jsx("div", { className: "ui-chart__plot", hidden: !hasData, children: _jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${width} ${height}`, children: [_jsx("g", { className: "ui-chart__grid", children: ticks.map(tick => {
266
+ const y = scaleLumenChartValue(tick, domain, height - padding, padding);
267
+ return (_jsxs(Fragment, { children: [_jsx("line", { x1: padding, x2: width - padding, y1: y, y2: y }), _jsx("text", { x: padding - 8, y: y, children: formatValue(tick) })] }, tick));
268
+ }) }), _jsx("g", { className: "ui-chart__axis-labels", children: categories.map((category, index) => {
269
+ if (index % labelStep !== 0 && index !== categories.length - 1)
270
+ return null;
271
+ const denominator = Math.max(1, categories.length - 1);
272
+ const x = padding + (index / denominator) * (width - padding * 2);
273
+ return (_jsx("text", { textAnchor: "middle", x: x, y: height - 14, children: series
274
+ .flatMap(item => item.data)
275
+ .find(datum => datum.x === category)?.xLabel ??
276
+ formatCategory(category) }, `${typeof category}:${String(category)}`));
277
+ }) }), referenceY !== undefined && (_jsx("line", { className: "ui-chart__reference", x1: padding, x2: width - padding, y1: referenceY, y2: referenceY })), geometries.map((geometry, index) => {
278
+ const item = series[index];
279
+ if (!item)
280
+ return null;
281
+ const tone = resolveLumenChartTone(item.tone, index);
282
+ return (_jsxs("g", { className: composeClassName('ui-line-chart__series', getLumenChartToneClassName(tone)), children: [area &&
283
+ geometry.areaPaths.map(path => (_jsx("path", { className: "ui-line-chart__area", d: path }, path))), _jsx("path", { className: "ui-line-chart__line", d: geometry.path }), Number.isFinite(markerStep) &&
284
+ geometry.points.map((point, pointIndex) => pointIndex % markerStep === 0 && (_jsx("circle", { className: "ui-line-chart__point", cx: point.xCoordinate, cy: point.yCoordinate, r: "3", children: _jsx("title", { children: `${point.xLabel ?? formatCategory(point.x)} · ${item.label}: ${formatValue(point.y ?? 0)}` }) }, `${typeof point.x}:${String(point.x)}`)))] }, item.id));
285
+ })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, formatCategory: formatCategory, formatValue: formatValue, series: series }))] }));
286
+ };
287
+ export const PieChart = ({ centerLabel, centerValue, className, series = emptyPieSeries, showLegend = true, showTable = true, valueFormatter = String, variant = 'donut', ...props }) => {
288
+ const geometry = createLumenPieGeometry(series.data, { variant });
289
+ const hasData = hasLumenPieData(series.data);
290
+ const hasCenterLabel = centerLabel !== null && centerLabel !== undefined;
291
+ const hasCenterValue = centerValue !== null && centerValue !== undefined;
292
+ return (_jsxs(Chart, { className: composeClassName('ui-pie-chart', getLumenPieChartVariantClassName(variant), className), ...props, children: [showLegend && hasData && (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: geometry.slices.map(slice => (_jsxs("li", { className: getLumenChartToneClassName(slice.tone), children: [_jsx("span", { "aria-hidden": "true" }), slice.label] }, `${typeof slice.x}:${String(slice.x)}`))) })), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: "No chart data available." })), _jsxs("div", { className: "ui-chart__plot ui-pie-chart__plot", hidden: !hasData, children: [_jsx("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${geometry.size} ${geometry.size}`, children: _jsx("g", { className: "ui-pie-chart__slices", children: geometry.slices.map(slice => (_jsx("path", { className: getLumenChartToneClassName(slice.tone), d: slice.path, fillRule: "evenodd", children: _jsx("title", { children: `${slice.label}: ${valueFormatter(slice.value)} (${formatChartPercentage(slice.percentage)})` }) }, `${typeof slice.x}:${String(slice.x)}`))) }) }), variant === 'donut' && (hasCenterLabel || hasCenterValue) && (_jsxs(_Fragment, { children: [_jsxs("div", { "aria-hidden": "true", className: "ui-pie-chart__center", children: [hasCenterValue && _jsx("strong", { children: centerValue }), hasCenterLabel && _jsx("span", { children: centerLabel })] }), _jsxs("div", { className: "ui-sr-only", children: [centerValue, ' ', centerLabel] })] }))] }), showTable && hasData && (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: "View chart data" }), _jsx("div", { children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), _jsx("th", { scope: "col", children: "Value" }), _jsx("th", { scope: "col", children: "Share" })] }) }), _jsx("tbody", { children: geometry.slices.map(slice => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: slice.label }), _jsx("td", { children: valueFormatter(slice.value) }), _jsx("td", { children: formatChartPercentage(slice.percentage) })] }, `${typeof slice.x}:${String(slice.x)}`))) })] }) })] }))] }));
293
+ };
294
+ export const Checkbox = ({ className, ref, ...props }) => (_jsx("input", { className: composeClassName('ui-checkbox', className), ref: ref, type: "checkbox", ...props }));
295
+ export const CheckboxGroup = ({ children, className, invalid = false, legend, orientation = 'vertical', ...props }) => (_jsxs("fieldset", { "aria-invalid": invalid || undefined, className: composeClassName('ui-checkbox-group', `ui-checkbox-group--${orientation}`, className), "data-invalid": invalid ? 'true' : undefined, "data-orientation": orientation, "data-ui-checkbox-group": true, ...props, children: [legend && _jsx("legend", { children: legend }), children] }));
149
296
  export const Collapsible = ({ className, ...props }) => (_jsx("details", { className: composeClassName('ui-collapsible', className), "data-ui-collapsible": true, ...props }));
150
297
  const renderCodeToken = (token) => {
151
298
  if (!token.kind)
152
299
  return token.value;
153
300
  return (_jsx("span", { className: lumenCodeTokenClassNames[token.kind], children: token.value }, `${token.start}-${token.kind}`));
154
301
  };
155
- const renderCodeChildren = (code, children, language) => code === undefined ? children : tokenizeLumenCode(code, language).map(renderCodeToken);
302
+ const renderCodeChildren = (code, children, language) => code === undefined ?
303
+ children :
304
+ tokenizeLumenCode(code, language).map(renderCodeToken);
156
305
  const renderCodeCopyButton = () => (_jsxs("button", { "aria-label": "Copy code to clipboard", className: "ui-code__copy", "data-ui-code-copy": true, type: "button", children: [renderLucideIcon('copy', 'ui-code__copy-icon'), renderLucideIcon('check', 'ui-code__check-icon')] }));
157
306
  const renderCodeHeader = ({ copy, label, language }) => {
158
307
  if (!copy && !label && !language)
@@ -162,9 +311,59 @@ const renderCodeHeader = ({ copy, label, language }) => {
162
311
  export const Code = ({ children, className, code, copy = false, highlighted = false, label, language, theme = 'auto', variant = 'inline', wrap = false, ...props }) => {
163
312
  const codeChildren = renderCodeChildren(code, children, language);
164
313
  if (variant === 'block') {
165
- return (_jsxs("figure", { className: composeClassName('ui-code ui-code--block', wrap && 'ui-code--wrap', className), "data-code-theme": theme, "data-language": language, "data-ui-code": true, ...props, children: [renderCodeHeader({ copy, label, language }), highlighted ? children : _jsx("pre", { children: _jsx("code", { children: codeChildren }) })] }));
314
+ return (_jsxs("figure", { className: composeClassName('ui-code ui-code--block', wrap && 'ui-code--wrap', className), "data-code-theme": theme, "data-language": language, "data-slot": "code", "data-ui-code": true, ...props, children: [renderCodeHeader({ copy, label, language }), highlighted ?
315
+ (children) :
316
+ (_jsx("pre", { children: _jsx("code", { children: codeChildren }) }))] }));
166
317
  }
167
- return (_jsx("code", { className: composeClassName('ui-code ui-code--inline', className), "data-code-theme": theme, "data-language": language, ...props, children: code ?? children }));
318
+ return (_jsx("code", { className: composeClassName('ui-code ui-code--inline', className), "data-code-theme": theme, "data-language": language, "data-slot": "code", ...props, children: code ?? children }));
319
+ };
320
+ const dispatchCopyToast = (enabled, title, variant) => {
321
+ if (!enabled)
322
+ return;
323
+ document.dispatchEvent(new CustomEvent('ui:toast', {
324
+ detail: { title, variant }
325
+ }));
326
+ };
327
+ const getCopyButtonLabel = (state, copiedLabel, errorLabel, label) => ({ copied: copiedLabel, error: errorLabel, idle: label })[state];
328
+ export const CopyButton = ({ children = 'Copy', className, copiedLabel = 'Copied to clipboard', errorLabel = 'Could not copy to clipboard', label = 'Copy to clipboard', onClick, resetAfter = 2000, target, toast = false, type = 'button', value, ...props }) => {
329
+ const [state, setState] = useState('idle');
330
+ const resetTimerRef = useRef(undefined);
331
+ useEffect(() => () => {
332
+ globalThis.clearTimeout(resetTimerRef.current);
333
+ }, []);
334
+ const handleClick = async (event) => {
335
+ onClick?.(event);
336
+ if (event.defaultPrevented)
337
+ return;
338
+ const button = event.currentTarget;
339
+ const targetElement = target ? document.querySelector(target) : null;
340
+ const text = value ?? targetElement?.innerText ?? targetElement?.textContent;
341
+ try {
342
+ if (text === undefined)
343
+ throw new Error(errorLabel);
344
+ await navigator.clipboard.writeText(text);
345
+ setState('copied');
346
+ button.dispatchEvent(new CustomEvent('ui:copy-success', {
347
+ bubbles: true,
348
+ detail: { value: text }
349
+ }));
350
+ dispatchCopyToast(toast, copiedLabel, 'success');
351
+ }
352
+ catch (error) {
353
+ setState('error');
354
+ button.dispatchEvent(new CustomEvent('ui:copy-error', {
355
+ bubbles: true,
356
+ detail: { error }
357
+ }));
358
+ dispatchCopyToast(toast, errorLabel, 'destructive');
359
+ }
360
+ globalThis.clearTimeout(resetTimerRef.current);
361
+ resetTimerRef.current = globalThis.setTimeout(() => {
362
+ setState('idle');
363
+ }, resetAfter);
364
+ };
365
+ const accessibleLabel = getCopyButtonLabel(state, copiedLabel, errorLabel, label);
366
+ return (_jsx("button", { "aria-label": accessibleLabel, className: composeClassName('ui-button ui-button--outline ui-copy-button', className), "data-state": state, "data-ui-copy-button": true, onClick: handleClick, type: type, ...props, children: children }));
168
367
  };
169
368
  export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onChange, onFocus, onKeyDown, options = emptyStringOptions, type = 'text', value: valueProp, wrapperClassName, ...props }) => {
170
369
  const inputId = id ?? `${list}-input`;
@@ -184,7 +383,7 @@ export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onC
184
383
  if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
185
384
  setOpen(false);
186
385
  }
187
- }, children: [label && _jsx("label", { className: "ui-label", htmlFor: inputId, children: label }), _jsx("input", { "aria-autocomplete": "list", "aria-controls": list, "aria-expanded": open, className: composeClassName('ui-input', className), id: inputId, role: "combobox", type: type, value: renderedValue, onBlur: onBlur, onChange: event => {
386
+ }, children: [label && (_jsx("label", { className: "ui-label", htmlFor: inputId, children: label })), _jsx("input", { "aria-autocomplete": "list", "aria-controls": list, "aria-expanded": open, className: composeClassName('ui-input', className), id: inputId, role: "combobox", type: type, value: renderedValue, onBlur: onBlur, onChange: event => {
188
387
  if (valueProp === undefined) {
189
388
  setValue(event.currentTarget.value);
190
389
  }
@@ -202,14 +401,18 @@ export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onC
202
401
  selectOption(visibleOptions[0]);
203
402
  }
204
403
  onKeyDown?.(event);
205
- }, ...props }), _jsx("div", { className: "ui-combobox__list", hidden: !open, id: list, role: "listbox", children: visibleOptions.map(option => (_jsx("button", { "data-ui-combobox-option": true, "data-value": option, role: "option", type: "button", onClick: () => { selectOption(option); }, onMouseDown: event => { event.preventDefault(); }, children: option }, option))) })] }));
404
+ }, ...props }), _jsx("div", { className: "ui-combobox__list", hidden: !open, id: list, role: "listbox", children: visibleOptions.map(option => (_jsx("button", { "data-ui-combobox-option": true, "data-value": option, role: "option", type: "button", onClick: () => {
405
+ selectOption(option);
406
+ }, onMouseDown: event => {
407
+ event.preventDefault();
408
+ }, children: option }, option))) })] }));
206
409
  };
207
410
  export const Command = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-command', glassClass('ui-command', glass), className), "data-ui-command": true, ...props }));
208
- export const ContextMenu = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("menu", { className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-context-menu": true, role: props.role ?? 'menu', ...props }));
411
+ export const ContextMenu = ({ className, glass = false, ...props }) => (_jsx("menu", { className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', glass), className), "data-surface": resolveSurface(glass), "data-ui-context-menu": true, role: props.role ?? 'menu', ...props }));
209
412
  export const ColorPicker = ({ className, type = 'color', ...props }) => (_jsx("input", { className: composeClassName('ui-color-picker', className), type: type, ...props }));
210
- export const DataTable = ({ children, className, columns = emptyDataTableColumns, glass = false, name, rows = emptyDataTableRows, selectable = false, ...props }) => (_jsx("div", { className: composeClassName('ui-data-table', glassClass('ui-data-table', glass), className), "data-ui-datatable": true, "data-ui-datatable-name": name, "data-ui-datatable-selectable": selectable ? 'true' : undefined, "data-ui-glass-track": glass ? true : undefined, ...props, children: columns.length > 0
211
- ? (_jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: columns.map(column => (_jsx("th", { "data-ui-datatable-sort-type": column.sort, "data-ui-datatable-sortable": column.sortable ? 'true' : undefined, scope: "col", children: column.header ?? column.label ?? column.key }, column.key))) }) }), _jsx("tbody", { children: rows.map((row, rowIndex) => (_jsx("tr", { "data-ui-datatable-row": true, "data-value": getDataTableRowValue(row, rowIndex), children: columns.map(column => (_jsx("td", { "data-sort-value": getDataTableSortValue(row[column.key]), children: formatDataTableCell(row[column.key]) }, column.key))) }, getDataTableRowValue(row, rowIndex)))) })] }))
212
- : children }));
413
+ export const DataTable = ({ children, className, columns = emptyDataTableColumns, glass = false, name, rows = emptyDataTableRows, selectable = false, ...props }) => (_jsx("div", { className: composeClassName('ui-data-table', glassClass('ui-data-table', glass), className), "data-ui-datatable": true, "data-ui-datatable-name": name, "data-ui-datatable-selectable": selectable ? 'true' : undefined, "data-ui-glass-track": glass ? true : undefined, ...props, children: columns.length > 0 ?
414
+ (_jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: columns.map(column => (_jsx("th", { "data-ui-datatable-sort-type": column.sort, "data-ui-datatable-sortable": column.sortable ? 'true' : undefined, scope: "col", children: column.header ?? column.label ?? column.key }, column.key))) }) }), _jsx("tbody", { children: rows.map((row, rowIndex) => (_jsx("tr", { "data-ui-datatable-row": true, "data-value": getDataTableRowValue(row, rowIndex), children: columns.map(column => (_jsx("td", { "data-sort-value": getDataTableSortValue(row[column.key]), children: formatDataTableCell(row[column.key]) }, column.key))) }, getDataTableRowValue(row, rowIndex)))) })] })) :
415
+ (children) }));
213
416
  const formatDatePickerDisplayValue = (value, placeholder) => {
214
417
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value))
215
418
  return placeholder;
@@ -223,15 +426,15 @@ const formatDatePickerDisplayValue = (value, placeholder) => {
223
426
  year: 'numeric'
224
427
  }).format(date);
225
428
  };
226
- const stringifyDatePickerConstraint = (value) => value === undefined ? undefined : String(value);
429
+ const stringifyDatePickerConstraint = (value) => (value === undefined ? undefined : String(value));
227
430
  /* eslint-disable complexity -- DatePicker coordinates controlled input, disclosure, Calendar, and native form contracts. */
228
- export const DatePicker = ({ className, defaultValue, disabled, glass = false, id, max, min, name, onChange, onInvalid, onValueChange, placeholder, required, value, ...props }) => {
431
+ export const DatePicker = ({ className, defaultValue, disabled, glass = false, id, inputRef, max, min, name, onChange, onInvalid, onValueChange, placeholder, required, value, ...props }) => {
229
432
  const generatedId = useId();
230
433
  const datePickerId = id ?? generatedId;
231
434
  const triggerId = `${datePickerId}-trigger`;
232
435
  const popoverId = `${datePickerId}-popover`;
233
436
  const rootRef = useRef(null);
234
- const inputRef = useRef(null);
437
+ const nativeInputRef = useRef(null);
235
438
  const triggerRef = useRef(null);
236
439
  const [internalValue, setInternalValue] = useState(defaultValue ?? '');
237
440
  const [open, setOpen] = useState(false);
@@ -245,7 +448,8 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
245
448
  if (!open)
246
449
  return;
247
450
  const handlePointerDown = (event) => {
248
- if (event.target instanceof Node && !rootRef.current?.contains(event.target))
451
+ if (event.target instanceof Node &&
452
+ !rootRef.current?.contains(event.target))
249
453
  setOpen(false);
250
454
  };
251
455
  const handleKeyDown = (event) => {
@@ -265,10 +469,10 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
265
469
  if (value === undefined)
266
470
  setInternalValue(nextValue);
267
471
  onValueChange?.(nextValue);
268
- if (inputRef.current) {
269
- inputRef.current.value = nextValue;
270
- inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
271
- inputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
472
+ if (nativeInputRef.current) {
473
+ nativeInputRef.current.value = nextValue;
474
+ nativeInputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
475
+ nativeInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
272
476
  }
273
477
  setOpen(false);
274
478
  globalThis.setTimeout(() => triggerRef.current?.focus());
@@ -282,7 +486,12 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
282
486
  triggerRef.current?.setAttribute('aria-invalid', 'true');
283
487
  triggerRef.current?.focus();
284
488
  onInvalid?.(event);
285
- }, ref: inputRef, required: required, tabIndex: -1, type: "date", value: selectedValue, ...props }), _jsxs("div", { className: "ui-date-picker__control", "data-ui-date-picker-control": true, children: [_jsxs("button", { "aria-controls": popoverId, "aria-expanded": open, "aria-haspopup": "dialog", "aria-label": accessibleLabel, "aria-required": required ?? undefined, className: "ui-input ui-date-picker ui-date-picker__trigger", "data-ui-date-picker-trigger": true, disabled: disabled, id: triggerId, onClick: () => { setOpen(current => !current); }, onKeyDown: event => {
489
+ }, ref: node => {
490
+ nativeInputRef.current = node;
491
+ setRefValue(inputRef, node);
492
+ }, required: required, tabIndex: -1, type: "date", value: selectedValue, ...props }), _jsxs("div", { className: "ui-date-picker__control", "data-ui-date-picker-control": true, children: [_jsxs("button", { "aria-controls": popoverId, "aria-expanded": open, "aria-haspopup": "dialog", "aria-label": accessibleLabel, "aria-required": required ?? undefined, className: "ui-input ui-date-picker ui-date-picker__trigger", "data-ui-date-picker-trigger": true, disabled: disabled, id: triggerId, onClick: () => {
493
+ setOpen(current => !current);
494
+ }, onKeyDown: event => {
286
495
  if (event.key !== 'ArrowDown')
287
496
  return;
288
497
  event.preventDefault();
@@ -301,7 +510,9 @@ const getDateRangeState = (start, end) => {
301
510
  return end ? 'complete' : 'selecting-end';
302
511
  };
303
512
  const syncDateRangePickerElement = (root) => {
304
- const inputs = [...root.querySelectorAll('[data-ui-date-picker-native]')];
513
+ const inputs = [
514
+ ...root.querySelectorAll('[data-ui-date-picker-native]')
515
+ ];
305
516
  const start = inputs[0];
306
517
  const end = inputs.at(-1);
307
518
  if (!(start && end) || start === end)
@@ -330,15 +541,41 @@ export const DateRangePicker = ({ className, onInput, onRangeChange, ...props })
330
541
  onRangeChange?.(syncDateRangePickerElement(event.currentTarget));
331
542
  }, ref: rootRef, ...props }));
332
543
  };
333
- export const Dialog = ({ className, defaultOpen, glass = false, layout = 'centered', onClick, onClose, onOpenChange, open, surface = 'default', ...props }) => {
544
+ export const Dialog = ({ className, defaultOpen, glass = false, layout = 'centered', onClick, onClose, onOpenChange, open, ...props }) => {
334
545
  const dialog = useDialog({ defaultOpen, onOpenChange, open });
335
- return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog', layout === 'fullscreen' && 'ui-dialog--fullscreen', glassSurfaceClass('ui-dialog', surface, glass), className), "data-layout": layout, "data-surface": resolveSurface(surface, glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
546
+ return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog', layout === 'fullscreen' && 'ui-dialog--fullscreen', glassSurfaceClass('ui-dialog', glass), className), "data-layout": layout, "data-surface": resolveSurface(glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
336
547
  };
337
548
  export const Direction = ({ className, dir = 'ltr', ...props }) => (_jsx("div", { className: composeClassName('ui-direction', className), dir: dir, ...props }));
338
- export const Drawer = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("dialog", { className: composeClassName('ui-drawer', glassSurfaceClass('ui-drawer', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-drawer": true, ...props }));
339
- export const DropdownMenu = ({ children, className, defaultOpen, glass = false, onOpenChange, open, surface = 'default', ...props }) => {
549
+ export const Container = ({ as = 'div', className, size = 'lg', ...props }) => createElement(as, {
550
+ className: composeClassName('ui-container', `ui-container--${size}`, className),
551
+ 'data-size': size,
552
+ 'data-ui-container': true,
553
+ ...props
554
+ });
555
+ export const Grid = ({ as = 'div', className, columns = 'auto', gap = 'md', minItemWidth, style, ...props }) => {
556
+ const gridStyle = {
557
+ ...style,
558
+ '--ui-grid-min': minItemWidth
559
+ };
560
+ return createElement(as, {
561
+ className: composeClassName('ui-grid', `ui-grid--columns-${columns}`, `ui-grid--gap-${gap}`, className),
562
+ 'data-columns': columns,
563
+ 'data-ui-grid': true,
564
+ style: gridStyle,
565
+ ...props
566
+ });
567
+ };
568
+ export const Stack = ({ align = 'stretch', as = 'div', className, direction = 'vertical', gap = 'md', justify = 'start', wrap = false, ...props }) => createElement(as, {
569
+ className: composeClassName('ui-stack', `ui-stack--${direction}`, `ui-stack--gap-${gap}`, `ui-stack--align-${align}`, `ui-stack--justify-${justify}`, wrap && 'ui-stack--wrap', className),
570
+ 'data-direction': direction,
571
+ 'data-ui-stack': true,
572
+ ...props
573
+ });
574
+ export const VisuallyHidden = ({ className, focusable = false, ...props }) => (_jsx("span", { className: composeClassName('ui-visually-hidden', focusable && 'ui-visually-hidden--focusable', className), "data-ui-visually-hidden": true, ...props }));
575
+ export const Drawer = ({ className, glass = false, ...props }) => (_jsx("dialog", { className: composeClassName('ui-drawer', glassSurfaceClass('ui-drawer', glass), className), "data-surface": resolveSurface(glass), "data-ui-drawer": true, ...props }));
576
+ export const DropdownMenu = ({ children, className, defaultOpen, glass = false, onOpenChange, open, ...props }) => {
340
577
  const menu = useDropdownMenu({ defaultOpen, onOpenChange, open });
341
- return (_jsx(DropdownMenuContext.Provider, { value: menu, children: _jsx("menu", { ...menu.rootProps, ...props, className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-dropdown-menu": true, children: children }) }));
578
+ return (_jsx(DropdownMenuContext.Provider, { value: menu, children: _jsx("menu", { ...menu.rootProps, ...props, className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', glass), className), "data-surface": resolveSurface(glass), "data-ui-dropdown-menu": true, children: children }) }));
342
579
  };
343
580
  export const DropdownMenuTrigger = ({ onClick, onKeyDown, ...props }) => {
344
581
  const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuTrigger');
@@ -349,23 +586,93 @@ export const DropdownMenuContent = ({ className, onKeyDown, ...props }) => {
349
586
  return (_jsx("div", { ...menu.panelProps, ...props, className: composeClassName('ui-menu__content', className), onKeyDown: composeHandlers(onKeyDown, menu.panelProps.onKeyDown), role: props.role ?? 'menu' }));
350
587
  };
351
588
  export const Empty = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-empty', glassClass('ui-empty', glass), className), ...props }));
352
- export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, ...props }) => {
589
+ export const ErrorSummary = ({ className, errors, heading = 'There is a problem', id = 'ui-error-summary', ...props }) => {
590
+ const normalizedErrors = normalizeLumenFormErrors(errors);
591
+ const allErrors = [
592
+ ...normalizedErrors.form.map(message => ({ message })),
593
+ ...normalizedErrors.fields
594
+ ];
595
+ const headingId = `${id}-heading`;
596
+ return (_jsxs("section", { "aria-labelledby": headingId, className: composeClassName('ui-error-summary', className), "data-ui-error-summary": true, hidden: allErrors.length === 0, id: id, tabIndex: -1, ...props, children: [_jsx("h2", { id: headingId, children: heading }), _jsx("ul", { children: allErrors.map(error => (_jsx("li", { children: error.controlId ?
597
+ (_jsx("a", { href: `#${error.controlId}`, children: error.message })) :
598
+ (error.message) }, `${error.name ?? 'form'}:${error.controlId ?? ''}:${error.message}`))) })] }));
599
+ };
600
+ export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, invalid = false, ...props }) => {
353
601
  const fieldDescribedBy = [ariaDescribedby, describedBy].filter(Boolean).join(' ') || undefined;
354
- return (_jsx("div", { "aria-describedby": ariaDescribedby, className: composeClassName('ui-field', glassClass('ui-field', glass), className), "data-ui-field": true, "data-ui-field-control": controlId, "data-ui-field-describedby": fieldDescribedBy, ...props }));
602
+ return (_jsx("div", { "aria-describedby": ariaDescribedby, className: composeClassName('ui-field', glassClass('ui-field', glass), className), "data-invalid": invalid ? 'true' : undefined, "data-ui-field": true, "data-ui-field-control": controlId, "data-ui-field-describedby": fieldDescribedBy, ...props }));
355
603
  };
356
- export const HoverCard = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-hover-card', glassSurfaceClass('ui-hover-card', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-hover-card": true, ...props }));
604
+ export const FieldError = ({ children, className, hidden, message, ...props }) => {
605
+ const content = message ?? children;
606
+ return (_jsx("p", { className: composeClassName('ui-field-error', className), "data-ui-field-error": true, hidden: hidden ?? !content, ...props, children: content }));
607
+ };
608
+ export const Form = ({ 'aria-busy': ariaBusy, className, enhance = false, status = 'idle', ...props }) => (_jsx("form", { "aria-busy": ariaBusy ?? (status === 'submitting' ? true : undefined), className: composeClassName('ui-form', className), "data-status": status, "data-ui-form": enhance ? true : undefined, ...props }));
609
+ export const HoverCard = ({ className, glass = false, ...props }) => (_jsx("aside", { className: composeClassName('ui-hover-card', glassSurfaceClass('ui-hover-card', glass), className), "data-surface": resolveSurface(glass), "data-ui-hover-card": true, ...props }));
357
610
  export const Icon = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, children, className, decorative, label, name, role, size = 'default', ...props }) => {
358
611
  const icon = name ? getLumenIcon(name) : undefined;
359
- const accessibility = getIconAccessibility({ ariaHidden, ariaLabel, decorative, label, role });
612
+ const accessibility = getIconAccessibility({
613
+ ariaHidden,
614
+ ariaLabel,
615
+ decorative,
616
+ label,
617
+ role
618
+ });
360
619
  return (_jsx("span", { "aria-hidden": accessibility.ariaHidden, "aria-label": accessibility.ariaLabel, className: composeClassName('ui-icon', iconSizeClass(size), className), role: accessibility.role, ...props, children: icon ? renderNamedIcon(icon) : children }));
361
620
  };
362
621
  export const Input = ({ className, ref, type = 'text', visualSize = 'default', ...props }) => (_jsx("input", { className: composeClassName('ui-input', visualSize === 'sm' && 'ui-input--sm', visualSize === 'lg' && 'ui-input--lg', className), ref: ref, type: type, ...props }));
622
+ export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
623
+ const getPasswordFieldDescribedBy = (ariaDescribedBy, hintId, errorId) => [ariaDescribedBy, hintId, errorId].filter(Boolean).join(' ') || undefined;
624
+ const usePasswordVisibility = (inputRef) => {
625
+ const [visible, setVisible] = useState(false);
626
+ useEffect(() => {
627
+ const form = inputRef.current?.form;
628
+ if (!form)
629
+ return;
630
+ const handleReset = () => {
631
+ setVisible(false);
632
+ };
633
+ const hideAfterSuccess = () => {
634
+ if (form.dataset.status === 'success')
635
+ setVisible(false);
636
+ };
637
+ form.addEventListener('reset', handleReset);
638
+ const observer = new MutationObserver(hideAfterSuccess);
639
+ observer.observe(form, {
640
+ attributeFilter: ['data-status'],
641
+ attributes: true
642
+ });
643
+ return () => {
644
+ form.removeEventListener('reset', handleReset);
645
+ observer.disconnect();
646
+ };
647
+ }, [inputRef]);
648
+ const toggleVisibility = () => {
649
+ setVisible(current => !current);
650
+ inputRef.current?.focus({ preventScroll: true });
651
+ };
652
+ return { toggleVisibility, visible };
653
+ };
654
+ const PasswordFieldControl = ({ controlId, describedBy, error, forwardedRef, hideLabel, inputProps, inputRef, name, showLabel, toggleVisibility, visible }) => (_jsxs("div", { className: "ui-password-field__control", "data-ui-password-field": true, children: [_jsx(Input, { ...inputProps, "aria-describedby": describedBy, "aria-invalid": error ? true : undefined, id: controlId, name: name, ref: node => {
655
+ inputRef.current = node;
656
+ setRefValue(forwardedRef, node);
657
+ }, type: visible ? 'text' : 'password' }), _jsx(Button, { "aria-controls": controlId, "aria-label": visible ? hideLabel : showLabel, "aria-pressed": visible, "data-ui-password-toggle": true, onClick: toggleVisibility, size: "icon", type: "button", variant: "ghost", children: _jsx("span", { "aria-hidden": "true", children: visible ? 'Hide' : 'Show' }) })] }));
658
+ const PasswordFieldMessages = ({ error, errorId, hint, hintId }) => (_jsxs(_Fragment, { children: [hint && (_jsx("p", { className: "ui-field__hint", "data-ui-field-hint": true, id: hintId, children: hint })), _jsx(FieldError, { id: errorId, message: error })] }));
659
+ export const PasswordField = ({ 'aria-describedby': ariaDescribedby, className, error, hideLabel = 'Hide password', hint, id, label = 'Password', name, ref, showLabel = 'Show password', ...props }) => {
660
+ const generatedId = useId();
661
+ const controlId = id ?? `ui-password-${generatedId}`;
662
+ const hintId = hint ? `${controlId}-hint` : undefined;
663
+ const errorId = `${controlId}-error`;
664
+ const describedBy = getPasswordFieldDescribedBy(ariaDescribedby, hintId, error ? errorId : undefined);
665
+ const inputRef = useRef(null);
666
+ const { toggleVisibility, visible } = usePasswordVisibility(inputRef);
667
+ return (_jsxs(Field, { className: composeClassName('ui-password-field', className), controlId: controlId, invalid: Boolean(error), ...(describedBy ? { describedBy } : {}), children: [_jsx(Label, { htmlFor: controlId, children: label }), _jsx(PasswordFieldControl, { controlId: controlId, describedBy: describedBy, error: error, forwardedRef: ref, hideLabel: hideLabel, inputProps: props, inputRef: inputRef, name: name, showLabel: showLabel, toggleVisibility: toggleVisibility, visible: visible }), _jsx(PasswordFieldMessages, { error: error, errorId: errorId, hint: hint, hintId: hintId })] }));
668
+ };
363
669
  export const InputGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-input-group', className), ...props }));
364
- export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', value, ...props }) => {
670
+ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', ref, value, ...props }) => {
365
671
  const otp = useInputOTP({
366
672
  defaultValue: toInputValue(defaultValue),
367
673
  disabled,
368
674
  inputMode,
675
+ inputRef: ref,
369
676
  invalid: ariaInvalid === true || ariaInvalid === 'true',
370
677
  length,
371
678
  pattern,
@@ -381,17 +688,22 @@ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue,
381
688
  pattern
382
689
  }) }), _jsx("div", { ...otp.segmentsProps, children: otp.segmentIndexes.map(index => (_jsx("button", { ...otp.getSegmentProps(index), type: "button", children: _jsx("span", { "data-ui-input-otp-char": true, children: otp.getSegmentChar(index) }) }, index))) })] }));
383
690
  };
384
- export const NumberField = ({ className, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), type: type, ...props }));
385
- export const Item = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-item', glassClass('ui-item', glass), className), ...props }));
691
+ export const NumberField = ({ className, ref, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), ref: ref, type: type, ...props }));
692
+ export const Item = ({ as = 'div', className, glass = false, ...props }) => createElement(as, {
693
+ className: composeClassName('ui-item', glassClass('ui-item', glass), className),
694
+ 'data-slot': 'item',
695
+ ...props
696
+ });
386
697
  export const Kbd = ({ className, ...props }) => (_jsx("kbd", { className: composeClassName('ui-kbd', className), ...props }));
387
- export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
388
698
  export const Marker = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-marker', variantClass('ui-marker', variant), className), "data-variant": variant, ...props }));
389
- export const Menubar = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-menubar', glassSurfaceClass('ui-menubar', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-menubar": true, ...props }));
699
+ export const Menubar = ({ className, glass = false, ...props }) => (_jsx("nav", { className: composeClassName('ui-menubar', glassSurfaceClass('ui-menubar', glass), className), "data-surface": resolveSurface(glass), "data-ui-menubar": true, ...props }));
390
700
  export const Message = ({ className, from = 'assistant', ...props }) => (_jsx("article", { className: composeClassName('ui-message', `ui-message--${from}`, className), "data-from": from, ...props }));
391
701
  export const MessageScroller = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-message-scroller', glassClass('ui-message-scroller', glass), className), ...props }));
392
- export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, value, ...props }) => {
393
- const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ? '' : undefined;
394
- return (_jsxs("select", { className: composeClassName('ui-select', className), defaultValue: defaultValue ?? placeholderDefaultValue, value: value, ...props, children: [placeholder && _jsx("option", { disabled: true, value: "", children: placeholder }), children, options.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }));
702
+ export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, ref, value, visualSize = 'default', ...props }) => {
703
+ const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ?
704
+ '' :
705
+ undefined;
706
+ return (_jsxs("select", { className: composeClassName('ui-select', visualSize === 'sm' && 'ui-select--sm', visualSize === 'lg' && 'ui-select--lg', className), defaultValue: defaultValue ?? placeholderDefaultValue, ref: ref, value: value, ...props, children: [placeholder && (_jsx("option", { disabled: true, value: "", children: placeholder })), children, options.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }));
395
707
  };
396
708
  const phoneInputSizeModifiers = (size) => {
397
709
  if (size === 'sm') {
@@ -406,11 +718,11 @@ export const PhoneInput = ({ className, countries = emptyOptions, countryLabel =
406
718
  const { selectClass, inputClass } = phoneInputSizeModifiers(size);
407
719
  return (_jsxs("div", { className: composeClassName('ui-phone-input ui-input-group', className), ...props, children: [_jsx("select", { "aria-label": countryLabel, className: composeClassName('ui-select ui-phone-input__country', selectClass), defaultValue: defaultCountryValue, name: countryName, children: countries.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value))) }), _jsx("input", { autoComplete: "tel", className: composeClassName('ui-input ui-phone-input__number', inputClass), defaultValue: defaultValue, inputMode: "tel", name: name, placeholder: placeholder, type: "tel" })] }));
408
720
  };
409
- export const NavigationMenu = ({ className, glass = false, surface = 'default', variant = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-navigation-menu', variant === 'unstyled' && 'ui-navigation-menu--unstyled', glassSurfaceClass('ui-navigation-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-navigation-menu": true, "data-variant": variant, ...props }));
721
+ export const NavigationMenu = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-navigation-menu', variant === 'unstyled' && 'ui-navigation-menu--unstyled', glassSurfaceClass('ui-navigation-menu', glass), className), "data-slot": "navigation-menu", "data-surface": resolveSurface(glass), "data-ui-navigation-menu": true, "data-variant": variant, ...props }));
410
722
  export const Pagination = ({ 'aria-label': ariaLabel = 'Pagination', className, ...props }) => (_jsx("nav", { "aria-label": ariaLabel, className: composeClassName('ui-pagination', className), ...props }));
411
- export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, surface = 'default', ...props }) => {
723
+ export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, ...props }) => {
412
724
  const popover = usePopover({ defaultOpen, onOpenChange, open });
413
- return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-popover": true, children: children }) }));
725
+ return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover', glass), className), "data-surface": resolveSurface(glass), "data-ui-popover": true, children: children }) }));
414
726
  };
415
727
  export const PopoverTrigger = ({ onClick, onKeyDown, ...props }) => {
416
728
  const popover = requireContext(useContext(PopoverContext), 'PopoverTrigger');
@@ -424,7 +736,7 @@ export const Progress = ({ className, max = 100, value = 0, ...props }) => {
424
736
  const safeMax = max > 0 ? max : 100;
425
737
  const safeValue = Math.min(safeMax, Math.max(0, value));
426
738
  const percentage = (safeValue / safeMax) * 100;
427
- return (_jsx("div", { "aria-valuemax": safeMax, "aria-valuemin": 0, "aria-valuenow": safeValue, className: composeClassName('ui-progress', className), role: "progressbar", ...props, children: _jsx("span", { className: "ui-progress__bar", style: { width: `${percentage}%` } }) }));
739
+ return (_jsx("div", { "aria-valuemax": safeMax, "aria-valuemin": 0, "aria-valuenow": safeValue, className: composeClassName('ui-progress', className), role: "progressbar", ...props, children: _jsx("span", { className: "ui-progress__bar", "data-slot": "progress-indicator", style: { width: `${percentage}%` } }) }));
428
740
  };
429
741
  export const RadioGroup = ({ className, ...props }) => (_jsx("fieldset", { className: composeClassName('ui-radio-group', className), "data-ui-radio-group": true, ...props }));
430
742
  const renderResizablePane = (child, index, panelProps) => {
@@ -455,8 +767,44 @@ export const Resizable = ({ children, className, defaultSizes, direction = 'hori
455
767
  };
456
768
  export const RichTextEditor = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-rich-text-editor', glassClass('ui-rich-text-editor', glass), className), "data-ui-rich-text-editor": true, ...props }));
457
769
  export const ScrollArea = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-scroll-area', glassClass('ui-scroll-area', glass), className), ...props }));
770
+ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', className, position = 'top', ...props }) => {
771
+ const rootRef = useRef(null);
772
+ useEffect(() => {
773
+ const root = rootRef.current;
774
+ if (!root)
775
+ return;
776
+ let frame = 0;
777
+ const update = () => {
778
+ frame = 0;
779
+ const scrollingElement = document.scrollingElement ?? document.documentElement;
780
+ const maximum = scrollingElement.scrollHeight - scrollingElement.clientHeight;
781
+ const percentage = maximum > 0 ?
782
+ Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100)) :
783
+ 0;
784
+ const bar = root.querySelector('.ui-scroll-progress__bar');
785
+ root.setAttribute('aria-valuenow', `${Math.round(percentage)}`);
786
+ if (bar)
787
+ bar.style.transform = `scaleX(${percentage / 100})`;
788
+ };
789
+ const scheduleUpdate = () => {
790
+ if (frame)
791
+ return;
792
+ frame = window.requestAnimationFrame(update);
793
+ };
794
+ scheduleUpdate();
795
+ window.addEventListener('resize', scheduleUpdate, { passive: true });
796
+ window.addEventListener('scroll', scheduleUpdate, { passive: true });
797
+ return () => {
798
+ if (frame)
799
+ window.cancelAnimationFrame(frame);
800
+ window.removeEventListener('resize', scheduleUpdate);
801
+ window.removeEventListener('scroll', scheduleUpdate);
802
+ };
803
+ }, []);
804
+ return (_jsx("div", { "aria-label": ariaLabel, "aria-valuemax": 100, "aria-valuemin": 0, "aria-valuenow": 0, className: composeClassName('ui-scroll-progress', position === 'bottom' && 'ui-scroll-progress--bottom', className), "data-position": position, "data-ui-scroll-progress": true, ref: rootRef, role: "progressbar", ...props, children: _jsx("span", { className: "ui-scroll-progress__bar" }) }));
805
+ };
458
806
  export const Schedule = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-schedule', glassClass('ui-schedule', glass), className), "data-ui-schedule": true, ...props }));
459
- export const SearchField = ({ className, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), type: type, ...props }));
807
+ export const SearchField = ({ className, ref, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), ref: ref, type: type, ...props }));
460
808
  const getSelectSizeClass = (size) => {
461
809
  if (size === 'sm')
462
810
  return 'ui-select--sm';
@@ -464,7 +812,7 @@ const getSelectSizeClass = (size) => {
464
812
  return 'ui-select--lg';
465
813
  return false;
466
814
  };
467
- export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
815
+ export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, inputRef, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
468
816
  const select = useSelect({
469
817
  defaultValue,
470
818
  disabled,
@@ -477,23 +825,236 @@ export const Select = ({ children, className, defaultValue, disabled = false, gl
477
825
  value
478
826
  });
479
827
  const sizeClass = getSelectSizeClass(size);
480
- return (_jsxs("div", { ...select.rootProps, className: composeClassName('ui-select-field', glassClass('ui-select-field', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsxs("select", { ...select.nativeSelectProps, ...props, className: composeClassName('ui-select ui-select__native', sizeClass), onChange: composeHandlers(onChange, select.nativeSelectProps.onChange), children: [placeholder && _jsx("option", { "data-ui-select-placeholder": true, disabled: true, value: "", children: placeholder }), children, select.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }), _jsxs("div", { ...select.controlProps, className: "ui-select__control", children: [_jsx("button", { ...select.triggerProps, className: composeClassName('ui-select ui-select__trigger', sizeClass), type: select.triggerProps.type ?? 'button', children: _jsx("span", { "data-ui-select-value": true, children: select.triggerText }) }), _jsx("div", { ...select.listProps, className: "ui-select__list", children: select.options.map(option => {
828
+ return (_jsxs("div", { ...select.rootProps, className: composeClassName('ui-select-field', glassClass('ui-select-field', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsxs("select", { ...select.nativeSelectProps, ...props, className: composeClassName('ui-select ui-select__native', sizeClass), onChange: composeHandlers(onChange, select.nativeSelectProps.onChange), ref: inputRef, children: [placeholder && (_jsx("option", { "data-ui-select-placeholder": true, disabled: true, value: "", children: placeholder })), children, select.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }), _jsxs("div", { ...select.controlProps, className: "ui-select__control", children: [_jsx("button", { ...select.triggerProps, className: composeClassName('ui-select ui-select__trigger', sizeClass), type: select.triggerProps.type ?? 'button', children: _jsx("span", { "data-ui-select-value": true, children: select.triggerText }) }), _jsx("div", { ...select.listProps, className: "ui-select__list", children: select.options.map(option => {
481
829
  const optionProps = select.getOptionProps(option);
482
830
  return (_jsx("button", { ...optionProps, type: optionProps.type ?? 'button', children: option.label }, option.value));
483
831
  }) })] })] }));
484
832
  };
833
+ const groupListBoxOptions = (options) => {
834
+ const groups = [];
835
+ options.forEach((option, index) => {
836
+ const currentGroup = groups.at(-1);
837
+ if (!currentGroup || currentGroup.label !== option.section) {
838
+ groups.push({
839
+ ...(option.section ? { label: option.section } : {}),
840
+ options: [{ ...option, index }]
841
+ });
842
+ return;
843
+ }
844
+ currentGroup.options.push({ ...option, index });
845
+ });
846
+ return groups;
847
+ };
848
+ const getListBoxGroupKey = (group) => {
849
+ const values = group.options.map(option => option.value).join('|');
850
+ return group.label ? `${group.label}:${values}` : values;
851
+ };
852
+ const optionalTrue = (value) => value ? true : undefined;
853
+ const ListBoxNativeOptions = ({ groups }) => groups.map(group => {
854
+ const options = group.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)));
855
+ return group.label ?
856
+ (_jsx("optgroup", { label: group.label, children: options }, getListBoxGroupKey(group))) :
857
+ (_jsx(Fragment, { children: options }, getListBoxGroupKey(group)));
858
+ });
859
+ const ListBoxVisibleOptions = ({ activeIndex, controlId, emptyMessage, groups, loading, onSelect, selectedValues }) => {
860
+ if (loading)
861
+ return null;
862
+ if (!groups.length) {
863
+ return (_jsx("div", { "aria-disabled": "true", className: "ui-list-box__empty", role: "option", children: emptyMessage }));
864
+ }
865
+ return groups.map(group => {
866
+ const groupKey = getListBoxGroupKey(group);
867
+ const safeGroupKey = groupKey.replaceAll(/[^a-zA-Z0-9_-]/g, '-');
868
+ const groupLabelId = group.label ? `${controlId}-group-${safeGroupKey}` : undefined;
869
+ const options = group.options.map(option => (_jsx("div", { "aria-disabled": option.disabled ? true : undefined, "aria-selected": selectedValues.has(option.value), className: composeClassName('ui-list-box__option', option.index === activeIndex && 'ui-list-box__option--active'), "data-active": option.index === activeIndex ? 'true' : undefined, "data-ui-list-box-option": true, "data-value": option.value, onClick: () => {
870
+ onSelect(option.index);
871
+ }, role: "option", children: option.label }, option.value)));
872
+ if (!group.label)
873
+ return _jsx(Fragment, { children: options }, groupKey);
874
+ return (_jsxs("div", { "aria-labelledby": groupLabelId, role: "group", children: [_jsx("div", { className: "ui-list-box__section", id: groupLabelId, children: group.label }), options] }, groupKey));
875
+ });
876
+ };
877
+ const normalizeListBoxValues = (value) => {
878
+ if (value === undefined)
879
+ return [];
880
+ return Array.isArray(value) ? value : [value];
881
+ };
882
+ const findLastEnabledOptionIndex = (options) => {
883
+ let lastEnabledIndex = -1;
884
+ for (const [index, option] of options.entries()) {
885
+ if (!option.disabled)
886
+ lastEnabledIndex = index;
887
+ }
888
+ return lastEnabledIndex;
889
+ };
890
+ const getListBoxSelectedValues = (controlledValue, internalValues) => controlledValue === undefined ?
891
+ internalValues :
892
+ normalizeListBoxValues(controlledValue);
893
+ const getListBoxSelectValue = (multiple, selectedValues) => (multiple ? selectedValues : (selectedValues[0] ?? ''));
894
+ const ListBoxLabel = ({ controlId, label }) => label ?
895
+ (_jsx("label", { className: "ui-label", htmlFor: controlId, children: label })) :
896
+ null;
897
+ const normalizeListBoxProps = ({ disabled = false, emptyMessage = 'No options available', loading = false, loadingMessage = 'Loading options', multiple = false, options = emptyOptions, required = false, selectionFollowsFocus = false, ...props }) => ({
898
+ disabled,
899
+ emptyMessage,
900
+ loading,
901
+ loadingMessage,
902
+ multiple,
903
+ options,
904
+ required,
905
+ selectionFollowsFocus,
906
+ ...props
907
+ });
908
+ export const ListBox = (inputProps) => {
909
+ const { className, defaultValue, disabled, emptyMessage, id, label, loading, loadingMessage, multiple, name, onValueChange, options, ref, required, selectionFollowsFocus, value, ...props } = normalizeListBoxProps(inputProps);
910
+ const generatedId = useId();
911
+ const controlId = id ?? `ui-list-box-${generatedId}`;
912
+ const normalizedOptions = options.map(option => typeof option === 'string' ? { label: option, value: option } : option);
913
+ const optionGroups = groupListBoxOptions(normalizedOptions);
914
+ const [internalValues, setInternalValues] = useState(() => normalizeListBoxValues(defaultValue));
915
+ const selectedValues = getListBoxSelectedValues(value, internalValues);
916
+ const selectedSet = new Set(selectedValues);
917
+ const firstEnabledIndex = Math.max(0, normalizedOptions.findIndex(option => !option.disabled));
918
+ const [activeIndex, setActiveIndex] = useState(firstEnabledIndex);
919
+ const selectRef = useRef(null);
920
+ const typeaheadRef = useRef('');
921
+ const typeaheadTimerRef = useRef(undefined);
922
+ const commitValues = (nextValues) => {
923
+ if (value === undefined) {
924
+ setInternalValues(nextValues);
925
+ }
926
+ onValueChange?.(nextValues);
927
+ const select = selectRef.current;
928
+ if (!select)
929
+ return;
930
+ for (const option of select.options) {
931
+ option.selected = nextValues.includes(option.value);
932
+ }
933
+ select.dispatchEvent(new Event('input', { bubbles: true }));
934
+ select.dispatchEvent(new Event('change', { bubbles: true }));
935
+ };
936
+ const selectOption = (index) => {
937
+ const option = normalizedOptions[index];
938
+ if (!option || option.disabled || disabled || loading)
939
+ return;
940
+ const optionValue = option.value;
941
+ let nextValues;
942
+ if (!multiple) {
943
+ nextValues = [optionValue];
944
+ }
945
+ else if (selectedSet.has(optionValue)) {
946
+ nextValues = selectedValues.filter(selectedValue => selectedValue !== optionValue);
947
+ }
948
+ else {
949
+ nextValues = [...selectedValues, optionValue];
950
+ }
951
+ commitValues(nextValues);
952
+ };
953
+ const moveActive = (direction) => {
954
+ if (!normalizedOptions.length || loading)
955
+ return;
956
+ let nextIndex = activeIndex;
957
+ for (const _option of normalizedOptions) {
958
+ nextIndex =
959
+ (nextIndex + direction + normalizedOptions.length) %
960
+ normalizedOptions.length;
961
+ if (!normalizedOptions[nextIndex]?.disabled) {
962
+ setActiveIndex(nextIndex);
963
+ if (selectionFollowsFocus)
964
+ selectOption(nextIndex);
965
+ return;
966
+ }
967
+ }
968
+ };
969
+ useEffect(() => () => {
970
+ if (typeaheadTimerRef.current)
971
+ clearTimeout(typeaheadTimerRef.current);
972
+ }, []);
973
+ useEffect(() => {
974
+ const select = selectRef.current;
975
+ const form = select?.form;
976
+ if (!form || value !== undefined)
977
+ return;
978
+ const handleReset = () => {
979
+ setInternalValues(normalizeListBoxValues(defaultValue));
980
+ setActiveIndex(firstEnabledIndex);
981
+ };
982
+ form.addEventListener('reset', handleReset);
983
+ return () => {
984
+ form.removeEventListener('reset', handleReset);
985
+ };
986
+ }, [defaultValue, firstEnabledIndex, value]);
987
+ const handleNavigationKey = (key) => {
988
+ if (key === 'ArrowDown')
989
+ moveActive(1);
990
+ else if (key === 'ArrowUp')
991
+ moveActive(-1);
992
+ else if (key === 'Home') {
993
+ setActiveIndex(firstEnabledIndex);
994
+ if (selectionFollowsFocus)
995
+ selectOption(firstEnabledIndex);
996
+ }
997
+ else if (key === 'End') {
998
+ const lastEnabledIndex = findLastEnabledOptionIndex(normalizedOptions);
999
+ if (lastEnabledIndex >= 0) {
1000
+ setActiveIndex(lastEnabledIndex);
1001
+ if (selectionFollowsFocus)
1002
+ selectOption(lastEnabledIndex);
1003
+ }
1004
+ }
1005
+ else if (key === 'Enter' || key === ' ')
1006
+ selectOption(activeIndex);
1007
+ else
1008
+ return false;
1009
+ return true;
1010
+ };
1011
+ const handleTypeaheadKey = (event) => {
1012
+ if (event.key.length !== 1 ||
1013
+ event.ctrlKey ||
1014
+ event.metaKey ||
1015
+ event.altKey)
1016
+ return false;
1017
+ typeaheadRef.current += event.key.toLowerCase();
1018
+ if (typeaheadTimerRef.current)
1019
+ clearTimeout(typeaheadTimerRef.current);
1020
+ typeaheadTimerRef.current = setTimeout(() => {
1021
+ typeaheadRef.current = '';
1022
+ }, 700);
1023
+ const nextIndex = normalizedOptions.findIndex(option => !option.disabled &&
1024
+ option.label.toLowerCase().startsWith(typeaheadRef.current));
1025
+ if (nextIndex >= 0) {
1026
+ setActiveIndex(nextIndex);
1027
+ if (selectionFollowsFocus)
1028
+ selectOption(nextIndex);
1029
+ }
1030
+ return true;
1031
+ };
1032
+ const handleListKeyDown = (event) => {
1033
+ if (handleNavigationKey(event.key) || handleTypeaheadKey(event))
1034
+ event.preventDefault();
1035
+ };
1036
+ return (_jsxs("div", { className: composeClassName('ui-list-box', className), "data-multiple": multiple ? 'true' : undefined, "data-ui-list-box": true, children: [_jsx(ListBoxLabel, { controlId: controlId, label: label }), _jsx("select", { ...props, className: "ui-list-box__native", "data-ui-enhanced": true, "data-ui-list-box-native": true, disabled: disabled || loading, id: controlId, multiple: multiple, name: name, ref: node => {
1037
+ selectRef.current = node;
1038
+ setRefValue(ref, node);
1039
+ }, required: required, value: getListBoxSelectValue(multiple, selectedValues), children: _jsx(ListBoxNativeOptions, { groups: optionGroups }) }), _jsx("div", { "aria-busy": optionalTrue(loading), "aria-disabled": optionalTrue(disabled || loading), "aria-multiselectable": optionalTrue(multiple), className: "ui-list-box__list", "data-ui-list-box-list": true, onKeyDown: handleListKeyDown, role: "listbox", tabIndex: disabled || loading ? undefined : 0, children: _jsx(ListBoxVisibleOptions, { activeIndex: activeIndex, controlId: controlId, emptyMessage: emptyMessage, groups: optionGroups, loading: loading, onSelect: index => {
1040
+ setActiveIndex(index);
1041
+ selectOption(index);
1042
+ }, selectedValues: selectedSet }) }), loading ?
1043
+ (_jsx("p", { className: "ui-list-box__status", role: "status", children: loadingMessage })) :
1044
+ null] }));
1045
+ };
485
1046
  export const Separator = ({ className, orientation = 'horizontal', ...props }) => (_jsx("hr", { "aria-orientation": orientation, className: composeClassName('ui-separator', `ui-separator--${orientation}`, className), "data-orientation": orientation, ...props }));
486
- export const Sheet = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("dialog", { className: composeClassName('ui-sheet', glassSurfaceClass('ui-sheet', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-sheet": true, ...props }));
487
- export const Sidebar = ({ className, glass = false, surface = 'default', variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-sidebar', variant === 'unstyled' && 'ui-sidebar--unstyled', glassSurfaceClass('ui-sidebar', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-variant": variant, ...props }));
1047
+ export const Sheet = ({ className, glass = false, ...props }) => (_jsx("dialog", { className: composeClassName('ui-sheet', glassSurfaceClass('ui-sheet', glass), className), "data-surface": resolveSurface(glass), "data-ui-sheet": true, ...props }));
1048
+ export const Sidebar = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-sidebar', variant === 'unstyled' && 'ui-sidebar--unstyled', glassSurfaceClass('ui-sidebar', glass), className), "data-slot": "sidebar", "data-surface": resolveSurface(glass), "data-variant": variant, ...props }));
488
1049
  export const Skeleton = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-skeleton', className), ...props }));
489
- export const Slider = ({ className, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), type: type, ...props }));
1050
+ export const Slider = ({ className, ref, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), ref: ref, type: type, ...props }));
490
1051
  export const Sonner = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-sonner', className), "data-ui-sonner": true, ...props }));
491
1052
  export const Spinner = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, className, role, ...props }) => {
492
1053
  const isAccessible = Boolean(ariaLabel || ariaLabelledby);
493
1054
  return (_jsx("span", { "aria-hidden": ariaHidden ?? (isAccessible ? undefined : true), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: composeClassName('ui-spinner', className), role: role ?? (isAccessible ? 'status' : undefined), ...props }));
494
1055
  };
495
- export const Switch = ({ className, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), role: role, type: "checkbox", ...props }));
496
- export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), ...props }));
1056
+ export const Switch = ({ className, ref, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), ref: ref, role: role, type: "checkbox", ...props }));
1057
+ export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), "data-slot": "table", ...props }));
497
1058
  export const Tabs = ({ children, className, defaultValue, glass = false, onValueChange, orientation, value, ...props }) => {
498
1059
  const tabs = useTabs({ defaultValue, onValueChange, orientation, value });
499
1060
  return (_jsx(TabsContext.Provider, { value: tabs, children: _jsx("div", { ...tabs.rootProps, ...props, className: composeClassName('ui-tabs', glassClass('ui-tabs', glass), className), children: children }) }));
@@ -523,9 +1084,9 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
523
1084
  // Storage can be unavailable in privacy modes; tab selection should still work.
524
1085
  }
525
1086
  }
526
- return storedValue && items.some(item => item.value === storedValue)
527
- ? storedValue
528
- : (selectedValue ?? '');
1087
+ return storedValue && items.some(item => item.value === storedValue) ?
1088
+ storedValue :
1089
+ (selectedValue ?? '');
529
1090
  });
530
1091
  useEffect(() => {
531
1092
  const synchronize = (event) => {
@@ -556,13 +1117,15 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
556
1117
  detail: { storageKey, value: nextValue }
557
1118
  }));
558
1119
  };
559
- return (_jsxs(Tabs, { className: composeClassName('ui-code-tabs', className), "data-storage-key": storageKey, onValueChange: selectValue, value: value, ...props, children: [_jsx(TabsList, { "aria-label": ariaLabel, className: "ui-code-tabs__list", children: items.map(item => (_jsx(TabsTrigger, { className: "ui-code-tabs__tab", value: item.value, children: item.label }, item.value))) }), items.map(item => (_jsx(TabsPanel, { className: "ui-code-tabs__panel", value: item.value, children: _jsx(Code, { code: item.code, copy: copy, ...(item.language === undefined ? {} : { language: item.language }), theme: theme, variant: "block", wrap: wrap }) }, item.value)))] }));
1120
+ return (_jsxs(Tabs, { className: composeClassName('ui-code-tabs', className), "data-slot": "code-tabs", "data-storage-key": storageKey, onValueChange: selectValue, value: value, ...props, children: [_jsx(TabsList, { "aria-label": ariaLabel, className: "ui-code-tabs__list", children: items.map(item => (_jsx(TabsTrigger, { className: "ui-code-tabs__tab", value: item.value, children: item.label }, item.value))) }), items.map(item => (_jsx(TabsPanel, { className: "ui-code-tabs__panel", value: item.value, children: _jsx(Code, { code: item.code, copy: copy, ...(item.language === undefined ?
1121
+ {} :
1122
+ { language: item.language }), theme: theme, variant: "block", wrap: wrap }) }, item.value)))] }));
560
1123
  };
561
1124
  export const TagGroup = ({ className, role = 'list', ...props }) => (_jsx("div", { className: composeClassName('ui-tag-group', className), role: role, ...props }));
562
- export const Textarea = ({ className, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), rows: rows, ...props }));
1125
+ export const Textarea = ({ className, ref, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), ref: ref, rows: rows, ...props }));
563
1126
  export const ThemeBuilder = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-theme-builder', glassClass('ui-theme-builder', glass), className), "data-ui-theme-builder": true, ...props }));
564
- export const TimeField = ({ className, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), type: type, ...props }));
565
- export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role, surface = 'default', variant = 'default', ...props }) => (_jsx("aside", { "aria-live": ariaLive ?? (variant === 'destructive' ? 'assertive' : 'polite'), className: composeClassName('ui-toast', variantClass('ui-toast', variant), glassSurfaceClass('ui-toast', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-toast": true, "data-variant": variant, role: role ?? (variant === 'destructive' ? 'alert' : 'status'), ...props }));
1127
+ export const TimeField = ({ className, ref, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), ref: ref, type: type, ...props }));
1128
+ export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role, variant = 'default', ...props }) => (_jsx("aside", { "aria-live": ariaLive ?? (variant === 'destructive' ? 'assertive' : 'polite'), className: composeClassName('ui-toast', variantClass('ui-toast', variant), glassSurfaceClass('ui-toast', glass), className), "data-surface": resolveSurface(glass), "data-ui-toast": true, "data-variant": variant, role: role ?? (variant === 'destructive' ? 'alert' : 'status'), ...props }));
566
1129
  export const Toggle = ({ className, controlled = false, pressed = false, type = 'button', ...props }) => (_jsx("button", { "aria-pressed": pressed, className: composeClassName('ui-toggle', className), "data-ui-controlled": controlled || undefined, "data-ui-toggle": true, type: type, ...props }));
567
1130
  export const ToggleGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-toggle-group', className), "data-ui-toggle-group": true, ...props }));
568
1131
  export const Tooltip = ({ children, className, defaultOpen, delay, onKeyDown, onMouseEnter, onMouseLeave, onOpenChange, open, ...props }) => {
@@ -591,18 +1154,54 @@ export const Image = ({ as, className, invertOnDark = false, loading, ...props }
591
1154
  ...(resolvedLoading ? { loading: resolvedLoading } : {})
592
1155
  });
593
1156
  };
594
- export const Link = ({ className, newTab = false, ref, rel, target, variant = 'default', ...props }) => (_jsx("a", { className: composeClassName('ui-link', variant === 'inherit' && 'ui-link--inherit', className), "data-variant": variant, ref: ref, rel: newTab ? [...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))].join(' ') : rel, target: newTab ? '_blank' : target, ...props }));
595
- export const Pill = ({ className, count, children, href, ...props }) => {
1157
+ export const Link = ({ className, newTab = false, ref, rel, target, variant = 'default', ...props }) => (_jsx("a", { className: composeClassName('ui-link', variant === 'inherit' && 'ui-link--inherit', className), "data-slot": "link", "data-variant": variant, ref: ref, rel: newTab ?
1158
+ [
1159
+ ...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))
1160
+ ].join(' ') :
1161
+ rel, target: newTab ? '_blank' : target, ...props }));
1162
+ export const Pill = ({ className, count, children, href, variant = 'neutral', ...props }) => {
596
1163
  const content = (_jsxs(_Fragment, { children: [children, count !== undefined && _jsx("span", { className: "ui-pill__count", children: count })] }));
597
- return href
598
- ? _jsx("a", { className: composeClassName('ui-pill', className), href: href, ...props, children: content })
599
- : _jsx("span", { className: composeClassName('ui-pill', className), ...props, children: content });
1164
+ return href ?
1165
+ (_jsx("a", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, href: href, ...props, children: content })) :
1166
+ (_jsx("span", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, ...props, children: content }));
600
1167
  };
601
1168
  export const Prose = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-prose', className), ...props }));
602
1169
  export const SkipLink = ({ className, ...props }) => (_jsx("a", { className: composeClassName('ui-skip-link', className), ...props }));
603
- export const ThemeToggle = ({ className, type = 'button', ...props }) => (_jsxs("button", { className: composeClassName('ui-theme-toggle', className), type: type, ...props, children: [_jsx("span", { className: "ui-sr-only", children: "Toggle color theme" }), _jsx(Icon, { className: "ui-theme-toggle__sun", name: "sun" }), _jsx(Icon, { className: "ui-theme-toggle__moon", name: "moon" })] }));
1170
+ export const ThemeToggle = ({ className, type = 'button', ...props }) => (_jsxs("button", { className: composeClassName('ui-theme-toggle', className), "data-slot": "theme-toggle", type: type, ...props, children: [_jsx("span", { className: "ui-sr-only", children: "Toggle color theme" }), _jsx(Icon, { className: "ui-theme-toggle__sun", name: "sun" }), _jsx(Icon, { className: "ui-theme-toggle__moon", name: "moon" })] }));
604
1171
  export const LanguageToggle = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-language-toggle', className), type: type, ...props }));
605
- export const Particles = ({ className, density = 'medium', ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props }));
1172
+ const particleDensityConfig = { high: 40, low: 15, medium: 25 };
1173
+ const particleTones = [
1174
+ 'var(--brand)',
1175
+ 'var(--accent)',
1176
+ 'var(--glow, var(--brand))'
1177
+ ];
1178
+ const createParticles = (density) => Array.from({ length: particleDensityConfig[density] }, (_, index) => {
1179
+ const size = Math.random() * 12 + 8;
1180
+ const left = Math.random() * 100;
1181
+ const top = Math.random() * 100;
1182
+ return {
1183
+ id: `${index}-${left}-${top}`,
1184
+ style: {
1185
+ '--ui-particle-delay': `${Math.random() * 8}s`,
1186
+ '--ui-particle-duration': `${20 + Math.random() * 15}s`,
1187
+ '--ui-particle-left': `${left}%`,
1188
+ '--ui-particle-size': `${size}px`,
1189
+ '--ui-particle-tone': particleTones[Math.floor(Math.random() * particleTones.length)],
1190
+ '--ui-particle-top': `${top}%`
1191
+ }
1192
+ };
1193
+ });
1194
+ export const Particles = ({ children, className, density = 'medium', ...props }) => {
1195
+ const [particles, setParticles] = useState([]);
1196
+ useEffect(() => {
1197
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches)
1198
+ return;
1199
+ // Particle geometry is client-only to keep SSR output stable while retaining per-load variation.
1200
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
1201
+ setParticles(createParticles(density));
1202
+ }, [density]);
1203
+ return (_jsxs("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props, children: [children, particles.map(particle => (_jsx("span", { className: "ui-particles__particle", style: particle.style }, particle.id)))] }));
1204
+ };
606
1205
  const parseMotionDuration = (value) => {
607
1206
  const parsedDuration = Number.parseFloat(value);
608
1207
  if (!Number.isFinite(parsedDuration))
@@ -616,8 +1215,8 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
616
1215
  if (!root)
617
1216
  return;
618
1217
  root.dataset.uiScrollRevealBound = 'true';
619
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
620
- || typeof IntersectionObserver === 'undefined') {
1218
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1219
+ typeof IntersectionObserver === 'undefined') {
621
1220
  root.classList.add('is-revealed');
622
1221
  return;
623
1222
  }
@@ -629,9 +1228,14 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
629
1228
  observer.disconnect();
630
1229
  }, { threshold: Math.min(1, Math.max(0, threshold)) });
631
1230
  observer.observe(root);
632
- return () => { observer.disconnect(); };
1231
+ return () => {
1232
+ observer.disconnect();
1233
+ };
633
1234
  }, [once, threshold]);
634
- return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: { ...style, '--ui-reveal-delay': `${Math.max(0, delay)}ms` }, ...props }));
1235
+ return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: {
1236
+ ...style,
1237
+ '--ui-reveal-delay': `${Math.max(0, delay)}ms`
1238
+ }, ...props }));
635
1239
  };
636
1240
  export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, duration = 'slow', once = true, stagger = 80, style, threshold = 0.15, ...props }) => {
637
1241
  const rootRef = useRef(null);
@@ -645,8 +1249,8 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
645
1249
  child.style.setProperty('--ui-reveal-index', String(index));
646
1250
  }
647
1251
  }
648
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
649
- || typeof IntersectionObserver === 'undefined') {
1252
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1253
+ typeof IntersectionObserver === 'undefined') {
650
1254
  root.classList.add('is-revealed');
651
1255
  return;
652
1256
  }
@@ -658,7 +1262,9 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
658
1262
  observer.disconnect();
659
1263
  }, { threshold: Math.min(1, Math.max(0, threshold)) });
660
1264
  observer.observe(root);
661
- return () => { observer.disconnect(); };
1265
+ return () => {
1266
+ observer.disconnect();
1267
+ };
662
1268
  }, [once, threshold]);
663
1269
  return (_jsx("div", { className: composeClassName('ui-reveal-group', `ui-reveal-group-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-group": true, "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), ref: rootRef, style: {
664
1270
  ...style,
@@ -679,8 +1285,8 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
679
1285
  if (!output)
680
1286
  return;
681
1287
  const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
682
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
683
- || typeof IntersectionObserver === 'undefined') {
1288
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1289
+ typeof IntersectionObserver === 'undefined') {
684
1290
  output.textContent = format(value);
685
1291
  return;
686
1292
  }
@@ -692,13 +1298,15 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
692
1298
  if (!entries.some(entry => entry.isIntersecting))
693
1299
  return;
694
1300
  observer.disconnect();
695
- const durationValue = getComputedStyle(root).getPropertyValue('--ui-motion-duration').trim();
1301
+ const durationValue = getComputedStyle(root)
1302
+ .getPropertyValue('--ui-motion-duration')
1303
+ .trim();
696
1304
  const durationMs = parseMotionDuration(durationValue);
697
1305
  const startedAt = performance.now();
698
1306
  const update = (now) => {
699
1307
  const progress = Math.min(1, (now - startedAt) / Math.max(1, durationMs));
700
1308
  const eased = 1 - Math.pow(1 - progress, 3);
701
- output.textContent = format(from + ((value - from) * eased));
1309
+ output.textContent = format(from + (value - from) * eased);
702
1310
  if (progress < 1) {
703
1311
  requestAnimationFrame(update);
704
1312
  }
@@ -709,34 +1317,54 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
709
1317
  requestAnimationFrame(update);
710
1318
  }, { threshold: 0.15 });
711
1319
  observer.observe(root);
712
- return () => { observer.disconnect(); };
1320
+ return () => {
1321
+ observer.disconnect();
1322
+ };
713
1323
  }, [formatter, from, prefix, suffix, value]);
714
1324
  return (_jsxs("span", { className: composeClassName('ui-animated-number', `ui-motion-duration-${duration}`, className), "data-ui-animated-number": true, ...props, children: [_jsx("span", { "aria-hidden": "true", "data-ui-animated-number-output": true, ref: outputRef, children: formattedValue }), _jsx("span", { className: "ui-sr-only", children: formattedValue })] }));
715
1325
  };
716
- export const Stat = ({ className, label, value, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-stat', className), ...props, children: [label && _jsx("div", { className: "ui-stat-label", children: label }), value && _jsx("div", { className: "ui-stat-value", children: value }), children] }));
1326
+ export const Stat = ({ as, className, label, value, variant = 'default', children, ...props }) => (_jsxs(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant !== 'default' && `ui-stat--${variant}`, className), "data-slot": "stat", "data-variant": variant, ...props, uiClassName: "ui-stat", children: [label && (_jsx("div", { className: "ui-stat-label", "data-slot": "stat-label", children: label })), value && (_jsx("div", { className: "ui-stat-value", "data-slot": "stat-value", children: value })), children] }));
1327
+ export const StatLabel = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-label", ...props, uiClassName: "ui-stat-label" }));
1328
+ export const StatValue = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-value", ...props, uiClassName: "ui-stat-value" }));
1329
+ export const StatDescription = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-description", ...props, uiClassName: "ui-stat-description" }));
1330
+ export const StatIcon = ({ className, ...props }) => (_jsx("span", { className: composeClassName('ui-stat-icon', className), "data-slot": "stat-icon", ...props }));
1331
+ export const StatTrend = ({ className, tone = 'neutral', ...props }) => (_jsx("span", { className: composeClassName('ui-stat-trend', `ui-stat-trend--${tone}`, className), "data-slot": "stat-trend", "data-tone": tone, ...props }));
717
1332
  export const Meter = ({ className, ...props }) => (_jsx("meter", { className: composeClassName('ui-meter', className), ...props }));
718
1333
  export const Note = ({ className, label, borderPosition = 'left', children, ...props }) => (_jsxs("div", { className: composeClassName('ui-note', `ui-note--border-${borderPosition}`, className), ...props, children: [label && _jsx("p", { className: "ui-note__label", children: label }), _jsx("div", { className: "ui-note__content", children: children })] }));
719
1334
  export const Rating = ({ className, value = 0, max = 5, readonly = false, ...props }) => {
720
1335
  const stars = Array.from({ length: max }, (_, i) => i + 1);
721
- return (_jsx("div", { className: composeClassName('ui-rating', readonly && 'ui-rating--readonly', className), "data-ui-rating": true, "data-value": value, "data-max": max, "data-readonly": readonly ? 'true' : 'false', ...props, children: stars.map((star) => (_jsx("button", { type: "button", className: "ui-rating__star", "data-active": star <= value ? 'true' : 'false', disabled: readonly, "aria-label": `Rate ${star} out of ${max}`, children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", className: "ui-icon", children: _jsx("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) }) }, star))) }));
1336
+ return (_jsx("div", { className: composeClassName('ui-rating', readonly && 'ui-rating--readonly', className), "data-ui-rating": true, "data-value": value, "data-max": max, "data-readonly": readonly ? 'true' : 'false', ...props, children: stars.map(star => (_jsx("button", { type: "button", className: "ui-rating__star", "data-active": star <= value ? 'true' : 'false', disabled: readonly, "aria-label": `Rate ${star} out of ${max}`, children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", className: "ui-icon", children: _jsx("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) }) }, star))) }));
722
1337
  };
723
1338
  export const Timeline = ({ className, ...props }) => (_jsx("ol", { className: composeClassName('ui-timeline', className), ...props }));
724
1339
  export const TimelineItem = ({ className, children, dot, ...props }) => (_jsxs("li", { className: composeClassName('ui-timeline-item', className), ...props, children: [_jsx("div", { className: "ui-timeline-item__tail" }), _jsx("div", { className: "ui-timeline-item__dot", children: dot }), _jsx("div", { className: "ui-timeline-item__content", children: children })] }));
725
1340
  export const AnimatedLogo = ({ animation = 'reveal', className, ...props }) => (_jsx("span", { className: composeClassName('ui-animated-logo', className), "data-animation": animation, "data-ui-animated-logo": true, ...props }));
726
1341
  export const AnimatedPortrait = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-animated-portrait relative isolate w-full animate-reveal-lcp motion-reduce:animate-none', className), ...props }));
727
- export const ButtonLink = ({ className, ref, shape = 'default', variant = 'primary', ...props }) => {
728
- let variantClass = `ui-button ui-button--${variant} min-h-11 rounded-full border`;
729
- if (variant === 'unstyled') {
730
- variantClass = 'ui-button-link--unstyled';
731
- }
732
- else if (variant === 'inline') {
733
- variantClass = 'ui-button ui-button--inline';
1342
+ const getButtonLinkVariantClass = (variant) => {
1343
+ if (variant === 'unstyled')
1344
+ return 'ui-button-link--unstyled';
1345
+ if (variant === 'inline')
1346
+ return 'ui-button ui-button--inline';
1347
+ return `ui-button ui-button--${variant} min-h-11 rounded-full border`;
1348
+ };
1349
+ export const ButtonLink = ({ asChild = false, children, className, ref, shape = 'default', variant = 'primary', ...props }) => {
1350
+ const variantClass = getButtonLinkVariantClass(variant);
1351
+ const shapeClass = shape === 'icon' && variant !== 'inline' ?
1352
+ 'ui-button-link--icon' :
1353
+ undefined;
1354
+ const buttonLinkClassName = composeClassName('ui-button-link', variant !== 'unstyled' &&
1355
+ 'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className);
1356
+ if (asChild) {
1357
+ const child = Children.only(children);
1358
+ if (!isValidElement(child)) {
1359
+ throw new Error('ButtonLink with asChild requires exactly one valid React element child.');
1360
+ }
1361
+ const ChildComponent = child.type;
1362
+ return (_jsx(ChildComponent, { ...child.props, ...props, className: composeClassName(buttonLinkClassName, child.props.className), "data-slot": "button-link", ref: ref, children: child.props.children }));
734
1363
  }
735
- const shapeClass = shape === 'icon' && variant !== 'inline' ? 'ui-button-link--icon' : undefined;
736
- return (_jsx("a", { className: composeClassName('ui-button-link', variant !== 'unstyled' && 'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className), ref: ref, ...props }));
1364
+ return (_jsx("a", { className: buttonLinkClassName, "data-slot": "button-link", ref: ref, ...props, children: children }));
737
1365
  };
738
- export const CoverImage = ({ className, hover = false, showBottomGradient = false, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-cover-image relative overflow-hidden bg-surface-muted', hover && 'ui-cover-image--hover', className), ...props, children: [children, showBottomGradient && _jsx("div", { "aria-hidden": "true", className: "ui-cover-image__bottom-gradient" })] }));
739
- export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "h-px w-full max-w-5xl bg-gradient-to-r from-transparent via-brand/40 to-transparent" }) }));
1366
+ export const CoverImage = ({ className, hover = false, showBottomGradient = false, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-cover-image relative overflow-hidden bg-surface-muted', hover && 'ui-cover-image--hover', className), ...props, children: [children, showBottomGradient && (_jsx("div", { "aria-hidden": "true", className: "ui-cover-image__bottom-gradient" }))] }));
1367
+ export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "\n via-brand/40 h-px w-full max-w-5xl bg-linear-to-r from-transparent\n to-transparent\n " }) }));
740
1368
  const emptySteps = [];
741
1369
  const normalizeStep = (step) => typeof step === 'string' ? { description: undefined, title: step } : step;
742
1370
  export const Stepper = ({ children, className, currentStep = 0, orientation = 'horizontal', steps = emptySteps, ...props }) => (_jsxs("ol", { className: composeClassName('ui-stepper', orientation === 'vertical' && 'ui-stepper--vertical', className), "data-orientation": orientation, ...props, children: [steps.map(normalizeStep).map((step, index) => {
@@ -747,24 +1375,104 @@ export const Stepper = ({ children, className, currentStep = 0, orientation = 'h
747
1375
  else if (index === currentStep) {
748
1376
  state = 'current';
749
1377
  }
750
- return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, className: "ui-stepper__step", "data-state": state, children: [_jsx("span", { className: "ui-stepper__marker", children: index + 1 }), _jsxs("span", { className: "ui-stepper__content", children: [_jsx("span", { className: "ui-stepper__title", children: step.title }), step.description && _jsx("span", { className: "ui-stepper__description", children: step.description })] })] }, step.title));
1378
+ return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, className: "ui-stepper__step", "data-state": state, children: [_jsx("span", { className: "ui-stepper__marker", children: index + 1 }), _jsxs("span", { className: "ui-stepper__content", children: [_jsx("span", { className: "ui-stepper__title", children: step.title }), step.description && (_jsx("span", { className: "ui-stepper__description", children: step.description }))] })] }, step.title));
751
1379
  }), children] }));
752
- export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ...props }) => {
1380
+ export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ref, ...props }) => {
753
1381
  const generatedId = useId();
754
1382
  const inputId = id ?? `ui-file-upload-${generatedId.replaceAll(':', '')}`;
755
1383
  const [selectedFiles, setSelectedFiles] = useState([]);
756
- const selectedFileText = selectedFiles.length > 1
757
- ? `${selectedFiles.length} files selected`
758
- : selectedFiles[0] ?? '';
1384
+ const selectedFileText = selectedFiles.length > 1 ?
1385
+ `${selectedFiles.length} files selected` :
1386
+ (selectedFiles[0] ?? '');
759
1387
  return (_jsxs("label", { className: composeClassName('ui-file-upload', className), "data-state": selectedFiles.length > 0 ? 'selected' : 'idle', "data-ui-file-upload": true, htmlFor: inputId, children: [_jsx("input", { className: composeClassName('ui-file-upload__input', inputClassName), "data-ui-file-upload-input": true, id: inputId, onChange: event => {
760
1388
  setSelectedFiles([...(event.currentTarget.files ?? [])].map(file => file.name));
761
1389
  onChange?.(event);
762
- }, type: "file", ...props }), _jsxs("svg", { "aria-hidden": "true", className: "ui-file-upload__icon", fill: "none", focusable: "false", viewBox: "0 0 24 24", children: [_jsx("path", { d: "M12 16V4m0 0L8 8m4-4 4 4" }), _jsx("path", { d: "M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" })] }), _jsx("span", { className: "ui-file-upload__prompt", children: children ?? label }), hint && _jsx("span", { className: "ui-file-upload__hint", children: hint }), _jsx("span", { "aria-live": "polite", className: "ui-file-upload__files", "data-ui-file-upload-files": true, children: selectedFileText })] }));
1390
+ }, ref: ref, type: "file", ...props }), _jsxs("svg", { "aria-hidden": "true", className: "ui-file-upload__icon", fill: "none", focusable: "false", viewBox: "0 0 24 24", children: [_jsx("path", { d: "M12 16V4m0 0L8 8m4-4 4 4" }), _jsx("path", { d: "M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" })] }), _jsx("span", { className: "ui-file-upload__prompt", children: children ?? label }), hint && _jsx("span", { className: "ui-file-upload__hint", children: hint }), _jsx("span", { "aria-live": "polite", className: "ui-file-upload__files", "data-ui-file-upload-files": true, children: selectedFileText })] }));
763
1391
  };
764
1392
  const emptyTourSteps = [];
765
1393
  export const Tour = ({ children, className, closeLabel = 'Done', hidden = true, nextLabel = 'Next', steps = emptyTourSteps, ...props }) => (_jsxs("div", { className: composeClassName('ui-tour', className), "data-ui-tour": true, hidden: hidden, ...props, children: [_jsx("div", { "aria-hidden": "true", className: "ui-tour__backdrop", "data-ui-tour-backdrop": true }), _jsxs("div", { className: "ui-tour__popover", "data-ui-tour-popover": true, role: "dialog", children: [steps.map((step, index) => (_jsxs("div", { className: "ui-tour__step", "data-target": step.target, "data-ui-tour-step": true, hidden: index !== 0, children: [step.title && _jsx("p", { className: "ui-tour__title", children: step.title }), _jsx("p", { className: "ui-tour__content", children: step.content }), _jsxs("div", { className: "ui-tour__actions", children: [_jsx("button", { className: "ui-button ui-button--ghost ui-button--sm", "data-ui-tour-close": true, type: "button", children: closeLabel }), index < steps.length - 1 && (_jsx("button", { className: "ui-button ui-button--default ui-button--sm", "data-ui-tour-next": true, type: "button", children: nextLabel }))] })] }, step.target))), children] })] }));
766
1394
  const emptyAnchorLinks = [];
767
- export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', children, className, items = emptyAnchorLinks, ...props }) => (_jsxs("nav", { "aria-label": ariaLabel, className: composeClassName('ui-anchor', className), "data-ui-anchor": true, ...props, children: [items.length > 0 && (_jsx("ol", { children: items.map((item, index) => (_jsx("li", { children: _jsx("a", { "data-active": index === 0 ? 'true' : 'false', href: item.href, children: item.label }) }, item.href))) })), children] }));
1395
+ const createAnchorRef = () => {
1396
+ let cleanup;
1397
+ return (root) => {
1398
+ cleanup?.();
1399
+ cleanup = undefined;
1400
+ if (!root)
1401
+ return;
1402
+ const links = [...root.querySelectorAll('a[href^="#"]')];
1403
+ const targets = links.flatMap(link => {
1404
+ const id = decodeURIComponent(link.getAttribute('href')?.slice(1) ?? '');
1405
+ const target = id ? document.getElementById(id) : null;
1406
+ return target ? [{ link, target }] : [];
1407
+ });
1408
+ if (!targets.length)
1409
+ return;
1410
+ const abortController = new AbortController();
1411
+ let frame = 0;
1412
+ const setActive = (active) => {
1413
+ for (const link of links) {
1414
+ const isActive = link === active;
1415
+ link.dataset.active = String(isActive);
1416
+ if (isActive) {
1417
+ link.setAttribute('aria-current', 'location');
1418
+ }
1419
+ else {
1420
+ link.removeAttribute('aria-current');
1421
+ }
1422
+ }
1423
+ };
1424
+ const update = () => {
1425
+ frame = 0;
1426
+ const scrollingElement = document.scrollingElement ?? document.documentElement;
1427
+ const atEnd = scrollingElement.scrollTop + scrollingElement.clientHeight >=
1428
+ scrollingElement.scrollHeight - 1;
1429
+ const offset = Number(root.dataset.uiAnchorOffset) || 0;
1430
+ let active = targets[0]?.link;
1431
+ if (atEnd) {
1432
+ active = targets.at(-1)?.link;
1433
+ }
1434
+ else {
1435
+ for (const item of targets) {
1436
+ if (item.target.getBoundingClientRect().top > offset)
1437
+ break;
1438
+ active = item.link;
1439
+ }
1440
+ }
1441
+ if (active)
1442
+ setActive(active);
1443
+ };
1444
+ const requestUpdate = () => {
1445
+ if (frame)
1446
+ return;
1447
+ frame = requestAnimationFrame(update);
1448
+ };
1449
+ for (const { link } of targets) {
1450
+ link.addEventListener('click', () => {
1451
+ setActive(link);
1452
+ }, {
1453
+ signal: abortController.signal
1454
+ });
1455
+ }
1456
+ window.addEventListener('resize', requestUpdate, {
1457
+ passive: true,
1458
+ signal: abortController.signal
1459
+ });
1460
+ window.addEventListener('scroll', requestUpdate, {
1461
+ passive: true,
1462
+ signal: abortController.signal
1463
+ });
1464
+ update();
1465
+ cleanup = () => {
1466
+ abortController.abort();
1467
+ if (frame)
1468
+ cancelAnimationFrame(frame);
1469
+ };
1470
+ };
1471
+ };
1472
+ export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', activationOffset = 96, children, className, items = emptyAnchorLinks, ...props }) => {
1473
+ const anchorRef = createAnchorRef();
1474
+ return (_jsxs("nav", { "aria-label": ariaLabel, className: composeClassName('ui-anchor', className), "data-ui-anchor": true, "data-ui-anchor-offset": Math.max(0, activationOffset), ref: anchorRef, ...props, children: [items.length > 0 && (_jsx("ol", { children: items.map((item, index) => (_jsx("li", { children: _jsxs("a", { "aria-current": index === 0 ? 'location' : undefined, "data-active": index === 0 ? 'true' : 'false', "data-depth": item.depth, href: item.href, children: [item.index !== undefined && (_jsx("span", { className: "ui-anchor__index", children: item.index })), _jsxs("span", { className: "ui-anchor__content", children: [_jsx("span", { className: "ui-anchor__label", children: item.label }), item.description && (_jsx("span", { className: "ui-anchor__description", children: item.description }))] })] }) }, item.href))) })), children] }));
1475
+ };
768
1476
  export const Segmented = ({ children, className, defaultValue, name = 'segmented', onValueChange, options = emptyOptions, size = 'default', value = defaultValue, ...props }) => (_jsxs("div", { className: composeClassName('ui-segmented', size === 'sm' && 'ui-segmented--sm', size === 'lg' && 'ui-segmented--lg', className), role: "group", ...props, children: [options.map(normalizeOption).map(option => (_jsxs("label", { className: "ui-segmented__option", children: [_jsx("input", { defaultChecked: value === option.value, className: "ui-segmented__input", disabled: option.disabled, name: name, type: "radio", value: option.value, onChange: () => onValueChange?.(option.value) }), _jsx("span", { className: "ui-segmented__label", children: option.label })] }, option.value))), children] }));
769
1477
  export const Toolbar = ({ 'aria-label': ariaLabel = 'Toolbar', className, orientation = 'horizontal', ...props }) => (_jsx("div", { "aria-label": ariaLabel, "aria-orientation": orientation, className: composeClassName('ui-toolbar', orientation === 'vertical' && 'ui-toolbar--vertical', className), "data-orientation": orientation, "data-ui-toolbar": true, role: "toolbar", ...props }));
770
1478
  const emptyDescriptionsItems = [];
@@ -814,13 +1522,93 @@ const flattenTreeSelect = (nodes, depth = 0, acc = []) => {
814
1522
  };
815
1523
  const emptyTreeSelectNodes = [];
816
1524
  export const TreeSelect = ({ children, className, name, placeholder = 'Select…', treeData = emptyTreeSelectNodes, ...props }) => (_jsxs("div", { className: composeClassName('ui-tree-select', className), "data-surface": "default", "data-ui-popover": true, "data-ui-tree-select": true, ...props, children: [_jsx("button", { "aria-expanded": "false", "aria-haspopup": "tree", className: "ui-select ui-select__trigger ui-tree-select__trigger", "data-ui-trigger": true, type: "button", children: _jsx("span", { "data-ui-tree-select-placeholder": placeholder, "data-ui-tree-select-value": true, children: placeholder }) }), _jsxs("div", { className: "ui-tree-select__panel", "data-ui-panel": true, hidden: true, children: [_jsx("div", { className: "ui-tree-select__tree", role: "tree", children: flattenTreeSelect(treeData).map(row => (_jsx("button", { className: "ui-tree-select__option", "data-value": row.value, role: "treeitem", style: { ['--ui-tree-depth']: row.depth }, type: "button", children: row.label }, row.value))) }), children] }), name && _jsx("input", { "data-ui-tree-select-input": true, name: name, type: "hidden" })] }));
817
- export const Mentions = ({ className, name, options = emptyOptions, placeholder, trigger = '@', ...props }) => (_jsxs("div", { className: composeClassName('ui-mentions', className), "data-ui-mentions": true, "data-ui-mentions-trigger": trigger, ...props, children: [_jsx("textarea", { className: "ui-textarea ui-mentions__input", "data-ui-mentions-input": true, name: name, placeholder: placeholder, rows: 3 }), _jsx("ul", { className: "ui-mentions__list", "data-ui-mentions-list": true, hidden: true, role: "listbox", children: options.map(normalizeOption).map(option => (_jsx("li", { children: _jsx("button", { className: "ui-mentions__option", "data-ui-mentions-option": true, "data-value": option.value, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
1525
+ export const Mentions = ({ className, defaultValue = '', label = 'Mentions', name, onValueChange, options = emptyOptions, placeholder, trigger = '@', value, ...props }) => {
1526
+ const generatedId = useId();
1527
+ const inputRef = useRef(null);
1528
+ const [activeIndex, setActiveIndex] = useState(-1);
1529
+ const [internalValue, setInternalValue] = useState(defaultValue);
1530
+ const [query, setQuery] = useState(null);
1531
+ const currentValue = value ?? internalValue;
1532
+ const listId = `ui-mentions-${generatedId}-list`;
1533
+ const escapedTrigger = trigger.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
1534
+ const normalizedOptions = options.map(normalizeOption);
1535
+ const visibleOptions = query === null ?
1536
+ [] :
1537
+ normalizedOptions.filter(option => option.value.toLowerCase().startsWith(query));
1538
+ const expanded = visibleOptions.length > 0;
1539
+ const resolvedActiveIndex = expanded ?
1540
+ Math.min(Math.max(activeIndex, 0), visibleOptions.length - 1) :
1541
+ -1;
1542
+ const activeOption = visibleOptions[resolvedActiveIndex];
1543
+ const setNextValue = (nextValue) => {
1544
+ if (value === undefined)
1545
+ setInternalValue(nextValue);
1546
+ onValueChange?.(nextValue);
1547
+ };
1548
+ const closeList = () => {
1549
+ setQuery(null);
1550
+ setActiveIndex(-1);
1551
+ };
1552
+ const insertMention = (mention) => {
1553
+ const caret = inputRef.current?.selectionStart ?? currentValue.length;
1554
+ const before = currentValue
1555
+ .slice(0, caret)
1556
+ .replace(new RegExp(`${escapedTrigger}\\w*$`), `${trigger}${mention} `);
1557
+ const nextValue = before + currentValue.slice(caret);
1558
+ setNextValue(nextValue);
1559
+ closeList();
1560
+ requestAnimationFrame(() => {
1561
+ inputRef.current?.focus();
1562
+ inputRef.current?.setSelectionRange(before.length, before.length);
1563
+ });
1564
+ };
1565
+ const handleKeyDown = (event) => {
1566
+ if (event.key === 'Escape' && expanded) {
1567
+ event.preventDefault();
1568
+ closeList();
1569
+ return;
1570
+ }
1571
+ if (expanded && (event.key === 'ArrowDown' || event.key === 'ArrowUp' ||
1572
+ event.key === 'End' || event.key === 'Home')) {
1573
+ event.preventDefault();
1574
+ setActiveIndex(current => {
1575
+ if (event.key === 'Home')
1576
+ return 0;
1577
+ if (event.key === 'End')
1578
+ return visibleOptions.length - 1;
1579
+ if (event.key === 'ArrowDown')
1580
+ return (current + 1) % visibleOptions.length;
1581
+ return (current - 1 + visibleOptions.length) % visibleOptions.length;
1582
+ });
1583
+ return;
1584
+ }
1585
+ if (event.key === 'Enter' && activeOption) {
1586
+ event.preventDefault();
1587
+ insertMention(activeOption.value);
1588
+ }
1589
+ };
1590
+ return (_jsxs("div", { className: composeClassName('ui-mentions', className), "data-ui-mentions": true, "data-ui-mentions-trigger": trigger, ...props, children: [_jsx("textarea", { "aria-activedescendant": activeOption ? `${listId}-option-${resolvedActiveIndex}` : undefined, "aria-autocomplete": "list", "aria-controls": listId, "aria-expanded": expanded, "aria-label": label, className: "ui-textarea ui-mentions__input", "data-ui-mentions-input": true, name: name, onBlur: closeList, onChange: event => {
1591
+ const nextValue = event.currentTarget.value;
1592
+ const match = new RegExp(`${escapedTrigger}(\\w*)$`)
1593
+ .exec(nextValue.slice(0, event.currentTarget.selectionStart));
1594
+ const nextQuery = match ? (match[1] ?? '').toLowerCase() : null;
1595
+ setNextValue(nextValue);
1596
+ setQuery(nextQuery);
1597
+ setActiveIndex(nextQuery === null ? -1 : 0);
1598
+ }, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputRef, role: "combobox", rows: 3, value: currentValue }), _jsx("ul", { className: "ui-mentions__list", "data-ui-mentions-list": true, hidden: !expanded, id: listId, role: "listbox", children: visibleOptions.map((option, index) => (_jsx("li", { children: _jsx("button", { "aria-selected": index === resolvedActiveIndex, className: "ui-mentions__option", "data-ui-mentions-option": true, "data-value": option.value, id: `${listId}-option-${index}`, onClick: () => {
1599
+ insertMention(option.value);
1600
+ }, onMouseDown: event => {
1601
+ event.preventDefault();
1602
+ }, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
1603
+ };
818
1604
  export const QRCode = ({ children, className, size = 160, src, style, value, ...props }) => {
819
1605
  const qrSvg = !src && value ? renderSVG(value) : null;
820
- const qrSrc = qrSvg ? `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` : src;
821
- return (_jsxs("figure", { className: composeClassName('ui-qr-code', className), "data-ui-qr-code": true, style: { ['--ui-qr-code-size']: `${size}px`, ...style }, ...props, children: [_jsx("div", { className: "ui-qr-code__frame", children: qrSrc
822
- ? _jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })
823
- : children }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
1606
+ const qrSrc = qrSvg ?
1607
+ `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` :
1608
+ src;
1609
+ return (_jsxs("figure", { className: composeClassName('ui-qr-code', className), "data-ui-qr-code": true, style: { ['--ui-qr-code-size']: `${size}px`, ...style }, ...props, children: [_jsx("div", { className: "ui-qr-code__frame", children: qrSrc ?
1610
+ (_jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })) :
1611
+ (children) }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
824
1612
  };
825
1613
  export const Watermark = ({ children, className, content, gap = 120, rotate = -22, style, text = 'Confidential', ...props }) => {
826
1614
  const watermarkText = content ?? text;
@@ -830,14 +1618,15 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
830
1618
  '<': '&lt;',
831
1619
  '>': '&gt;',
832
1620
  '"': '&quot;',
833
- "'": '&apos;'
1621
+ '\'': '&apos;'
834
1622
  };
835
1623
  const escapedWatermarkText = watermarkText.replaceAll(/[&<>"']/g, character => watermarkXmlEntities[character] ?? character);
836
1624
  const watermarkSvg = [
837
- `<svg xmlns="http://www.w3.org/2000/svg" width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
1625
+ '<svg xmlns="http://www.w3.org/2000/svg"',
1626
+ ` width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
838
1627
  `<text x="${tileSize / 2}" y="${tileSize / 2}" dominant-baseline="middle" text-anchor="middle"`,
839
1628
  ` transform="rotate(${rotate} ${tileSize / 2} ${tileSize / 2})"`,
840
- ' fill="black" font-family="system-ui, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
1629
+ ' fill="black" font-family="Montserrat, Avenir Next, Segoe UI, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
841
1630
  `${escapedWatermarkText}</text></svg>`
842
1631
  ].join('');
843
1632
  const watermarkImage = `url("data:image/svg+xml,${encodeURIComponent(watermarkSvg)}")`;
@@ -850,10 +1639,20 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
850
1639
  export const Affix = ({ className, offset = 0, offsetBottom, offsetTop, position: positionProp, style, ...props }) => {
851
1640
  const position = positionProp ?? (offsetBottom === undefined ? 'top' : 'bottom');
852
1641
  const resolvedOffset = offsetBottom ?? offsetTop ?? offset;
853
- return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: { ['--ui-affix-offset']: `${resolvedOffset}px`, ...style }, ...props }));
1642
+ return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: {
1643
+ ['--ui-affix-offset']: `${resolvedOffset}px`,
1644
+ ...style
1645
+ }, ...props }));
854
1646
  };
855
1647
  const emptySpeedDialActions = [];
856
- const speedDialNavigationKeys = ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End', 'Home'];
1648
+ const speedDialNavigationKeys = [
1649
+ 'ArrowDown',
1650
+ 'ArrowLeft',
1651
+ 'ArrowRight',
1652
+ 'ArrowUp',
1653
+ 'End',
1654
+ 'Home'
1655
+ ];
857
1656
  const getSpeedDialNavigationIndex = (key, currentIndex, itemCount) => {
858
1657
  if (key === 'Home')
859
1658
  return 0;
@@ -902,7 +1701,9 @@ export const SpeedDial = ({ actions = emptySpeedDialActions, children, className
902
1701
  if (event.target.closest('[role="menuitem"], button, a'))
903
1702
  close();
904
1703
  }, onKeyDown: event => {
905
- const items = [...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])];
1704
+ const items = [
1705
+ ...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])
1706
+ ];
906
1707
  if (event.key === 'Escape') {
907
1708
  event.preventDefault();
908
1709
  close(true);