@santi020k/lumen-react 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -7
- package/dist/components.d.ts +459 -61
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +806 -163
- package/dist/hooks.d.ts +4 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +289 -139
- package/package.json +5 -5
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');
|
|
@@ -439,23 +586,93 @@ export const DropdownMenuContent = ({ className, onKeyDown, ...props }) => {
|
|
|
439
586
|
return (_jsx("div", { ...menu.panelProps, ...props, className: composeClassName('ui-menu__content', className), onKeyDown: composeHandlers(onKeyDown, menu.panelProps.onKeyDown), role: props.role ?? 'menu' }));
|
|
440
587
|
};
|
|
441
588
|
export const Empty = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-empty', glassClass('ui-empty', glass), className), ...props }));
|
|
442
|
-
export const
|
|
589
|
+
export const ErrorSummary = ({ className, errors, heading = 'There is a problem', id = 'ui-error-summary', ...props }) => {
|
|
590
|
+
const normalizedErrors = normalizeLumenFormErrors(errors);
|
|
591
|
+
const allErrors = [
|
|
592
|
+
...normalizedErrors.form.map(message => ({ message })),
|
|
593
|
+
...normalizedErrors.fields
|
|
594
|
+
];
|
|
595
|
+
const headingId = `${id}-heading`;
|
|
596
|
+
return (_jsxs("section", { "aria-labelledby": headingId, className: composeClassName('ui-error-summary', className), "data-ui-error-summary": true, hidden: allErrors.length === 0, id: id, tabIndex: -1, ...props, children: [_jsx("h2", { id: headingId, children: heading }), _jsx("ul", { children: allErrors.map(error => (_jsx("li", { children: error.controlId ?
|
|
597
|
+
(_jsx("a", { href: `#${error.controlId}`, children: error.message })) :
|
|
598
|
+
(error.message) }, `${error.name ?? 'form'}:${error.controlId ?? ''}:${error.message}`))) })] }));
|
|
599
|
+
};
|
|
600
|
+
export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, invalid = false, ...props }) => {
|
|
443
601
|
const fieldDescribedBy = [ariaDescribedby, describedBy].filter(Boolean).join(' ') || undefined;
|
|
444
|
-
return (_jsx("div", { "aria-describedby": ariaDescribedby, className: composeClassName('ui-field', glassClass('ui-field', glass), className), "data-ui-field": true, "data-ui-field-control": controlId, "data-ui-field-describedby": fieldDescribedBy, ...props }));
|
|
602
|
+
return (_jsx("div", { "aria-describedby": ariaDescribedby, className: composeClassName('ui-field', glassClass('ui-field', glass), className), "data-invalid": invalid ? 'true' : undefined, "data-ui-field": true, "data-ui-field-control": controlId, "data-ui-field-describedby": fieldDescribedBy, ...props }));
|
|
445
603
|
};
|
|
446
|
-
export const
|
|
604
|
+
export const FieldError = ({ children, className, hidden, message, ...props }) => {
|
|
605
|
+
const content = message ?? children;
|
|
606
|
+
return (_jsx("p", { className: composeClassName('ui-field-error', className), "data-ui-field-error": true, hidden: hidden ?? !content, ...props, children: content }));
|
|
607
|
+
};
|
|
608
|
+
export const Form = ({ 'aria-busy': ariaBusy, className, enhance = false, status = 'idle', ...props }) => (_jsx("form", { "aria-busy": ariaBusy ?? (status === 'submitting' ? true : undefined), className: composeClassName('ui-form', className), "data-status": status, "data-ui-form": enhance ? true : undefined, ...props }));
|
|
609
|
+
export const HoverCard = ({ className, glass = false, ...props }) => (_jsx("aside", { className: composeClassName('ui-hover-card', glassSurfaceClass('ui-hover-card', glass), className), "data-surface": resolveSurface(glass), "data-ui-hover-card": true, ...props }));
|
|
447
610
|
export const Icon = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, children, className, decorative, label, name, role, size = 'default', ...props }) => {
|
|
448
611
|
const icon = name ? getLumenIcon(name) : undefined;
|
|
449
|
-
const accessibility = getIconAccessibility({
|
|
612
|
+
const accessibility = getIconAccessibility({
|
|
613
|
+
ariaHidden,
|
|
614
|
+
ariaLabel,
|
|
615
|
+
decorative,
|
|
616
|
+
label,
|
|
617
|
+
role
|
|
618
|
+
});
|
|
450
619
|
return (_jsx("span", { "aria-hidden": accessibility.ariaHidden, "aria-label": accessibility.ariaLabel, className: composeClassName('ui-icon', iconSizeClass(size), className), role: accessibility.role, ...props, children: icon ? renderNamedIcon(icon) : children }));
|
|
451
620
|
};
|
|
452
621
|
export const Input = ({ className, ref, type = 'text', visualSize = 'default', ...props }) => (_jsx("input", { className: composeClassName('ui-input', visualSize === 'sm' && 'ui-input--sm', visualSize === 'lg' && 'ui-input--lg', className), ref: ref, type: type, ...props }));
|
|
622
|
+
export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
|
|
623
|
+
const getPasswordFieldDescribedBy = (ariaDescribedBy, hintId, errorId) => [ariaDescribedBy, hintId, errorId].filter(Boolean).join(' ') || undefined;
|
|
624
|
+
const usePasswordVisibility = (inputRef) => {
|
|
625
|
+
const [visible, setVisible] = useState(false);
|
|
626
|
+
useEffect(() => {
|
|
627
|
+
const form = inputRef.current?.form;
|
|
628
|
+
if (!form)
|
|
629
|
+
return;
|
|
630
|
+
const handleReset = () => {
|
|
631
|
+
setVisible(false);
|
|
632
|
+
};
|
|
633
|
+
const hideAfterSuccess = () => {
|
|
634
|
+
if (form.dataset.status === 'success')
|
|
635
|
+
setVisible(false);
|
|
636
|
+
};
|
|
637
|
+
form.addEventListener('reset', handleReset);
|
|
638
|
+
const observer = new MutationObserver(hideAfterSuccess);
|
|
639
|
+
observer.observe(form, {
|
|
640
|
+
attributeFilter: ['data-status'],
|
|
641
|
+
attributes: true
|
|
642
|
+
});
|
|
643
|
+
return () => {
|
|
644
|
+
form.removeEventListener('reset', handleReset);
|
|
645
|
+
observer.disconnect();
|
|
646
|
+
};
|
|
647
|
+
}, [inputRef]);
|
|
648
|
+
const toggleVisibility = () => {
|
|
649
|
+
setVisible(current => !current);
|
|
650
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
651
|
+
};
|
|
652
|
+
return { toggleVisibility, visible };
|
|
653
|
+
};
|
|
654
|
+
const PasswordFieldControl = ({ controlId, describedBy, error, forwardedRef, hideLabel, inputProps, inputRef, name, showLabel, toggleVisibility, visible }) => (_jsxs("div", { className: "ui-password-field__control", "data-ui-password-field": true, children: [_jsx(Input, { ...inputProps, "aria-describedby": describedBy, "aria-invalid": error ? true : undefined, id: controlId, name: name, ref: node => {
|
|
655
|
+
inputRef.current = node;
|
|
656
|
+
setRefValue(forwardedRef, node);
|
|
657
|
+
}, type: visible ? 'text' : 'password' }), _jsx(Button, { "aria-controls": controlId, "aria-label": visible ? hideLabel : showLabel, "aria-pressed": visible, "data-ui-password-toggle": true, onClick: toggleVisibility, size: "icon", type: "button", variant: "ghost", children: _jsx("span", { "aria-hidden": "true", children: visible ? 'Hide' : 'Show' }) })] }));
|
|
658
|
+
const PasswordFieldMessages = ({ error, errorId, hint, hintId }) => (_jsxs(_Fragment, { children: [hint && (_jsx("p", { className: "ui-field__hint", "data-ui-field-hint": true, id: hintId, children: hint })), _jsx(FieldError, { id: errorId, message: error })] }));
|
|
659
|
+
export const PasswordField = ({ 'aria-describedby': ariaDescribedby, className, error, hideLabel = 'Hide password', hint, id, label = 'Password', name, ref, showLabel = 'Show password', ...props }) => {
|
|
660
|
+
const generatedId = useId();
|
|
661
|
+
const controlId = id ?? `ui-password-${generatedId}`;
|
|
662
|
+
const hintId = hint ? `${controlId}-hint` : undefined;
|
|
663
|
+
const errorId = `${controlId}-error`;
|
|
664
|
+
const describedBy = getPasswordFieldDescribedBy(ariaDescribedby, hintId, error ? errorId : undefined);
|
|
665
|
+
const inputRef = useRef(null);
|
|
666
|
+
const { toggleVisibility, visible } = usePasswordVisibility(inputRef);
|
|
667
|
+
return (_jsxs(Field, { className: composeClassName('ui-password-field', className), controlId: controlId, invalid: Boolean(error), ...(describedBy ? { describedBy } : {}), children: [_jsx(Label, { htmlFor: controlId, children: label }), _jsx(PasswordFieldControl, { controlId: controlId, describedBy: describedBy, error: error, forwardedRef: ref, hideLabel: hideLabel, inputProps: props, inputRef: inputRef, name: name, showLabel: showLabel, toggleVisibility: toggleVisibility, visible: visible }), _jsx(PasswordFieldMessages, { error: error, errorId: errorId, hint: hint, hintId: hintId })] }));
|
|
668
|
+
};
|
|
453
669
|
export const InputGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-input-group', className), ...props }));
|
|
454
|
-
export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', value, ...props }) => {
|
|
670
|
+
export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', ref, value, ...props }) => {
|
|
455
671
|
const otp = useInputOTP({
|
|
456
672
|
defaultValue: toInputValue(defaultValue),
|
|
457
673
|
disabled,
|
|
458
674
|
inputMode,
|
|
675
|
+
inputRef: ref,
|
|
459
676
|
invalid: ariaInvalid === true || ariaInvalid === 'true',
|
|
460
677
|
length,
|
|
461
678
|
pattern,
|
|
@@ -471,17 +688,22 @@ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue,
|
|
|
471
688
|
pattern
|
|
472
689
|
}) }), _jsx("div", { ...otp.segmentsProps, children: otp.segmentIndexes.map(index => (_jsx("button", { ...otp.getSegmentProps(index), type: "button", children: _jsx("span", { "data-ui-input-otp-char": true, children: otp.getSegmentChar(index) }) }, index))) })] }));
|
|
473
690
|
};
|
|
474
|
-
export const NumberField = ({ className, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), type: type, ...props }));
|
|
475
|
-
export const Item = ({ className, glass = false, ...props }) => (
|
|
691
|
+
export const NumberField = ({ className, ref, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), ref: ref, type: type, ...props }));
|
|
692
|
+
export const Item = ({ as = 'div', className, glass = false, ...props }) => createElement(as, {
|
|
693
|
+
className: composeClassName('ui-item', glassClass('ui-item', glass), className),
|
|
694
|
+
'data-slot': 'item',
|
|
695
|
+
...props
|
|
696
|
+
});
|
|
476
697
|
export const Kbd = ({ className, ...props }) => (_jsx("kbd", { className: composeClassName('ui-kbd', className), ...props }));
|
|
477
|
-
export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
|
|
478
698
|
export const Marker = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-marker', variantClass('ui-marker', variant), className), "data-variant": variant, ...props }));
|
|
479
|
-
export const Menubar = ({ className, glass = false,
|
|
699
|
+
export const Menubar = ({ className, glass = false, ...props }) => (_jsx("nav", { className: composeClassName('ui-menubar', glassSurfaceClass('ui-menubar', glass), className), "data-surface": resolveSurface(glass), "data-ui-menubar": true, ...props }));
|
|
480
700
|
export const Message = ({ className, from = 'assistant', ...props }) => (_jsx("article", { className: composeClassName('ui-message', `ui-message--${from}`, className), "data-from": from, ...props }));
|
|
481
701
|
export const MessageScroller = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-message-scroller', glassClass('ui-message-scroller', glass), className), ...props }));
|
|
482
|
-
export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, value, ...props }) => {
|
|
483
|
-
const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ?
|
|
484
|
-
|
|
702
|
+
export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, ref, value, visualSize = 'default', ...props }) => {
|
|
703
|
+
const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ?
|
|
704
|
+
'' :
|
|
705
|
+
undefined;
|
|
706
|
+
return (_jsxs("select", { className: composeClassName('ui-select', visualSize === 'sm' && 'ui-select--sm', visualSize === 'lg' && 'ui-select--lg', className), defaultValue: defaultValue ?? placeholderDefaultValue, ref: ref, value: value, ...props, children: [placeholder && (_jsx("option", { disabled: true, value: "", children: placeholder })), children, options.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }));
|
|
485
707
|
};
|
|
486
708
|
const phoneInputSizeModifiers = (size) => {
|
|
487
709
|
if (size === 'sm') {
|
|
@@ -496,11 +718,11 @@ export const PhoneInput = ({ className, countries = emptyOptions, countryLabel =
|
|
|
496
718
|
const { selectClass, inputClass } = phoneInputSizeModifiers(size);
|
|
497
719
|
return (_jsxs("div", { className: composeClassName('ui-phone-input ui-input-group', className), ...props, children: [_jsx("select", { "aria-label": countryLabel, className: composeClassName('ui-select ui-phone-input__country', selectClass), defaultValue: defaultCountryValue, name: countryName, children: countries.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value))) }), _jsx("input", { autoComplete: "tel", className: composeClassName('ui-input ui-phone-input__number', inputClass), defaultValue: defaultValue, inputMode: "tel", name: name, placeholder: placeholder, type: "tel" })] }));
|
|
498
720
|
};
|
|
499
|
-
export const NavigationMenu = ({ className, glass = false,
|
|
721
|
+
export const NavigationMenu = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-navigation-menu', variant === 'unstyled' && 'ui-navigation-menu--unstyled', glassSurfaceClass('ui-navigation-menu', glass), className), "data-slot": "navigation-menu", "data-surface": resolveSurface(glass), "data-ui-navigation-menu": true, "data-variant": variant, ...props }));
|
|
500
722
|
export const Pagination = ({ 'aria-label': ariaLabel = 'Pagination', className, ...props }) => (_jsx("nav", { "aria-label": ariaLabel, className: composeClassName('ui-pagination', className), ...props }));
|
|
501
|
-
export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open,
|
|
723
|
+
export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, ...props }) => {
|
|
502
724
|
const popover = usePopover({ defaultOpen, onOpenChange, open });
|
|
503
|
-
return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover',
|
|
725
|
+
return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover', glass), className), "data-surface": resolveSurface(glass), "data-ui-popover": true, children: children }) }));
|
|
504
726
|
};
|
|
505
727
|
export const PopoverTrigger = ({ onClick, onKeyDown, ...props }) => {
|
|
506
728
|
const popover = requireContext(useContext(PopoverContext), 'PopoverTrigger');
|
|
@@ -514,7 +736,7 @@ export const Progress = ({ className, max = 100, value = 0, ...props }) => {
|
|
|
514
736
|
const safeMax = max > 0 ? max : 100;
|
|
515
737
|
const safeValue = Math.min(safeMax, Math.max(0, value));
|
|
516
738
|
const percentage = (safeValue / safeMax) * 100;
|
|
517
|
-
return (_jsx("div", { "aria-valuemax": safeMax, "aria-valuemin": 0, "aria-valuenow": safeValue, className: composeClassName('ui-progress', className), role: "progressbar", ...props, children: _jsx("span", { className: "ui-progress__bar", style: { width: `${percentage}%` } }) }));
|
|
739
|
+
return (_jsx("div", { "aria-valuemax": safeMax, "aria-valuemin": 0, "aria-valuenow": safeValue, className: composeClassName('ui-progress', className), role: "progressbar", ...props, children: _jsx("span", { className: "ui-progress__bar", "data-slot": "progress-indicator", style: { width: `${percentage}%` } }) }));
|
|
518
740
|
};
|
|
519
741
|
export const RadioGroup = ({ className, ...props }) => (_jsx("fieldset", { className: composeClassName('ui-radio-group', className), "data-ui-radio-group": true, ...props }));
|
|
520
742
|
const renderResizablePane = (child, index, panelProps) => {
|
|
@@ -556,9 +778,9 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
|
|
|
556
778
|
frame = 0;
|
|
557
779
|
const scrollingElement = document.scrollingElement ?? document.documentElement;
|
|
558
780
|
const maximum = scrollingElement.scrollHeight - scrollingElement.clientHeight;
|
|
559
|
-
const percentage = maximum > 0
|
|
560
|
-
|
|
561
|
-
|
|
781
|
+
const percentage = maximum > 0 ?
|
|
782
|
+
Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100)) :
|
|
783
|
+
0;
|
|
562
784
|
const bar = root.querySelector('.ui-scroll-progress__bar');
|
|
563
785
|
root.setAttribute('aria-valuenow', `${Math.round(percentage)}`);
|
|
564
786
|
if (bar)
|
|
@@ -582,7 +804,7 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
|
|
|
582
804
|
return (_jsx("div", { "aria-label": ariaLabel, "aria-valuemax": 100, "aria-valuemin": 0, "aria-valuenow": 0, className: composeClassName('ui-scroll-progress', position === 'bottom' && 'ui-scroll-progress--bottom', className), "data-position": position, "data-ui-scroll-progress": true, ref: rootRef, role: "progressbar", ...props, children: _jsx("span", { className: "ui-scroll-progress__bar" }) }));
|
|
583
805
|
};
|
|
584
806
|
export const Schedule = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-schedule', glassClass('ui-schedule', glass), className), "data-ui-schedule": true, ...props }));
|
|
585
|
-
export const SearchField = ({ className, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), type: type, ...props }));
|
|
807
|
+
export const SearchField = ({ className, ref, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), ref: ref, type: type, ...props }));
|
|
586
808
|
const getSelectSizeClass = (size) => {
|
|
587
809
|
if (size === 'sm')
|
|
588
810
|
return 'ui-select--sm';
|
|
@@ -590,7 +812,7 @@ const getSelectSizeClass = (size) => {
|
|
|
590
812
|
return 'ui-select--lg';
|
|
591
813
|
return false;
|
|
592
814
|
};
|
|
593
|
-
export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
|
|
815
|
+
export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, inputRef, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
|
|
594
816
|
const select = useSelect({
|
|
595
817
|
defaultValue,
|
|
596
818
|
disabled,
|
|
@@ -603,23 +825,236 @@ export const Select = ({ children, className, defaultValue, disabled = false, gl
|
|
|
603
825
|
value
|
|
604
826
|
});
|
|
605
827
|
const sizeClass = getSelectSizeClass(size);
|
|
606
|
-
return (_jsxs("div", { ...select.rootProps, className: composeClassName('ui-select-field', glassClass('ui-select-field', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsxs("select", { ...select.nativeSelectProps, ...props, className: composeClassName('ui-select ui-select__native', sizeClass), onChange: composeHandlers(onChange, select.nativeSelectProps.onChange), children: [placeholder && _jsx("option", { "data-ui-select-placeholder": true, disabled: true, value: "", children: placeholder }), children, select.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }), _jsxs("div", { ...select.controlProps, className: "ui-select__control", children: [_jsx("button", { ...select.triggerProps, className: composeClassName('ui-select ui-select__trigger', sizeClass), type: select.triggerProps.type ?? 'button', children: _jsx("span", { "data-ui-select-value": true, children: select.triggerText }) }), _jsx("div", { ...select.listProps, className: "ui-select__list", children: select.options.map(option => {
|
|
828
|
+
return (_jsxs("div", { ...select.rootProps, className: composeClassName('ui-select-field', glassClass('ui-select-field', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsxs("select", { ...select.nativeSelectProps, ...props, className: composeClassName('ui-select ui-select__native', sizeClass), onChange: composeHandlers(onChange, select.nativeSelectProps.onChange), ref: inputRef, children: [placeholder && (_jsx("option", { "data-ui-select-placeholder": true, disabled: true, value: "", children: placeholder })), children, select.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }), _jsxs("div", { ...select.controlProps, className: "ui-select__control", children: [_jsx("button", { ...select.triggerProps, className: composeClassName('ui-select ui-select__trigger', sizeClass), type: select.triggerProps.type ?? 'button', children: _jsx("span", { "data-ui-select-value": true, children: select.triggerText }) }), _jsx("div", { ...select.listProps, className: "ui-select__list", children: select.options.map(option => {
|
|
607
829
|
const optionProps = select.getOptionProps(option);
|
|
608
830
|
return (_jsx("button", { ...optionProps, type: optionProps.type ?? 'button', children: option.label }, option.value));
|
|
609
831
|
}) })] })] }));
|
|
610
832
|
};
|
|
833
|
+
const groupListBoxOptions = (options) => {
|
|
834
|
+
const groups = [];
|
|
835
|
+
options.forEach((option, index) => {
|
|
836
|
+
const currentGroup = groups.at(-1);
|
|
837
|
+
if (!currentGroup || currentGroup.label !== option.section) {
|
|
838
|
+
groups.push({
|
|
839
|
+
...(option.section ? { label: option.section } : {}),
|
|
840
|
+
options: [{ ...option, index }]
|
|
841
|
+
});
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
currentGroup.options.push({ ...option, index });
|
|
845
|
+
});
|
|
846
|
+
return groups;
|
|
847
|
+
};
|
|
848
|
+
const getListBoxGroupKey = (group) => {
|
|
849
|
+
const values = group.options.map(option => option.value).join('|');
|
|
850
|
+
return group.label ? `${group.label}:${values}` : values;
|
|
851
|
+
};
|
|
852
|
+
const optionalTrue = (value) => value ? true : undefined;
|
|
853
|
+
const ListBoxNativeOptions = ({ groups }) => groups.map(group => {
|
|
854
|
+
const options = group.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)));
|
|
855
|
+
return group.label ?
|
|
856
|
+
(_jsx("optgroup", { label: group.label, children: options }, getListBoxGroupKey(group))) :
|
|
857
|
+
(_jsx(Fragment, { children: options }, getListBoxGroupKey(group)));
|
|
858
|
+
});
|
|
859
|
+
const ListBoxVisibleOptions = ({ activeIndex, controlId, emptyMessage, groups, loading, onSelect, selectedValues }) => {
|
|
860
|
+
if (loading)
|
|
861
|
+
return null;
|
|
862
|
+
if (!groups.length) {
|
|
863
|
+
return (_jsx("div", { "aria-disabled": "true", className: "ui-list-box__empty", role: "option", children: emptyMessage }));
|
|
864
|
+
}
|
|
865
|
+
return groups.map(group => {
|
|
866
|
+
const groupKey = getListBoxGroupKey(group);
|
|
867
|
+
const safeGroupKey = groupKey.replaceAll(/[^a-zA-Z0-9_-]/g, '-');
|
|
868
|
+
const groupLabelId = group.label ? `${controlId}-group-${safeGroupKey}` : undefined;
|
|
869
|
+
const options = group.options.map(option => (_jsx("div", { "aria-disabled": option.disabled ? true : undefined, "aria-selected": selectedValues.has(option.value), className: composeClassName('ui-list-box__option', option.index === activeIndex && 'ui-list-box__option--active'), "data-active": option.index === activeIndex ? 'true' : undefined, "data-ui-list-box-option": true, "data-value": option.value, onClick: () => {
|
|
870
|
+
onSelect(option.index);
|
|
871
|
+
}, role: "option", children: option.label }, option.value)));
|
|
872
|
+
if (!group.label)
|
|
873
|
+
return _jsx(Fragment, { children: options }, groupKey);
|
|
874
|
+
return (_jsxs("div", { "aria-labelledby": groupLabelId, role: "group", children: [_jsx("div", { className: "ui-list-box__section", id: groupLabelId, children: group.label }), options] }, groupKey));
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
const normalizeListBoxValues = (value) => {
|
|
878
|
+
if (value === undefined)
|
|
879
|
+
return [];
|
|
880
|
+
return Array.isArray(value) ? value : [value];
|
|
881
|
+
};
|
|
882
|
+
const findLastEnabledOptionIndex = (options) => {
|
|
883
|
+
let lastEnabledIndex = -1;
|
|
884
|
+
for (const [index, option] of options.entries()) {
|
|
885
|
+
if (!option.disabled)
|
|
886
|
+
lastEnabledIndex = index;
|
|
887
|
+
}
|
|
888
|
+
return lastEnabledIndex;
|
|
889
|
+
};
|
|
890
|
+
const getListBoxSelectedValues = (controlledValue, internalValues) => controlledValue === undefined ?
|
|
891
|
+
internalValues :
|
|
892
|
+
normalizeListBoxValues(controlledValue);
|
|
893
|
+
const getListBoxSelectValue = (multiple, selectedValues) => (multiple ? selectedValues : (selectedValues[0] ?? ''));
|
|
894
|
+
const ListBoxLabel = ({ controlId, label }) => label ?
|
|
895
|
+
(_jsx("label", { className: "ui-label", htmlFor: controlId, children: label })) :
|
|
896
|
+
null;
|
|
897
|
+
const normalizeListBoxProps = ({ disabled = false, emptyMessage = 'No options available', loading = false, loadingMessage = 'Loading options', multiple = false, options = emptyOptions, required = false, selectionFollowsFocus = false, ...props }) => ({
|
|
898
|
+
disabled,
|
|
899
|
+
emptyMessage,
|
|
900
|
+
loading,
|
|
901
|
+
loadingMessage,
|
|
902
|
+
multiple,
|
|
903
|
+
options,
|
|
904
|
+
required,
|
|
905
|
+
selectionFollowsFocus,
|
|
906
|
+
...props
|
|
907
|
+
});
|
|
908
|
+
export const ListBox = (inputProps) => {
|
|
909
|
+
const { className, defaultValue, disabled, emptyMessage, id, label, loading, loadingMessage, multiple, name, onValueChange, options, ref, required, selectionFollowsFocus, value, ...props } = normalizeListBoxProps(inputProps);
|
|
910
|
+
const generatedId = useId();
|
|
911
|
+
const controlId = id ?? `ui-list-box-${generatedId}`;
|
|
912
|
+
const normalizedOptions = options.map(option => typeof option === 'string' ? { label: option, value: option } : option);
|
|
913
|
+
const optionGroups = groupListBoxOptions(normalizedOptions);
|
|
914
|
+
const [internalValues, setInternalValues] = useState(() => normalizeListBoxValues(defaultValue));
|
|
915
|
+
const selectedValues = getListBoxSelectedValues(value, internalValues);
|
|
916
|
+
const selectedSet = new Set(selectedValues);
|
|
917
|
+
const firstEnabledIndex = Math.max(0, normalizedOptions.findIndex(option => !option.disabled));
|
|
918
|
+
const [activeIndex, setActiveIndex] = useState(firstEnabledIndex);
|
|
919
|
+
const selectRef = useRef(null);
|
|
920
|
+
const typeaheadRef = useRef('');
|
|
921
|
+
const typeaheadTimerRef = useRef(undefined);
|
|
922
|
+
const commitValues = (nextValues) => {
|
|
923
|
+
if (value === undefined) {
|
|
924
|
+
setInternalValues(nextValues);
|
|
925
|
+
}
|
|
926
|
+
onValueChange?.(nextValues);
|
|
927
|
+
const select = selectRef.current;
|
|
928
|
+
if (!select)
|
|
929
|
+
return;
|
|
930
|
+
for (const option of select.options) {
|
|
931
|
+
option.selected = nextValues.includes(option.value);
|
|
932
|
+
}
|
|
933
|
+
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
934
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
935
|
+
};
|
|
936
|
+
const selectOption = (index) => {
|
|
937
|
+
const option = normalizedOptions[index];
|
|
938
|
+
if (!option || option.disabled || disabled || loading)
|
|
939
|
+
return;
|
|
940
|
+
const optionValue = option.value;
|
|
941
|
+
let nextValues;
|
|
942
|
+
if (!multiple) {
|
|
943
|
+
nextValues = [optionValue];
|
|
944
|
+
}
|
|
945
|
+
else if (selectedSet.has(optionValue)) {
|
|
946
|
+
nextValues = selectedValues.filter(selectedValue => selectedValue !== optionValue);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
nextValues = [...selectedValues, optionValue];
|
|
950
|
+
}
|
|
951
|
+
commitValues(nextValues);
|
|
952
|
+
};
|
|
953
|
+
const moveActive = (direction) => {
|
|
954
|
+
if (!normalizedOptions.length || loading)
|
|
955
|
+
return;
|
|
956
|
+
let nextIndex = activeIndex;
|
|
957
|
+
for (const _option of normalizedOptions) {
|
|
958
|
+
nextIndex =
|
|
959
|
+
(nextIndex + direction + normalizedOptions.length) %
|
|
960
|
+
normalizedOptions.length;
|
|
961
|
+
if (!normalizedOptions[nextIndex]?.disabled) {
|
|
962
|
+
setActiveIndex(nextIndex);
|
|
963
|
+
if (selectionFollowsFocus)
|
|
964
|
+
selectOption(nextIndex);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
useEffect(() => () => {
|
|
970
|
+
if (typeaheadTimerRef.current)
|
|
971
|
+
clearTimeout(typeaheadTimerRef.current);
|
|
972
|
+
}, []);
|
|
973
|
+
useEffect(() => {
|
|
974
|
+
const select = selectRef.current;
|
|
975
|
+
const form = select?.form;
|
|
976
|
+
if (!form || value !== undefined)
|
|
977
|
+
return;
|
|
978
|
+
const handleReset = () => {
|
|
979
|
+
setInternalValues(normalizeListBoxValues(defaultValue));
|
|
980
|
+
setActiveIndex(firstEnabledIndex);
|
|
981
|
+
};
|
|
982
|
+
form.addEventListener('reset', handleReset);
|
|
983
|
+
return () => {
|
|
984
|
+
form.removeEventListener('reset', handleReset);
|
|
985
|
+
};
|
|
986
|
+
}, [defaultValue, firstEnabledIndex, value]);
|
|
987
|
+
const handleNavigationKey = (key) => {
|
|
988
|
+
if (key === 'ArrowDown')
|
|
989
|
+
moveActive(1);
|
|
990
|
+
else if (key === 'ArrowUp')
|
|
991
|
+
moveActive(-1);
|
|
992
|
+
else if (key === 'Home') {
|
|
993
|
+
setActiveIndex(firstEnabledIndex);
|
|
994
|
+
if (selectionFollowsFocus)
|
|
995
|
+
selectOption(firstEnabledIndex);
|
|
996
|
+
}
|
|
997
|
+
else if (key === 'End') {
|
|
998
|
+
const lastEnabledIndex = findLastEnabledOptionIndex(normalizedOptions);
|
|
999
|
+
if (lastEnabledIndex >= 0) {
|
|
1000
|
+
setActiveIndex(lastEnabledIndex);
|
|
1001
|
+
if (selectionFollowsFocus)
|
|
1002
|
+
selectOption(lastEnabledIndex);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
else if (key === 'Enter' || key === ' ')
|
|
1006
|
+
selectOption(activeIndex);
|
|
1007
|
+
else
|
|
1008
|
+
return false;
|
|
1009
|
+
return true;
|
|
1010
|
+
};
|
|
1011
|
+
const handleTypeaheadKey = (event) => {
|
|
1012
|
+
if (event.key.length !== 1 ||
|
|
1013
|
+
event.ctrlKey ||
|
|
1014
|
+
event.metaKey ||
|
|
1015
|
+
event.altKey)
|
|
1016
|
+
return false;
|
|
1017
|
+
typeaheadRef.current += event.key.toLowerCase();
|
|
1018
|
+
if (typeaheadTimerRef.current)
|
|
1019
|
+
clearTimeout(typeaheadTimerRef.current);
|
|
1020
|
+
typeaheadTimerRef.current = setTimeout(() => {
|
|
1021
|
+
typeaheadRef.current = '';
|
|
1022
|
+
}, 700);
|
|
1023
|
+
const nextIndex = normalizedOptions.findIndex(option => !option.disabled &&
|
|
1024
|
+
option.label.toLowerCase().startsWith(typeaheadRef.current));
|
|
1025
|
+
if (nextIndex >= 0) {
|
|
1026
|
+
setActiveIndex(nextIndex);
|
|
1027
|
+
if (selectionFollowsFocus)
|
|
1028
|
+
selectOption(nextIndex);
|
|
1029
|
+
}
|
|
1030
|
+
return true;
|
|
1031
|
+
};
|
|
1032
|
+
const handleListKeyDown = (event) => {
|
|
1033
|
+
if (handleNavigationKey(event.key) || handleTypeaheadKey(event))
|
|
1034
|
+
event.preventDefault();
|
|
1035
|
+
};
|
|
1036
|
+
return (_jsxs("div", { className: composeClassName('ui-list-box', className), "data-multiple": multiple ? 'true' : undefined, "data-ui-list-box": true, children: [_jsx(ListBoxLabel, { controlId: controlId, label: label }), _jsx("select", { ...props, className: "ui-list-box__native", "data-ui-enhanced": true, "data-ui-list-box-native": true, disabled: disabled || loading, id: controlId, multiple: multiple, name: name, ref: node => {
|
|
1037
|
+
selectRef.current = node;
|
|
1038
|
+
setRefValue(ref, node);
|
|
1039
|
+
}, required: required, value: getListBoxSelectValue(multiple, selectedValues), children: _jsx(ListBoxNativeOptions, { groups: optionGroups }) }), _jsx("div", { "aria-busy": optionalTrue(loading), "aria-disabled": optionalTrue(disabled || loading), "aria-multiselectable": optionalTrue(multiple), className: "ui-list-box__list", "data-ui-list-box-list": true, onKeyDown: handleListKeyDown, role: "listbox", tabIndex: disabled || loading ? undefined : 0, children: _jsx(ListBoxVisibleOptions, { activeIndex: activeIndex, controlId: controlId, emptyMessage: emptyMessage, groups: optionGroups, loading: loading, onSelect: index => {
|
|
1040
|
+
setActiveIndex(index);
|
|
1041
|
+
selectOption(index);
|
|
1042
|
+
}, selectedValues: selectedSet }) }), loading ?
|
|
1043
|
+
(_jsx("p", { className: "ui-list-box__status", role: "status", children: loadingMessage })) :
|
|
1044
|
+
null] }));
|
|
1045
|
+
};
|
|
611
1046
|
export const Separator = ({ className, orientation = 'horizontal', ...props }) => (_jsx("hr", { "aria-orientation": orientation, className: composeClassName('ui-separator', `ui-separator--${orientation}`, className), "data-orientation": orientation, ...props }));
|
|
612
|
-
export const Sheet = ({ className, glass = false,
|
|
613
|
-
export const Sidebar = ({ className, glass = false,
|
|
1047
|
+
export const Sheet = ({ className, glass = false, ...props }) => (_jsx("dialog", { className: composeClassName('ui-sheet', glassSurfaceClass('ui-sheet', glass), className), "data-surface": resolveSurface(glass), "data-ui-sheet": true, ...props }));
|
|
1048
|
+
export const Sidebar = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-sidebar', variant === 'unstyled' && 'ui-sidebar--unstyled', glassSurfaceClass('ui-sidebar', glass), className), "data-slot": "sidebar", "data-surface": resolveSurface(glass), "data-variant": variant, ...props }));
|
|
614
1049
|
export const Skeleton = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-skeleton', className), ...props }));
|
|
615
|
-
export const Slider = ({ className, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), type: type, ...props }));
|
|
1050
|
+
export const Slider = ({ className, ref, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), ref: ref, type: type, ...props }));
|
|
616
1051
|
export const Sonner = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-sonner', className), "data-ui-sonner": true, ...props }));
|
|
617
1052
|
export const Spinner = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, className, role, ...props }) => {
|
|
618
1053
|
const isAccessible = Boolean(ariaLabel || ariaLabelledby);
|
|
619
1054
|
return (_jsx("span", { "aria-hidden": ariaHidden ?? (isAccessible ? undefined : true), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: composeClassName('ui-spinner', className), role: role ?? (isAccessible ? 'status' : undefined), ...props }));
|
|
620
1055
|
};
|
|
621
|
-
export const Switch = ({ className, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), role: role, type: "checkbox", ...props }));
|
|
622
|
-
export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), ...props }));
|
|
1056
|
+
export const Switch = ({ className, ref, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), ref: ref, role: role, type: "checkbox", ...props }));
|
|
1057
|
+
export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), "data-slot": "table", ...props }));
|
|
623
1058
|
export const Tabs = ({ children, className, defaultValue, glass = false, onValueChange, orientation, value, ...props }) => {
|
|
624
1059
|
const tabs = useTabs({ defaultValue, onValueChange, orientation, value });
|
|
625
1060
|
return (_jsx(TabsContext.Provider, { value: tabs, children: _jsx("div", { ...tabs.rootProps, ...props, className: composeClassName('ui-tabs', glassClass('ui-tabs', glass), className), children: children }) }));
|
|
@@ -649,9 +1084,9 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
|
|
|
649
1084
|
// Storage can be unavailable in privacy modes; tab selection should still work.
|
|
650
1085
|
}
|
|
651
1086
|
}
|
|
652
|
-
return storedValue && items.some(item => item.value === storedValue)
|
|
653
|
-
|
|
654
|
-
|
|
1087
|
+
return storedValue && items.some(item => item.value === storedValue) ?
|
|
1088
|
+
storedValue :
|
|
1089
|
+
(selectedValue ?? '');
|
|
655
1090
|
});
|
|
656
1091
|
useEffect(() => {
|
|
657
1092
|
const synchronize = (event) => {
|
|
@@ -682,13 +1117,15 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
|
|
|
682
1117
|
detail: { storageKey, value: nextValue }
|
|
683
1118
|
}));
|
|
684
1119
|
};
|
|
685
|
-
return (_jsxs(Tabs, { className: composeClassName('ui-code-tabs', className), "data-storage-key": storageKey, onValueChange: selectValue, value: value, ...props, children: [_jsx(TabsList, { "aria-label": ariaLabel, className: "ui-code-tabs__list", children: items.map(item => (_jsx(TabsTrigger, { className: "ui-code-tabs__tab", value: item.value, children: item.label }, item.value))) }), items.map(item => (_jsx(TabsPanel, { className: "ui-code-tabs__panel", value: item.value, children: _jsx(Code, { code: item.code, copy: copy, ...(item.language === undefined ?
|
|
1120
|
+
return (_jsxs(Tabs, { className: composeClassName('ui-code-tabs', className), "data-slot": "code-tabs", "data-storage-key": storageKey, onValueChange: selectValue, value: value, ...props, children: [_jsx(TabsList, { "aria-label": ariaLabel, className: "ui-code-tabs__list", children: items.map(item => (_jsx(TabsTrigger, { className: "ui-code-tabs__tab", value: item.value, children: item.label }, item.value))) }), items.map(item => (_jsx(TabsPanel, { className: "ui-code-tabs__panel", value: item.value, children: _jsx(Code, { code: item.code, copy: copy, ...(item.language === undefined ?
|
|
1121
|
+
{} :
|
|
1122
|
+
{ language: item.language }), theme: theme, variant: "block", wrap: wrap }) }, item.value)))] }));
|
|
686
1123
|
};
|
|
687
1124
|
export const TagGroup = ({ className, role = 'list', ...props }) => (_jsx("div", { className: composeClassName('ui-tag-group', className), role: role, ...props }));
|
|
688
|
-
export const Textarea = ({ className, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), rows: rows, ...props }));
|
|
1125
|
+
export const Textarea = ({ className, ref, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), ref: ref, rows: rows, ...props }));
|
|
689
1126
|
export const ThemeBuilder = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-theme-builder', glassClass('ui-theme-builder', glass), className), "data-ui-theme-builder": true, ...props }));
|
|
690
|
-
export const TimeField = ({ className, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), type: type, ...props }));
|
|
691
|
-
export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role,
|
|
1127
|
+
export const TimeField = ({ className, ref, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), ref: ref, type: type, ...props }));
|
|
1128
|
+
export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role, variant = 'default', ...props }) => (_jsx("aside", { "aria-live": ariaLive ?? (variant === 'destructive' ? 'assertive' : 'polite'), className: composeClassName('ui-toast', variantClass('ui-toast', variant), glassSurfaceClass('ui-toast', glass), className), "data-surface": resolveSurface(glass), "data-ui-toast": true, "data-variant": variant, role: role ?? (variant === 'destructive' ? 'alert' : 'status'), ...props }));
|
|
692
1129
|
export const Toggle = ({ className, controlled = false, pressed = false, type = 'button', ...props }) => (_jsx("button", { "aria-pressed": pressed, className: composeClassName('ui-toggle', className), "data-ui-controlled": controlled || undefined, "data-ui-toggle": true, type: type, ...props }));
|
|
693
1130
|
export const ToggleGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-toggle-group', className), "data-ui-toggle-group": true, ...props }));
|
|
694
1131
|
export const Tooltip = ({ children, className, defaultOpen, delay, onKeyDown, onMouseEnter, onMouseLeave, onOpenChange, open, ...props }) => {
|
|
@@ -717,16 +1154,20 @@ export const Image = ({ as, className, invertOnDark = false, loading, ...props }
|
|
|
717
1154
|
...(resolvedLoading ? { loading: resolvedLoading } : {})
|
|
718
1155
|
});
|
|
719
1156
|
};
|
|
720
|
-
export const Link = ({ className, newTab = false, ref, rel, target, variant = 'default', ...props }) => (_jsx("a", { className: composeClassName('ui-link', variant === 'inherit' && 'ui-link--inherit', className), "data-variant": variant, ref: ref, rel: newTab ?
|
|
1157
|
+
export const Link = ({ className, newTab = false, ref, rel, target, variant = 'default', ...props }) => (_jsx("a", { className: composeClassName('ui-link', variant === 'inherit' && 'ui-link--inherit', className), "data-slot": "link", "data-variant": variant, ref: ref, rel: newTab ?
|
|
1158
|
+
[
|
|
1159
|
+
...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))
|
|
1160
|
+
].join(' ') :
|
|
1161
|
+
rel, target: newTab ? '_blank' : target, ...props }));
|
|
721
1162
|
export const Pill = ({ className, count, children, href, variant = 'neutral', ...props }) => {
|
|
722
1163
|
const content = (_jsxs(_Fragment, { children: [children, count !== undefined && _jsx("span", { className: "ui-pill__count", children: count })] }));
|
|
723
|
-
return href
|
|
724
|
-
|
|
725
|
-
|
|
1164
|
+
return href ?
|
|
1165
|
+
(_jsx("a", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, href: href, ...props, children: content })) :
|
|
1166
|
+
(_jsx("span", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, ...props, children: content }));
|
|
726
1167
|
};
|
|
727
1168
|
export const Prose = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-prose', className), ...props }));
|
|
728
1169
|
export const SkipLink = ({ className, ...props }) => (_jsx("a", { className: composeClassName('ui-skip-link', className), ...props }));
|
|
729
|
-
export const ThemeToggle = ({ className, type = 'button', ...props }) => (_jsxs("button", { className: composeClassName('ui-theme-toggle', className), type: type, ...props, children: [_jsx("span", { className: "ui-sr-only", children: "Toggle color theme" }), _jsx(Icon, { className: "ui-theme-toggle__sun", name: "sun" }), _jsx(Icon, { className: "ui-theme-toggle__moon", name: "moon" })] }));
|
|
1170
|
+
export const ThemeToggle = ({ className, type = 'button', ...props }) => (_jsxs("button", { className: composeClassName('ui-theme-toggle', className), "data-slot": "theme-toggle", type: type, ...props, children: [_jsx("span", { className: "ui-sr-only", children: "Toggle color theme" }), _jsx(Icon, { className: "ui-theme-toggle__sun", name: "sun" }), _jsx(Icon, { className: "ui-theme-toggle__moon", name: "moon" })] }));
|
|
730
1171
|
export const LanguageToggle = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-language-toggle', className), type: type, ...props }));
|
|
731
1172
|
const particleDensityConfig = { high: 40, low: 15, medium: 25 };
|
|
732
1173
|
const particleTones = [
|
|
@@ -759,7 +1200,7 @@ export const Particles = ({ children, className, density = 'medium', ...props })
|
|
|
759
1200
|
// eslint-disable-next-line @eslint-react/set-state-in-effect
|
|
760
1201
|
setParticles(createParticles(density));
|
|
761
1202
|
}, [density]);
|
|
762
|
-
return (_jsxs("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props, children: [children, particles.map(particle => _jsx("span", { className: "ui-particles__particle", style: particle.style }, particle.id))] }));
|
|
1203
|
+
return (_jsxs("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props, children: [children, particles.map(particle => (_jsx("span", { className: "ui-particles__particle", style: particle.style }, particle.id)))] }));
|
|
763
1204
|
};
|
|
764
1205
|
const parseMotionDuration = (value) => {
|
|
765
1206
|
const parsedDuration = Number.parseFloat(value);
|
|
@@ -774,8 +1215,8 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
|
|
|
774
1215
|
if (!root)
|
|
775
1216
|
return;
|
|
776
1217
|
root.dataset.uiScrollRevealBound = 'true';
|
|
777
|
-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
778
|
-
|
|
1218
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
1219
|
+
typeof IntersectionObserver === 'undefined') {
|
|
779
1220
|
root.classList.add('is-revealed');
|
|
780
1221
|
return;
|
|
781
1222
|
}
|
|
@@ -787,9 +1228,14 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
|
|
|
787
1228
|
observer.disconnect();
|
|
788
1229
|
}, { threshold: Math.min(1, Math.max(0, threshold)) });
|
|
789
1230
|
observer.observe(root);
|
|
790
|
-
return () => {
|
|
1231
|
+
return () => {
|
|
1232
|
+
observer.disconnect();
|
|
1233
|
+
};
|
|
791
1234
|
}, [once, threshold]);
|
|
792
|
-
return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: {
|
|
1235
|
+
return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: {
|
|
1236
|
+
...style,
|
|
1237
|
+
'--ui-reveal-delay': `${Math.max(0, delay)}ms`
|
|
1238
|
+
}, ...props }));
|
|
793
1239
|
};
|
|
794
1240
|
export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, duration = 'slow', once = true, stagger = 80, style, threshold = 0.15, ...props }) => {
|
|
795
1241
|
const rootRef = useRef(null);
|
|
@@ -803,8 +1249,8 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
|
|
|
803
1249
|
child.style.setProperty('--ui-reveal-index', String(index));
|
|
804
1250
|
}
|
|
805
1251
|
}
|
|
806
|
-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
807
|
-
|
|
1252
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
1253
|
+
typeof IntersectionObserver === 'undefined') {
|
|
808
1254
|
root.classList.add('is-revealed');
|
|
809
1255
|
return;
|
|
810
1256
|
}
|
|
@@ -816,7 +1262,9 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
|
|
|
816
1262
|
observer.disconnect();
|
|
817
1263
|
}, { threshold: Math.min(1, Math.max(0, threshold)) });
|
|
818
1264
|
observer.observe(root);
|
|
819
|
-
return () => {
|
|
1265
|
+
return () => {
|
|
1266
|
+
observer.disconnect();
|
|
1267
|
+
};
|
|
820
1268
|
}, [once, threshold]);
|
|
821
1269
|
return (_jsx("div", { className: composeClassName('ui-reveal-group', `ui-reveal-group-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-group": true, "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), ref: rootRef, style: {
|
|
822
1270
|
...style,
|
|
@@ -837,8 +1285,8 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
|
|
|
837
1285
|
if (!output)
|
|
838
1286
|
return;
|
|
839
1287
|
const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
|
|
840
|
-
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
841
|
-
|
|
1288
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
1289
|
+
typeof IntersectionObserver === 'undefined') {
|
|
842
1290
|
output.textContent = format(value);
|
|
843
1291
|
return;
|
|
844
1292
|
}
|
|
@@ -850,13 +1298,15 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
|
|
|
850
1298
|
if (!entries.some(entry => entry.isIntersecting))
|
|
851
1299
|
return;
|
|
852
1300
|
observer.disconnect();
|
|
853
|
-
const durationValue = getComputedStyle(root)
|
|
1301
|
+
const durationValue = getComputedStyle(root)
|
|
1302
|
+
.getPropertyValue('--ui-motion-duration')
|
|
1303
|
+
.trim();
|
|
854
1304
|
const durationMs = parseMotionDuration(durationValue);
|
|
855
1305
|
const startedAt = performance.now();
|
|
856
1306
|
const update = (now) => {
|
|
857
1307
|
const progress = Math.min(1, (now - startedAt) / Math.max(1, durationMs));
|
|
858
1308
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
859
|
-
output.textContent = format(from + (
|
|
1309
|
+
output.textContent = format(from + (value - from) * eased);
|
|
860
1310
|
if (progress < 1) {
|
|
861
1311
|
requestAnimationFrame(update);
|
|
862
1312
|
}
|
|
@@ -867,34 +1317,54 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
|
|
|
867
1317
|
requestAnimationFrame(update);
|
|
868
1318
|
}, { threshold: 0.15 });
|
|
869
1319
|
observer.observe(root);
|
|
870
|
-
return () => {
|
|
1320
|
+
return () => {
|
|
1321
|
+
observer.disconnect();
|
|
1322
|
+
};
|
|
871
1323
|
}, [formatter, from, prefix, suffix, value]);
|
|
872
1324
|
return (_jsxs("span", { className: composeClassName('ui-animated-number', `ui-motion-duration-${duration}`, className), "data-ui-animated-number": true, ...props, children: [_jsx("span", { "aria-hidden": "true", "data-ui-animated-number-output": true, ref: outputRef, children: formattedValue }), _jsx("span", { className: "ui-sr-only", children: formattedValue })] }));
|
|
873
1325
|
};
|
|
874
|
-
export const Stat = ({ as, className, label, value, variant = 'default', children, ...props }) => (_jsxs(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant !== 'default' && `ui-stat--${variant}`, className), "data-variant": variant, ...props, uiClassName: "ui-stat", children: [label && _jsx("div", { className: "ui-stat-label", children: label }), value && _jsx("div", { className: "ui-stat-value", children: value }), children] }));
|
|
1326
|
+
export const Stat = ({ as, className, label, value, variant = 'default', children, ...props }) => (_jsxs(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant !== 'default' && `ui-stat--${variant}`, className), "data-slot": "stat", "data-variant": variant, ...props, uiClassName: "ui-stat", children: [label && (_jsx("div", { className: "ui-stat-label", "data-slot": "stat-label", children: label })), value && (_jsx("div", { className: "ui-stat-value", "data-slot": "stat-value", children: value })), children] }));
|
|
1327
|
+
export const StatLabel = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-label", ...props, uiClassName: "ui-stat-label" }));
|
|
1328
|
+
export const StatValue = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-value", ...props, uiClassName: "ui-stat-value" }));
|
|
1329
|
+
export const StatDescription = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-description", ...props, uiClassName: "ui-stat-description" }));
|
|
1330
|
+
export const StatIcon = ({ className, ...props }) => (_jsx("span", { className: composeClassName('ui-stat-icon', className), "data-slot": "stat-icon", ...props }));
|
|
1331
|
+
export const StatTrend = ({ className, tone = 'neutral', ...props }) => (_jsx("span", { className: composeClassName('ui-stat-trend', `ui-stat-trend--${tone}`, className), "data-slot": "stat-trend", "data-tone": tone, ...props }));
|
|
875
1332
|
export const Meter = ({ className, ...props }) => (_jsx("meter", { className: composeClassName('ui-meter', className), ...props }));
|
|
876
1333
|
export const Note = ({ className, label, borderPosition = 'left', children, ...props }) => (_jsxs("div", { className: composeClassName('ui-note', `ui-note--border-${borderPosition}`, className), ...props, children: [label && _jsx("p", { className: "ui-note__label", children: label }), _jsx("div", { className: "ui-note__content", children: children })] }));
|
|
877
1334
|
export const Rating = ({ className, value = 0, max = 5, readonly = false, ...props }) => {
|
|
878
1335
|
const stars = Array.from({ length: max }, (_, i) => i + 1);
|
|
879
|
-
return (_jsx("div", { className: composeClassName('ui-rating', readonly && 'ui-rating--readonly', className), "data-ui-rating": true, "data-value": value, "data-max": max, "data-readonly": readonly ? 'true' : 'false', ...props, children: stars.map(
|
|
1336
|
+
return (_jsx("div", { className: composeClassName('ui-rating', readonly && 'ui-rating--readonly', className), "data-ui-rating": true, "data-value": value, "data-max": max, "data-readonly": readonly ? 'true' : 'false', ...props, children: stars.map(star => (_jsx("button", { type: "button", className: "ui-rating__star", "data-active": star <= value ? 'true' : 'false', disabled: readonly, "aria-label": `Rate ${star} out of ${max}`, children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", className: "ui-icon", children: _jsx("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) }) }, star))) }));
|
|
880
1337
|
};
|
|
881
1338
|
export const Timeline = ({ className, ...props }) => (_jsx("ol", { className: composeClassName('ui-timeline', className), ...props }));
|
|
882
1339
|
export const TimelineItem = ({ className, children, dot, ...props }) => (_jsxs("li", { className: composeClassName('ui-timeline-item', className), ...props, children: [_jsx("div", { className: "ui-timeline-item__tail" }), _jsx("div", { className: "ui-timeline-item__dot", children: dot }), _jsx("div", { className: "ui-timeline-item__content", children: children })] }));
|
|
883
1340
|
export const AnimatedLogo = ({ animation = 'reveal', className, ...props }) => (_jsx("span", { className: composeClassName('ui-animated-logo', className), "data-animation": animation, "data-ui-animated-logo": true, ...props }));
|
|
884
1341
|
export const AnimatedPortrait = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-animated-portrait relative isolate w-full animate-reveal-lcp motion-reduce:animate-none', className), ...props }));
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1342
|
+
const getButtonLinkVariantClass = (variant) => {
|
|
1343
|
+
if (variant === 'unstyled')
|
|
1344
|
+
return 'ui-button-link--unstyled';
|
|
1345
|
+
if (variant === 'inline')
|
|
1346
|
+
return 'ui-button ui-button--inline';
|
|
1347
|
+
return `ui-button ui-button--${variant} min-h-11 rounded-full border`;
|
|
1348
|
+
};
|
|
1349
|
+
export const ButtonLink = ({ asChild = false, children, className, ref, shape = 'default', variant = 'primary', ...props }) => {
|
|
1350
|
+
const variantClass = getButtonLinkVariantClass(variant);
|
|
1351
|
+
const shapeClass = shape === 'icon' && variant !== 'inline' ?
|
|
1352
|
+
'ui-button-link--icon' :
|
|
1353
|
+
undefined;
|
|
1354
|
+
const buttonLinkClassName = composeClassName('ui-button-link', variant !== 'unstyled' &&
|
|
1355
|
+
'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className);
|
|
1356
|
+
if (asChild) {
|
|
1357
|
+
const child = Children.only(children);
|
|
1358
|
+
if (!isValidElement(child)) {
|
|
1359
|
+
throw new Error('ButtonLink with asChild requires exactly one valid React element child.');
|
|
1360
|
+
}
|
|
1361
|
+
const ChildComponent = child.type;
|
|
1362
|
+
return (_jsx(ChildComponent, { ...child.props, ...props, className: composeClassName(buttonLinkClassName, child.props.className), "data-slot": "button-link", ref: ref, children: child.props.children }));
|
|
892
1363
|
}
|
|
893
|
-
|
|
894
|
-
return (_jsx("a", { className: composeClassName('ui-button-link', variant !== 'unstyled' && 'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className), ref: ref, ...props }));
|
|
1364
|
+
return (_jsx("a", { className: buttonLinkClassName, "data-slot": "button-link", ref: ref, ...props, children: children }));
|
|
895
1365
|
};
|
|
896
|
-
export const CoverImage = ({ className, hover = false, showBottomGradient = false, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-cover-image relative overflow-hidden bg-surface-muted', hover && 'ui-cover-image--hover', className), ...props, children: [children, showBottomGradient && _jsx("div", { "aria-hidden": "true", className: "ui-cover-image__bottom-gradient" })] }));
|
|
897
|
-
export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "h-px w-full max-w-5xl bg-
|
|
1366
|
+
export const CoverImage = ({ className, hover = false, showBottomGradient = false, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-cover-image relative overflow-hidden bg-surface-muted', hover && 'ui-cover-image--hover', className), ...props, children: [children, showBottomGradient && (_jsx("div", { "aria-hidden": "true", className: "ui-cover-image__bottom-gradient" }))] }));
|
|
1367
|
+
export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "\n via-brand/40 h-px w-full max-w-5xl bg-linear-to-r from-transparent\n to-transparent\n " }) }));
|
|
898
1368
|
const emptySteps = [];
|
|
899
1369
|
const normalizeStep = (step) => typeof step === 'string' ? { description: undefined, title: step } : step;
|
|
900
1370
|
export const Stepper = ({ children, className, currentStep = 0, orientation = 'horizontal', steps = emptySteps, ...props }) => (_jsxs("ol", { className: composeClassName('ui-stepper', orientation === 'vertical' && 'ui-stepper--vertical', className), "data-orientation": orientation, ...props, children: [steps.map(normalizeStep).map((step, index) => {
|
|
@@ -905,24 +1375,104 @@ export const Stepper = ({ children, className, currentStep = 0, orientation = 'h
|
|
|
905
1375
|
else if (index === currentStep) {
|
|
906
1376
|
state = 'current';
|
|
907
1377
|
}
|
|
908
|
-
return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, className: "ui-stepper__step", "data-state": state, children: [_jsx("span", { className: "ui-stepper__marker", children: index + 1 }), _jsxs("span", { className: "ui-stepper__content", children: [_jsx("span", { className: "ui-stepper__title", children: step.title }), step.description && _jsx("span", { className: "ui-stepper__description", children: step.description })] })] }, step.title));
|
|
1378
|
+
return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, className: "ui-stepper__step", "data-state": state, children: [_jsx("span", { className: "ui-stepper__marker", children: index + 1 }), _jsxs("span", { className: "ui-stepper__content", children: [_jsx("span", { className: "ui-stepper__title", children: step.title }), step.description && (_jsx("span", { className: "ui-stepper__description", children: step.description }))] })] }, step.title));
|
|
909
1379
|
}), children] }));
|
|
910
|
-
export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ...props }) => {
|
|
1380
|
+
export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ref, ...props }) => {
|
|
911
1381
|
const generatedId = useId();
|
|
912
1382
|
const inputId = id ?? `ui-file-upload-${generatedId.replaceAll(':', '')}`;
|
|
913
1383
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
914
|
-
const selectedFileText = selectedFiles.length > 1
|
|
915
|
-
|
|
916
|
-
|
|
1384
|
+
const selectedFileText = selectedFiles.length > 1 ?
|
|
1385
|
+
`${selectedFiles.length} files selected` :
|
|
1386
|
+
(selectedFiles[0] ?? '');
|
|
917
1387
|
return (_jsxs("label", { className: composeClassName('ui-file-upload', className), "data-state": selectedFiles.length > 0 ? 'selected' : 'idle', "data-ui-file-upload": true, htmlFor: inputId, children: [_jsx("input", { className: composeClassName('ui-file-upload__input', inputClassName), "data-ui-file-upload-input": true, id: inputId, onChange: event => {
|
|
918
1388
|
setSelectedFiles([...(event.currentTarget.files ?? [])].map(file => file.name));
|
|
919
1389
|
onChange?.(event);
|
|
920
|
-
}, type: "file", ...props }), _jsxs("svg", { "aria-hidden": "true", className: "ui-file-upload__icon", fill: "none", focusable: "false", viewBox: "0 0 24 24", children: [_jsx("path", { d: "M12 16V4m0 0L8 8m4-4 4 4" }), _jsx("path", { d: "M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" })] }), _jsx("span", { className: "ui-file-upload__prompt", children: children ?? label }), hint && _jsx("span", { className: "ui-file-upload__hint", children: hint }), _jsx("span", { "aria-live": "polite", className: "ui-file-upload__files", "data-ui-file-upload-files": true, children: selectedFileText })] }));
|
|
1390
|
+
}, ref: ref, type: "file", ...props }), _jsxs("svg", { "aria-hidden": "true", className: "ui-file-upload__icon", fill: "none", focusable: "false", viewBox: "0 0 24 24", children: [_jsx("path", { d: "M12 16V4m0 0L8 8m4-4 4 4" }), _jsx("path", { d: "M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" })] }), _jsx("span", { className: "ui-file-upload__prompt", children: children ?? label }), hint && _jsx("span", { className: "ui-file-upload__hint", children: hint }), _jsx("span", { "aria-live": "polite", className: "ui-file-upload__files", "data-ui-file-upload-files": true, children: selectedFileText })] }));
|
|
921
1391
|
};
|
|
922
1392
|
const emptyTourSteps = [];
|
|
923
1393
|
export const Tour = ({ children, className, closeLabel = 'Done', hidden = true, nextLabel = 'Next', steps = emptyTourSteps, ...props }) => (_jsxs("div", { className: composeClassName('ui-tour', className), "data-ui-tour": true, hidden: hidden, ...props, children: [_jsx("div", { "aria-hidden": "true", className: "ui-tour__backdrop", "data-ui-tour-backdrop": true }), _jsxs("div", { className: "ui-tour__popover", "data-ui-tour-popover": true, role: "dialog", children: [steps.map((step, index) => (_jsxs("div", { className: "ui-tour__step", "data-target": step.target, "data-ui-tour-step": true, hidden: index !== 0, children: [step.title && _jsx("p", { className: "ui-tour__title", children: step.title }), _jsx("p", { className: "ui-tour__content", children: step.content }), _jsxs("div", { className: "ui-tour__actions", children: [_jsx("button", { className: "ui-button ui-button--ghost ui-button--sm", "data-ui-tour-close": true, type: "button", children: closeLabel }), index < steps.length - 1 && (_jsx("button", { className: "ui-button ui-button--default ui-button--sm", "data-ui-tour-next": true, type: "button", children: nextLabel }))] })] }, step.target))), children] })] }));
|
|
924
1394
|
const emptyAnchorLinks = [];
|
|
925
|
-
|
|
1395
|
+
const createAnchorRef = () => {
|
|
1396
|
+
let cleanup;
|
|
1397
|
+
return (root) => {
|
|
1398
|
+
cleanup?.();
|
|
1399
|
+
cleanup = undefined;
|
|
1400
|
+
if (!root)
|
|
1401
|
+
return;
|
|
1402
|
+
const links = [...root.querySelectorAll('a[href^="#"]')];
|
|
1403
|
+
const targets = links.flatMap(link => {
|
|
1404
|
+
const id = decodeURIComponent(link.getAttribute('href')?.slice(1) ?? '');
|
|
1405
|
+
const target = id ? document.getElementById(id) : null;
|
|
1406
|
+
return target ? [{ link, target }] : [];
|
|
1407
|
+
});
|
|
1408
|
+
if (!targets.length)
|
|
1409
|
+
return;
|
|
1410
|
+
const abortController = new AbortController();
|
|
1411
|
+
let frame = 0;
|
|
1412
|
+
const setActive = (active) => {
|
|
1413
|
+
for (const link of links) {
|
|
1414
|
+
const isActive = link === active;
|
|
1415
|
+
link.dataset.active = String(isActive);
|
|
1416
|
+
if (isActive) {
|
|
1417
|
+
link.setAttribute('aria-current', 'location');
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
link.removeAttribute('aria-current');
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
const update = () => {
|
|
1425
|
+
frame = 0;
|
|
1426
|
+
const scrollingElement = document.scrollingElement ?? document.documentElement;
|
|
1427
|
+
const atEnd = scrollingElement.scrollTop + scrollingElement.clientHeight >=
|
|
1428
|
+
scrollingElement.scrollHeight - 1;
|
|
1429
|
+
const offset = Number(root.dataset.uiAnchorOffset) || 0;
|
|
1430
|
+
let active = targets[0]?.link;
|
|
1431
|
+
if (atEnd) {
|
|
1432
|
+
active = targets.at(-1)?.link;
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
for (const item of targets) {
|
|
1436
|
+
if (item.target.getBoundingClientRect().top > offset)
|
|
1437
|
+
break;
|
|
1438
|
+
active = item.link;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (active)
|
|
1442
|
+
setActive(active);
|
|
1443
|
+
};
|
|
1444
|
+
const requestUpdate = () => {
|
|
1445
|
+
if (frame)
|
|
1446
|
+
return;
|
|
1447
|
+
frame = requestAnimationFrame(update);
|
|
1448
|
+
};
|
|
1449
|
+
for (const { link } of targets) {
|
|
1450
|
+
link.addEventListener('click', () => {
|
|
1451
|
+
setActive(link);
|
|
1452
|
+
}, {
|
|
1453
|
+
signal: abortController.signal
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
window.addEventListener('resize', requestUpdate, {
|
|
1457
|
+
passive: true,
|
|
1458
|
+
signal: abortController.signal
|
|
1459
|
+
});
|
|
1460
|
+
window.addEventListener('scroll', requestUpdate, {
|
|
1461
|
+
passive: true,
|
|
1462
|
+
signal: abortController.signal
|
|
1463
|
+
});
|
|
1464
|
+
update();
|
|
1465
|
+
cleanup = () => {
|
|
1466
|
+
abortController.abort();
|
|
1467
|
+
if (frame)
|
|
1468
|
+
cancelAnimationFrame(frame);
|
|
1469
|
+
};
|
|
1470
|
+
};
|
|
1471
|
+
};
|
|
1472
|
+
export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', activationOffset = 96, children, className, items = emptyAnchorLinks, ...props }) => {
|
|
1473
|
+
const anchorRef = createAnchorRef();
|
|
1474
|
+
return (_jsxs("nav", { "aria-label": ariaLabel, className: composeClassName('ui-anchor', className), "data-ui-anchor": true, "data-ui-anchor-offset": Math.max(0, activationOffset), ref: anchorRef, ...props, children: [items.length > 0 && (_jsx("ol", { children: items.map((item, index) => (_jsx("li", { children: _jsxs("a", { "aria-current": index === 0 ? 'location' : undefined, "data-active": index === 0 ? 'true' : 'false', "data-depth": item.depth, href: item.href, children: [item.index !== undefined && (_jsx("span", { className: "ui-anchor__index", children: item.index })), _jsxs("span", { className: "ui-anchor__content", children: [_jsx("span", { className: "ui-anchor__label", children: item.label }), item.description && (_jsx("span", { className: "ui-anchor__description", children: item.description }))] })] }) }, item.href))) })), children] }));
|
|
1475
|
+
};
|
|
926
1476
|
export const Segmented = ({ children, className, defaultValue, name = 'segmented', onValueChange, options = emptyOptions, size = 'default', value = defaultValue, ...props }) => (_jsxs("div", { className: composeClassName('ui-segmented', size === 'sm' && 'ui-segmented--sm', size === 'lg' && 'ui-segmented--lg', className), role: "group", ...props, children: [options.map(normalizeOption).map(option => (_jsxs("label", { className: "ui-segmented__option", children: [_jsx("input", { defaultChecked: value === option.value, className: "ui-segmented__input", disabled: option.disabled, name: name, type: "radio", value: option.value, onChange: () => onValueChange?.(option.value) }), _jsx("span", { className: "ui-segmented__label", children: option.label })] }, option.value))), children] }));
|
|
927
1477
|
export const Toolbar = ({ 'aria-label': ariaLabel = 'Toolbar', className, orientation = 'horizontal', ...props }) => (_jsx("div", { "aria-label": ariaLabel, "aria-orientation": orientation, className: composeClassName('ui-toolbar', orientation === 'vertical' && 'ui-toolbar--vertical', className), "data-orientation": orientation, "data-ui-toolbar": true, role: "toolbar", ...props }));
|
|
928
1478
|
const emptyDescriptionsItems = [];
|
|
@@ -972,13 +1522,93 @@ const flattenTreeSelect = (nodes, depth = 0, acc = []) => {
|
|
|
972
1522
|
};
|
|
973
1523
|
const emptyTreeSelectNodes = [];
|
|
974
1524
|
export const TreeSelect = ({ children, className, name, placeholder = 'Select…', treeData = emptyTreeSelectNodes, ...props }) => (_jsxs("div", { className: composeClassName('ui-tree-select', className), "data-surface": "default", "data-ui-popover": true, "data-ui-tree-select": true, ...props, children: [_jsx("button", { "aria-expanded": "false", "aria-haspopup": "tree", className: "ui-select ui-select__trigger ui-tree-select__trigger", "data-ui-trigger": true, type: "button", children: _jsx("span", { "data-ui-tree-select-placeholder": placeholder, "data-ui-tree-select-value": true, children: placeholder }) }), _jsxs("div", { className: "ui-tree-select__panel", "data-ui-panel": true, hidden: true, children: [_jsx("div", { className: "ui-tree-select__tree", role: "tree", children: flattenTreeSelect(treeData).map(row => (_jsx("button", { className: "ui-tree-select__option", "data-value": row.value, role: "treeitem", style: { ['--ui-tree-depth']: row.depth }, type: "button", children: row.label }, row.value))) }), children] }), name && _jsx("input", { "data-ui-tree-select-input": true, name: name, type: "hidden" })] }));
|
|
975
|
-
export const Mentions = ({ className,
|
|
1525
|
+
export const Mentions = ({ className, defaultValue = '', label = 'Mentions', name, onValueChange, options = emptyOptions, placeholder, trigger = '@', value, ...props }) => {
|
|
1526
|
+
const generatedId = useId();
|
|
1527
|
+
const inputRef = useRef(null);
|
|
1528
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
1529
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
1530
|
+
const [query, setQuery] = useState(null);
|
|
1531
|
+
const currentValue = value ?? internalValue;
|
|
1532
|
+
const listId = `ui-mentions-${generatedId}-list`;
|
|
1533
|
+
const escapedTrigger = trigger.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1534
|
+
const normalizedOptions = options.map(normalizeOption);
|
|
1535
|
+
const visibleOptions = query === null ?
|
|
1536
|
+
[] :
|
|
1537
|
+
normalizedOptions.filter(option => option.value.toLowerCase().startsWith(query));
|
|
1538
|
+
const expanded = visibleOptions.length > 0;
|
|
1539
|
+
const resolvedActiveIndex = expanded ?
|
|
1540
|
+
Math.min(Math.max(activeIndex, 0), visibleOptions.length - 1) :
|
|
1541
|
+
-1;
|
|
1542
|
+
const activeOption = visibleOptions[resolvedActiveIndex];
|
|
1543
|
+
const setNextValue = (nextValue) => {
|
|
1544
|
+
if (value === undefined)
|
|
1545
|
+
setInternalValue(nextValue);
|
|
1546
|
+
onValueChange?.(nextValue);
|
|
1547
|
+
};
|
|
1548
|
+
const closeList = () => {
|
|
1549
|
+
setQuery(null);
|
|
1550
|
+
setActiveIndex(-1);
|
|
1551
|
+
};
|
|
1552
|
+
const insertMention = (mention) => {
|
|
1553
|
+
const caret = inputRef.current?.selectionStart ?? currentValue.length;
|
|
1554
|
+
const before = currentValue
|
|
1555
|
+
.slice(0, caret)
|
|
1556
|
+
.replace(new RegExp(`${escapedTrigger}\\w*$`), `${trigger}${mention} `);
|
|
1557
|
+
const nextValue = before + currentValue.slice(caret);
|
|
1558
|
+
setNextValue(nextValue);
|
|
1559
|
+
closeList();
|
|
1560
|
+
requestAnimationFrame(() => {
|
|
1561
|
+
inputRef.current?.focus();
|
|
1562
|
+
inputRef.current?.setSelectionRange(before.length, before.length);
|
|
1563
|
+
});
|
|
1564
|
+
};
|
|
1565
|
+
const handleKeyDown = (event) => {
|
|
1566
|
+
if (event.key === 'Escape' && expanded) {
|
|
1567
|
+
event.preventDefault();
|
|
1568
|
+
closeList();
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
if (expanded && (event.key === 'ArrowDown' || event.key === 'ArrowUp' ||
|
|
1572
|
+
event.key === 'End' || event.key === 'Home')) {
|
|
1573
|
+
event.preventDefault();
|
|
1574
|
+
setActiveIndex(current => {
|
|
1575
|
+
if (event.key === 'Home')
|
|
1576
|
+
return 0;
|
|
1577
|
+
if (event.key === 'End')
|
|
1578
|
+
return visibleOptions.length - 1;
|
|
1579
|
+
if (event.key === 'ArrowDown')
|
|
1580
|
+
return (current + 1) % visibleOptions.length;
|
|
1581
|
+
return (current - 1 + visibleOptions.length) % visibleOptions.length;
|
|
1582
|
+
});
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
if (event.key === 'Enter' && activeOption) {
|
|
1586
|
+
event.preventDefault();
|
|
1587
|
+
insertMention(activeOption.value);
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
return (_jsxs("div", { className: composeClassName('ui-mentions', className), "data-ui-mentions": true, "data-ui-mentions-trigger": trigger, ...props, children: [_jsx("textarea", { "aria-activedescendant": activeOption ? `${listId}-option-${resolvedActiveIndex}` : undefined, "aria-autocomplete": "list", "aria-controls": listId, "aria-expanded": expanded, "aria-label": label, className: "ui-textarea ui-mentions__input", "data-ui-mentions-input": true, name: name, onBlur: closeList, onChange: event => {
|
|
1591
|
+
const nextValue = event.currentTarget.value;
|
|
1592
|
+
const match = new RegExp(`${escapedTrigger}(\\w*)$`)
|
|
1593
|
+
.exec(nextValue.slice(0, event.currentTarget.selectionStart));
|
|
1594
|
+
const nextQuery = match ? (match[1] ?? '').toLowerCase() : null;
|
|
1595
|
+
setNextValue(nextValue);
|
|
1596
|
+
setQuery(nextQuery);
|
|
1597
|
+
setActiveIndex(nextQuery === null ? -1 : 0);
|
|
1598
|
+
}, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputRef, role: "combobox", rows: 3, value: currentValue }), _jsx("ul", { className: "ui-mentions__list", "data-ui-mentions-list": true, hidden: !expanded, id: listId, role: "listbox", children: visibleOptions.map((option, index) => (_jsx("li", { children: _jsx("button", { "aria-selected": index === resolvedActiveIndex, className: "ui-mentions__option", "data-ui-mentions-option": true, "data-value": option.value, id: `${listId}-option-${index}`, onClick: () => {
|
|
1599
|
+
insertMention(option.value);
|
|
1600
|
+
}, onMouseDown: event => {
|
|
1601
|
+
event.preventDefault();
|
|
1602
|
+
}, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
|
|
1603
|
+
};
|
|
976
1604
|
export const QRCode = ({ children, className, size = 160, src, style, value, ...props }) => {
|
|
977
1605
|
const qrSvg = !src && value ? renderSVG(value) : null;
|
|
978
|
-
const qrSrc = qrSvg ?
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1606
|
+
const qrSrc = qrSvg ?
|
|
1607
|
+
`data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` :
|
|
1608
|
+
src;
|
|
1609
|
+
return (_jsxs("figure", { className: composeClassName('ui-qr-code', className), "data-ui-qr-code": true, style: { ['--ui-qr-code-size']: `${size}px`, ...style }, ...props, children: [_jsx("div", { className: "ui-qr-code__frame", children: qrSrc ?
|
|
1610
|
+
(_jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })) :
|
|
1611
|
+
(children) }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
|
|
982
1612
|
};
|
|
983
1613
|
export const Watermark = ({ children, className, content, gap = 120, rotate = -22, style, text = 'Confidential', ...props }) => {
|
|
984
1614
|
const watermarkText = content ?? text;
|
|
@@ -988,11 +1618,12 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
|
|
|
988
1618
|
'<': '<',
|
|
989
1619
|
'>': '>',
|
|
990
1620
|
'"': '"',
|
|
991
|
-
|
|
1621
|
+
'\'': '''
|
|
992
1622
|
};
|
|
993
1623
|
const escapedWatermarkText = watermarkText.replaceAll(/[&<>"']/g, character => watermarkXmlEntities[character] ?? character);
|
|
994
1624
|
const watermarkSvg = [
|
|
995
|
-
|
|
1625
|
+
'<svg xmlns="http://www.w3.org/2000/svg"',
|
|
1626
|
+
` width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
|
|
996
1627
|
`<text x="${tileSize / 2}" y="${tileSize / 2}" dominant-baseline="middle" text-anchor="middle"`,
|
|
997
1628
|
` transform="rotate(${rotate} ${tileSize / 2} ${tileSize / 2})"`,
|
|
998
1629
|
' fill="black" font-family="Montserrat, Avenir Next, Segoe UI, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
|
|
@@ -1008,10 +1639,20 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
|
|
|
1008
1639
|
export const Affix = ({ className, offset = 0, offsetBottom, offsetTop, position: positionProp, style, ...props }) => {
|
|
1009
1640
|
const position = positionProp ?? (offsetBottom === undefined ? 'top' : 'bottom');
|
|
1010
1641
|
const resolvedOffset = offsetBottom ?? offsetTop ?? offset;
|
|
1011
|
-
return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: {
|
|
1642
|
+
return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: {
|
|
1643
|
+
['--ui-affix-offset']: `${resolvedOffset}px`,
|
|
1644
|
+
...style
|
|
1645
|
+
}, ...props }));
|
|
1012
1646
|
};
|
|
1013
1647
|
const emptySpeedDialActions = [];
|
|
1014
|
-
const speedDialNavigationKeys = [
|
|
1648
|
+
const speedDialNavigationKeys = [
|
|
1649
|
+
'ArrowDown',
|
|
1650
|
+
'ArrowLeft',
|
|
1651
|
+
'ArrowRight',
|
|
1652
|
+
'ArrowUp',
|
|
1653
|
+
'End',
|
|
1654
|
+
'Home'
|
|
1655
|
+
];
|
|
1015
1656
|
const getSpeedDialNavigationIndex = (key, currentIndex, itemCount) => {
|
|
1016
1657
|
if (key === 'Home')
|
|
1017
1658
|
return 0;
|
|
@@ -1060,7 +1701,9 @@ export const SpeedDial = ({ actions = emptySpeedDialActions, children, className
|
|
|
1060
1701
|
if (event.target.closest('[role="menuitem"], button, a'))
|
|
1061
1702
|
close();
|
|
1062
1703
|
}, onKeyDown: event => {
|
|
1063
|
-
const items = [
|
|
1704
|
+
const items = [
|
|
1705
|
+
...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])
|
|
1706
|
+
];
|
|
1064
1707
|
if (event.key === 'Escape') {
|
|
1065
1708
|
event.preventDefault();
|
|
1066
1709
|
close(true);
|