@santi020k/lumen-react 0.4.0 → 1.2.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.
- package/README.md +77 -7
- package/dist/components.d.ts +474 -64
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +810 -164
- package/dist/hooks.d.ts +17 -4
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +490 -139
- package/package.json +14 -6
package/dist/components.js
CHANGED
|
@@ -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]) => [
|
|
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 = (
|
|
64
|
-
const glassIntensityClass = (glass) => glass === 'subtle' ?
|
|
65
|
-
|
|
66
|
-
|
|
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) ||
|
|
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,
|
|
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',
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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') &&
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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 &&
|
|
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, {
|
|
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.
|
|
183
|
-
const margin = orientation === 'horizontal'
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
:
|
|
216
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
231
|
-
|
|
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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: () => {
|
|
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,
|
|
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
|
-
|
|
302
|
-
|
|
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
|
|
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 &&
|
|
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 (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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:
|
|
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 = [
|
|
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,
|
|
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',
|
|
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
|
|
429
|
-
|
|
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',
|
|
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');
|
|
@@ -438,24 +585,96 @@ export const DropdownMenuContent = ({ className, onKeyDown, ...props }) => {
|
|
|
438
585
|
const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuContent');
|
|
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
|
-
export const Empty = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-empty', glassClass('ui-empty', glass), className), ...props }));
|
|
442
|
-
export const
|
|
588
|
+
export const Empty = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("section", { className: composeClassName('ui-empty', variant === 'compact' && 'ui-empty--compact', glassClass('ui-empty', glass), className), "data-variant": variant, ...props }));
|
|
589
|
+
export const KanbanBoard = ({ className, tabIndex = 0, ...props }) => (_jsx("section", { "aria-orientation": "horizontal", className: composeClassName('ui-kanban-board ui-kanban', className), "data-ui-kanban": true, tabIndex: tabIndex, ...props }));
|
|
590
|
+
export const KanbanColumn = ({ className, value, ...props }) => (_jsx("section", { className: composeClassName('ui-kanban__column', className), "data-ui-kanban-column": value, ...props }));
|
|
591
|
+
export const ErrorSummary = ({ className, errors, heading = 'There is a problem', id = 'ui-error-summary', ...props }) => {
|
|
592
|
+
const normalizedErrors = normalizeLumenFormErrors(errors);
|
|
593
|
+
const allErrors = [
|
|
594
|
+
...normalizedErrors.form.map(message => ({ message })),
|
|
595
|
+
...normalizedErrors.fields
|
|
596
|
+
];
|
|
597
|
+
const headingId = `${id}-heading`;
|
|
598
|
+
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 ?
|
|
599
|
+
(_jsx("a", { href: `#${error.controlId}`, children: error.message })) :
|
|
600
|
+
(error.message) }, `${error.name ?? 'form'}:${error.controlId ?? ''}:${error.message}`))) })] }));
|
|
601
|
+
};
|
|
602
|
+
export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, invalid = false, ...props }) => {
|
|
443
603
|
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 }));
|
|
604
|
+
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
605
|
};
|
|
446
|
-
export const
|
|
606
|
+
export const FieldError = ({ children, className, hidden, message, ...props }) => {
|
|
607
|
+
const content = message ?? children;
|
|
608
|
+
return (_jsx("p", { className: composeClassName('ui-field-error', className), "data-ui-field-error": true, hidden: hidden ?? !content, ...props, children: content }));
|
|
609
|
+
};
|
|
610
|
+
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 }));
|
|
611
|
+
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
612
|
export const Icon = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, children, className, decorative, label, name, role, size = 'default', ...props }) => {
|
|
448
613
|
const icon = name ? getLumenIcon(name) : undefined;
|
|
449
|
-
const accessibility = getIconAccessibility({
|
|
614
|
+
const accessibility = getIconAccessibility({
|
|
615
|
+
ariaHidden,
|
|
616
|
+
ariaLabel,
|
|
617
|
+
decorative,
|
|
618
|
+
label,
|
|
619
|
+
role
|
|
620
|
+
});
|
|
450
621
|
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
622
|
};
|
|
452
623
|
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 }));
|
|
624
|
+
export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
|
|
625
|
+
const getPasswordFieldDescribedBy = (ariaDescribedBy, hintId, errorId) => [ariaDescribedBy, hintId, errorId].filter(Boolean).join(' ') || undefined;
|
|
626
|
+
const usePasswordVisibility = (inputRef) => {
|
|
627
|
+
const [visible, setVisible] = useState(false);
|
|
628
|
+
useEffect(() => {
|
|
629
|
+
const form = inputRef.current?.form;
|
|
630
|
+
if (!form)
|
|
631
|
+
return;
|
|
632
|
+
const handleReset = () => {
|
|
633
|
+
setVisible(false);
|
|
634
|
+
};
|
|
635
|
+
const hideAfterSuccess = () => {
|
|
636
|
+
if (form.dataset.status === 'success')
|
|
637
|
+
setVisible(false);
|
|
638
|
+
};
|
|
639
|
+
form.addEventListener('reset', handleReset);
|
|
640
|
+
const observer = new MutationObserver(hideAfterSuccess);
|
|
641
|
+
observer.observe(form, {
|
|
642
|
+
attributeFilter: ['data-status'],
|
|
643
|
+
attributes: true
|
|
644
|
+
});
|
|
645
|
+
return () => {
|
|
646
|
+
form.removeEventListener('reset', handleReset);
|
|
647
|
+
observer.disconnect();
|
|
648
|
+
};
|
|
649
|
+
}, [inputRef]);
|
|
650
|
+
const toggleVisibility = () => {
|
|
651
|
+
setVisible(current => !current);
|
|
652
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
653
|
+
};
|
|
654
|
+
return { toggleVisibility, visible };
|
|
655
|
+
};
|
|
656
|
+
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 => {
|
|
657
|
+
inputRef.current = node;
|
|
658
|
+
setRefValue(forwardedRef, node);
|
|
659
|
+
}, 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' }) })] }));
|
|
660
|
+
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 })] }));
|
|
661
|
+
export const PasswordField = ({ 'aria-describedby': ariaDescribedby, className, error, hideLabel = 'Hide password', hint, id, label = 'Password', name, ref, showLabel = 'Show password', ...props }) => {
|
|
662
|
+
const generatedId = useId();
|
|
663
|
+
const controlId = id ?? `ui-password-${generatedId}`;
|
|
664
|
+
const hintId = hint ? `${controlId}-hint` : undefined;
|
|
665
|
+
const errorId = `${controlId}-error`;
|
|
666
|
+
const describedBy = getPasswordFieldDescribedBy(ariaDescribedby, hintId, error ? errorId : undefined);
|
|
667
|
+
const inputRef = useRef(null);
|
|
668
|
+
const { toggleVisibility, visible } = usePasswordVisibility(inputRef);
|
|
669
|
+
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 })] }));
|
|
670
|
+
};
|
|
453
671
|
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 }) => {
|
|
672
|
+
export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', ref, value, ...props }) => {
|
|
455
673
|
const otp = useInputOTP({
|
|
456
674
|
defaultValue: toInputValue(defaultValue),
|
|
457
675
|
disabled,
|
|
458
676
|
inputMode,
|
|
677
|
+
inputRef: ref,
|
|
459
678
|
invalid: ariaInvalid === true || ariaInvalid === 'true',
|
|
460
679
|
length,
|
|
461
680
|
pattern,
|
|
@@ -471,17 +690,22 @@ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue,
|
|
|
471
690
|
pattern
|
|
472
691
|
}) }), _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
692
|
};
|
|
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 }) => (
|
|
693
|
+
export const NumberField = ({ className, ref, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), ref: ref, type: type, ...props }));
|
|
694
|
+
export const Item = ({ as = 'div', className, glass = false, ...props }) => createElement(as, {
|
|
695
|
+
className: composeClassName('ui-item', glassClass('ui-item', glass), className),
|
|
696
|
+
'data-slot': 'item',
|
|
697
|
+
...props
|
|
698
|
+
});
|
|
476
699
|
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
700
|
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,
|
|
701
|
+
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
702
|
export const Message = ({ className, from = 'assistant', ...props }) => (_jsx("article", { className: composeClassName('ui-message', `ui-message--${from}`, className), "data-from": from, ...props }));
|
|
481
703
|
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 ?
|
|
484
|
-
|
|
704
|
+
export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, ref, value, visualSize = 'default', ...props }) => {
|
|
705
|
+
const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ?
|
|
706
|
+
'' :
|
|
707
|
+
undefined;
|
|
708
|
+
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
709
|
};
|
|
486
710
|
const phoneInputSizeModifiers = (size) => {
|
|
487
711
|
if (size === 'sm') {
|
|
@@ -496,11 +720,12 @@ export const PhoneInput = ({ className, countries = emptyOptions, countryLabel =
|
|
|
496
720
|
const { selectClass, inputClass } = phoneInputSizeModifiers(size);
|
|
497
721
|
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
722
|
};
|
|
499
|
-
export const NavigationMenu = ({ className, glass = false,
|
|
723
|
+
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 }));
|
|
724
|
+
export const ContextNavigation = ({ children, className, context, variant = 'default', ...props }) => (_jsxs("div", { className: composeClassName('ui-context-navigation', variant === 'unstyled' && 'ui-context-navigation--unstyled', className), "data-slot": "context-navigation", "data-variant": variant, ...props, children: [context && (_jsx("div", { className: "ui-context-navigation__context", "data-slot": "context-navigation-context", children: context })), children] }));
|
|
500
725
|
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,
|
|
726
|
+
export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, ...props }) => {
|
|
502
727
|
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',
|
|
728
|
+
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
729
|
};
|
|
505
730
|
export const PopoverTrigger = ({ onClick, onKeyDown, ...props }) => {
|
|
506
731
|
const popover = requireContext(useContext(PopoverContext), 'PopoverTrigger');
|
|
@@ -514,7 +739,7 @@ export const Progress = ({ className, max = 100, value = 0, ...props }) => {
|
|
|
514
739
|
const safeMax = max > 0 ? max : 100;
|
|
515
740
|
const safeValue = Math.min(safeMax, Math.max(0, value));
|
|
516
741
|
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}%` } }) }));
|
|
742
|
+
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
743
|
};
|
|
519
744
|
export const RadioGroup = ({ className, ...props }) => (_jsx("fieldset", { className: composeClassName('ui-radio-group', className), "data-ui-radio-group": true, ...props }));
|
|
520
745
|
const renderResizablePane = (child, index, panelProps) => {
|
|
@@ -556,9 +781,9 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
|
|
|
556
781
|
frame = 0;
|
|
557
782
|
const scrollingElement = document.scrollingElement ?? document.documentElement;
|
|
558
783
|
const maximum = scrollingElement.scrollHeight - scrollingElement.clientHeight;
|
|
559
|
-
const percentage = maximum > 0
|
|
560
|
-
|
|
561
|
-
|
|
784
|
+
const percentage = maximum > 0 ?
|
|
785
|
+
Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100)) :
|
|
786
|
+
0;
|
|
562
787
|
const bar = root.querySelector('.ui-scroll-progress__bar');
|
|
563
788
|
root.setAttribute('aria-valuenow', `${Math.round(percentage)}`);
|
|
564
789
|
if (bar)
|
|
@@ -582,7 +807,7 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
|
|
|
582
807
|
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
808
|
};
|
|
584
809
|
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 }));
|
|
810
|
+
export const SearchField = ({ className, ref, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), ref: ref, type: type, ...props }));
|
|
586
811
|
const getSelectSizeClass = (size) => {
|
|
587
812
|
if (size === 'sm')
|
|
588
813
|
return 'ui-select--sm';
|
|
@@ -590,7 +815,7 @@ const getSelectSizeClass = (size) => {
|
|
|
590
815
|
return 'ui-select--lg';
|
|
591
816
|
return false;
|
|
592
817
|
};
|
|
593
|
-
export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
|
|
818
|
+
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
819
|
const select = useSelect({
|
|
595
820
|
defaultValue,
|
|
596
821
|
disabled,
|
|
@@ -603,23 +828,236 @@ export const Select = ({ children, className, defaultValue, disabled = false, gl
|
|
|
603
828
|
value
|
|
604
829
|
});
|
|
605
830
|
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 => {
|
|
831
|
+
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
832
|
const optionProps = select.getOptionProps(option);
|
|
608
833
|
return (_jsx("button", { ...optionProps, type: optionProps.type ?? 'button', children: option.label }, option.value));
|
|
609
834
|
}) })] })] }));
|
|
610
835
|
};
|
|
836
|
+
const groupListBoxOptions = (options) => {
|
|
837
|
+
const groups = [];
|
|
838
|
+
options.forEach((option, index) => {
|
|
839
|
+
const currentGroup = groups.at(-1);
|
|
840
|
+
if (!currentGroup || currentGroup.label !== option.section) {
|
|
841
|
+
groups.push({
|
|
842
|
+
...(option.section ? { label: option.section } : {}),
|
|
843
|
+
options: [{ ...option, index }]
|
|
844
|
+
});
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
currentGroup.options.push({ ...option, index });
|
|
848
|
+
});
|
|
849
|
+
return groups;
|
|
850
|
+
};
|
|
851
|
+
const getListBoxGroupKey = (group) => {
|
|
852
|
+
const values = group.options.map(option => option.value).join('|');
|
|
853
|
+
return group.label ? `${group.label}:${values}` : values;
|
|
854
|
+
};
|
|
855
|
+
const optionalTrue = (value) => value ? true : undefined;
|
|
856
|
+
const ListBoxNativeOptions = ({ groups }) => groups.map(group => {
|
|
857
|
+
const options = group.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)));
|
|
858
|
+
return group.label ?
|
|
859
|
+
(_jsx("optgroup", { label: group.label, children: options }, getListBoxGroupKey(group))) :
|
|
860
|
+
(_jsx(Fragment, { children: options }, getListBoxGroupKey(group)));
|
|
861
|
+
});
|
|
862
|
+
const ListBoxVisibleOptions = ({ activeIndex, controlId, emptyMessage, groups, loading, onSelect, selectedValues }) => {
|
|
863
|
+
if (loading)
|
|
864
|
+
return null;
|
|
865
|
+
if (!groups.length) {
|
|
866
|
+
return (_jsx("div", { "aria-disabled": "true", className: "ui-list-box__empty", role: "option", children: emptyMessage }));
|
|
867
|
+
}
|
|
868
|
+
return groups.map(group => {
|
|
869
|
+
const groupKey = getListBoxGroupKey(group);
|
|
870
|
+
const safeGroupKey = groupKey.replaceAll(/[^a-zA-Z0-9_-]/g, '-');
|
|
871
|
+
const groupLabelId = group.label ? `${controlId}-group-${safeGroupKey}` : undefined;
|
|
872
|
+
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: () => {
|
|
873
|
+
onSelect(option.index);
|
|
874
|
+
}, role: "option", children: option.label }, option.value)));
|
|
875
|
+
if (!group.label)
|
|
876
|
+
return _jsx(Fragment, { children: options }, groupKey);
|
|
877
|
+
return (_jsxs("div", { "aria-labelledby": groupLabelId, role: "group", children: [_jsx("div", { className: "ui-list-box__section", id: groupLabelId, children: group.label }), options] }, groupKey));
|
|
878
|
+
});
|
|
879
|
+
};
|
|
880
|
+
const normalizeListBoxValues = (value) => {
|
|
881
|
+
if (value === undefined)
|
|
882
|
+
return [];
|
|
883
|
+
return Array.isArray(value) ? value : [value];
|
|
884
|
+
};
|
|
885
|
+
const findLastEnabledOptionIndex = (options) => {
|
|
886
|
+
let lastEnabledIndex = -1;
|
|
887
|
+
for (const [index, option] of options.entries()) {
|
|
888
|
+
if (!option.disabled)
|
|
889
|
+
lastEnabledIndex = index;
|
|
890
|
+
}
|
|
891
|
+
return lastEnabledIndex;
|
|
892
|
+
};
|
|
893
|
+
const getListBoxSelectedValues = (controlledValue, internalValues) => controlledValue === undefined ?
|
|
894
|
+
internalValues :
|
|
895
|
+
normalizeListBoxValues(controlledValue);
|
|
896
|
+
const getListBoxSelectValue = (multiple, selectedValues) => (multiple ? selectedValues : (selectedValues[0] ?? ''));
|
|
897
|
+
const ListBoxLabel = ({ controlId, label }) => label ?
|
|
898
|
+
(_jsx("label", { className: "ui-label", htmlFor: controlId, children: label })) :
|
|
899
|
+
null;
|
|
900
|
+
const normalizeListBoxProps = ({ disabled = false, emptyMessage = 'No options available', loading = false, loadingMessage = 'Loading options', multiple = false, options = emptyOptions, required = false, selectionFollowsFocus = false, ...props }) => ({
|
|
901
|
+
disabled,
|
|
902
|
+
emptyMessage,
|
|
903
|
+
loading,
|
|
904
|
+
loadingMessage,
|
|
905
|
+
multiple,
|
|
906
|
+
options,
|
|
907
|
+
required,
|
|
908
|
+
selectionFollowsFocus,
|
|
909
|
+
...props
|
|
910
|
+
});
|
|
911
|
+
export const ListBox = (inputProps) => {
|
|
912
|
+
const { className, defaultValue, disabled, emptyMessage, id, label, loading, loadingMessage, multiple, name, onValueChange, options, ref, required, selectionFollowsFocus, value, ...props } = normalizeListBoxProps(inputProps);
|
|
913
|
+
const generatedId = useId();
|
|
914
|
+
const controlId = id ?? `ui-list-box-${generatedId}`;
|
|
915
|
+
const normalizedOptions = options.map(option => typeof option === 'string' ? { label: option, value: option } : option);
|
|
916
|
+
const optionGroups = groupListBoxOptions(normalizedOptions);
|
|
917
|
+
const [internalValues, setInternalValues] = useState(() => normalizeListBoxValues(defaultValue));
|
|
918
|
+
const selectedValues = getListBoxSelectedValues(value, internalValues);
|
|
919
|
+
const selectedSet = new Set(selectedValues);
|
|
920
|
+
const firstEnabledIndex = Math.max(0, normalizedOptions.findIndex(option => !option.disabled));
|
|
921
|
+
const [activeIndex, setActiveIndex] = useState(firstEnabledIndex);
|
|
922
|
+
const selectRef = useRef(null);
|
|
923
|
+
const typeaheadRef = useRef('');
|
|
924
|
+
const typeaheadTimerRef = useRef(undefined);
|
|
925
|
+
const commitValues = (nextValues) => {
|
|
926
|
+
if (value === undefined) {
|
|
927
|
+
setInternalValues(nextValues);
|
|
928
|
+
}
|
|
929
|
+
onValueChange?.(nextValues);
|
|
930
|
+
const select = selectRef.current;
|
|
931
|
+
if (!select)
|
|
932
|
+
return;
|
|
933
|
+
for (const option of select.options) {
|
|
934
|
+
option.selected = nextValues.includes(option.value);
|
|
935
|
+
}
|
|
936
|
+
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
937
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
938
|
+
};
|
|
939
|
+
const selectOption = (index) => {
|
|
940
|
+
const option = normalizedOptions[index];
|
|
941
|
+
if (!option || option.disabled || disabled || loading)
|
|
942
|
+
return;
|
|
943
|
+
const optionValue = option.value;
|
|
944
|
+
let nextValues;
|
|
945
|
+
if (!multiple) {
|
|
946
|
+
nextValues = [optionValue];
|
|
947
|
+
}
|
|
948
|
+
else if (selectedSet.has(optionValue)) {
|
|
949
|
+
nextValues = selectedValues.filter(selectedValue => selectedValue !== optionValue);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
nextValues = [...selectedValues, optionValue];
|
|
953
|
+
}
|
|
954
|
+
commitValues(nextValues);
|
|
955
|
+
};
|
|
956
|
+
const moveActive = (direction) => {
|
|
957
|
+
if (!normalizedOptions.length || loading)
|
|
958
|
+
return;
|
|
959
|
+
let nextIndex = activeIndex;
|
|
960
|
+
for (const _option of normalizedOptions) {
|
|
961
|
+
nextIndex =
|
|
962
|
+
(nextIndex + direction + normalizedOptions.length) %
|
|
963
|
+
normalizedOptions.length;
|
|
964
|
+
if (!normalizedOptions[nextIndex]?.disabled) {
|
|
965
|
+
setActiveIndex(nextIndex);
|
|
966
|
+
if (selectionFollowsFocus)
|
|
967
|
+
selectOption(nextIndex);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
useEffect(() => () => {
|
|
973
|
+
if (typeaheadTimerRef.current)
|
|
974
|
+
clearTimeout(typeaheadTimerRef.current);
|
|
975
|
+
}, []);
|
|
976
|
+
useEffect(() => {
|
|
977
|
+
const select = selectRef.current;
|
|
978
|
+
const form = select?.form;
|
|
979
|
+
if (!form || value !== undefined)
|
|
980
|
+
return;
|
|
981
|
+
const handleReset = () => {
|
|
982
|
+
setInternalValues(normalizeListBoxValues(defaultValue));
|
|
983
|
+
setActiveIndex(firstEnabledIndex);
|
|
984
|
+
};
|
|
985
|
+
form.addEventListener('reset', handleReset);
|
|
986
|
+
return () => {
|
|
987
|
+
form.removeEventListener('reset', handleReset);
|
|
988
|
+
};
|
|
989
|
+
}, [defaultValue, firstEnabledIndex, value]);
|
|
990
|
+
const handleNavigationKey = (key) => {
|
|
991
|
+
if (key === 'ArrowDown')
|
|
992
|
+
moveActive(1);
|
|
993
|
+
else if (key === 'ArrowUp')
|
|
994
|
+
moveActive(-1);
|
|
995
|
+
else if (key === 'Home') {
|
|
996
|
+
setActiveIndex(firstEnabledIndex);
|
|
997
|
+
if (selectionFollowsFocus)
|
|
998
|
+
selectOption(firstEnabledIndex);
|
|
999
|
+
}
|
|
1000
|
+
else if (key === 'End') {
|
|
1001
|
+
const lastEnabledIndex = findLastEnabledOptionIndex(normalizedOptions);
|
|
1002
|
+
if (lastEnabledIndex >= 0) {
|
|
1003
|
+
setActiveIndex(lastEnabledIndex);
|
|
1004
|
+
if (selectionFollowsFocus)
|
|
1005
|
+
selectOption(lastEnabledIndex);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
else if (key === 'Enter' || key === ' ')
|
|
1009
|
+
selectOption(activeIndex);
|
|
1010
|
+
else
|
|
1011
|
+
return false;
|
|
1012
|
+
return true;
|
|
1013
|
+
};
|
|
1014
|
+
const handleTypeaheadKey = (event) => {
|
|
1015
|
+
if (event.key.length !== 1 ||
|
|
1016
|
+
event.ctrlKey ||
|
|
1017
|
+
event.metaKey ||
|
|
1018
|
+
event.altKey)
|
|
1019
|
+
return false;
|
|
1020
|
+
typeaheadRef.current += event.key.toLowerCase();
|
|
1021
|
+
if (typeaheadTimerRef.current)
|
|
1022
|
+
clearTimeout(typeaheadTimerRef.current);
|
|
1023
|
+
typeaheadTimerRef.current = setTimeout(() => {
|
|
1024
|
+
typeaheadRef.current = '';
|
|
1025
|
+
}, 700);
|
|
1026
|
+
const nextIndex = normalizedOptions.findIndex(option => !option.disabled &&
|
|
1027
|
+
option.label.toLowerCase().startsWith(typeaheadRef.current));
|
|
1028
|
+
if (nextIndex >= 0) {
|
|
1029
|
+
setActiveIndex(nextIndex);
|
|
1030
|
+
if (selectionFollowsFocus)
|
|
1031
|
+
selectOption(nextIndex);
|
|
1032
|
+
}
|
|
1033
|
+
return true;
|
|
1034
|
+
};
|
|
1035
|
+
const handleListKeyDown = (event) => {
|
|
1036
|
+
if (handleNavigationKey(event.key) || handleTypeaheadKey(event))
|
|
1037
|
+
event.preventDefault();
|
|
1038
|
+
};
|
|
1039
|
+
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 => {
|
|
1040
|
+
selectRef.current = node;
|
|
1041
|
+
setRefValue(ref, node);
|
|
1042
|
+
}, 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 => {
|
|
1043
|
+
setActiveIndex(index);
|
|
1044
|
+
selectOption(index);
|
|
1045
|
+
}, selectedValues: selectedSet }) }), loading ?
|
|
1046
|
+
(_jsx("p", { className: "ui-list-box__status", role: "status", children: loadingMessage })) :
|
|
1047
|
+
null] }));
|
|
1048
|
+
};
|
|
611
1049
|
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,
|
|
613
|
-
export const Sidebar = ({ className, glass = false,
|
|
1050
|
+
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 }));
|
|
1051
|
+
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
1052
|
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 }));
|
|
1053
|
+
export const Slider = ({ className, ref, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), ref: ref, type: type, ...props }));
|
|
616
1054
|
export const Sonner = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-sonner', className), "data-ui-sonner": true, ...props }));
|
|
617
1055
|
export const Spinner = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, className, role, ...props }) => {
|
|
618
1056
|
const isAccessible = Boolean(ariaLabel || ariaLabelledby);
|
|
619
1057
|
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
1058
|
};
|
|
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 }));
|
|
1059
|
+
export const Switch = ({ className, ref, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), ref: ref, role: role, type: "checkbox", ...props }));
|
|
1060
|
+
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
1061
|
export const Tabs = ({ children, className, defaultValue, glass = false, onValueChange, orientation, value, ...props }) => {
|
|
624
1062
|
const tabs = useTabs({ defaultValue, onValueChange, orientation, value });
|
|
625
1063
|
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 +1087,9 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
|
|
|
649
1087
|
// Storage can be unavailable in privacy modes; tab selection should still work.
|
|
650
1088
|
}
|
|
651
1089
|
}
|
|
652
|
-
return storedValue && items.some(item => item.value === storedValue)
|
|
653
|
-
|
|
654
|
-
|
|
1090
|
+
return storedValue && items.some(item => item.value === storedValue) ?
|
|
1091
|
+
storedValue :
|
|
1092
|
+
(selectedValue ?? '');
|
|
655
1093
|
});
|
|
656
1094
|
useEffect(() => {
|
|
657
1095
|
const synchronize = (event) => {
|
|
@@ -682,13 +1120,15 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
|
|
|
682
1120
|
detail: { storageKey, value: nextValue }
|
|
683
1121
|
}));
|
|
684
1122
|
};
|
|
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 ?
|
|
1123
|
+
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 ?
|
|
1124
|
+
{} :
|
|
1125
|
+
{ language: item.language }), theme: theme, variant: "block", wrap: wrap }) }, item.value)))] }));
|
|
686
1126
|
};
|
|
687
1127
|
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 }));
|
|
1128
|
+
export const Textarea = ({ className, ref, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), ref: ref, rows: rows, ...props }));
|
|
689
1129
|
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,
|
|
1130
|
+
export const TimeField = ({ className, ref, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), ref: ref, type: type, ...props }));
|
|
1131
|
+
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
1132
|
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
1133
|
export const ToggleGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-toggle-group', className), "data-ui-toggle-group": true, ...props }));
|
|
694
1134
|
export const Tooltip = ({ children, className, defaultOpen, delay, onKeyDown, onMouseEnter, onMouseLeave, onOpenChange, open, ...props }) => {
|
|
@@ -717,16 +1157,20 @@ export const Image = ({ as, className, invertOnDark = false, loading, ...props }
|
|
|
717
1157
|
...(resolvedLoading ? { loading: resolvedLoading } : {})
|
|
718
1158
|
});
|
|
719
1159
|
};
|
|
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 ?
|
|
1160
|
+
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 ?
|
|
1161
|
+
[
|
|
1162
|
+
...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))
|
|
1163
|
+
].join(' ') :
|
|
1164
|
+
rel, target: newTab ? '_blank' : target, ...props }));
|
|
721
1165
|
export const Pill = ({ className, count, children, href, variant = 'neutral', ...props }) => {
|
|
722
1166
|
const content = (_jsxs(_Fragment, { children: [children, count !== undefined && _jsx("span", { className: "ui-pill__count", children: count })] }));
|
|
723
|
-
return href
|
|
724
|
-
|
|
725
|
-
|
|
1167
|
+
return href ?
|
|
1168
|
+
(_jsx("a", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, href: href, ...props, children: content })) :
|
|
1169
|
+
(_jsx("span", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, ...props, children: content }));
|
|
726
1170
|
};
|
|
727
1171
|
export const Prose = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-prose', className), ...props }));
|
|
728
1172
|
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" })] }));
|
|
1173
|
+
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
1174
|
export const LanguageToggle = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-language-toggle', className), type: type, ...props }));
|
|
731
1175
|
const particleDensityConfig = { high: 40, low: 15, medium: 25 };
|
|
732
1176
|
const particleTones = [
|
|
@@ -759,7 +1203,7 @@ export const Particles = ({ children, className, density = 'medium', ...props })
|
|
|
759
1203
|
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
760
1204
|
setParticles(createParticles(density));
|
|
761
1205
|
}, [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))] }));
|
|
1206
|
+
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
1207
|
};
|
|
764
1208
|
const parseMotionDuration = (value) => {
|
|
765
1209
|
const parsedDuration = Number.parseFloat(value);
|
|
@@ -774,8 +1218,8 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
|
|
|
774
1218
|
if (!root)
|
|
775
1219
|
return;
|
|
776
1220
|
root.dataset.uiScrollRevealBound = 'true';
|
|
777
|
-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
778
|
-
|
|
1221
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
1222
|
+
typeof IntersectionObserver === 'undefined') {
|
|
779
1223
|
root.classList.add('is-revealed');
|
|
780
1224
|
return;
|
|
781
1225
|
}
|
|
@@ -787,9 +1231,14 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
|
|
|
787
1231
|
observer.disconnect();
|
|
788
1232
|
}, { threshold: Math.min(1, Math.max(0, threshold)) });
|
|
789
1233
|
observer.observe(root);
|
|
790
|
-
return () => {
|
|
1234
|
+
return () => {
|
|
1235
|
+
observer.disconnect();
|
|
1236
|
+
};
|
|
791
1237
|
}, [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: {
|
|
1238
|
+
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: {
|
|
1239
|
+
...style,
|
|
1240
|
+
'--ui-reveal-delay': `${Math.max(0, delay)}ms`
|
|
1241
|
+
}, ...props }));
|
|
793
1242
|
};
|
|
794
1243
|
export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, duration = 'slow', once = true, stagger = 80, style, threshold = 0.15, ...props }) => {
|
|
795
1244
|
const rootRef = useRef(null);
|
|
@@ -803,8 +1252,8 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
|
|
|
803
1252
|
child.style.setProperty('--ui-reveal-index', String(index));
|
|
804
1253
|
}
|
|
805
1254
|
}
|
|
806
|
-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
807
|
-
|
|
1255
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
1256
|
+
typeof IntersectionObserver === 'undefined') {
|
|
808
1257
|
root.classList.add('is-revealed');
|
|
809
1258
|
return;
|
|
810
1259
|
}
|
|
@@ -816,7 +1265,9 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
|
|
|
816
1265
|
observer.disconnect();
|
|
817
1266
|
}, { threshold: Math.min(1, Math.max(0, threshold)) });
|
|
818
1267
|
observer.observe(root);
|
|
819
|
-
return () => {
|
|
1268
|
+
return () => {
|
|
1269
|
+
observer.disconnect();
|
|
1270
|
+
};
|
|
820
1271
|
}, [once, threshold]);
|
|
821
1272
|
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
1273
|
...style,
|
|
@@ -837,8 +1288,8 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
|
|
|
837
1288
|
if (!output)
|
|
838
1289
|
return;
|
|
839
1290
|
const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
|
|
840
|
-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
841
|
-
|
|
1291
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
1292
|
+
typeof IntersectionObserver === 'undefined') {
|
|
842
1293
|
output.textContent = format(value);
|
|
843
1294
|
return;
|
|
844
1295
|
}
|
|
@@ -850,13 +1301,15 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
|
|
|
850
1301
|
if (!entries.some(entry => entry.isIntersecting))
|
|
851
1302
|
return;
|
|
852
1303
|
observer.disconnect();
|
|
853
|
-
const durationValue = getComputedStyle(root)
|
|
1304
|
+
const durationValue = getComputedStyle(root)
|
|
1305
|
+
.getPropertyValue('--ui-motion-duration')
|
|
1306
|
+
.trim();
|
|
854
1307
|
const durationMs = parseMotionDuration(durationValue);
|
|
855
1308
|
const startedAt = performance.now();
|
|
856
1309
|
const update = (now) => {
|
|
857
1310
|
const progress = Math.min(1, (now - startedAt) / Math.max(1, durationMs));
|
|
858
1311
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
859
|
-
output.textContent = format(from + (
|
|
1312
|
+
output.textContent = format(from + (value - from) * eased);
|
|
860
1313
|
if (progress < 1) {
|
|
861
1314
|
requestAnimationFrame(update);
|
|
862
1315
|
}
|
|
@@ -867,34 +1320,54 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
|
|
|
867
1320
|
requestAnimationFrame(update);
|
|
868
1321
|
}, { threshold: 0.15 });
|
|
869
1322
|
observer.observe(root);
|
|
870
|
-
return () => {
|
|
1323
|
+
return () => {
|
|
1324
|
+
observer.disconnect();
|
|
1325
|
+
};
|
|
871
1326
|
}, [formatter, from, prefix, suffix, value]);
|
|
872
1327
|
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
1328
|
};
|
|
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] }));
|
|
1329
|
+
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] }));
|
|
1330
|
+
export const StatLabel = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-label", ...props, uiClassName: "ui-stat-label" }));
|
|
1331
|
+
export const StatValue = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-value", ...props, uiClassName: "ui-stat-value" }));
|
|
1332
|
+
export const StatDescription = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-description", ...props, uiClassName: "ui-stat-description" }));
|
|
1333
|
+
export const StatIcon = ({ className, ...props }) => (_jsx("span", { className: composeClassName('ui-stat-icon', className), "data-slot": "stat-icon", ...props }));
|
|
1334
|
+
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
1335
|
export const Meter = ({ className, ...props }) => (_jsx("meter", { className: composeClassName('ui-meter', className), ...props }));
|
|
876
1336
|
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
1337
|
export const Rating = ({ className, value = 0, max = 5, readonly = false, ...props }) => {
|
|
878
1338
|
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(
|
|
1339
|
+
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
1340
|
};
|
|
881
1341
|
export const Timeline = ({ className, ...props }) => (_jsx("ol", { className: composeClassName('ui-timeline', className), ...props }));
|
|
882
1342
|
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
1343
|
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
1344
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1345
|
+
const getButtonLinkVariantClass = (variant) => {
|
|
1346
|
+
if (variant === 'unstyled')
|
|
1347
|
+
return 'ui-button-link--unstyled';
|
|
1348
|
+
if (variant === 'inline')
|
|
1349
|
+
return 'ui-button ui-button--inline';
|
|
1350
|
+
return `ui-button ui-button--${variant} min-h-11 rounded-full border`;
|
|
1351
|
+
};
|
|
1352
|
+
export const ButtonLink = ({ asChild = false, children, className, ref, shape = 'default', variant = 'primary', ...props }) => {
|
|
1353
|
+
const variantClass = getButtonLinkVariantClass(variant);
|
|
1354
|
+
const shapeClass = shape === 'icon' && variant !== 'inline' ?
|
|
1355
|
+
'ui-button-link--icon' :
|
|
1356
|
+
undefined;
|
|
1357
|
+
const buttonLinkClassName = composeClassName('ui-button-link', variant !== 'unstyled' &&
|
|
1358
|
+
'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className);
|
|
1359
|
+
if (asChild) {
|
|
1360
|
+
const child = Children.only(children);
|
|
1361
|
+
if (!isValidElement(child)) {
|
|
1362
|
+
throw new Error('ButtonLink with asChild requires exactly one valid React element child.');
|
|
1363
|
+
}
|
|
1364
|
+
const ChildComponent = child.type;
|
|
1365
|
+
return (_jsx(ChildComponent, { ...child.props, ...props, className: composeClassName(buttonLinkClassName, child.props.className), "data-slot": "button-link", ref: ref, children: child.props.children }));
|
|
892
1366
|
}
|
|
893
|
-
|
|
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 }));
|
|
1367
|
+
return (_jsx("a", { className: buttonLinkClassName, "data-slot": "button-link", ref: ref, ...props, children: children }));
|
|
895
1368
|
};
|
|
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-
|
|
1369
|
+
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" }))] }));
|
|
1370
|
+
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
1371
|
const emptySteps = [];
|
|
899
1372
|
const normalizeStep = (step) => typeof step === 'string' ? { description: undefined, title: step } : step;
|
|
900
1373
|
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 +1378,104 @@ export const Stepper = ({ children, className, currentStep = 0, orientation = 'h
|
|
|
905
1378
|
else if (index === currentStep) {
|
|
906
1379
|
state = 'current';
|
|
907
1380
|
}
|
|
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));
|
|
1381
|
+
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
1382
|
}), children] }));
|
|
910
|
-
export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ...props }) => {
|
|
1383
|
+
export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ref, ...props }) => {
|
|
911
1384
|
const generatedId = useId();
|
|
912
1385
|
const inputId = id ?? `ui-file-upload-${generatedId.replaceAll(':', '')}`;
|
|
913
1386
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
914
|
-
const selectedFileText = selectedFiles.length > 1
|
|
915
|
-
|
|
916
|
-
|
|
1387
|
+
const selectedFileText = selectedFiles.length > 1 ?
|
|
1388
|
+
`${selectedFiles.length} files selected` :
|
|
1389
|
+
(selectedFiles[0] ?? '');
|
|
917
1390
|
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
1391
|
setSelectedFiles([...(event.currentTarget.files ?? [])].map(file => file.name));
|
|
919
1392
|
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 })] }));
|
|
1393
|
+
}, 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
1394
|
};
|
|
922
1395
|
const emptyTourSteps = [];
|
|
923
1396
|
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
1397
|
const emptyAnchorLinks = [];
|
|
925
|
-
|
|
1398
|
+
const createAnchorRef = () => {
|
|
1399
|
+
let cleanup;
|
|
1400
|
+
return (root) => {
|
|
1401
|
+
cleanup?.();
|
|
1402
|
+
cleanup = undefined;
|
|
1403
|
+
if (!root)
|
|
1404
|
+
return;
|
|
1405
|
+
const links = [...root.querySelectorAll('a[href^="#"]')];
|
|
1406
|
+
const targets = links.flatMap(link => {
|
|
1407
|
+
const id = decodeURIComponent(link.getAttribute('href')?.slice(1) ?? '');
|
|
1408
|
+
const target = id ? document.getElementById(id) : null;
|
|
1409
|
+
return target ? [{ link, target }] : [];
|
|
1410
|
+
});
|
|
1411
|
+
if (!targets.length)
|
|
1412
|
+
return;
|
|
1413
|
+
const abortController = new AbortController();
|
|
1414
|
+
let frame = 0;
|
|
1415
|
+
const setActive = (active) => {
|
|
1416
|
+
for (const link of links) {
|
|
1417
|
+
const isActive = link === active;
|
|
1418
|
+
link.dataset.active = String(isActive);
|
|
1419
|
+
if (isActive) {
|
|
1420
|
+
link.setAttribute('aria-current', 'location');
|
|
1421
|
+
}
|
|
1422
|
+
else {
|
|
1423
|
+
link.removeAttribute('aria-current');
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
const update = () => {
|
|
1428
|
+
frame = 0;
|
|
1429
|
+
const scrollingElement = document.scrollingElement ?? document.documentElement;
|
|
1430
|
+
const atEnd = scrollingElement.scrollTop + scrollingElement.clientHeight >=
|
|
1431
|
+
scrollingElement.scrollHeight - 1;
|
|
1432
|
+
const offset = Number(root.dataset.uiAnchorOffset) || 0;
|
|
1433
|
+
let active = targets[0]?.link;
|
|
1434
|
+
if (atEnd) {
|
|
1435
|
+
active = targets.at(-1)?.link;
|
|
1436
|
+
}
|
|
1437
|
+
else {
|
|
1438
|
+
for (const item of targets) {
|
|
1439
|
+
if (item.target.getBoundingClientRect().top > offset)
|
|
1440
|
+
break;
|
|
1441
|
+
active = item.link;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (active)
|
|
1445
|
+
setActive(active);
|
|
1446
|
+
};
|
|
1447
|
+
const requestUpdate = () => {
|
|
1448
|
+
if (frame)
|
|
1449
|
+
return;
|
|
1450
|
+
frame = requestAnimationFrame(update);
|
|
1451
|
+
};
|
|
1452
|
+
for (const { link } of targets) {
|
|
1453
|
+
link.addEventListener('click', () => {
|
|
1454
|
+
setActive(link);
|
|
1455
|
+
}, {
|
|
1456
|
+
signal: abortController.signal
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
window.addEventListener('resize', requestUpdate, {
|
|
1460
|
+
passive: true,
|
|
1461
|
+
signal: abortController.signal
|
|
1462
|
+
});
|
|
1463
|
+
window.addEventListener('scroll', requestUpdate, {
|
|
1464
|
+
passive: true,
|
|
1465
|
+
signal: abortController.signal
|
|
1466
|
+
});
|
|
1467
|
+
update();
|
|
1468
|
+
cleanup = () => {
|
|
1469
|
+
abortController.abort();
|
|
1470
|
+
if (frame)
|
|
1471
|
+
cancelAnimationFrame(frame);
|
|
1472
|
+
};
|
|
1473
|
+
};
|
|
1474
|
+
};
|
|
1475
|
+
export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', activationOffset = 96, children, className, items = emptyAnchorLinks, ...props }) => {
|
|
1476
|
+
const anchorRef = createAnchorRef();
|
|
1477
|
+
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] }));
|
|
1478
|
+
};
|
|
926
1479
|
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
1480
|
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
1481
|
const emptyDescriptionsItems = [];
|
|
@@ -972,13 +1525,93 @@ const flattenTreeSelect = (nodes, depth = 0, acc = []) => {
|
|
|
972
1525
|
};
|
|
973
1526
|
const emptyTreeSelectNodes = [];
|
|
974
1527
|
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,
|
|
1528
|
+
export const Mentions = ({ className, defaultValue = '', label = 'Mentions', name, onValueChange, options = emptyOptions, placeholder, trigger = '@', value, ...props }) => {
|
|
1529
|
+
const generatedId = useId();
|
|
1530
|
+
const inputRef = useRef(null);
|
|
1531
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
1532
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
1533
|
+
const [query, setQuery] = useState(null);
|
|
1534
|
+
const currentValue = value ?? internalValue;
|
|
1535
|
+
const listId = `ui-mentions-${generatedId}-list`;
|
|
1536
|
+
const escapedTrigger = trigger.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1537
|
+
const normalizedOptions = options.map(normalizeOption);
|
|
1538
|
+
const visibleOptions = query === null ?
|
|
1539
|
+
[] :
|
|
1540
|
+
normalizedOptions.filter(option => option.value.toLowerCase().startsWith(query));
|
|
1541
|
+
const expanded = visibleOptions.length > 0;
|
|
1542
|
+
const resolvedActiveIndex = expanded ?
|
|
1543
|
+
Math.min(Math.max(activeIndex, 0), visibleOptions.length - 1) :
|
|
1544
|
+
-1;
|
|
1545
|
+
const activeOption = visibleOptions[resolvedActiveIndex];
|
|
1546
|
+
const setNextValue = (nextValue) => {
|
|
1547
|
+
if (value === undefined)
|
|
1548
|
+
setInternalValue(nextValue);
|
|
1549
|
+
onValueChange?.(nextValue);
|
|
1550
|
+
};
|
|
1551
|
+
const closeList = () => {
|
|
1552
|
+
setQuery(null);
|
|
1553
|
+
setActiveIndex(-1);
|
|
1554
|
+
};
|
|
1555
|
+
const insertMention = (mention) => {
|
|
1556
|
+
const caret = inputRef.current?.selectionStart ?? currentValue.length;
|
|
1557
|
+
const before = currentValue
|
|
1558
|
+
.slice(0, caret)
|
|
1559
|
+
.replace(new RegExp(`${escapedTrigger}\\w*$`), `${trigger}${mention} `);
|
|
1560
|
+
const nextValue = before + currentValue.slice(caret);
|
|
1561
|
+
setNextValue(nextValue);
|
|
1562
|
+
closeList();
|
|
1563
|
+
requestAnimationFrame(() => {
|
|
1564
|
+
inputRef.current?.focus();
|
|
1565
|
+
inputRef.current?.setSelectionRange(before.length, before.length);
|
|
1566
|
+
});
|
|
1567
|
+
};
|
|
1568
|
+
const handleKeyDown = (event) => {
|
|
1569
|
+
if (event.key === 'Escape' && expanded) {
|
|
1570
|
+
event.preventDefault();
|
|
1571
|
+
closeList();
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
if (expanded && (event.key === 'ArrowDown' || event.key === 'ArrowUp' ||
|
|
1575
|
+
event.key === 'End' || event.key === 'Home')) {
|
|
1576
|
+
event.preventDefault();
|
|
1577
|
+
setActiveIndex(current => {
|
|
1578
|
+
if (event.key === 'Home')
|
|
1579
|
+
return 0;
|
|
1580
|
+
if (event.key === 'End')
|
|
1581
|
+
return visibleOptions.length - 1;
|
|
1582
|
+
if (event.key === 'ArrowDown')
|
|
1583
|
+
return (current + 1) % visibleOptions.length;
|
|
1584
|
+
return (current - 1 + visibleOptions.length) % visibleOptions.length;
|
|
1585
|
+
});
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (event.key === 'Enter' && activeOption) {
|
|
1589
|
+
event.preventDefault();
|
|
1590
|
+
insertMention(activeOption.value);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
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 => {
|
|
1594
|
+
const nextValue = event.currentTarget.value;
|
|
1595
|
+
const match = new RegExp(`${escapedTrigger}(\\w*)$`)
|
|
1596
|
+
.exec(nextValue.slice(0, event.currentTarget.selectionStart));
|
|
1597
|
+
const nextQuery = match ? (match[1] ?? '').toLowerCase() : null;
|
|
1598
|
+
setNextValue(nextValue);
|
|
1599
|
+
setQuery(nextQuery);
|
|
1600
|
+
setActiveIndex(nextQuery === null ? -1 : 0);
|
|
1601
|
+
}, 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: () => {
|
|
1602
|
+
insertMention(option.value);
|
|
1603
|
+
}, onMouseDown: event => {
|
|
1604
|
+
event.preventDefault();
|
|
1605
|
+
}, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
|
|
1606
|
+
};
|
|
976
1607
|
export const QRCode = ({ children, className, size = 160, src, style, value, ...props }) => {
|
|
977
1608
|
const qrSvg = !src && value ? renderSVG(value) : null;
|
|
978
|
-
const qrSrc = qrSvg ?
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1609
|
+
const qrSrc = qrSvg ?
|
|
1610
|
+
`data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` :
|
|
1611
|
+
src;
|
|
1612
|
+
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 ?
|
|
1613
|
+
(_jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })) :
|
|
1614
|
+
(children) }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
|
|
982
1615
|
};
|
|
983
1616
|
export const Watermark = ({ children, className, content, gap = 120, rotate = -22, style, text = 'Confidential', ...props }) => {
|
|
984
1617
|
const watermarkText = content ?? text;
|
|
@@ -988,11 +1621,12 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
|
|
|
988
1621
|
'<': '<',
|
|
989
1622
|
'>': '>',
|
|
990
1623
|
'"': '"',
|
|
991
|
-
|
|
1624
|
+
'\'': '''
|
|
992
1625
|
};
|
|
993
1626
|
const escapedWatermarkText = watermarkText.replaceAll(/[&<>"']/g, character => watermarkXmlEntities[character] ?? character);
|
|
994
1627
|
const watermarkSvg = [
|
|
995
|
-
|
|
1628
|
+
'<svg xmlns="http://www.w3.org/2000/svg"',
|
|
1629
|
+
` width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
|
|
996
1630
|
`<text x="${tileSize / 2}" y="${tileSize / 2}" dominant-baseline="middle" text-anchor="middle"`,
|
|
997
1631
|
` transform="rotate(${rotate} ${tileSize / 2} ${tileSize / 2})"`,
|
|
998
1632
|
' fill="black" font-family="Montserrat, Avenir Next, Segoe UI, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
|
|
@@ -1008,10 +1642,20 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
|
|
|
1008
1642
|
export const Affix = ({ className, offset = 0, offsetBottom, offsetTop, position: positionProp, style, ...props }) => {
|
|
1009
1643
|
const position = positionProp ?? (offsetBottom === undefined ? 'top' : 'bottom');
|
|
1010
1644
|
const resolvedOffset = offsetBottom ?? offsetTop ?? offset;
|
|
1011
|
-
return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: {
|
|
1645
|
+
return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: {
|
|
1646
|
+
['--ui-affix-offset']: `${resolvedOffset}px`,
|
|
1647
|
+
...style
|
|
1648
|
+
}, ...props }));
|
|
1012
1649
|
};
|
|
1013
1650
|
const emptySpeedDialActions = [];
|
|
1014
|
-
const speedDialNavigationKeys = [
|
|
1651
|
+
const speedDialNavigationKeys = [
|
|
1652
|
+
'ArrowDown',
|
|
1653
|
+
'ArrowLeft',
|
|
1654
|
+
'ArrowRight',
|
|
1655
|
+
'ArrowUp',
|
|
1656
|
+
'End',
|
|
1657
|
+
'Home'
|
|
1658
|
+
];
|
|
1015
1659
|
const getSpeedDialNavigationIndex = (key, currentIndex, itemCount) => {
|
|
1016
1660
|
if (key === 'Home')
|
|
1017
1661
|
return 0;
|
|
@@ -1060,7 +1704,9 @@ export const SpeedDial = ({ actions = emptySpeedDialActions, children, className
|
|
|
1060
1704
|
if (event.target.closest('[role="menuitem"], button, a'))
|
|
1061
1705
|
close();
|
|
1062
1706
|
}, onKeyDown: event => {
|
|
1063
|
-
const items = [
|
|
1707
|
+
const items = [
|
|
1708
|
+
...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])
|
|
1709
|
+
];
|
|
1064
1710
|
if (event.key === 'Escape') {
|
|
1065
1711
|
event.preventDefault();
|
|
1066
1712
|
close(true);
|