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