@santi020k/lumen-react 0.1.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/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/components.d.ts +634 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +842 -0
- package/dist/hooks.d.ts +378 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +2056 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/package.json +71 -0
- package/styles.css +5 -0
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/* eslint-disable @eslint-react/no-context-provider, @eslint-react/no-use-context -- Lumen React keeps React 18 peer support, so React 19 context shorthand and use() are not available. */
|
|
3
|
+
import { composeClassName, getLumenIcon, lumenCodeTokenClassNames, tokenizeLumenCode } from '@santi020k/lumen-core';
|
|
4
|
+
import { Children, cloneElement, createContext, createElement, Fragment, isValidElement, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { renderSVG } from 'uqr';
|
|
6
|
+
import { useCalendar, useDialog, useDropdownMenu, useInputOTP, usePopover, useResizable, useSelect, useTabs, useTooltip } from './hooks.js';
|
|
7
|
+
const emptyOptions = [];
|
|
8
|
+
const emptyStringOptions = [];
|
|
9
|
+
const emptyDataTableColumns = [];
|
|
10
|
+
const emptyDataTableRows = [];
|
|
11
|
+
const variantClass = (base, variant, defaultVariant = 'default') => variant === defaultVariant ? false : `${base}--${variant}`;
|
|
12
|
+
const iconSizeClass = (size) => size === 'default' ? undefined : `ui-icon--${size}`;
|
|
13
|
+
const reactSvgAttributeNames = {
|
|
14
|
+
'clip-rule': 'clipRule',
|
|
15
|
+
'fill-rule': 'fillRule',
|
|
16
|
+
'stroke-dasharray': 'strokeDasharray',
|
|
17
|
+
'stroke-dashoffset': 'strokeDashoffset',
|
|
18
|
+
'stroke-linecap': 'strokeLinecap',
|
|
19
|
+
'stroke-linejoin': 'strokeLinejoin',
|
|
20
|
+
'stroke-miterlimit': 'strokeMiterlimit',
|
|
21
|
+
'stroke-width': 'strokeWidth',
|
|
22
|
+
tabindex: 'tabIndex'
|
|
23
|
+
};
|
|
24
|
+
const toReactSvgAttributes = (attributes) => Object.fromEntries(Object.entries(attributes).map(([name, value]) => [reactSvgAttributeNames[name] ?? name, value]));
|
|
25
|
+
const renderIconNode = ([tagName, attributes, children], index) => createElement(tagName, {
|
|
26
|
+
...toReactSvgAttributes(attributes),
|
|
27
|
+
key: attributes.key ?? index
|
|
28
|
+
}, children?.map(renderIconNode));
|
|
29
|
+
const getIconAccessibility = ({ ariaHidden, ariaLabel, decorative, label, role }) => {
|
|
30
|
+
const accessibleLabel = label ?? ariaLabel;
|
|
31
|
+
const isDecorative = decorative ?? !accessibleLabel;
|
|
32
|
+
if (isDecorative) {
|
|
33
|
+
return {
|
|
34
|
+
ariaHidden: ariaHidden ?? true,
|
|
35
|
+
ariaLabel: undefined,
|
|
36
|
+
role
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
ariaHidden,
|
|
41
|
+
ariaLabel: accessibleLabel,
|
|
42
|
+
role: role ?? 'img'
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const renderIconSvg = (icon, className) => (_jsx("svg", { "aria-hidden": "true", className: className, fill: "none", focusable: "false", height: "1em", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", width: "1em", xmlns: "http://www.w3.org/2000/svg", children: icon.node.map(renderIconNode) }));
|
|
46
|
+
const renderNamedIcon = (icon) => renderIconSvg(icon, `ui-icon__svg lucide-${icon.name}`);
|
|
47
|
+
const renderLucideIcon = (name, className) => {
|
|
48
|
+
const icon = getLumenIcon(name);
|
|
49
|
+
return icon ? renderIconSvg(icon, className) : null;
|
|
50
|
+
};
|
|
51
|
+
const resolveSurface = (surface, glass) => glass || surface === 'glass' ? 'glass' : surface;
|
|
52
|
+
const glassIntensityClass = (glass) => glass === 'subtle' ? 'ui-glass-subtle' : glass === 'strong' && 'ui-glass-strong';
|
|
53
|
+
const glassSurfaceClass = (base, surface, glass) => resolveSurface(surface, glass) === 'glass' && composeClassName(`${base}--glass`, glassIntensityClass(glass));
|
|
54
|
+
const glassClass = (base, glass) => Boolean(glass) && composeClassName(`${base}--glass`, glassIntensityClass(glass));
|
|
55
|
+
const normalizeOption = (option) => typeof option === 'string' ? { label: option, value: option } : option;
|
|
56
|
+
const isDataTableCellObject = (cell) => typeof cell === 'object' && cell !== null;
|
|
57
|
+
const formatDataTableCell = (cell) => {
|
|
58
|
+
const value = isDataTableCellObject(cell) ? cell.label ?? cell.value : cell;
|
|
59
|
+
return value === undefined || value === null ? '' : String(value);
|
|
60
|
+
};
|
|
61
|
+
const getDataTableSortValue = (cell) => {
|
|
62
|
+
if (!isDataTableCellObject(cell) || cell.sortValue === undefined || cell.sortValue === null)
|
|
63
|
+
return undefined;
|
|
64
|
+
return String(cell.sortValue);
|
|
65
|
+
};
|
|
66
|
+
const getDataTableRowValue = (row, index) => String(row.rowValue ?? row.value ?? row.id ?? index);
|
|
67
|
+
const toInputValue = (value) => {
|
|
68
|
+
if (value === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
return String(value);
|
|
71
|
+
};
|
|
72
|
+
const composeHandlers = (userHandler, lumenHandler) => (event) => {
|
|
73
|
+
userHandler?.(event);
|
|
74
|
+
lumenHandler?.(event);
|
|
75
|
+
};
|
|
76
|
+
const Primitive = ({ as, className, uiClassName, ...props }) => {
|
|
77
|
+
const Tag = as ?? 'div';
|
|
78
|
+
const safeClassName = typeof className === 'string' ? className : undefined;
|
|
79
|
+
return _jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props });
|
|
80
|
+
};
|
|
81
|
+
const DropdownMenuContext = createContext(null);
|
|
82
|
+
const PopoverContext = createContext(null);
|
|
83
|
+
const TabsContext = createContext(null);
|
|
84
|
+
const TooltipContext = createContext(null);
|
|
85
|
+
const requireContext = (context, componentName) => {
|
|
86
|
+
if (!context) {
|
|
87
|
+
throw new Error(`${componentName} must be used inside its matching Lumen root component.`);
|
|
88
|
+
}
|
|
89
|
+
return context;
|
|
90
|
+
};
|
|
91
|
+
export const Accordion = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-accordion', className), ...props }));
|
|
92
|
+
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 }));
|
|
93
|
+
export const AlertDialog = ({ className, defaultOpen, glass = false, onClick, onClose, onOpenChange, open, surface = 'default', ...props }) => {
|
|
94
|
+
const dialog = useDialog({ alert: true, defaultOpen, onOpenChange, open });
|
|
95
|
+
return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog ui-alert-dialog', glassSurfaceClass('ui-dialog', surface, glass), className), "data-surface": resolveSurface(surface, glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
|
|
96
|
+
};
|
|
97
|
+
export const Agenda = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-agenda', glassClass('ui-agenda', glass), className), ...props }));
|
|
98
|
+
export const AspectRatio = ({ className, ratio = '16 / 9', style, ...props }) => (_jsx("div", { className: composeClassName('ui-aspect-ratio', className), style: { aspectRatio: ratio, ...style }, ...props }));
|
|
99
|
+
export const Autocomplete = ({ className, type = 'search', ...props }) => (_jsx("input", { "aria-autocomplete": "list", className: composeClassName('ui-input ui-autocomplete', className), role: "combobox", type: type, ...props }));
|
|
100
|
+
export const Attachment = ({ className, glass = false, href, ...props }) => {
|
|
101
|
+
const nextClassName = composeClassName('ui-attachment', glassClass('ui-attachment', glass), className);
|
|
102
|
+
return href
|
|
103
|
+
? _jsx("a", { className: nextClassName, href: href, ...props })
|
|
104
|
+
: _jsx("article", { className: nextClassName, ...props });
|
|
105
|
+
};
|
|
106
|
+
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] }));
|
|
107
|
+
export const Badge = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-badge', `ui-badge--${variant}`, className), "data-variant": variant, ...props }));
|
|
108
|
+
export const Breadcrumb = ({ 'aria-label': ariaLabel = 'Breadcrumb', className, ...props }) => (_jsx("nav", { "aria-label": ariaLabel, className: composeClassName('ui-breadcrumb', className), ...props }));
|
|
109
|
+
export const Bubble = ({ className, from = 'assistant', glass = false, ...props }) => (_jsx("article", { className: composeClassName('ui-bubble', from === 'user' && 'ui-bubble--user', glassClass('ui-bubble', glass), className), "data-from": from, ...props }));
|
|
110
|
+
export const Button = ({ children, className, disabled, loading = false, size = 'default', type = 'button', variant = 'default', ...props }) => (_jsxs("button", { "aria-busy": loading || undefined, className: composeClassName('ui-button', `ui-button--${variant}`, size === 'default' ? 'ui-button--default-size' : `ui-button--${size}`, disabled && 'ui-button--disabled', loading && 'ui-button--loading', className), disabled: disabled, type: type, ...props, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), children] }));
|
|
111
|
+
export const ButtonGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-button-group', className), ...props }));
|
|
112
|
+
export const Calendar = ({ className, defaultValue, disabled = false, glass = false, locale, max, min, month, name, onValueChange, value, ...props }) => {
|
|
113
|
+
const calendar = useCalendar({
|
|
114
|
+
defaultValue,
|
|
115
|
+
disabled,
|
|
116
|
+
locale,
|
|
117
|
+
max,
|
|
118
|
+
min,
|
|
119
|
+
month,
|
|
120
|
+
name,
|
|
121
|
+
onValueChange,
|
|
122
|
+
value
|
|
123
|
+
});
|
|
124
|
+
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'))) })] })] }));
|
|
125
|
+
};
|
|
126
|
+
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', (glass || variant === 'glass') && composeClassName('ui-card--glass', glassIntensityClass(glass)), className), "data-variant": variant, ...props, uiClassName: "ui-card" }));
|
|
127
|
+
export const Carousel = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-carousel', glassClass('ui-carousel', glass), className), "data-ui-carousel": true, ...props }));
|
|
128
|
+
export const Chart = ({ className, glass = false, ...props }) => (_jsx("figure", { className: composeClassName('ui-chart', glassClass('ui-chart', glass), className), ...props }));
|
|
129
|
+
export const Checkbox = ({ className, ...props }) => (_jsx("input", { className: composeClassName('ui-checkbox', className), type: "checkbox", ...props }));
|
|
130
|
+
export const Collapsible = ({ className, ...props }) => (_jsx("details", { className: composeClassName('ui-collapsible', className), "data-ui-collapsible": true, ...props }));
|
|
131
|
+
const renderCodeToken = (token) => {
|
|
132
|
+
if (!token.kind)
|
|
133
|
+
return token.value;
|
|
134
|
+
return (_jsx("span", { className: lumenCodeTokenClassNames[token.kind], children: token.value }, `${token.start}-${token.kind}`));
|
|
135
|
+
};
|
|
136
|
+
const renderCodeChildren = (code, children, language) => code === undefined ? children : tokenizeLumenCode(code, language).map(renderCodeToken);
|
|
137
|
+
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')] }));
|
|
138
|
+
const renderCodeHeader = ({ copy, label, language }) => {
|
|
139
|
+
if (!copy && !label && !language)
|
|
140
|
+
return null;
|
|
141
|
+
return (_jsxs("figcaption", { className: "ui-code__header", children: [_jsxs("span", { "aria-hidden": "true", className: "ui-code__dots", children: [_jsx("span", { className: "ui-code__dot ui-code__dot--red" }), _jsx("span", { className: "ui-code__dot ui-code__dot--yellow" }), _jsx("span", { className: "ui-code__dot ui-code__dot--green" })] }), _jsxs("span", { className: "ui-code__meta", children: [language && _jsx("span", { className: "ui-code__language", children: language }), label && _jsx("span", { className: "ui-code__label", children: label })] }), copy && renderCodeCopyButton()] }));
|
|
142
|
+
};
|
|
143
|
+
export const Code = ({ children, className, code, copy = false, highlighted = false, label, language, theme = 'auto', variant = 'inline', ...props }) => {
|
|
144
|
+
const codeChildren = renderCodeChildren(code, children, language);
|
|
145
|
+
if (variant === 'block') {
|
|
146
|
+
return (_jsxs("figure", { className: composeClassName('ui-code ui-code--block', className), "data-code-theme": theme, "data-language": language, "data-ui-code": true, ...props, children: [renderCodeHeader({ copy, label, language }), highlighted ? children : _jsx("pre", { children: _jsx("code", { children: codeChildren }) })] }));
|
|
147
|
+
}
|
|
148
|
+
return (_jsx("code", { className: composeClassName('ui-code ui-code--inline', className), "data-code-theme": theme, "data-language": language, ...props, children: code ?? children }));
|
|
149
|
+
};
|
|
150
|
+
export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onChange, onFocus, onKeyDown, options = emptyStringOptions, type = 'text', value: valueProp, wrapperClassName, ...props }) => {
|
|
151
|
+
const inputId = id ?? `${list}-input`;
|
|
152
|
+
const [open, setOpen] = useState(false);
|
|
153
|
+
const [value, setValue] = useState(() => String(defaultValue ?? ''));
|
|
154
|
+
const renderedValue = valueProp ?? value;
|
|
155
|
+
const query = String(renderedValue).trim().toLowerCase();
|
|
156
|
+
const visibleOptions = useMemo(() => options.filter(option => !query || option.toLowerCase().includes(query)), [options, query]);
|
|
157
|
+
const selectOption = (option) => {
|
|
158
|
+
if (valueProp === undefined) {
|
|
159
|
+
setValue(option);
|
|
160
|
+
}
|
|
161
|
+
setOpen(false);
|
|
162
|
+
};
|
|
163
|
+
return (_jsxs("div", { className: composeClassName('ui-combobox', wrapperClassName), "data-ui-combobox": true, onBlur: event => {
|
|
164
|
+
const nextTarget = event.relatedTarget;
|
|
165
|
+
if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
|
|
166
|
+
setOpen(false);
|
|
167
|
+
}
|
|
168
|
+
}, 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 => {
|
|
169
|
+
if (valueProp === undefined) {
|
|
170
|
+
setValue(event.currentTarget.value);
|
|
171
|
+
}
|
|
172
|
+
setOpen(true);
|
|
173
|
+
onChange?.(event);
|
|
174
|
+
}, onFocus: event => {
|
|
175
|
+
setOpen(true);
|
|
176
|
+
onFocus?.(event);
|
|
177
|
+
}, onKeyDown: event => {
|
|
178
|
+
if (event.key === 'Escape') {
|
|
179
|
+
setOpen(false);
|
|
180
|
+
}
|
|
181
|
+
if (event.key === 'Enter' && visibleOptions[0]) {
|
|
182
|
+
event.preventDefault();
|
|
183
|
+
selectOption(visibleOptions[0]);
|
|
184
|
+
}
|
|
185
|
+
onKeyDown?.(event);
|
|
186
|
+
}, ...props }), _jsx("div", { className: "ui-combobox__list", hidden: !open, id: list, role: "listbox", children: visibleOptions.map(option => (_jsx("button", { "data-ui-combobox-option": true, "data-value": option, role: "option", type: "button", onClick: () => { selectOption(option); }, onMouseDown: event => { event.preventDefault(); }, children: option }, option))) })] }));
|
|
187
|
+
};
|
|
188
|
+
export const Command = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-command', glassClass('ui-command', glass), className), "data-ui-command": true, ...props }));
|
|
189
|
+
export const ContextMenu = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("menu", { className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-context-menu": true, role: props.role ?? 'menu', ...props }));
|
|
190
|
+
export const ColorPicker = ({ className, type = 'color', ...props }) => (_jsx("input", { className: composeClassName('ui-color-picker', className), type: type, ...props }));
|
|
191
|
+
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
|
|
192
|
+
? (_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)))) })] }))
|
|
193
|
+
: children }));
|
|
194
|
+
const formatDatePickerDisplayValue = (value, placeholder) => {
|
|
195
|
+
if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
196
|
+
return placeholder;
|
|
197
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
198
|
+
if (Number.isNaN(date.getTime()))
|
|
199
|
+
return placeholder;
|
|
200
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
201
|
+
day: 'numeric',
|
|
202
|
+
month: 'short',
|
|
203
|
+
timeZone: 'UTC',
|
|
204
|
+
year: 'numeric'
|
|
205
|
+
}).format(date);
|
|
206
|
+
};
|
|
207
|
+
const stringifyDatePickerConstraint = (value) => value === undefined ? undefined : String(value);
|
|
208
|
+
/* eslint-disable complexity -- DatePicker coordinates controlled input, disclosure, Calendar, and native form contracts. */
|
|
209
|
+
export const DatePicker = ({ className, defaultValue, disabled, glass = false, id, max, min, name, onChange, onInvalid, onValueChange, placeholder, required, value, ...props }) => {
|
|
210
|
+
const generatedId = useId();
|
|
211
|
+
const datePickerId = id ?? generatedId;
|
|
212
|
+
const triggerId = `${datePickerId}-trigger`;
|
|
213
|
+
const popoverId = `${datePickerId}-popover`;
|
|
214
|
+
const rootRef = useRef(null);
|
|
215
|
+
const inputRef = useRef(null);
|
|
216
|
+
const triggerRef = useRef(null);
|
|
217
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
218
|
+
const [open, setOpen] = useState(false);
|
|
219
|
+
const selectedValue = value ?? internalValue;
|
|
220
|
+
const hasSelectedValue = selectedValue !== '';
|
|
221
|
+
const placeholderText = placeholder ?? 'Choose a date';
|
|
222
|
+
const maxStr = stringifyDatePickerConstraint(max);
|
|
223
|
+
const minStr = stringifyDatePickerConstraint(min);
|
|
224
|
+
const accessibleLabel = props['aria-label'];
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
if (!open)
|
|
227
|
+
return;
|
|
228
|
+
const handlePointerDown = (event) => {
|
|
229
|
+
if (event.target instanceof Node && !rootRef.current?.contains(event.target))
|
|
230
|
+
setOpen(false);
|
|
231
|
+
};
|
|
232
|
+
const handleKeyDown = (event) => {
|
|
233
|
+
if (event.key !== 'Escape')
|
|
234
|
+
return;
|
|
235
|
+
setOpen(false);
|
|
236
|
+
triggerRef.current?.focus();
|
|
237
|
+
};
|
|
238
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
239
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
240
|
+
return () => {
|
|
241
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
242
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
243
|
+
};
|
|
244
|
+
}, [open]);
|
|
245
|
+
const selectDate = (nextValue) => {
|
|
246
|
+
if (value === undefined)
|
|
247
|
+
setInternalValue(nextValue);
|
|
248
|
+
onValueChange?.(nextValue);
|
|
249
|
+
if (inputRef.current) {
|
|
250
|
+
inputRef.current.value = nextValue;
|
|
251
|
+
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
|
|
252
|
+
inputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
|
|
253
|
+
}
|
|
254
|
+
setOpen(false);
|
|
255
|
+
globalThis.setTimeout(() => triggerRef.current?.focus());
|
|
256
|
+
};
|
|
257
|
+
return (_jsxs("div", { className: composeClassName('ui-date-picker-field', glass && 'ui-date-picker-field--glass', glass === 'subtle' && 'ui-glass-subtle', glass === 'strong' && 'ui-glass-strong', className), "data-placeholder": hasSelectedValue ? undefined : 'true', "data-ui-date-picker": true, "data-ui-glass-track": glass ? true : undefined, ref: rootRef, children: [_jsx("input", { "aria-hidden": "true", className: "ui-date-picker ui-date-picker__native", "data-ui-date-picker-native": true, "data-ui-enhanced": "true", disabled: disabled, id: datePickerId, max: max, min: min, name: name, onChange: event => {
|
|
258
|
+
if (value === undefined)
|
|
259
|
+
setInternalValue(event.currentTarget.value);
|
|
260
|
+
onChange?.(event);
|
|
261
|
+
}, onInvalid: event => {
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
triggerRef.current?.setAttribute('aria-invalid', 'true');
|
|
264
|
+
triggerRef.current?.focus();
|
|
265
|
+
onInvalid?.(event);
|
|
266
|
+
}, ref: inputRef, required: required, tabIndex: -1, type: "date", value: selectedValue, ...props }), _jsxs("div", { className: "ui-date-picker__control", "data-ui-date-picker-control": true, children: [_jsxs("button", { "aria-controls": popoverId, "aria-expanded": open, "aria-haspopup": "dialog", "aria-label": accessibleLabel, "aria-required": required ?? undefined, className: "ui-input ui-date-picker ui-date-picker__trigger", "data-ui-date-picker-trigger": true, disabled: disabled, id: triggerId, onClick: () => { setOpen(current => !current); }, onKeyDown: event => {
|
|
267
|
+
if (event.key !== 'ArrowDown')
|
|
268
|
+
return;
|
|
269
|
+
event.preventDefault();
|
|
270
|
+
setOpen(true);
|
|
271
|
+
}, ref: triggerRef, type: "button", children: [_jsx("span", { "data-ui-date-picker-value": true, children: formatDatePickerDisplayValue(selectedValue, placeholderText) }), _jsx("svg", { "aria-hidden": "true", className: "ui-date-picker__icon", fill: "none", viewBox: "0 0 24 24", children: _jsx("path", { d: "M7 2v3M17 2v3M3.5 9h17M5 4h14a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z" }) })] }), _jsx("div", { "aria-label": "Choose date", className: "ui-date-picker__popover", "data-state": open ? 'open' : 'closed', "data-ui-date-picker-popover": true, hidden: !open, id: popoverId, role: "dialog", children: _jsx(Calendar, { disabled: disabled, max: maxStr, min: minStr, onValueChange: selectDate, value: selectedValue }) })] })] }));
|
|
272
|
+
};
|
|
273
|
+
const setDateRangeConstraint = (input, property, value) => {
|
|
274
|
+
if (value)
|
|
275
|
+
input[property] = value;
|
|
276
|
+
else
|
|
277
|
+
input.removeAttribute(property);
|
|
278
|
+
};
|
|
279
|
+
const getDateRangeState = (start, end) => {
|
|
280
|
+
if (!start)
|
|
281
|
+
return 'empty';
|
|
282
|
+
return end ? 'complete' : 'selecting-end';
|
|
283
|
+
};
|
|
284
|
+
const syncDateRangePickerElement = (root) => {
|
|
285
|
+
const inputs = [...root.querySelectorAll('[data-ui-date-picker-native]')];
|
|
286
|
+
const start = inputs[0];
|
|
287
|
+
const end = inputs.at(-1);
|
|
288
|
+
if (!(start && end) || start === end)
|
|
289
|
+
return {};
|
|
290
|
+
const datePickers = root.querySelectorAll('[data-ui-date-picker]');
|
|
291
|
+
datePickers[0]?.setAttribute('data-range-part', 'start');
|
|
292
|
+
datePickers[datePickers.length - 1]?.setAttribute('data-range-part', 'end');
|
|
293
|
+
setDateRangeConstraint(end, 'min', start.value);
|
|
294
|
+
setDateRangeConstraint(start, 'max', end.value);
|
|
295
|
+
root.dataset.rangeState = getDateRangeState(start.value, end.value);
|
|
296
|
+
const range = {};
|
|
297
|
+
if (end.value)
|
|
298
|
+
range.end = end.value;
|
|
299
|
+
if (start.value)
|
|
300
|
+
range.start = start.value;
|
|
301
|
+
return range;
|
|
302
|
+
};
|
|
303
|
+
export const DateRangePicker = ({ className, onInput, onRangeChange, ...props }) => {
|
|
304
|
+
const rootRef = useRef(null);
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
if (rootRef.current)
|
|
307
|
+
syncDateRangePickerElement(rootRef.current);
|
|
308
|
+
});
|
|
309
|
+
return (_jsx("div", { className: composeClassName('ui-date-range-picker', className), "data-ui-date-range-picker": true, onInput: event => {
|
|
310
|
+
onInput?.(event);
|
|
311
|
+
onRangeChange?.(syncDateRangePickerElement(event.currentTarget));
|
|
312
|
+
}, ref: rootRef, ...props }));
|
|
313
|
+
};
|
|
314
|
+
export const Dialog = ({ className, defaultOpen, glass = false, onClick, onClose, onOpenChange, open, surface = 'default', ...props }) => {
|
|
315
|
+
const dialog = useDialog({ defaultOpen, onOpenChange, open });
|
|
316
|
+
return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog', glassSurfaceClass('ui-dialog', surface, glass), className), "data-surface": resolveSurface(surface, glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
|
|
317
|
+
};
|
|
318
|
+
export const Direction = ({ className, dir = 'ltr', ...props }) => (_jsx("div", { className: composeClassName('ui-direction', className), dir: dir, ...props }));
|
|
319
|
+
export const Drawer = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("dialog", { className: composeClassName('ui-drawer', glassSurfaceClass('ui-drawer', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-drawer": true, ...props }));
|
|
320
|
+
export const DropdownMenu = ({ children, className, defaultOpen, glass = false, onOpenChange, open, surface = 'default', ...props }) => {
|
|
321
|
+
const menu = useDropdownMenu({ defaultOpen, onOpenChange, open });
|
|
322
|
+
return (_jsx(DropdownMenuContext.Provider, { value: menu, children: _jsx("menu", { ...menu.rootProps, ...props, className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-dropdown-menu": true, children: children }) }));
|
|
323
|
+
};
|
|
324
|
+
export const DropdownMenuTrigger = ({ onClick, onKeyDown, ...props }) => {
|
|
325
|
+
const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuTrigger');
|
|
326
|
+
return (_jsx("button", { ...menu.triggerProps, ...props, onClick: composeHandlers(onClick, menu.triggerProps.onClick), onKeyDown: composeHandlers(onKeyDown, menu.triggerProps.onKeyDown), type: props.type ?? 'button' }));
|
|
327
|
+
};
|
|
328
|
+
export const DropdownMenuContent = ({ className, onKeyDown, ...props }) => {
|
|
329
|
+
const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuContent');
|
|
330
|
+
return (_jsx("div", { ...menu.panelProps, ...props, className: composeClassName('ui-menu__content', className), onKeyDown: composeHandlers(onKeyDown, menu.panelProps.onKeyDown), role: props.role ?? 'menu' }));
|
|
331
|
+
};
|
|
332
|
+
export const Empty = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-empty', glassClass('ui-empty', glass), className), ...props }));
|
|
333
|
+
export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, ...props }) => {
|
|
334
|
+
const fieldDescribedBy = [ariaDescribedby, describedBy].filter(Boolean).join(' ') || undefined;
|
|
335
|
+
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 }));
|
|
336
|
+
};
|
|
337
|
+
export const HoverCard = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-hover-card', glassSurfaceClass('ui-hover-card', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-hover-card": true, ...props }));
|
|
338
|
+
export const Icon = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, children, className, decorative, label, name, role, size = 'default', ...props }) => {
|
|
339
|
+
const icon = name ? getLumenIcon(name) : undefined;
|
|
340
|
+
const accessibility = getIconAccessibility({ ariaHidden, ariaLabel, decorative, label, role });
|
|
341
|
+
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 }));
|
|
342
|
+
};
|
|
343
|
+
export const Input = ({ className, size = 'default', type = 'text', ...props }) => (_jsx("input", { className: composeClassName('ui-input', size === 'sm' && 'ui-input--sm', size === 'lg' && 'ui-input--lg', className), type: type, ...props }));
|
|
344
|
+
export const InputGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-input-group', className), ...props }));
|
|
345
|
+
export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', value, ...props }) => {
|
|
346
|
+
const otp = useInputOTP({
|
|
347
|
+
defaultValue: toInputValue(defaultValue),
|
|
348
|
+
disabled,
|
|
349
|
+
inputMode,
|
|
350
|
+
invalid: ariaInvalid === true || ariaInvalid === 'true',
|
|
351
|
+
length,
|
|
352
|
+
pattern,
|
|
353
|
+
value: toInputValue(value)
|
|
354
|
+
});
|
|
355
|
+
return (_jsxs("div", { ...otp.rootProps, className: composeClassName(otp.rootProps.className, className), children: [_jsx("input", { ...otp.getInputProps({
|
|
356
|
+
...props,
|
|
357
|
+
'aria-invalid': ariaInvalid,
|
|
358
|
+
className: inputClassName,
|
|
359
|
+
disabled,
|
|
360
|
+
inputMode,
|
|
361
|
+
maxLength,
|
|
362
|
+
pattern
|
|
363
|
+
}) }), _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))) })] }));
|
|
364
|
+
};
|
|
365
|
+
export const NumberField = ({ className, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), type: type, ...props }));
|
|
366
|
+
export const Item = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-item', glassClass('ui-item', glass), className), ...props }));
|
|
367
|
+
export const Kbd = ({ className, ...props }) => (_jsx("kbd", { className: composeClassName('ui-kbd', className), ...props }));
|
|
368
|
+
export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
|
|
369
|
+
export const Marker = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-marker', variantClass('ui-marker', variant), className), "data-variant": variant, ...props }));
|
|
370
|
+
export const Menubar = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-menubar', glassSurfaceClass('ui-menubar', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-menubar": true, ...props }));
|
|
371
|
+
export const Message = ({ className, from = 'assistant', ...props }) => (_jsx("article", { className: composeClassName('ui-message', `ui-message--${from}`, className), "data-from": from, ...props }));
|
|
372
|
+
export const MessageScroller = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-message-scroller', glassClass('ui-message-scroller', glass), className), ...props }));
|
|
373
|
+
export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, value, ...props }) => {
|
|
374
|
+
const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ? '' : undefined;
|
|
375
|
+
return (_jsxs("select", { className: composeClassName('ui-select', className), defaultValue: defaultValue ?? placeholderDefaultValue, value: value, ...props, children: [placeholder && _jsx("option", { disabled: true, value: "", children: placeholder }), children, options.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }));
|
|
376
|
+
};
|
|
377
|
+
const phoneInputSizeModifiers = (size) => {
|
|
378
|
+
if (size === 'sm') {
|
|
379
|
+
return { inputClass: 'ui-input--sm', selectClass: 'ui-select--sm' };
|
|
380
|
+
}
|
|
381
|
+
if (size === 'lg') {
|
|
382
|
+
return { inputClass: 'ui-input--lg', selectClass: 'ui-select--lg' };
|
|
383
|
+
}
|
|
384
|
+
return { inputClass: undefined, selectClass: undefined };
|
|
385
|
+
};
|
|
386
|
+
export const PhoneInput = ({ className, countries = emptyOptions, countryLabel = 'Country code', countryName = 'country', defaultCountryValue, defaultValue, name = 'phone', placeholder = 'Phone number', size = 'default', ...props }) => {
|
|
387
|
+
const { selectClass, inputClass } = phoneInputSizeModifiers(size);
|
|
388
|
+
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" })] }));
|
|
389
|
+
};
|
|
390
|
+
export const NavigationMenu = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-navigation-menu', glassSurfaceClass('ui-navigation-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-navigation-menu": true, ...props }));
|
|
391
|
+
export const Pagination = ({ 'aria-label': ariaLabel = 'Pagination', className, ...props }) => (_jsx("nav", { "aria-label": ariaLabel, className: composeClassName('ui-pagination', className), ...props }));
|
|
392
|
+
export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, surface = 'default', ...props }) => {
|
|
393
|
+
const popover = usePopover({ defaultOpen, onOpenChange, open });
|
|
394
|
+
return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-popover": true, children: children }) }));
|
|
395
|
+
};
|
|
396
|
+
export const PopoverTrigger = ({ onClick, onKeyDown, ...props }) => {
|
|
397
|
+
const popover = requireContext(useContext(PopoverContext), 'PopoverTrigger');
|
|
398
|
+
return (_jsx("button", { ...popover.triggerProps, ...props, onClick: composeHandlers(onClick, popover.triggerProps.onClick), onKeyDown: composeHandlers(onKeyDown, popover.triggerProps.onKeyDown), type: props.type ?? 'button' }));
|
|
399
|
+
};
|
|
400
|
+
export const PopoverPanel = ({ onKeyDown, ...props }) => {
|
|
401
|
+
const popover = requireContext(useContext(PopoverContext), 'PopoverPanel');
|
|
402
|
+
return (_jsx("div", { ...popover.panelProps, ...props, onKeyDown: composeHandlers(onKeyDown, popover.panelProps.onKeyDown) }));
|
|
403
|
+
};
|
|
404
|
+
export const Progress = ({ className, max = 100, value = 0, ...props }) => {
|
|
405
|
+
const safeMax = max > 0 ? max : 100;
|
|
406
|
+
const safeValue = Math.min(safeMax, Math.max(0, value));
|
|
407
|
+
const percentage = (safeValue / safeMax) * 100;
|
|
408
|
+
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}%` } }) }));
|
|
409
|
+
};
|
|
410
|
+
export const RadioGroup = ({ className, ...props }) => (_jsx("fieldset", { className: composeClassName('ui-radio-group', className), "data-ui-radio-group": true, ...props }));
|
|
411
|
+
const renderResizablePane = (child, index, panelProps) => {
|
|
412
|
+
if (isValidElement(child)) {
|
|
413
|
+
const childProps = child.props;
|
|
414
|
+
return cloneElement(child, {
|
|
415
|
+
...panelProps,
|
|
416
|
+
className: composeClassName(childProps.className, panelProps.className),
|
|
417
|
+
style: {
|
|
418
|
+
...childProps.style,
|
|
419
|
+
...panelProps.style
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return _jsx("div", { ...panelProps, children: child });
|
|
424
|
+
};
|
|
425
|
+
export const Resizable = ({ children, className, defaultSizes, direction = 'horizontal', maxSize, minSize, resetOnDoubleClick = true, ...props }) => {
|
|
426
|
+
const panes = Children.toArray(children);
|
|
427
|
+
const resizable = useResizable({
|
|
428
|
+
defaultSizes,
|
|
429
|
+
direction,
|
|
430
|
+
maxSize,
|
|
431
|
+
minSize,
|
|
432
|
+
panelCount: panes.length,
|
|
433
|
+
resetOnDoubleClick
|
|
434
|
+
});
|
|
435
|
+
return (_jsx("div", { ...props, ...resizable.rootProps, className: composeClassName(resizable.rootProps.className, className), children: panes.map((child, index) => (_jsxs(Fragment, { children: [renderResizablePane(child, index, resizable.getPanelProps(index)), index < panes.length - 1 && (_jsx("button", { ...resizable.getHandleProps(index), type: "button" }))] }, index))) }));
|
|
436
|
+
};
|
|
437
|
+
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 }));
|
|
438
|
+
export const ScrollArea = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-scroll-area', glassClass('ui-scroll-area', glass), className), ...props }));
|
|
439
|
+
export const Schedule = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-schedule', glassClass('ui-schedule', glass), className), "data-ui-schedule": true, ...props }));
|
|
440
|
+
export const SearchField = ({ className, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), type: type, ...props }));
|
|
441
|
+
const getSelectSizeClass = (size) => {
|
|
442
|
+
if (size === 'sm')
|
|
443
|
+
return 'ui-select--sm';
|
|
444
|
+
if (size === 'lg')
|
|
445
|
+
return 'ui-select--lg';
|
|
446
|
+
return false;
|
|
447
|
+
};
|
|
448
|
+
export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
|
|
449
|
+
const select = useSelect({
|
|
450
|
+
defaultValue,
|
|
451
|
+
disabled,
|
|
452
|
+
id,
|
|
453
|
+
name,
|
|
454
|
+
onValueChange,
|
|
455
|
+
options,
|
|
456
|
+
placeholder,
|
|
457
|
+
required,
|
|
458
|
+
value
|
|
459
|
+
});
|
|
460
|
+
const sizeClass = getSelectSizeClass(size);
|
|
461
|
+
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 => {
|
|
462
|
+
const optionProps = select.getOptionProps(option);
|
|
463
|
+
return (_jsx("button", { ...optionProps, type: optionProps.type ?? 'button', children: option.label }, option.value));
|
|
464
|
+
}) })] })] }));
|
|
465
|
+
};
|
|
466
|
+
export const Separator = ({ className, orientation = 'horizontal', ...props }) => (_jsx("hr", { "aria-orientation": orientation, className: composeClassName('ui-separator', `ui-separator--${orientation}`, className), "data-orientation": orientation, ...props }));
|
|
467
|
+
export const Sheet = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("dialog", { className: composeClassName('ui-sheet', glassSurfaceClass('ui-sheet', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-sheet": true, ...props }));
|
|
468
|
+
export const Sidebar = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-sidebar', glassSurfaceClass('ui-sidebar', surface, glass), className), "data-surface": resolveSurface(surface, glass), ...props }));
|
|
469
|
+
export const Skeleton = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-skeleton', className), ...props }));
|
|
470
|
+
export const Slider = ({ className, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), type: type, ...props }));
|
|
471
|
+
export const Sonner = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-sonner', className), "data-ui-sonner": true, ...props }));
|
|
472
|
+
export const Spinner = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, className, role, ...props }) => {
|
|
473
|
+
const isAccessible = Boolean(ariaLabel || ariaLabelledby);
|
|
474
|
+
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 }));
|
|
475
|
+
};
|
|
476
|
+
export const Switch = ({ className, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), role: role, type: "checkbox", ...props }));
|
|
477
|
+
export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), ...props }));
|
|
478
|
+
export const Tabs = ({ children, className, defaultValue, glass = false, onValueChange, orientation, value, ...props }) => {
|
|
479
|
+
const tabs = useTabs({ defaultValue, onValueChange, orientation, value });
|
|
480
|
+
return (_jsx(TabsContext.Provider, { value: tabs, children: _jsx("div", { ...tabs.rootProps, ...props, className: composeClassName('ui-tabs', glassClass('ui-tabs', glass), className), children: children }) }));
|
|
481
|
+
};
|
|
482
|
+
export const TabsList = ({ ...props }) => {
|
|
483
|
+
const tabs = requireContext(useContext(TabsContext), 'TabsList');
|
|
484
|
+
return _jsx("div", { ...tabs.listProps, ...props });
|
|
485
|
+
};
|
|
486
|
+
export const TabsTrigger = ({ onClick, onKeyDown, value, ...props }) => {
|
|
487
|
+
const tabs = requireContext(useContext(TabsContext), 'TabsTrigger');
|
|
488
|
+
return (_jsx("button", { ...tabs.getTriggerProps(value, props), onClick: composeHandlers(onClick, tabs.getTriggerProps(value).onClick), onKeyDown: composeHandlers(onKeyDown, tabs.getTriggerProps(value).onKeyDown), type: props.type ?? 'button' }));
|
|
489
|
+
};
|
|
490
|
+
export const TabsPanel = ({ value, ...props }) => {
|
|
491
|
+
const tabs = requireContext(useContext(TabsContext), 'TabsPanel');
|
|
492
|
+
return _jsx("div", { ...tabs.getPanelProps(value, props) });
|
|
493
|
+
};
|
|
494
|
+
export const TagGroup = ({ className, role = 'list', ...props }) => (_jsx("div", { className: composeClassName('ui-tag-group', className), role: role, ...props }));
|
|
495
|
+
export const Textarea = ({ className, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), rows: rows, ...props }));
|
|
496
|
+
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 }));
|
|
497
|
+
export const TimeField = ({ className, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), type: type, ...props }));
|
|
498
|
+
export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role, surface = 'default', variant = 'default', ...props }) => (_jsx("aside", { "aria-live": ariaLive ?? (variant === 'destructive' ? 'assertive' : 'polite'), className: composeClassName('ui-toast', variantClass('ui-toast', variant), glassSurfaceClass('ui-toast', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-toast": true, "data-variant": variant, role: role ?? (variant === 'destructive' ? 'alert' : 'status'), ...props }));
|
|
499
|
+
export const Toggle = ({ className, pressed = false, type = 'button', ...props }) => (_jsx("button", { "aria-pressed": pressed, className: composeClassName('ui-toggle', pressed && 'ui-toggle--pressed', className), "data-ui-toggle": true, type: type, ...props }));
|
|
500
|
+
export const ToggleGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-toggle-group', className), "data-ui-toggle-group": true, ...props }));
|
|
501
|
+
export const Tooltip = ({ children, className, defaultOpen, delay, onKeyDown, onMouseEnter, onMouseLeave, onOpenChange, open, ...props }) => {
|
|
502
|
+
const tooltip = useTooltip({ defaultOpen, delay, onOpenChange, open });
|
|
503
|
+
return (_jsx(TooltipContext.Provider, { value: tooltip, children: _jsx("span", { ...tooltip.rootProps, ...props, className: composeClassName('ui-tooltip', className), onKeyDown: composeHandlers(onKeyDown, tooltip.rootProps.onKeyDown), onMouseEnter: composeHandlers(onMouseEnter, tooltip.rootProps.onMouseEnter), onMouseLeave: composeHandlers(onMouseLeave, tooltip.rootProps.onMouseLeave), children: children }) }));
|
|
504
|
+
};
|
|
505
|
+
export const TooltipContent = ({ ...props }) => {
|
|
506
|
+
const tooltip = requireContext(useContext(TooltipContext), 'TooltipContent');
|
|
507
|
+
return _jsx("span", { ...tooltip.tooltipProps, ...props });
|
|
508
|
+
};
|
|
509
|
+
export const Tree = ({ className, glass = false, role = 'tree', ...props }) => (_jsx("div", { className: composeClassName('ui-tree', glassClass('ui-tree', glass), className), role: role, ...props }));
|
|
510
|
+
export const TreeGrid = ({ className, glass = false, role = 'treegrid', ...props }) => (_jsx("div", { className: composeClassName('ui-tree-grid', glassClass('ui-tree-grid', glass), className), role: role, ...props }));
|
|
511
|
+
export const Typography = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-typography', className), ...props }));
|
|
512
|
+
export const VirtualList = ({ className, glass = false, itemSize, overscan, ...props }) => (_jsx("div", { className: composeClassName('ui-virtual-list', glassClass('ui-virtual-list', glass), className), "data-ui-item-size": itemSize, "data-ui-overscan": overscan, "data-ui-virtual-list": true, ...props }));
|
|
513
|
+
export const BackToTop = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-back-to-top', className), type: type, ...props }));
|
|
514
|
+
export const Callout = ({ className, ...props }) => (_jsx("aside", { className: composeClassName('ui-callout', className), ...props }));
|
|
515
|
+
export const Eyebrow = ({ className, ...props }) => (_jsx("p", { className: composeClassName('ui-eyebrow', className), ...props }));
|
|
516
|
+
export const FloatingBadge = ({ className, ...props }) => (_jsx("span", { className: composeClassName('ui-floating-badge', className), ...props }));
|
|
517
|
+
export const FormattedDate = ({ className, ...props }) => (_jsx("time", { className: composeClassName('ui-formatted-date', className), ...props }));
|
|
518
|
+
export const Image = ({ as, className, invertOnDark = false, loading, ...props }) => {
|
|
519
|
+
const Component = as ?? 'img';
|
|
520
|
+
const resolvedLoading = loading ?? (as ? undefined : 'lazy');
|
|
521
|
+
return createElement(Component, {
|
|
522
|
+
...props,
|
|
523
|
+
className: composeClassName('ui-image', invertOnDark && 'ui-image--invert-dark', className),
|
|
524
|
+
...(resolvedLoading ? { loading: resolvedLoading } : {})
|
|
525
|
+
});
|
|
526
|
+
};
|
|
527
|
+
export const Link = ({ className, ...props }) => (_jsx("a", { className: composeClassName('ui-link', className), ...props }));
|
|
528
|
+
export const Pill = ({ className, count, children, ...props }) => (_jsxs("span", { className: composeClassName('ui-pill', className), ...props, children: [children, count !== undefined && _jsx("span", { className: "ui-pill__count", children: count })] }));
|
|
529
|
+
export const Prose = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-prose', className), ...props }));
|
|
530
|
+
export const SkipLink = ({ className, ...props }) => (_jsx("a", { className: composeClassName('ui-skip-link', className), ...props }));
|
|
531
|
+
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" })] }));
|
|
532
|
+
export const LanguageToggle = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-language-toggle', className), type: type, ...props }));
|
|
533
|
+
export const Particles = ({ className, density = 'medium', ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props }));
|
|
534
|
+
const parseMotionDuration = (value) => {
|
|
535
|
+
const parsedDuration = Number.parseFloat(value);
|
|
536
|
+
if (!Number.isFinite(parsedDuration))
|
|
537
|
+
return 300;
|
|
538
|
+
return value.endsWith('ms') ? parsedDuration : parsedDuration * 1000;
|
|
539
|
+
};
|
|
540
|
+
export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duration = 'slow', once = true, style, threshold = 0.15, ...props }) => {
|
|
541
|
+
const rootRef = useRef(null);
|
|
542
|
+
useEffect(() => {
|
|
543
|
+
const root = rootRef.current;
|
|
544
|
+
if (!root)
|
|
545
|
+
return;
|
|
546
|
+
root.dataset.uiScrollRevealBound = 'true';
|
|
547
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
548
|
+
|| typeof IntersectionObserver === 'undefined') {
|
|
549
|
+
root.classList.add('is-revealed');
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
553
|
+
if (!entry)
|
|
554
|
+
return;
|
|
555
|
+
root.classList.toggle('is-revealed', entry.isIntersecting);
|
|
556
|
+
if (entry.isIntersecting && once)
|
|
557
|
+
observer.disconnect();
|
|
558
|
+
}, { threshold: Math.min(1, Math.max(0, threshold)) });
|
|
559
|
+
observer.observe(root);
|
|
560
|
+
return () => { observer.disconnect(); };
|
|
561
|
+
}, [once, threshold]);
|
|
562
|
+
return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: { ...style, '--ui-reveal-delay': `${Math.max(0, delay)}ms` }, ...props }));
|
|
563
|
+
};
|
|
564
|
+
export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, duration = 'slow', once = true, stagger = 80, style, threshold = 0.15, ...props }) => {
|
|
565
|
+
const rootRef = useRef(null);
|
|
566
|
+
useEffect(() => {
|
|
567
|
+
const root = rootRef.current;
|
|
568
|
+
if (!root)
|
|
569
|
+
return;
|
|
570
|
+
root.dataset.uiScrollRevealBound = 'true';
|
|
571
|
+
for (const [index, child] of [...root.children].entries()) {
|
|
572
|
+
if (child instanceof HTMLElement) {
|
|
573
|
+
child.style.setProperty('--ui-reveal-index', String(index));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
577
|
+
|| typeof IntersectionObserver === 'undefined') {
|
|
578
|
+
root.classList.add('is-revealed');
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
582
|
+
if (!entry)
|
|
583
|
+
return;
|
|
584
|
+
root.classList.toggle('is-revealed', entry.isIntersecting);
|
|
585
|
+
if (entry.isIntersecting && once)
|
|
586
|
+
observer.disconnect();
|
|
587
|
+
}, { threshold: Math.min(1, Math.max(0, threshold)) });
|
|
588
|
+
observer.observe(root);
|
|
589
|
+
return () => { observer.disconnect(); };
|
|
590
|
+
}, [once, threshold]);
|
|
591
|
+
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: {
|
|
592
|
+
...style,
|
|
593
|
+
'--ui-reveal-delay': `${Math.max(0, delay)}ms`,
|
|
594
|
+
'--ui-reveal-stagger': `${Math.max(0, stagger)}ms`
|
|
595
|
+
}, ...props }));
|
|
596
|
+
};
|
|
597
|
+
export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', from = 0, locale, prefix = '', suffix = '', value, ...props }) => {
|
|
598
|
+
const outputRef = useRef(null);
|
|
599
|
+
const safeDecimals = Math.max(0, Math.min(20, Math.floor(decimals)));
|
|
600
|
+
const formatter = useMemo(() => new Intl.NumberFormat(locale, {
|
|
601
|
+
maximumFractionDigits: safeDecimals,
|
|
602
|
+
minimumFractionDigits: safeDecimals
|
|
603
|
+
}), [locale, safeDecimals]);
|
|
604
|
+
const formattedValue = `${prefix}${formatter.format(value)}${suffix}`;
|
|
605
|
+
useEffect(() => {
|
|
606
|
+
const output = outputRef.current;
|
|
607
|
+
if (!output)
|
|
608
|
+
return;
|
|
609
|
+
const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
|
|
610
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
611
|
+
|| typeof IntersectionObserver === 'undefined') {
|
|
612
|
+
output.textContent = format(value);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
output.textContent = format(from);
|
|
616
|
+
const root = output.parentElement;
|
|
617
|
+
if (!root)
|
|
618
|
+
return;
|
|
619
|
+
const observer = new IntersectionObserver(entries => {
|
|
620
|
+
if (!entries.some(entry => entry.isIntersecting))
|
|
621
|
+
return;
|
|
622
|
+
observer.disconnect();
|
|
623
|
+
const durationValue = getComputedStyle(root).getPropertyValue('--ui-motion-duration').trim();
|
|
624
|
+
const durationMs = parseMotionDuration(durationValue);
|
|
625
|
+
const startedAt = performance.now();
|
|
626
|
+
const update = (now) => {
|
|
627
|
+
const progress = Math.min(1, (now - startedAt) / Math.max(1, durationMs));
|
|
628
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
629
|
+
output.textContent = format(from + ((value - from) * eased));
|
|
630
|
+
if (progress < 1) {
|
|
631
|
+
requestAnimationFrame(update);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
output.textContent = format(value);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
requestAnimationFrame(update);
|
|
638
|
+
}, { threshold: 0.15 });
|
|
639
|
+
observer.observe(root);
|
|
640
|
+
return () => { observer.disconnect(); };
|
|
641
|
+
}, [formatter, from, prefix, suffix, value]);
|
|
642
|
+
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 })] }));
|
|
643
|
+
};
|
|
644
|
+
export const Stat = ({ className, label, value, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-stat', className), ...props, children: [label && _jsx("div", { className: "ui-stat-label", children: label }), value && _jsx("div", { className: "ui-stat-value", children: value }), children] }));
|
|
645
|
+
export const Meter = ({ className, ...props }) => (_jsx("meter", { className: composeClassName('ui-meter', className), ...props }));
|
|
646
|
+
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 })] }));
|
|
647
|
+
export const Rating = ({ className, value = 0, max = 5, readonly = false, ...props }) => {
|
|
648
|
+
const stars = Array.from({ length: max }, (_, i) => i + 1);
|
|
649
|
+
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))) }));
|
|
650
|
+
};
|
|
651
|
+
export const Timeline = ({ className, ...props }) => (_jsx("ol", { className: composeClassName('ui-timeline', className), ...props }));
|
|
652
|
+
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 })] }));
|
|
653
|
+
export const AnimatedLogo = ({ animation = 'reveal', className, ...props }) => (_jsx("span", { className: composeClassName('ui-animated-logo', className), "data-animation": animation, "data-ui-animated-logo": true, ...props }));
|
|
654
|
+
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 }));
|
|
655
|
+
export const ButtonLink = ({ className, shape = 'default', variant = 'primary', ...props }) => {
|
|
656
|
+
const variantClass = variant === 'inline'
|
|
657
|
+
? 'ui-button ui-button--inline'
|
|
658
|
+
: `ui-button ui-button--${variant} min-h-11 rounded-full border`;
|
|
659
|
+
const shapeClass = shape === 'icon' && variant !== 'inline' ? 'ui-button-link--icon' : undefined;
|
|
660
|
+
return (_jsx("a", { className: composeClassName('ui-button-link group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className), ...props }));
|
|
661
|
+
};
|
|
662
|
+
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" })] }));
|
|
663
|
+
export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "h-px w-full max-w-5xl bg-gradient-to-r from-transparent via-brand/40 to-transparent" }) }));
|
|
664
|
+
const emptySteps = [];
|
|
665
|
+
const normalizeStep = (step) => typeof step === 'string' ? { description: undefined, title: step } : step;
|
|
666
|
+
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) => {
|
|
667
|
+
let state = 'upcoming';
|
|
668
|
+
if (index < currentStep) {
|
|
669
|
+
state = 'complete';
|
|
670
|
+
}
|
|
671
|
+
else if (index === currentStep) {
|
|
672
|
+
state = 'current';
|
|
673
|
+
}
|
|
674
|
+
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));
|
|
675
|
+
}), children] }));
|
|
676
|
+
export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ...props }) => {
|
|
677
|
+
const generatedId = useId();
|
|
678
|
+
const inputId = id ?? `ui-file-upload-${generatedId.replaceAll(':', '')}`;
|
|
679
|
+
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
680
|
+
const selectedFileText = selectedFiles.length > 1
|
|
681
|
+
? `${selectedFiles.length} files selected`
|
|
682
|
+
: selectedFiles[0] ?? '';
|
|
683
|
+
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 => {
|
|
684
|
+
setSelectedFiles([...(event.currentTarget.files ?? [])].map(file => file.name));
|
|
685
|
+
onChange?.(event);
|
|
686
|
+
}, 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 })] }));
|
|
687
|
+
};
|
|
688
|
+
const emptyTourSteps = [];
|
|
689
|
+
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] })] }));
|
|
690
|
+
const emptyAnchorLinks = [];
|
|
691
|
+
export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', children, className, items = emptyAnchorLinks, ...props }) => (_jsxs("nav", { "aria-label": ariaLabel, className: composeClassName('ui-anchor', className), "data-ui-anchor": true, ...props, children: [items.length > 0 && (_jsx("ol", { children: items.map((item, index) => (_jsx("li", { children: _jsx("a", { "data-active": index === 0 ? 'true' : 'false', href: item.href, children: item.label }) }, item.href))) })), children] }));
|
|
692
|
+
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] }));
|
|
693
|
+
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 }));
|
|
694
|
+
const emptyDescriptionsItems = [];
|
|
695
|
+
export const Descriptions = ({ children, className, columns = 1, items = emptyDescriptionsItems, style, ...props }) => (_jsxs("dl", { className: composeClassName('ui-descriptions', className), "data-columns": columns, style: { ['--ui-descriptions-columns']: columns, ...style }, ...props, children: [items.map((item, index) => (_jsxs("div", { className: "ui-descriptions__item", children: [_jsx("dt", { className: "ui-descriptions__term", children: item.label }), _jsx("dd", { className: "ui-descriptions__detail", children: item.value })] }, index))), children] }));
|
|
696
|
+
export const Popconfirm = ({ cancelLabel = 'Cancel', children, className, confirmLabel = 'Confirm', title = 'Are you sure?', ...props }) => (_jsxs("div", { className: composeClassName('ui-popover ui-popconfirm', className), "data-surface": "default", "data-ui-popconfirm": true, "data-ui-popover": true, ...props, children: [_jsx("span", { "aria-expanded": "false", "aria-haspopup": "dialog", className: "ui-popconfirm__trigger", "data-ui-trigger": true, children: children }), _jsxs("div", { className: "ui-popover__panel ui-popconfirm__panel", "data-ui-panel": true, hidden: true, role: "dialog", children: [_jsx("p", { className: "ui-popconfirm__message", children: title }), _jsxs("div", { className: "ui-popconfirm__actions", children: [_jsx("button", { className: "ui-button ui-button--ghost ui-button--sm", "data-ui-popconfirm-cancel": true, type: "button", children: cancelLabel }), _jsx("button", { className: "ui-button ui-button--destructive ui-button--sm", "data-ui-popconfirm-confirm": true, type: "button", children: confirmLabel })] })] })] }));
|
|
697
|
+
const emptyTransferItems = [];
|
|
698
|
+
const renderTransferItems = (items) => items.map(item => (_jsx("li", { className: "ui-transfer__item", children: _jsxs("label", { className: "ui-transfer__option", children: [_jsx("input", { className: "ui-checkbox", "data-ui-transfer-item": true, type: "checkbox", value: item.key }), _jsx("span", { children: item.label })] }) }, item.key)));
|
|
699
|
+
export const Transfer = ({ className, items = emptyTransferItems, name, sourceTitle = 'Available', targetItems = emptyTransferItems, targetTitle = 'Selected', ...props }) => (_jsxs("div", { className: composeClassName('ui-transfer', className), "data-ui-transfer": true, "data-ui-transfer-name": name, ...props, children: [_jsxs("div", { className: "ui-transfer__panel", children: [_jsx("p", { className: "ui-transfer__title", children: sourceTitle }), _jsx("ul", { className: "ui-transfer__list", "data-side": "source", "data-ui-transfer-list": true, children: renderTransferItems(items) })] }), _jsxs("div", { className: "ui-transfer__controls", children: [_jsx("button", { "aria-label": `Move to ${targetTitle}`, className: "ui-button ui-button--outline ui-button--icon", "data-ui-transfer-move": "target", type: "button", children: _jsx(Icon, { name: "chevron-right" }) }), _jsx("button", { "aria-label": `Move to ${sourceTitle}`, className: "ui-button ui-button--outline ui-button--icon", "data-ui-transfer-move": "source", type: "button", children: _jsx(Icon, { name: "chevron-left" }) })] }), _jsxs("div", { className: "ui-transfer__panel", children: [_jsx("p", { className: "ui-transfer__title", children: targetTitle }), _jsx("ul", { className: "ui-transfer__list", "data-side": "target", "data-ui-transfer-list": true, children: renderTransferItems(targetItems) })] })] }));
|
|
700
|
+
const buildCascaderColumns = (options, idPrefix) => {
|
|
701
|
+
const columns = [];
|
|
702
|
+
let count = 0;
|
|
703
|
+
const build = (items, root) => {
|
|
704
|
+
count += 1;
|
|
705
|
+
const id = `${idPrefix}-column-${count}`;
|
|
706
|
+
const rendered = items.map(item => ({
|
|
707
|
+
label: item.label,
|
|
708
|
+
value: item.value
|
|
709
|
+
}));
|
|
710
|
+
columns.push({ id, options: rendered, root });
|
|
711
|
+
for (const [index, item] of items.entries()) {
|
|
712
|
+
const renderedOption = rendered[index];
|
|
713
|
+
if (item.children && item.children.length > 0 && renderedOption) {
|
|
714
|
+
renderedOption.nextId = build(item.children, false);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return id;
|
|
718
|
+
};
|
|
719
|
+
if (options.length > 0)
|
|
720
|
+
build(options, true);
|
|
721
|
+
return columns;
|
|
722
|
+
};
|
|
723
|
+
const emptyCascaderOptions = [];
|
|
724
|
+
export const Cascader = ({ children, className, id, name, options = emptyCascaderOptions, placeholder = 'Select…', ...props }) => {
|
|
725
|
+
const generatedId = useId().replaceAll(':', '');
|
|
726
|
+
const cascaderId = id ?? `ui-cascader-${generatedId}`;
|
|
727
|
+
const panelId = `${cascaderId}-panel`;
|
|
728
|
+
const columns = buildCascaderColumns(options, cascaderId);
|
|
729
|
+
return (_jsxs("div", { className: composeClassName('ui-cascader', className), "data-surface": "default", "data-ui-cascader": true, "data-ui-popover": true, id: id, ...props, children: [_jsx("button", { "aria-controls": panelId, "aria-expanded": "false", "aria-haspopup": "listbox", className: "ui-select ui-select__trigger ui-cascader__trigger", "data-ui-trigger": true, role: "combobox", type: "button", children: _jsx("span", { "data-ui-cascader-placeholder": placeholder, "data-ui-cascader-value": true, children: placeholder }) }), _jsxs("div", { className: "ui-cascader__panel", "data-ui-panel": true, hidden: true, id: panelId, children: [columns.map(column => (_jsx("ol", { "aria-label": column.root ? 'Options' : 'Sub-options', className: "ui-cascader__column", hidden: !column.root, id: column.id, role: "listbox", children: column.options.map(option => (_jsx("li", { role: "presentation", children: _jsxs("button", { "aria-selected": "false", className: "ui-cascader__option", "data-ui-cascader-next": option.nextId ? `#${option.nextId}` : undefined, "data-ui-cascader-option": true, "data-value": option.value, role: "option", type: "button", children: [_jsx("span", { children: option.label }), option.nextId && _jsx("span", { "aria-hidden": "true", children: "\u203A" })] }) }, option.value))) }, column.id))), children] }), name && _jsx("input", { "data-ui-cascader-input": true, name: name, type: "hidden" })] }));
|
|
730
|
+
};
|
|
731
|
+
const flattenTreeSelect = (nodes, depth = 0, acc = []) => {
|
|
732
|
+
for (const node of nodes) {
|
|
733
|
+
acc.push({ depth, label: node.label, value: node.value });
|
|
734
|
+
if (node.children && node.children.length > 0)
|
|
735
|
+
flattenTreeSelect(node.children, depth + 1, acc);
|
|
736
|
+
}
|
|
737
|
+
return acc;
|
|
738
|
+
};
|
|
739
|
+
const emptyTreeSelectNodes = [];
|
|
740
|
+
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" })] }));
|
|
741
|
+
export const Mentions = ({ className, name, options = emptyOptions, placeholder, trigger = '@', ...props }) => (_jsxs("div", { className: composeClassName('ui-mentions', className), "data-ui-mentions": true, "data-ui-mentions-trigger": trigger, ...props, children: [_jsx("textarea", { className: "ui-textarea ui-mentions__input", "data-ui-mentions-input": true, name: name, placeholder: placeholder, rows: 3 }), _jsx("ul", { className: "ui-mentions__list", "data-ui-mentions-list": true, hidden: true, role: "listbox", children: options.map(normalizeOption).map(option => (_jsx("li", { children: _jsx("button", { className: "ui-mentions__option", "data-ui-mentions-option": true, "data-value": option.value, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
|
|
742
|
+
export const QRCode = ({ children, className, size = 160, src, style, value, ...props }) => {
|
|
743
|
+
const qrSvg = !src && value ? renderSVG(value) : null;
|
|
744
|
+
const qrSrc = qrSvg ? `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` : src;
|
|
745
|
+
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
|
|
746
|
+
? _jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })
|
|
747
|
+
: children }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
|
|
748
|
+
};
|
|
749
|
+
export const Watermark = ({ children, className, content, gap = 120, rotate = -22, style, text = 'Confidential', ...props }) => {
|
|
750
|
+
const watermarkText = content ?? text;
|
|
751
|
+
const tileSize = Math.max(1, gap);
|
|
752
|
+
const watermarkXmlEntities = {
|
|
753
|
+
'&': '&',
|
|
754
|
+
'<': '<',
|
|
755
|
+
'>': '>',
|
|
756
|
+
'"': '"',
|
|
757
|
+
"'": '''
|
|
758
|
+
};
|
|
759
|
+
const escapedWatermarkText = watermarkText.replaceAll(/[&<>"']/g, character => watermarkXmlEntities[character] ?? character);
|
|
760
|
+
const watermarkSvg = [
|
|
761
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
|
|
762
|
+
`<text x="${tileSize / 2}" y="${tileSize / 2}" dominant-baseline="middle" text-anchor="middle"`,
|
|
763
|
+
` transform="rotate(${rotate} ${tileSize / 2} ${tileSize / 2})"`,
|
|
764
|
+
' fill="black" font-family="system-ui, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
|
|
765
|
+
`${escapedWatermarkText}</text></svg>`
|
|
766
|
+
].join('');
|
|
767
|
+
const watermarkImage = `url("data:image/svg+xml,${encodeURIComponent(watermarkSvg)}")`;
|
|
768
|
+
return (_jsxs("div", { className: composeClassName('ui-watermark', className), "data-ui-watermark": true, style: {
|
|
769
|
+
['--ui-watermark-gap']: `${tileSize}px`,
|
|
770
|
+
['--ui-watermark-image']: watermarkImage,
|
|
771
|
+
...style
|
|
772
|
+
}, ...props, children: [children, _jsx("div", { "aria-hidden": "true", className: "ui-watermark__overlay", "data-text": watermarkText })] }));
|
|
773
|
+
};
|
|
774
|
+
export const Affix = ({ className, offset = 0, offsetBottom, offsetTop, position: positionProp, style, ...props }) => {
|
|
775
|
+
const position = positionProp ?? (offsetBottom === undefined ? 'top' : 'bottom');
|
|
776
|
+
const resolvedOffset = offsetBottom ?? offsetTop ?? offset;
|
|
777
|
+
return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: { ['--ui-affix-offset']: `${resolvedOffset}px`, ...style }, ...props }));
|
|
778
|
+
};
|
|
779
|
+
const emptySpeedDialActions = [];
|
|
780
|
+
const speedDialNavigationKeys = ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End', 'Home'];
|
|
781
|
+
const getSpeedDialNavigationIndex = (key, currentIndex, itemCount) => {
|
|
782
|
+
if (key === 'Home')
|
|
783
|
+
return 0;
|
|
784
|
+
if (key === 'End')
|
|
785
|
+
return itemCount - 1;
|
|
786
|
+
if (key === 'ArrowDown' || key === 'ArrowRight')
|
|
787
|
+
return (currentIndex + 1) % itemCount;
|
|
788
|
+
return (currentIndex - 1 + itemCount) % itemCount;
|
|
789
|
+
};
|
|
790
|
+
export const SpeedDial = ({ actions = emptySpeedDialActions, children, className, defaultOpen = false, direction = 'up', label = 'Actions', onBlur, ...props }) => {
|
|
791
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
792
|
+
const menuRef = useRef(null);
|
|
793
|
+
const triggerRef = useRef(null);
|
|
794
|
+
const close = (restoreFocus = false) => {
|
|
795
|
+
setIsOpen(false);
|
|
796
|
+
if (restoreFocus)
|
|
797
|
+
triggerRef.current?.focus();
|
|
798
|
+
};
|
|
799
|
+
const focusAction = (position) => {
|
|
800
|
+
setIsOpen(true);
|
|
801
|
+
requestAnimationFrame(() => {
|
|
802
|
+
const items = menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]');
|
|
803
|
+
const target = position === 'first' ? items?.[0] : items?.[items.length - 1];
|
|
804
|
+
target?.focus();
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
return (_jsxs("div", { className: composeClassName('ui-speed-dial', `ui-speed-dial--${direction}`, className), ...props, "data-direction": direction, "data-state": isOpen ? 'open' : 'closed', "data-ui-bound": "true", "data-ui-speed-dial": true, onBlur: event => {
|
|
808
|
+
onBlur?.(event);
|
|
809
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
810
|
+
close();
|
|
811
|
+
}, children: [_jsx("button", { "aria-expanded": isOpen, "aria-haspopup": "menu", "aria-label": label, className: "ui-speed-dial__trigger", "data-ui-speed-dial-trigger": true, onClick: () => {
|
|
812
|
+
setIsOpen(open => !open);
|
|
813
|
+
}, onKeyDown: event => {
|
|
814
|
+
if (event.key === 'Escape')
|
|
815
|
+
close();
|
|
816
|
+
if (['ArrowDown', 'ArrowRight'].includes(event.key)) {
|
|
817
|
+
event.preventDefault();
|
|
818
|
+
focusAction('first');
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (['ArrowLeft', 'ArrowUp'].includes(event.key)) {
|
|
822
|
+
event.preventDefault();
|
|
823
|
+
focusAction('last');
|
|
824
|
+
}
|
|
825
|
+
}, ref: triggerRef, type: "button", children: _jsx("span", { "aria-hidden": "true", className: "ui-speed-dial__icon" }) }), _jsxs("menu", { "aria-label": label, className: "ui-speed-dial__actions", "data-ui-speed-dial-actions": true, onClick: event => {
|
|
826
|
+
if (event.target.closest('[role="menuitem"], button, a'))
|
|
827
|
+
close();
|
|
828
|
+
}, onKeyDown: event => {
|
|
829
|
+
const items = [...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])];
|
|
830
|
+
if (event.key === 'Escape') {
|
|
831
|
+
event.preventDefault();
|
|
832
|
+
close(true);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (!speedDialNavigationKeys.includes(event.key))
|
|
836
|
+
return;
|
|
837
|
+
const currentIndex = items.indexOf(document.activeElement);
|
|
838
|
+
const targetIndex = getSpeedDialNavigationIndex(event.key, currentIndex, items.length);
|
|
839
|
+
event.preventDefault();
|
|
840
|
+
items[targetIndex]?.focus();
|
|
841
|
+
}, ref: menuRef, role: "menu", children: [actions.map((action, index) => (_jsx("li", { role: "presentation", style: { '--ui-speed-dial-index': index }, children: _jsxs("button", { "aria-label": action.label, className: "ui-speed-dial__action", "data-icon": action.icon, "data-value": action.value ?? action.label, role: "menuitem", type: "button", children: [action.icon && (_jsx("span", { "aria-hidden": "true", className: "ui-speed-dial__action-icon ui-icon", children: renderLucideIcon(action.icon, `ui-icon__svg lucide-${action.icon}`) })), _jsx("span", { className: "ui-speed-dial__action-label", children: action.label })] }) }, action.value ?? action.label))), children] })] }));
|
|
842
|
+
};
|