@santi020k/lumen-react 0.4.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,10 +1,17 @@
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 { alignLumenChartSeries, composeClassName, createLumenBarGeometry, createLumenLineGeometry, createLumenPieGeometry, getLumenChartCategories, getLumenChartDomain, getLumenChartTicks, getLumenIcon, hasLumenChartData, hasLumenPieData, lumenCodeTokenClassNames, resolveLumenChartTone, scaleLumenChartValue, 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 = [];
@@ -16,7 +23,7 @@ const emptyPieSeries = {
16
23
  label: 'Values'
17
24
  };
18
25
  const emptyChartValues = [];
19
- const variantClass = (base, variant, defaultVariant = 'default') => variant === defaultVariant ? false : `${base}--${variant}`;
26
+ const variantClass = (base, variant, defaultVariant = 'default') => (variant === defaultVariant ? false : `${base}--${variant}`);
20
27
  const iconSizeClass = (size) => size === 'default' ? undefined : `ui-icon--${size}`;
21
28
  const reactSvgAttributeNames = {
22
29
  'clip-rule': 'clipRule',
@@ -29,7 +36,10 @@ const reactSvgAttributeNames = {
29
36
  'stroke-width': 'strokeWidth',
30
37
  tabindex: 'tabIndex'
31
38
  };
32
- 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
+ ]));
33
43
  const renderIconNode = ([tagName, attributes, children], index) => createElement(tagName, {
34
44
  ...toReactSvgAttributes(attributes),
35
45
  key: attributes.key ?? index
@@ -60,18 +70,24 @@ const renderLucideIcon = (name, className) => {
60
70
  const icon = getLumenIcon(name);
61
71
  return icon ? renderIconSvg(icon, className) : null;
62
72
  };
63
- const resolveSurface = (surface, glass) => glass || surface === 'glass' ? 'glass' : surface;
64
- const glassIntensityClass = (glass) => glass === 'subtle' ? 'ui-glass-subtle' : glass === 'strong' && 'ui-glass-strong';
65
- const glassSurfaceClass = (base, surface, glass) => resolveSurface(surface, glass) === 'glass' && composeClassName(`${base}--glass`, glassIntensityClass(glass));
66
- 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));
67
81
  const normalizeOption = (option) => typeof option === 'string' ? { label: option, value: option } : option;
68
82
  const isDataTableCellObject = (cell) => typeof cell === 'object' && cell !== null;
69
83
  const formatDataTableCell = (cell) => {
70
- const value = isDataTableCellObject(cell) ? cell.label ?? cell.value : cell;
84
+ const value = isDataTableCellObject(cell) ? (cell.label ?? cell.value) : cell;
71
85
  return value === undefined || value === null ? '' : String(value);
72
86
  };
73
87
  const getDataTableSortValue = (cell) => {
74
- if (!isDataTableCellObject(cell) || cell.sortValue === undefined || cell.sortValue === null)
88
+ if (!isDataTableCellObject(cell) ||
89
+ cell.sortValue === undefined ||
90
+ cell.sortValue === null)
75
91
  return undefined;
76
92
  return String(cell.sortValue);
77
93
  };
@@ -88,7 +104,7 @@ const composeHandlers = (userHandler, lumenHandler) => (event) => {
88
104
  const Primitive = ({ as, className, uiClassName, ...props }) => {
89
105
  const Tag = as ?? 'div';
90
106
  const safeClassName = typeof className === 'string' ? className : undefined;
91
- return _jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props });
107
+ return (_jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props }));
92
108
  };
93
109
  const DropdownMenuContext = createContext(null);
94
110
  const PopoverContext = createContext(null);
@@ -102,18 +118,18 @@ const requireContext = (context, componentName) => {
102
118
  };
103
119
  export const Accordion = ({ className, variant = 'default', ...props }) => (_jsx("div", { className: composeClassName('ui-accordion', variant === 'flush' && 'ui-accordion--flush', className), "data-variant": variant, ...props }));
104
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 }));
105
- 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 }) => {
106
122
  const dialog = useDialog({ alert: true, defaultOpen, onOpenChange, open });
107
- 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) }));
108
124
  };
109
125
  export const Agenda = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-agenda', glassClass('ui-agenda', glass), className), ...props }));
110
126
  export const AspectRatio = ({ className, ratio = '16 / 9', style, ...props }) => (_jsx("div", { className: composeClassName('ui-aspect-ratio', className), style: { aspectRatio: ratio, ...style }, ...props }));
111
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 }));
112
128
  export const Attachment = ({ className, glass = false, href, ...props }) => {
113
129
  const nextClassName = composeClassName('ui-attachment', glassClass('ui-attachment', glass), className);
114
- return href
115
- ? _jsx("a", { className: nextClassName, href: href, ...props })
116
- : _jsx("article", { className: nextClassName, ...props });
130
+ return href ?
131
+ (_jsx("a", { className: nextClassName, href: href, ...props })) :
132
+ (_jsx("article", { className: nextClassName, ...props }));
117
133
  };
118
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] }));
119
135
  export const Badge = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-badge', `ui-badge--${variant}`, className), "data-variant": variant, ...props }));
@@ -127,16 +143,10 @@ export const Button = ({ asChild = false, children, className, disabled, loading
127
143
  if (!isValidElement(child)) {
128
144
  throw new TypeError('Button with asChild requires exactly one React element child.');
129
145
  }
130
- return cloneElement(child, {
131
- ...props,
132
- 'aria-busy': loading ? true : undefined,
133
- 'aria-disabled': disabled ? true : undefined,
134
- className: composeClassName(buttonClassName, child.props.className),
135
- ref: ref,
136
- children: (_jsxs(_Fragment, { children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), child.props.children] }))
137
- });
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] }));
138
148
  }
139
- 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] }));
140
150
  };
141
151
  export const ButtonGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-button-group', className), ...props }));
142
152
  export const Calendar = ({ className, defaultValue, disabled = false, glass = false, locale, max, min, month, name, onValueChange, value, ...props }) => {
@@ -153,16 +163,30 @@ export const Calendar = ({ className, defaultValue, disabled = false, glass = fa
153
163
  });
154
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'))) })] })] }));
155
165
  };
156
- 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" }));
157
173
  export const Carousel = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-carousel', glassClass('ui-carousel', glass), className), "data-ui-carousel": true, ...props }));
158
- export const Chart = ({ caption, children, className, description, glass = false, heading, value, ...props }) => (_jsxs("figure", { className: composeClassName('ui-chart', glassClass('ui-chart', glass), className), ...props, children: [(heading || description || value) && (_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 })] })), children, caption && _jsx("figcaption", { children: caption })] }));
159
- const ChartLegend = ({ series }) => (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: series.map((item, index) => (_jsxs("li", { className: `ui-chart-tone--${resolveLumenChartTone(item.tone, index)}`, children: [_jsx("span", { "aria-hidden": "true" }), item.label] }, item.id))) }));
160
- const ChartDataTable = ({ categories, 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: String(category) }), series.map(item => {
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 => {
161
185
  const datum = item.data.find(candidate => candidate.x === category);
162
186
  return (_jsx("td", { children: datum?.label ??
163
- (datum?.y === null || datum === undefined
164
- ? 'Not available'
165
- : String(datum.y)) }, item.id));
187
+ (datum?.y === null || datum === undefined ?
188
+ 'Not available' :
189
+ formatValue(datum.y)) }, item.id));
166
190
  })] }, `${typeof category}:${String(category)}`))) })] }) })] }));
167
191
  const formatChartPercentage = (percentage) => new Intl.NumberFormat(undefined, {
168
192
  maximumFractionDigits: percentage < 0.01 ? 1 : 0,
@@ -172,25 +196,49 @@ export const Sparkline = ({ area = false, className, label = 'Trend', showEndpoi
172
196
  const geometry = createLumenLineGeometry(values.map((value, index) => ({ x: index, y: value })), { height: 40, padding: 3, width: 120 });
173
197
  const resolvedTone = resolveLumenChartTone(tone);
174
198
  const endpoint = geometry.points.at(-1);
175
- 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 && 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 })] }));
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 })] }));
176
201
  };
177
202
  /* eslint-disable complexity -- Chart renderers keep optional semantic and SVG layers colocated. */
178
- export const BarChart = ({ className, layout = 'grouped', orientation = 'vertical', series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
179
- const geometry = createLumenBarGeometry(series, { layout, orientation });
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
+ });
180
209
  const hasData = hasLumenChartData(series);
181
210
  const ticks = getLumenChartTicks(geometry.domain);
182
- const categories = geometry.categories.map(category => category.label);
183
- const margin = orientation === 'horizontal'
184
- ? { bottom: 24, left: 112, right: 20, top: 16 }
185
- : { bottom: 52, left: 52, right: 16, top: 16 };
186
- 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: "No chart data available." })), _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 => {
187
- const coordinate = orientation === 'horizontal'
188
- ? scaleLumenChartValue(tick, geometry.domain, margin.left, geometry.width - margin.right)
189
- : scaleLumenChartValue(tick, geometry.domain, geometry.height - margin.bottom, margin.top);
190
- return orientation === 'horizontal' ? (_jsx("line", { x1: coordinate, x2: coordinate, y1: margin.top, y2: geometry.height - margin.bottom }, tick)) : (_jsx("line", { x1: margin.left, x2: geometry.width - margin.right, y1: coordinate, y2: coordinate }, tick));
191
- }) }), _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: `ui-chart-tone--${mark.tone}`, height: mark.height, rx: "4", width: mark.width, x: mark.x, y: mark.y, children: _jsx("title", { children: `${String(mark.category)} · ${mark.seriesLabel}: ${mark.value}` }) }, `${mark.seriesId}:${String(mark.category)}`))) })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, series: series }))] }));
192
- };
193
- export const LineChart = ({ area = false, className, markers = true, referenceValue, series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
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 }) => {
194
242
  const width = 640;
195
243
  const height = 320;
196
244
  const padding = 44;
@@ -210,39 +258,50 @@ export const LineChart = ({ area = false, className, markers = true, referenceVa
210
258
  }));
211
259
  const ticks = getLumenChartTicks(domain);
212
260
  const labelStep = Math.max(1, Math.ceil(categories.length / 8));
213
- const referenceY = referenceValue === undefined
214
- ? undefined
215
- : scaleLumenChartValue(referenceValue, domain, height - padding, padding);
216
- 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: "No chart data available." })), _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 => {
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 => {
217
266
  const y = scaleLumenChartValue(tick, domain, height - padding, padding);
218
- return (_jsxs(Fragment, { children: [_jsx("line", { x1: padding, x2: width - padding, y1: y, y2: y }), _jsx("text", { x: padding - 8, y: y, children: Number(tick.toPrecision(4)) })] }, tick));
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));
219
268
  }) }), _jsx("g", { className: "ui-chart__axis-labels", children: categories.map((category, index) => {
220
269
  if (index % labelStep !== 0 && index !== categories.length - 1)
221
270
  return null;
222
271
  const denominator = Math.max(1, categories.length - 1);
223
272
  const x = padding + (index / denominator) * (width - padding * 2);
224
- return (_jsx("text", { textAnchor: "middle", x: x, y: height - 14, children: String(category) }, `${typeof category}:${String(category)}`));
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)}`));
225
277
  }) }), referenceY !== undefined && (_jsx("line", { className: "ui-chart__reference", x1: padding, x2: width - padding, y1: referenceY, y2: referenceY })), geometries.map((geometry, index) => {
226
278
  const item = series[index];
227
279
  if (!item)
228
280
  return null;
229
281
  const tone = resolveLumenChartTone(item.tone, index);
230
- return (_jsxs("g", { className: `ui-line-chart__series ui-chart-tone--${tone}`, children: [area && geometry.areaPaths.map(path => (_jsx("path", { className: "ui-line-chart__area", d: path }, path))), _jsx("path", { className: "ui-line-chart__line", d: geometry.path }), markers && geometry.points.map(point => (_jsx("circle", { className: "ui-line-chart__point", cx: point.xCoordinate, cy: point.yCoordinate, r: "3", children: _jsx("title", { children: `${point.label ?? String(point.x)} · ${item.label}: ${String(point.y)}` }) }, `${typeof point.x}:${String(point.x)}`)))] }, item.id));
231
- })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, series: series }))] }));
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 }))] }));
232
286
  };
233
287
  export const PieChart = ({ centerLabel, centerValue, className, series = emptyPieSeries, showLegend = true, showTable = true, valueFormatter = String, variant = 'donut', ...props }) => {
234
288
  const geometry = createLumenPieGeometry(series.data, { variant });
235
289
  const hasData = hasLumenPieData(series.data);
236
- return (_jsxs(Chart, { className: composeClassName('ui-pie-chart', `ui-pie-chart--${variant}`, className), ...props, children: [showLegend && hasData && (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: geometry.slices.map(slice => (_jsxs("li", { className: `ui-chart-tone--${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: `ui-chart-tone--${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' && (centerLabel != null || centerValue != null) && (_jsxs(_Fragment, { children: [_jsxs("div", { "aria-hidden": "true", className: "ui-pie-chart__center", children: [centerValue != null && _jsx("strong", { children: centerValue }), centerLabel != null && _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)}`))) })] }) })] }))] }));
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)}`))) })] }) })] }))] }));
237
293
  };
238
- export const Checkbox = ({ className, ...props }) => (_jsx("input", { className: composeClassName('ui-checkbox', className), type: "checkbox", ...props }));
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] }));
239
296
  export const Collapsible = ({ className, ...props }) => (_jsx("details", { className: composeClassName('ui-collapsible', className), "data-ui-collapsible": true, ...props }));
240
297
  const renderCodeToken = (token) => {
241
298
  if (!token.kind)
242
299
  return token.value;
243
300
  return (_jsx("span", { className: lumenCodeTokenClassNames[token.kind], children: token.value }, `${token.start}-${token.kind}`));
244
301
  };
245
- 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);
246
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')] }));
247
306
  const renderCodeHeader = ({ copy, label, language }) => {
248
307
  if (!copy && !label && !language)
@@ -252,9 +311,59 @@ const renderCodeHeader = ({ copy, label, language }) => {
252
311
  export const Code = ({ children, className, code, copy = false, highlighted = false, label, language, theme = 'auto', variant = 'inline', wrap = false, ...props }) => {
253
312
  const codeChildren = renderCodeChildren(code, children, language);
254
313
  if (variant === 'block') {
255
- 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 }) }))] }));
256
317
  }
257
- 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 }));
258
367
  };
259
368
  export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onChange, onFocus, onKeyDown, options = emptyStringOptions, type = 'text', value: valueProp, wrapperClassName, ...props }) => {
260
369
  const inputId = id ?? `${list}-input`;
@@ -274,7 +383,7 @@ export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onC
274
383
  if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
275
384
  setOpen(false);
276
385
  }
277
- }, 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 => {
278
387
  if (valueProp === undefined) {
279
388
  setValue(event.currentTarget.value);
280
389
  }
@@ -292,14 +401,18 @@ export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onC
292
401
  selectOption(visibleOptions[0]);
293
402
  }
294
403
  onKeyDown?.(event);
295
- }, ...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))) })] }));
296
409
  };
297
410
  export const Command = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-command', glassClass('ui-command', glass), className), "data-ui-command": true, ...props }));
298
- 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 }));
299
412
  export const ColorPicker = ({ className, type = 'color', ...props }) => (_jsx("input", { className: composeClassName('ui-color-picker', className), type: type, ...props }));
300
- 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
301
- ? (_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)))) })] }))
302
- : 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) }));
303
416
  const formatDatePickerDisplayValue = (value, placeholder) => {
304
417
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value))
305
418
  return placeholder;
@@ -313,15 +426,15 @@ const formatDatePickerDisplayValue = (value, placeholder) => {
313
426
  year: 'numeric'
314
427
  }).format(date);
315
428
  };
316
- const stringifyDatePickerConstraint = (value) => value === undefined ? undefined : String(value);
429
+ const stringifyDatePickerConstraint = (value) => (value === undefined ? undefined : String(value));
317
430
  /* eslint-disable complexity -- DatePicker coordinates controlled input, disclosure, Calendar, and native form contracts. */
318
- 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 }) => {
319
432
  const generatedId = useId();
320
433
  const datePickerId = id ?? generatedId;
321
434
  const triggerId = `${datePickerId}-trigger`;
322
435
  const popoverId = `${datePickerId}-popover`;
323
436
  const rootRef = useRef(null);
324
- const inputRef = useRef(null);
437
+ const nativeInputRef = useRef(null);
325
438
  const triggerRef = useRef(null);
326
439
  const [internalValue, setInternalValue] = useState(defaultValue ?? '');
327
440
  const [open, setOpen] = useState(false);
@@ -335,7 +448,8 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
335
448
  if (!open)
336
449
  return;
337
450
  const handlePointerDown = (event) => {
338
- if (event.target instanceof Node && !rootRef.current?.contains(event.target))
451
+ if (event.target instanceof Node &&
452
+ !rootRef.current?.contains(event.target))
339
453
  setOpen(false);
340
454
  };
341
455
  const handleKeyDown = (event) => {
@@ -355,10 +469,10 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
355
469
  if (value === undefined)
356
470
  setInternalValue(nextValue);
357
471
  onValueChange?.(nextValue);
358
- if (inputRef.current) {
359
- inputRef.current.value = nextValue;
360
- inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
361
- 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 }));
362
476
  }
363
477
  setOpen(false);
364
478
  globalThis.setTimeout(() => triggerRef.current?.focus());
@@ -372,7 +486,12 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
372
486
  triggerRef.current?.setAttribute('aria-invalid', 'true');
373
487
  triggerRef.current?.focus();
374
488
  onInvalid?.(event);
375
- }, 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 => {
376
495
  if (event.key !== 'ArrowDown')
377
496
  return;
378
497
  event.preventDefault();
@@ -391,7 +510,9 @@ const getDateRangeState = (start, end) => {
391
510
  return end ? 'complete' : 'selecting-end';
392
511
  };
393
512
  const syncDateRangePickerElement = (root) => {
394
- const inputs = [...root.querySelectorAll('[data-ui-date-picker-native]')];
513
+ const inputs = [
514
+ ...root.querySelectorAll('[data-ui-date-picker-native]')
515
+ ];
395
516
  const start = inputs[0];
396
517
  const end = inputs.at(-1);
397
518
  if (!(start && end) || start === end)
@@ -420,15 +541,41 @@ export const DateRangePicker = ({ className, onInput, onRangeChange, ...props })
420
541
  onRangeChange?.(syncDateRangePickerElement(event.currentTarget));
421
542
  }, ref: rootRef, ...props }));
422
543
  };
423
- 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 }) => {
424
545
  const dialog = useDialog({ defaultOpen, onOpenChange, open });
425
- 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) }));
426
547
  };
427
548
  export const Direction = ({ className, dir = 'ltr', ...props }) => (_jsx("div", { className: composeClassName('ui-direction', className), dir: dir, ...props }));
428
- 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 }));
429
- 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 }) => {
430
577
  const menu = useDropdownMenu({ defaultOpen, onOpenChange, open });
431
- 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 }) }));
432
579
  };
433
580
  export const DropdownMenuTrigger = ({ onClick, onKeyDown, ...props }) => {
434
581
  const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuTrigger');
@@ -439,23 +586,93 @@ export const DropdownMenuContent = ({ className, onKeyDown, ...props }) => {
439
586
  return (_jsx("div", { ...menu.panelProps, ...props, className: composeClassName('ui-menu__content', className), onKeyDown: composeHandlers(onKeyDown, menu.panelProps.onKeyDown), role: props.role ?? 'menu' }));
440
587
  };
441
588
  export const Empty = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-empty', glassClass('ui-empty', glass), className), ...props }));
442
- 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 }) => {
443
601
  const fieldDescribedBy = [ariaDescribedby, describedBy].filter(Boolean).join(' ') || undefined;
444
- 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 }));
445
603
  };
446
- 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 }));
447
610
  export const Icon = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, children, className, decorative, label, name, role, size = 'default', ...props }) => {
448
611
  const icon = name ? getLumenIcon(name) : undefined;
449
- const accessibility = getIconAccessibility({ ariaHidden, ariaLabel, decorative, label, role });
612
+ const accessibility = getIconAccessibility({
613
+ ariaHidden,
614
+ ariaLabel,
615
+ decorative,
616
+ label,
617
+ role
618
+ });
450
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 }));
451
620
  };
452
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
+ };
453
669
  export const InputGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-input-group', className), ...props }));
454
- 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 }) => {
455
671
  const otp = useInputOTP({
456
672
  defaultValue: toInputValue(defaultValue),
457
673
  disabled,
458
674
  inputMode,
675
+ inputRef: ref,
459
676
  invalid: ariaInvalid === true || ariaInvalid === 'true',
460
677
  length,
461
678
  pattern,
@@ -471,17 +688,22 @@ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue,
471
688
  pattern
472
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))) })] }));
473
690
  };
474
- export const NumberField = ({ className, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), type: type, ...props }));
475
- 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
+ });
476
697
  export const Kbd = ({ className, ...props }) => (_jsx("kbd", { className: composeClassName('ui-kbd', className), ...props }));
477
- export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
478
698
  export const Marker = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-marker', variantClass('ui-marker', variant), className), "data-variant": variant, ...props }));
479
- 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 }));
480
700
  export const Message = ({ className, from = 'assistant', ...props }) => (_jsx("article", { className: composeClassName('ui-message', `ui-message--${from}`, className), "data-from": from, ...props }));
481
701
  export const MessageScroller = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-message-scroller', glassClass('ui-message-scroller', glass), className), ...props }));
482
- export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, value, ...props }) => {
483
- const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ? '' : undefined;
484
- 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)))] }));
485
707
  };
486
708
  const phoneInputSizeModifiers = (size) => {
487
709
  if (size === 'sm') {
@@ -496,11 +718,11 @@ export const PhoneInput = ({ className, countries = emptyOptions, countryLabel =
496
718
  const { selectClass, inputClass } = phoneInputSizeModifiers(size);
497
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" })] }));
498
720
  };
499
- 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 }));
500
722
  export const Pagination = ({ 'aria-label': ariaLabel = 'Pagination', className, ...props }) => (_jsx("nav", { "aria-label": ariaLabel, className: composeClassName('ui-pagination', className), ...props }));
501
- 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 }) => {
502
724
  const popover = usePopover({ defaultOpen, onOpenChange, open });
503
- 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 }) }));
504
726
  };
505
727
  export const PopoverTrigger = ({ onClick, onKeyDown, ...props }) => {
506
728
  const popover = requireContext(useContext(PopoverContext), 'PopoverTrigger');
@@ -514,7 +736,7 @@ export const Progress = ({ className, max = 100, value = 0, ...props }) => {
514
736
  const safeMax = max > 0 ? max : 100;
515
737
  const safeValue = Math.min(safeMax, Math.max(0, value));
516
738
  const percentage = (safeValue / safeMax) * 100;
517
- 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}%` } }) }));
518
740
  };
519
741
  export const RadioGroup = ({ className, ...props }) => (_jsx("fieldset", { className: composeClassName('ui-radio-group', className), "data-ui-radio-group": true, ...props }));
520
742
  const renderResizablePane = (child, index, panelProps) => {
@@ -556,9 +778,9 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
556
778
  frame = 0;
557
779
  const scrollingElement = document.scrollingElement ?? document.documentElement;
558
780
  const maximum = scrollingElement.scrollHeight - scrollingElement.clientHeight;
559
- const percentage = maximum > 0
560
- ? Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100))
561
- : 0;
781
+ const percentage = maximum > 0 ?
782
+ Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100)) :
783
+ 0;
562
784
  const bar = root.querySelector('.ui-scroll-progress__bar');
563
785
  root.setAttribute('aria-valuenow', `${Math.round(percentage)}`);
564
786
  if (bar)
@@ -582,7 +804,7 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
582
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" }) }));
583
805
  };
584
806
  export const Schedule = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-schedule', glassClass('ui-schedule', glass), className), "data-ui-schedule": true, ...props }));
585
- 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 }));
586
808
  const getSelectSizeClass = (size) => {
587
809
  if (size === 'sm')
588
810
  return 'ui-select--sm';
@@ -590,7 +812,7 @@ const getSelectSizeClass = (size) => {
590
812
  return 'ui-select--lg';
591
813
  return false;
592
814
  };
593
- 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 }) => {
594
816
  const select = useSelect({
595
817
  defaultValue,
596
818
  disabled,
@@ -603,23 +825,236 @@ export const Select = ({ children, className, defaultValue, disabled = false, gl
603
825
  value
604
826
  });
605
827
  const sizeClass = getSelectSizeClass(size);
606
- 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 => {
607
829
  const optionProps = select.getOptionProps(option);
608
830
  return (_jsx("button", { ...optionProps, type: optionProps.type ?? 'button', children: option.label }, option.value));
609
831
  }) })] })] }));
610
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
+ };
611
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 }));
612
- 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 }));
613
- 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 }));
614
1049
  export const Skeleton = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-skeleton', className), ...props }));
615
- 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 }));
616
1051
  export const Sonner = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-sonner', className), "data-ui-sonner": true, ...props }));
617
1052
  export const Spinner = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, className, role, ...props }) => {
618
1053
  const isAccessible = Boolean(ariaLabel || ariaLabelledby);
619
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 }));
620
1055
  };
621
- export const Switch = ({ className, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), role: role, type: "checkbox", ...props }));
622
- 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 }));
623
1058
  export const Tabs = ({ children, className, defaultValue, glass = false, onValueChange, orientation, value, ...props }) => {
624
1059
  const tabs = useTabs({ defaultValue, onValueChange, orientation, value });
625
1060
  return (_jsx(TabsContext.Provider, { value: tabs, children: _jsx("div", { ...tabs.rootProps, ...props, className: composeClassName('ui-tabs', glassClass('ui-tabs', glass), className), children: children }) }));
@@ -649,9 +1084,9 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
649
1084
  // Storage can be unavailable in privacy modes; tab selection should still work.
650
1085
  }
651
1086
  }
652
- return storedValue && items.some(item => item.value === storedValue)
653
- ? storedValue
654
- : (selectedValue ?? '');
1087
+ return storedValue && items.some(item => item.value === storedValue) ?
1088
+ storedValue :
1089
+ (selectedValue ?? '');
655
1090
  });
656
1091
  useEffect(() => {
657
1092
  const synchronize = (event) => {
@@ -682,13 +1117,15 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
682
1117
  detail: { storageKey, value: nextValue }
683
1118
  }));
684
1119
  };
685
- 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)))] }));
686
1123
  };
687
1124
  export const TagGroup = ({ className, role = 'list', ...props }) => (_jsx("div", { className: composeClassName('ui-tag-group', className), role: role, ...props }));
688
- 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 }));
689
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 }));
690
- export const TimeField = ({ className, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), type: type, ...props }));
691
- 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 }));
692
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 }));
693
1130
  export const ToggleGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-toggle-group', className), "data-ui-toggle-group": true, ...props }));
694
1131
  export const Tooltip = ({ children, className, defaultOpen, delay, onKeyDown, onMouseEnter, onMouseLeave, onOpenChange, open, ...props }) => {
@@ -717,16 +1154,20 @@ export const Image = ({ as, className, invertOnDark = false, loading, ...props }
717
1154
  ...(resolvedLoading ? { loading: resolvedLoading } : {})
718
1155
  });
719
1156
  };
720
- 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 }));
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 }));
721
1162
  export const Pill = ({ className, count, children, href, variant = 'neutral', ...props }) => {
722
1163
  const content = (_jsxs(_Fragment, { children: [children, count !== undefined && _jsx("span", { className: "ui-pill__count", children: count })] }));
723
- return href
724
- ? (_jsx("a", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, href: href, ...props, children: content }))
725
- : (_jsx("span", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, ...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 }));
726
1167
  };
727
1168
  export const Prose = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-prose', className), ...props }));
728
1169
  export const SkipLink = ({ className, ...props }) => (_jsx("a", { className: composeClassName('ui-skip-link', className), ...props }));
729
- 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" })] }));
730
1171
  export const LanguageToggle = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-language-toggle', className), type: type, ...props }));
731
1172
  const particleDensityConfig = { high: 40, low: 15, medium: 25 };
732
1173
  const particleTones = [
@@ -759,7 +1200,7 @@ export const Particles = ({ children, className, density = 'medium', ...props })
759
1200
  // eslint-disable-next-line @eslint-react/set-state-in-effect
760
1201
  setParticles(createParticles(density));
761
1202
  }, [density]);
762
- 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))] }));
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)))] }));
763
1204
  };
764
1205
  const parseMotionDuration = (value) => {
765
1206
  const parsedDuration = Number.parseFloat(value);
@@ -774,8 +1215,8 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
774
1215
  if (!root)
775
1216
  return;
776
1217
  root.dataset.uiScrollRevealBound = 'true';
777
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
778
- || typeof IntersectionObserver === 'undefined') {
1218
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1219
+ typeof IntersectionObserver === 'undefined') {
779
1220
  root.classList.add('is-revealed');
780
1221
  return;
781
1222
  }
@@ -787,9 +1228,14 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
787
1228
  observer.disconnect();
788
1229
  }, { threshold: Math.min(1, Math.max(0, threshold)) });
789
1230
  observer.observe(root);
790
- return () => { observer.disconnect(); };
1231
+ return () => {
1232
+ observer.disconnect();
1233
+ };
791
1234
  }, [once, threshold]);
792
- 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 }));
793
1239
  };
794
1240
  export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, duration = 'slow', once = true, stagger = 80, style, threshold = 0.15, ...props }) => {
795
1241
  const rootRef = useRef(null);
@@ -803,8 +1249,8 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
803
1249
  child.style.setProperty('--ui-reveal-index', String(index));
804
1250
  }
805
1251
  }
806
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
807
- || typeof IntersectionObserver === 'undefined') {
1252
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1253
+ typeof IntersectionObserver === 'undefined') {
808
1254
  root.classList.add('is-revealed');
809
1255
  return;
810
1256
  }
@@ -816,7 +1262,9 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
816
1262
  observer.disconnect();
817
1263
  }, { threshold: Math.min(1, Math.max(0, threshold)) });
818
1264
  observer.observe(root);
819
- return () => { observer.disconnect(); };
1265
+ return () => {
1266
+ observer.disconnect();
1267
+ };
820
1268
  }, [once, threshold]);
821
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: {
822
1270
  ...style,
@@ -837,8 +1285,8 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
837
1285
  if (!output)
838
1286
  return;
839
1287
  const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
840
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
841
- || typeof IntersectionObserver === 'undefined') {
1288
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1289
+ typeof IntersectionObserver === 'undefined') {
842
1290
  output.textContent = format(value);
843
1291
  return;
844
1292
  }
@@ -850,13 +1298,15 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
850
1298
  if (!entries.some(entry => entry.isIntersecting))
851
1299
  return;
852
1300
  observer.disconnect();
853
- const durationValue = getComputedStyle(root).getPropertyValue('--ui-motion-duration').trim();
1301
+ const durationValue = getComputedStyle(root)
1302
+ .getPropertyValue('--ui-motion-duration')
1303
+ .trim();
854
1304
  const durationMs = parseMotionDuration(durationValue);
855
1305
  const startedAt = performance.now();
856
1306
  const update = (now) => {
857
1307
  const progress = Math.min(1, (now - startedAt) / Math.max(1, durationMs));
858
1308
  const eased = 1 - Math.pow(1 - progress, 3);
859
- output.textContent = format(from + ((value - from) * eased));
1309
+ output.textContent = format(from + (value - from) * eased);
860
1310
  if (progress < 1) {
861
1311
  requestAnimationFrame(update);
862
1312
  }
@@ -867,34 +1317,54 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
867
1317
  requestAnimationFrame(update);
868
1318
  }, { threshold: 0.15 });
869
1319
  observer.observe(root);
870
- return () => { observer.disconnect(); };
1320
+ return () => {
1321
+ observer.disconnect();
1322
+ };
871
1323
  }, [formatter, from, prefix, suffix, value]);
872
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 })] }));
873
1325
  };
874
- export const Stat = ({ as, className, label, value, variant = 'default', children, ...props }) => (_jsxs(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant !== 'default' && `ui-stat--${variant}`, className), "data-variant": variant, ...props, uiClassName: "ui-stat", 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 }));
875
1332
  export const Meter = ({ className, ...props }) => (_jsx("meter", { className: composeClassName('ui-meter', className), ...props }));
876
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 })] }));
877
1334
  export const Rating = ({ className, value = 0, max = 5, readonly = false, ...props }) => {
878
1335
  const stars = Array.from({ length: max }, (_, i) => i + 1);
879
- 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))) }));
880
1337
  };
881
1338
  export const Timeline = ({ className, ...props }) => (_jsx("ol", { className: composeClassName('ui-timeline', className), ...props }));
882
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 })] }));
883
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 }));
884
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 }));
885
- export const ButtonLink = ({ className, ref, shape = 'default', variant = 'primary', ...props }) => {
886
- let variantClass = `ui-button ui-button--${variant} min-h-11 rounded-full border`;
887
- if (variant === 'unstyled') {
888
- variantClass = 'ui-button-link--unstyled';
889
- }
890
- else if (variant === 'inline') {
891
- 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 }));
892
1363
  }
893
- const shapeClass = shape === 'icon' && variant !== 'inline' ? 'ui-button-link--icon' : undefined;
894
- 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 }));
895
1365
  };
896
- 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" })] }));
897
- 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 " }) }));
898
1368
  const emptySteps = [];
899
1369
  const normalizeStep = (step) => typeof step === 'string' ? { description: undefined, title: step } : step;
900
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) => {
@@ -905,24 +1375,104 @@ export const Stepper = ({ children, className, currentStep = 0, orientation = 'h
905
1375
  else if (index === currentStep) {
906
1376
  state = 'current';
907
1377
  }
908
- 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));
909
1379
  }), children] }));
910
- 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 }) => {
911
1381
  const generatedId = useId();
912
1382
  const inputId = id ?? `ui-file-upload-${generatedId.replaceAll(':', '')}`;
913
1383
  const [selectedFiles, setSelectedFiles] = useState([]);
914
- const selectedFileText = selectedFiles.length > 1
915
- ? `${selectedFiles.length} files selected`
916
- : selectedFiles[0] ?? '';
1384
+ const selectedFileText = selectedFiles.length > 1 ?
1385
+ `${selectedFiles.length} files selected` :
1386
+ (selectedFiles[0] ?? '');
917
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 => {
918
1388
  setSelectedFiles([...(event.currentTarget.files ?? [])].map(file => file.name));
919
1389
  onChange?.(event);
920
- }, 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 })] }));
921
1391
  };
922
1392
  const emptyTourSteps = [];
923
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] })] }));
924
1394
  const emptyAnchorLinks = [];
925
- 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', "data-depth": item.depth, 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
+ };
926
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] }));
927
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 }));
928
1478
  const emptyDescriptionsItems = [];
@@ -972,13 +1522,93 @@ const flattenTreeSelect = (nodes, depth = 0, acc = []) => {
972
1522
  };
973
1523
  const emptyTreeSelectNodes = [];
974
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" })] }));
975
- 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
+ };
976
1604
  export const QRCode = ({ children, className, size = 160, src, style, value, ...props }) => {
977
1605
  const qrSvg = !src && value ? renderSVG(value) : null;
978
- const qrSrc = qrSvg ? `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` : src;
979
- 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
980
- ? _jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })
981
- : 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 })] }));
982
1612
  };
983
1613
  export const Watermark = ({ children, className, content, gap = 120, rotate = -22, style, text = 'Confidential', ...props }) => {
984
1614
  const watermarkText = content ?? text;
@@ -988,11 +1618,12 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
988
1618
  '<': '&lt;',
989
1619
  '>': '&gt;',
990
1620
  '"': '&quot;',
991
- "'": '&apos;'
1621
+ '\'': '&apos;'
992
1622
  };
993
1623
  const escapedWatermarkText = watermarkText.replaceAll(/[&<>"']/g, character => watermarkXmlEntities[character] ?? character);
994
1624
  const watermarkSvg = [
995
- `<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}">`,
996
1627
  `<text x="${tileSize / 2}" y="${tileSize / 2}" dominant-baseline="middle" text-anchor="middle"`,
997
1628
  ` transform="rotate(${rotate} ${tileSize / 2} ${tileSize / 2})"`,
998
1629
  ' fill="black" font-family="Montserrat, Avenir Next, Segoe UI, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
@@ -1008,10 +1639,20 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
1008
1639
  export const Affix = ({ className, offset = 0, offsetBottom, offsetTop, position: positionProp, style, ...props }) => {
1009
1640
  const position = positionProp ?? (offsetBottom === undefined ? 'top' : 'bottom');
1010
1641
  const resolvedOffset = offsetBottom ?? offsetTop ?? offset;
1011
- 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 }));
1012
1646
  };
1013
1647
  const emptySpeedDialActions = [];
1014
- const speedDialNavigationKeys = ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End', 'Home'];
1648
+ const speedDialNavigationKeys = [
1649
+ 'ArrowDown',
1650
+ 'ArrowLeft',
1651
+ 'ArrowRight',
1652
+ 'ArrowUp',
1653
+ 'End',
1654
+ 'Home'
1655
+ ];
1015
1656
  const getSpeedDialNavigationIndex = (key, currentIndex, itemCount) => {
1016
1657
  if (key === 'Home')
1017
1658
  return 0;
@@ -1060,7 +1701,9 @@ export const SpeedDial = ({ actions = emptySpeedDialActions, children, className
1060
1701
  if (event.target.closest('[role="menuitem"], button, a'))
1061
1702
  close();
1062
1703
  }, onKeyDown: event => {
1063
- 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
+ ];
1064
1707
  if (event.key === 'Escape') {
1065
1708
  event.preventDefault();
1066
1709
  close(true);