@papernote/ui 1.10.16 → 1.10.18
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/dist/components/Autocomplete.d.ts.map +1 -1
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/DatePicker.d.ts.map +1 -1
- package/dist/components/DateRangePicker.d.ts.map +1 -1
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/Modal.d.ts.map +1 -1
- package/dist/components/Select.d.ts +2 -0
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/SkipLink.d.ts +48 -0
- package/dist/components/SkipLink.d.ts.map +1 -0
- package/dist/components/TimePicker.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +51 -2
- package/dist/index.esm.js +341 -27
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +340 -25
- package/dist/index.js.map +1 -1
- package/dist/styles.css +69 -0
- package/package.json +1 -1
- package/src/components/Autocomplete.tsx +1 -0
- package/src/components/Combobox.tsx +1 -0
- package/src/components/DataTable.tsx +243 -4
- package/src/components/DatePicker.tsx +1 -0
- package/src/components/DateRangePicker.tsx +1 -0
- package/src/components/Input.tsx +13 -3
- package/src/components/Modal.tsx +64 -8
- package/src/components/Select.tsx +7 -0
- package/src/components/SkipLink.stories.tsx +97 -0
- package/src/components/SkipLink.tsx +88 -0
- package/src/components/TimePicker.tsx +1 -0
- package/src/components/index.ts +3 -0
package/dist/index.js
CHANGED
|
@@ -327,7 +327,9 @@ function ButtonGroup({ options, value, values = [], onChange, onChangeMultiple,
|
|
|
327
327
|
* ```
|
|
328
328
|
*/
|
|
329
329
|
const Input = React.forwardRef(({ label, helperText, validationState, validationMessage, icon, iconPosition = 'left', showCount = false, prefix, suffix, prefixIcon, suffixIcon, showPasswordToggle = false, clearable = false, onClear, loading = false, className = '', id, type = 'text', value, maxLength, inputMode, enterKeyHint, size = 'md', ...props }, ref) => {
|
|
330
|
-
const
|
|
330
|
+
const generatedId = React.useId();
|
|
331
|
+
const inputId = id || generatedId;
|
|
332
|
+
const helperId = `${inputId}-helper`;
|
|
331
333
|
const [showPassword, setShowPassword] = React.useState(false);
|
|
332
334
|
// Handle clear button click
|
|
333
335
|
const handleClear = () => {
|
|
@@ -408,7 +410,7 @@ const Input = React.forwardRef(({ label, helperText, validationState, validation
|
|
|
408
410
|
return 'text-ink-600';
|
|
409
411
|
}
|
|
410
412
|
};
|
|
411
|
-
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { htmlFor: inputId, className: "label", children: [label, props.required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [prefix && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-ink-500 text-sm", children: prefix })), prefixIcon && !prefix && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-ink-400", children: prefixIcon })), icon && iconPosition === 'left' && !prefix && !prefixIcon && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-ink-400", children: icon })), jsxRuntime.jsx("input", { ref: ref, id: inputId, type: actualType, value: value, maxLength: maxLength, inputMode: effectiveInputMode, enterKeyHint: enterKeyHint, className: `
|
|
413
|
+
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { htmlFor: inputId, className: "label", children: [label, props.required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [prefix && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-ink-500 text-sm", children: prefix })), prefixIcon && !prefix && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-ink-400", children: prefixIcon })), icon && iconPosition === 'left' && !prefix && !prefixIcon && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-ink-400", children: icon })), jsxRuntime.jsx("input", { ref: ref, id: inputId, type: actualType, value: value, maxLength: maxLength, inputMode: effectiveInputMode, enterKeyHint: enterKeyHint, "aria-invalid": validationState === 'error', "aria-describedby": helperText || validationMessage ? helperId : undefined, "aria-required": props.required, className: `
|
|
412
414
|
input
|
|
413
415
|
${sizeClasses[size]}
|
|
414
416
|
${getValidationClasses()}
|
|
@@ -421,7 +423,7 @@ const Input = React.forwardRef(({ label, helperText, validationState, validation
|
|
|
421
423
|
${validationState && !suffix && !suffixIcon && !showPasswordToggle ? 'pr-10' : ''}
|
|
422
424
|
${(showPasswordToggle && type === 'password') || validationState || suffix || suffixIcon ? 'pr-20' : ''}
|
|
423
425
|
${className}
|
|
424
|
-
`, ...props }), suffix && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none text-ink-500 text-sm", children: suffix })), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center gap-1", children: [loading && (jsxRuntime.jsx("div", { className: "pointer-events-none text-ink-400", children: jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 animate-spin" }) })), suffixIcon && !suffix && !validationState && !showPasswordToggle && !showClearButton && !loading && (jsxRuntime.jsx("div", { className: "pointer-events-none text-ink-400", children: suffixIcon })), showClearButton && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: `text-ink-400 hover:text-ink-600 focus:outline-none cursor-pointer pointer-events-auto rounded-full hover:bg-paper-100 flex items-center justify-center ${buttonSizeClasses[size]}`, "aria-label": "Clear input", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) })), type === 'password' && showPasswordToggle && (jsxRuntime.jsx("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: `text-ink-400 hover:text-ink-600 focus:outline-none cursor-pointer pointer-events-auto rounded-full hover:bg-paper-100 flex items-center justify-center ${buttonSizeClasses[size]}`, "aria-label": showPassword ? 'Hide password' : 'Show password', children: showPassword ? jsxRuntime.jsx(lucideReact.EyeOff, { className: "h-5 w-5" }) : jsxRuntime.jsx(lucideReact.Eye, { className: "h-5 w-5" }) })), validationState && (jsxRuntime.jsx("div", { className: "pointer-events-none", children: getValidationIcon() })), icon && iconPosition === 'right' && !suffix && !suffixIcon && !validationState && (jsxRuntime.jsx("div", { className: "pointer-events-none text-ink-400", children: icon }))] })] }), jsxRuntime.jsxs("div", { className: "flex justify-between items-center mt-2", children: [(helperText || validationMessage) && (jsxRuntime.jsx("p", { className: `text-xs ${validationMessage ? getValidationMessageColor() : 'text-ink-600'}`, children: validationMessage || helperText })), showCounter && (jsxRuntime.jsxs("p", { className: `text-xs ml-auto ${currentLength > maxLength ? 'text-error-600' : 'text-ink-500'}`, children: [currentLength, " / ", maxLength] }))] })] }));
|
|
426
|
+
`, ...props }), suffix && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none text-ink-500 text-sm", children: suffix })), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center gap-1", children: [loading && (jsxRuntime.jsx("div", { className: "pointer-events-none text-ink-400", children: jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 animate-spin" }) })), suffixIcon && !suffix && !validationState && !showPasswordToggle && !showClearButton && !loading && (jsxRuntime.jsx("div", { className: "pointer-events-none text-ink-400", children: suffixIcon })), showClearButton && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: `text-ink-400 hover:text-ink-600 focus:outline-none cursor-pointer pointer-events-auto rounded-full hover:bg-paper-100 flex items-center justify-center ${buttonSizeClasses[size]}`, "aria-label": "Clear input", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) })), type === 'password' && showPasswordToggle && (jsxRuntime.jsx("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: `text-ink-400 hover:text-ink-600 focus:outline-none cursor-pointer pointer-events-auto rounded-full hover:bg-paper-100 flex items-center justify-center ${buttonSizeClasses[size]}`, "aria-label": showPassword ? 'Hide password' : 'Show password', children: showPassword ? jsxRuntime.jsx(lucideReact.EyeOff, { className: "h-5 w-5" }) : jsxRuntime.jsx(lucideReact.Eye, { className: "h-5 w-5" }) })), validationState && (jsxRuntime.jsx("div", { className: "pointer-events-none", children: getValidationIcon() })), icon && iconPosition === 'right' && !suffix && !suffixIcon && !validationState && (jsxRuntime.jsx("div", { className: "pointer-events-none text-ink-400", children: icon }))] })] }), jsxRuntime.jsxs("div", { className: "flex justify-between items-center mt-2", children: [(helperText || validationMessage) && (jsxRuntime.jsx("p", { id: helperId, className: `text-xs ${validationMessage ? getValidationMessageColor() : 'text-ink-600'}`, role: validationState === 'error' ? 'alert' : undefined, "aria-live": validationState === 'error' ? 'assertive' : undefined, children: validationMessage || helperText })), showCounter && (jsxRuntime.jsxs("p", { className: `text-xs ml-auto ${currentLength > maxLength ? 'text-error-600' : 'text-ink-500'}`, children: [currentLength, " / ", maxLength] }))] })] }));
|
|
425
427
|
});
|
|
426
428
|
Input.displayName = 'Input';
|
|
427
429
|
|
|
@@ -804,7 +806,7 @@ const optionSizeClasses = {
|
|
|
804
806
|
* ```
|
|
805
807
|
*/
|
|
806
808
|
const Select = React.forwardRef((props, ref) => {
|
|
807
|
-
const { options = [], groups = [], value, onChange, placeholder = 'Select an option', searchable = false, disabled = false, label, helperText, error, loading = false, clearable = false, creatable = false, onCreateOption, virtualized = false, virtualHeight = '300px', virtualItemHeight = 42, size = 'md', mobileMode = 'auto', usePortal = true, } = props;
|
|
809
|
+
const { options = [], groups = [], value, onChange, placeholder = 'Select an option', searchable = false, disabled = false, label, helperText, error, loading = false, clearable = false, creatable = false, onCreateOption, virtualized = false, virtualHeight = '300px', virtualItemHeight = 42, size = 'md', mobileMode = 'auto', usePortal = true, required = false, } = props;
|
|
808
810
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
809
811
|
const [searchQuery, setSearchQuery] = React.useState('');
|
|
810
812
|
const [scrollTop, setScrollTop] = React.useState(0);
|
|
@@ -1037,19 +1039,19 @@ const Select = React.forwardRef((props, ref) => {
|
|
|
1037
1039
|
};
|
|
1038
1040
|
// Native select for mobile (optional)
|
|
1039
1041
|
if (useNativeSelect) {
|
|
1040
|
-
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.
|
|
1042
|
+
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { id: labelId, className: "label", children: [label, required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("select", { ref: nativeSelectRef, value: value || '', onChange: (e) => onChange?.(e.target.value), disabled: disabled, className: `
|
|
1041
1043
|
input w-full appearance-none pr-10
|
|
1042
1044
|
${sizeClasses$b[effectiveSize]}
|
|
1043
1045
|
${error ? 'border-error-400 focus:border-error-400 focus:ring-error-400' : ''}
|
|
1044
1046
|
${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
|
|
1045
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : (helperText ? helperTextId : undefined), children: [jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }), options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))), groups.map((group) => (jsxRuntime.jsx("optgroup", { label: group.label, children: group.options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))) }, group.label)))] }), jsxRuntime.jsx(lucideReact.ChevronDown, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-500 pointer-events-none" })] }), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
|
|
1047
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : (helperText ? helperTextId : undefined), "aria-required": required, children: [jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }), options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))), groups.map((group) => (jsxRuntime.jsx("optgroup", { label: group.label, children: group.options.map((opt) => (jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))) }, group.label)))] }), jsxRuntime.jsx(lucideReact.ChevronDown, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-500 pointer-events-none" })] }), error && (jsxRuntime.jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsxRuntime.jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
|
|
1046
1048
|
}
|
|
1047
|
-
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.
|
|
1049
|
+
return (jsxRuntime.jsxs("div", { className: "w-full", children: [label && (jsxRuntime.jsxs("label", { id: labelId, className: "label", children: [label, required && jsxRuntime.jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxRuntime.jsx("div", { ref: selectRef, className: "relative", children: jsxRuntime.jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `
|
|
1048
1050
|
input w-full flex items-center justify-between px-3
|
|
1049
1051
|
${sizeClasses$b[effectiveSize]}
|
|
1050
1052
|
${error ? 'border-error-400 focus:border-error-400 focus:ring-error-400' : ''}
|
|
1051
1053
|
${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
|
|
1052
|
-
`, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : (helperText ? helperTextId : undefined), "aria-disabled": disabled, children: [jsxRuntime.jsxs("span", { className: `flex items-center gap-2 ${selectedOption ? 'text-ink-800' : 'text-ink-400'}`, children: [loading && jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-ink-500" }), !loading && selectedOption?.icon && jsxRuntime.jsx("span", { children: selectedOption.icon }), selectedOption ? selectedOption.label : placeholder] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [clearable && value && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
1054
|
+
`, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : (helperText ? helperTextId : undefined), "aria-disabled": disabled, "aria-required": required, children: [jsxRuntime.jsxs("span", { className: `flex items-center gap-2 ${selectedOption ? 'text-ink-800' : 'text-ink-400'}`, children: [loading && jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-ink-500" }), !loading && selectedOption?.icon && jsxRuntime.jsx("span", { children: selectedOption.icon }), selectedOption ? selectedOption.label : placeholder] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [clearable && value && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
1053
1055
|
e.stopPropagation();
|
|
1054
1056
|
onChange?.('');
|
|
1055
1057
|
setIsOpen(false);
|
|
@@ -1654,7 +1656,7 @@ const DatePicker = React.forwardRef(({ value, onChange, label, placeholder = 'Se
|
|
|
1654
1656
|
${disabled ? 'bg-paper-100 text-ink-400 cursor-not-allowed' : 'cursor-pointer'}
|
|
1655
1657
|
focus:outline-none focus:ring-2
|
|
1656
1658
|
pr-10
|
|
1657
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Date picker' : undefined, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-controls": dialogId, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : undefined, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-3 gap-1", children: [showClearButton && value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
1659
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Date picker' : undefined, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-controls": dialogId, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : undefined, "aria-required": required, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-3 gap-1", children: [showClearButton && value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
1658
1660
|
e.stopPropagation();
|
|
1659
1661
|
handleClear();
|
|
1660
1662
|
}, className: "text-ink-400 hover:text-ink-600 focus:outline-none", "aria-label": "Clear date", children: jsxRuntime.jsx(lucideReact.X, { className: iconSizeClasses[size] }) })), jsxRuntime.jsx(lucideReact.Calendar, { className: `${iconSizeClasses[size]} text-ink-400` })] })] }), validationMessage && (jsxRuntime.jsx("p", { id: descriptionId, className: `mt-1 text-xs ${validationState ? validationMessageColors[validationState] : 'text-ink-500'}`, role: "alert", "aria-live": "polite", children: validationMessage })), helperText && !validationMessage && (jsxRuntime.jsx("p", { className: "mt-1 text-xs text-ink-500", children: helperText })), isOpen && (jsxRuntime.jsxs("div", { id: dialogId, className: "absolute z-50 mt-1 bg-white rounded-lg shadow-lg border border-paper-200 p-3 w-72", role: "dialog", "aria-modal": "true", "aria-label": "Date picker calendar", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [jsxRuntime.jsx("button", { type: "button", onClick: goToPrevMonth, className: "p-1 rounded hover:bg-paper-100 text-ink-600 hover:text-ink-900", "aria-label": "Previous month", children: jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-5 w-5" }) }), jsxRuntime.jsxs("span", { className: "text-sm font-semibold text-ink-900", children: [MONTHS[viewDate.getMonth()], " ", viewDate.getFullYear()] }), jsxRuntime.jsx("button", { type: "button", onClick: goToNextMonth, className: "p-1 rounded hover:bg-paper-100 text-ink-600 hover:text-ink-900", "aria-label": "Next month", children: jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" }) })] }), jsxRuntime.jsx("div", { className: "grid grid-cols-7 mb-1", children: DAYS.map(day => (jsxRuntime.jsx("div", { className: "text-center text-xs font-medium text-ink-500 py-1", children: day }, day))) }), jsxRuntime.jsx("div", { className: "grid grid-cols-7", children: generateCalendarDays().map(({ date, isCurrentMonth }, index) => {
|
|
@@ -1845,7 +1847,7 @@ const TimePicker = React.forwardRef(({ value = null, onChange, label, placeholde
|
|
|
1845
1847
|
${disabled ? 'bg-paper-100 text-ink-400 cursor-not-allowed' : ''}
|
|
1846
1848
|
focus:outline-none focus:ring-2
|
|
1847
1849
|
pr-20
|
|
1848
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Time picker' : undefined, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-controls": dropdownId, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : undefined, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-2 gap-1", children: [value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
1850
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Time picker' : undefined, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-controls": dropdownId, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : undefined, "aria-required": required, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-2 gap-1", children: [value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
1849
1851
|
e.stopPropagation();
|
|
1850
1852
|
handleClear();
|
|
1851
1853
|
}, className: "p-0.5 text-ink-400 hover:text-ink-600 focus:outline-none", "aria-label": "Clear", tabIndex: -1, children: jsxRuntime.jsx(lucideReact.X, { className: iconSizeClasses[size] }) })), jsxRuntime.jsx(lucideReact.Clock, { className: `${iconSizeClasses[size]} text-ink-400` })] })] }), validationMessage && (jsxRuntime.jsx("p", { id: descriptionId, className: `mt-1 text-xs ${validationState ? validationMessageColors[validationState] : 'text-ink-500'}`, role: "alert", "aria-live": "polite", children: validationMessage })), helperText && !validationMessage && (jsxRuntime.jsx("p", { className: "mt-1 text-xs text-ink-500", children: helperText })), isOpen && (jsxRuntime.jsx("div", { id: dropdownId, className: "absolute z-50 mt-1 bg-white rounded-md shadow-lg border border-paper-200", role: "dialog", "aria-modal": "true", "aria-label": "Time selection", children: jsxRuntime.jsxs("div", { className: "p-4 flex items-center gap-4", children: [jsxRuntime.jsx(TimeSpinner, { value: timeValue.hours, min: use12Hour ? 1 : 0, max: use12Hour ? 12 : 23, onChange: (hours) => updateTime({ hours }), label: "Hour" }), jsxRuntime.jsx("span", { className: "text-2xl font-bold text-ink-600", children: ":" }), jsxRuntime.jsx(TimeSpinner, { value: timeValue.minutes, min: 0, max: 59, step: minuteStep, options: minuteOptions, onChange: (minutes) => updateTime({ minutes }), label: "Min" }), showSeconds && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "text-2xl font-bold text-ink-600", children: ":" }), jsxRuntime.jsx(TimeSpinner, { value: timeValue.seconds, min: 0, max: 59, onChange: (seconds) => updateTime({ seconds }), label: "Sec" })] })), use12Hour && (jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [jsxRuntime.jsx("button", { type: "button", onClick: () => updateTime({ period: 'AM' }), className: `
|
|
@@ -2105,7 +2107,7 @@ const DateRangePicker = React.forwardRef(({ value = { start: null, end: null },
|
|
|
2105
2107
|
${disabled ? 'bg-paper-100 text-ink-400 cursor-not-allowed' : ''}
|
|
2106
2108
|
focus:outline-none focus:ring-2
|
|
2107
2109
|
pr-20
|
|
2108
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Date range picker' : undefined, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-controls": dialogId, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : (isOpen ? hintId : undefined), role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-2 gap-1", children: [(value.start || value.end) && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
2110
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Date range picker' : undefined, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-controls": dialogId, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : (isOpen ? hintId : undefined), "aria-required": required, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-2 gap-1", children: [(value.start || value.end) && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
|
|
2109
2111
|
e.stopPropagation();
|
|
2110
2112
|
handleClear();
|
|
2111
2113
|
}, className: "p-0.5 text-ink-400 hover:text-ink-600 focus:outline-none", "aria-label": "Clear", tabIndex: -1, children: jsxRuntime.jsx(lucideReact.X, { className: iconSizeClasses[size] }) })), jsxRuntime.jsx(lucideReact.Calendar, { className: `${iconSizeClasses[size]} text-ink-400` })] })] }), validationMessage && (jsxRuntime.jsx("p", { id: descriptionId, className: `mt-1 text-xs ${validationState ? validationMessageColors[validationState] : 'text-ink-500'}`, role: "alert", "aria-live": "polite", children: validationMessage })), helperText && !validationMessage && (jsxRuntime.jsx("p", { className: "mt-1 text-xs text-ink-500", children: helperText })), isOpen && (jsxRuntime.jsx("div", { id: dialogId, className: "absolute z-50 mt-1 bg-white rounded-md shadow-lg border border-paper-200 p-4", role: "dialog", "aria-modal": "true", "aria-label": "Date range selection", children: jsxRuntime.jsxs("div", { className: "flex gap-4", children: [showPresets && (jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 border-r border-paper-200 pr-4", children: [jsxRuntime.jsx("button", { type: "button", onClick: () => handlePreset('today'), className: "text-left px-3 py-2 text-sm text-ink-700 hover:bg-primary-50 rounded whitespace-nowrap", children: "Today" }), jsxRuntime.jsx("button", { type: "button", onClick: () => handlePreset('yesterday'), className: "text-left px-3 py-2 text-sm text-ink-700 hover:bg-primary-50 rounded whitespace-nowrap", children: "Yesterday" }), jsxRuntime.jsx("button", { type: "button", onClick: () => handlePreset('last7days'), className: "text-left px-3 py-2 text-sm text-ink-700 hover:bg-primary-50 rounded whitespace-nowrap", children: "Last 7 days" }), jsxRuntime.jsx("button", { type: "button", onClick: () => handlePreset('last30days'), className: "text-left px-3 py-2 text-sm text-ink-700 hover:bg-primary-50 rounded whitespace-nowrap", children: "Last 30 days" }), jsxRuntime.jsx("button", { type: "button", onClick: () => handlePreset('thisMonth'), className: "text-left px-3 py-2 text-sm text-ink-700 hover:bg-primary-50 rounded whitespace-nowrap", children: "This month" }), jsxRuntime.jsx("button", { type: "button", onClick: () => handlePreset('lastMonth'), className: "text-left px-3 py-2 text-sm text-ink-700 hover:bg-primary-50 rounded whitespace-nowrap", children: "Last month" })] })), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-4", children: [jsxRuntime.jsx("button", { type: "button", onClick: previousMonth, className: "p-2 text-ink-600 hover:bg-paper-100 rounded", "aria-label": "Previous month", children: jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-5 w-5" }) }), jsxRuntime.jsx("div", { className: "text-sm font-semibold text-ink-900", children: monthYear }), jsxRuntime.jsx("button", { type: "button", onClick: nextMonth, className: "p-2 text-ink-600 hover:bg-paper-100 rounded", "aria-label": "Next month", children: jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" }) })] }), jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1 mb-2", children: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map((day) => (jsxRuntime.jsx("div", { className: "text-center text-xs font-medium text-ink-500 w-8", children: day }, day))) }), jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: calendarDays.map((date, index) => {
|
|
@@ -2667,7 +2669,7 @@ const Combobox = React.forwardRef(({ value = '', onChange, options, onSearch, on
|
|
|
2667
2669
|
${disabled ? 'bg-paper-100 text-ink-400 cursor-not-allowed' : ''}
|
|
2668
2670
|
focus:outline-none focus:ring-2
|
|
2669
2671
|
pr-20
|
|
2670
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Combobox' : undefined, "aria-expanded": isOpen, "aria-autocomplete": "list", "aria-controls": listboxId, "aria-activedescendant": isOpen && filteredOptions.length > 0 ? `option-${highlightedIndex}` : undefined, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : undefined, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-2 gap-1", children: [loading && (jsxRuntime.jsx("div", { className: "animate-spin", children: jsxRuntime.jsx(lucideReact.Search, { className: `${iconSizeClasses[size]} text-ink-400` }) })), !loading && value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: "p-0.5 text-ink-400 hover:text-ink-600 focus:outline-none", "aria-label": "Clear", tabIndex: -1, children: jsxRuntime.jsx(lucideReact.X, { className: iconSizeClasses[size] }) })), !loading && (jsxRuntime.jsx(lucideReact.ChevronDown, { className: `${iconSizeClasses[size]} text-ink-400 transition-transform ${isOpen ? 'rotate-180' : ''}` }))] })] }) }), validationMessage && (jsxRuntime.jsx("p", { id: descriptionId, className: `mt-1 text-xs ${validationState ? validationMessageColors[validationState] : 'text-ink-500'}`, role: "alert", "aria-live": "polite", children: validationMessage })), helperText && !validationMessage && (jsxRuntime.jsx("p", { className: "mt-1 text-xs text-ink-500", children: helperText })), isOpen && (jsxRuntime.jsx("div", { className: "absolute z-50 mt-1 w-full bg-white rounded-md shadow-lg border border-paper-200 max-h-60 overflow-auto", role: "listbox", id: listboxId, "aria-label": "Available options", children: loading ? (jsxRuntime.jsx("div", { className: "px-4 py-8 text-center text-ink-500 text-sm", role: "status", "aria-live": "polite", children: "Loading..." })) : filteredOptions.length === 0 && !canCreateOption ? (jsxRuntime.jsx("div", { className: "px-4 py-8 text-center text-ink-500 text-sm", role: "status", "aria-live": "polite", children: "No options found" })) : (jsxRuntime.jsxs("ul", { ref: listRef, children: [filteredOptions.map((option, index) => {
|
|
2672
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Combobox' : undefined, "aria-expanded": isOpen, "aria-autocomplete": "list", "aria-controls": listboxId, "aria-activedescendant": isOpen && filteredOptions.length > 0 ? `option-${highlightedIndex}` : undefined, "aria-invalid": validationState === 'error' ? 'true' : undefined, "aria-describedby": validationMessage ? descriptionId : undefined, "aria-required": required, role: "combobox" }), jsxRuntime.jsxs("div", { className: "absolute inset-y-0 right-0 flex items-center pr-2 gap-1", children: [loading && (jsxRuntime.jsx("div", { className: "animate-spin", children: jsxRuntime.jsx(lucideReact.Search, { className: `${iconSizeClasses[size]} text-ink-400` }) })), !loading && value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: "p-0.5 text-ink-400 hover:text-ink-600 focus:outline-none", "aria-label": "Clear", tabIndex: -1, children: jsxRuntime.jsx(lucideReact.X, { className: iconSizeClasses[size] }) })), !loading && (jsxRuntime.jsx(lucideReact.ChevronDown, { className: `${iconSizeClasses[size]} text-ink-400 transition-transform ${isOpen ? 'rotate-180' : ''}` }))] })] }) }), validationMessage && (jsxRuntime.jsx("p", { id: descriptionId, className: `mt-1 text-xs ${validationState ? validationMessageColors[validationState] : 'text-ink-500'}`, role: "alert", "aria-live": "polite", children: validationMessage })), helperText && !validationMessage && (jsxRuntime.jsx("p", { className: "mt-1 text-xs text-ink-500", children: helperText })), isOpen && (jsxRuntime.jsx("div", { className: "absolute z-50 mt-1 w-full bg-white rounded-md shadow-lg border border-paper-200 max-h-60 overflow-auto", role: "listbox", id: listboxId, "aria-label": "Available options", children: loading ? (jsxRuntime.jsx("div", { className: "px-4 py-8 text-center text-ink-500 text-sm", role: "status", "aria-live": "polite", children: "Loading..." })) : filteredOptions.length === 0 && !canCreateOption ? (jsxRuntime.jsx("div", { className: "px-4 py-8 text-center text-ink-500 text-sm", role: "status", "aria-live": "polite", children: "No options found" })) : (jsxRuntime.jsxs("ul", { ref: listRef, children: [filteredOptions.map((option, index) => {
|
|
2671
2673
|
const Icon = option.icon;
|
|
2672
2674
|
const isSelected = option.value === value;
|
|
2673
2675
|
const isHighlighted = index === highlightedIndex;
|
|
@@ -4606,6 +4608,8 @@ function BottomSheetActions({ children, className = '' }) {
|
|
|
4606
4608
|
return (jsxRuntime.jsx("div", { className: `flex flex-col gap-2 px-4 py-4 border-t border-paper-200 bg-white ${className}`, children: children }));
|
|
4607
4609
|
}
|
|
4608
4610
|
|
|
4611
|
+
// Selector for all focusable elements
|
|
4612
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
4609
4613
|
const sizeClasses$9 = {
|
|
4610
4614
|
sm: 'max-w-md',
|
|
4611
4615
|
md: 'max-w-lg',
|
|
@@ -4683,29 +4687,78 @@ const sizeClasses$9 = {
|
|
|
4683
4687
|
function Modal({ isOpen, onClose, title, children, size = 'md', showCloseButton = true, animation = 'scale', scrollable = false, maxHeight, mobileMode = 'auto', mobileHeight = 'lg', mobileShowHandle = true, }) {
|
|
4684
4688
|
const modalRef = React.useRef(null);
|
|
4685
4689
|
const mouseDownOnBackdrop = React.useRef(false);
|
|
4690
|
+
const previousActiveElement = React.useRef(null);
|
|
4686
4691
|
const titleId = React.useId();
|
|
4687
4692
|
const isMobile = useIsMobile();
|
|
4693
|
+
// Get all focusable elements within the modal
|
|
4694
|
+
const getFocusableElements = React.useCallback(() => {
|
|
4695
|
+
if (!modalRef.current)
|
|
4696
|
+
return [];
|
|
4697
|
+
return Array.from(modalRef.current.querySelectorAll(FOCUSABLE_SELECTOR))
|
|
4698
|
+
.filter(el => el.offsetParent !== null); // Filter out hidden elements
|
|
4699
|
+
}, []);
|
|
4688
4700
|
// Determine if we should use BottomSheet
|
|
4689
4701
|
const useBottomSheet = mobileMode === 'sheet' ||
|
|
4690
4702
|
(mobileMode === 'auto' && isMobile);
|
|
4691
|
-
// Handle escape key (only for modal mode, BottomSheet handles its own)
|
|
4703
|
+
// Handle escape key and focus trap (only for modal mode, BottomSheet handles its own)
|
|
4692
4704
|
React.useEffect(() => {
|
|
4693
4705
|
if (useBottomSheet)
|
|
4694
|
-
return; // BottomSheet handles
|
|
4695
|
-
const
|
|
4706
|
+
return; // BottomSheet handles its own focus
|
|
4707
|
+
const handleKeyDown = (e) => {
|
|
4696
4708
|
if (e.key === 'Escape' && isOpen) {
|
|
4697
4709
|
onClose();
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
// Focus trap: keep focus within modal
|
|
4713
|
+
if (e.key === 'Tab' && isOpen) {
|
|
4714
|
+
const focusableElements = getFocusableElements();
|
|
4715
|
+
if (focusableElements.length === 0)
|
|
4716
|
+
return;
|
|
4717
|
+
const firstElement = focusableElements[0];
|
|
4718
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
4719
|
+
if (e.shiftKey) {
|
|
4720
|
+
// Shift+Tab: if on first element, wrap to last
|
|
4721
|
+
if (document.activeElement === firstElement) {
|
|
4722
|
+
e.preventDefault();
|
|
4723
|
+
lastElement.focus();
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
else {
|
|
4727
|
+
// Tab: if on last element, wrap to first
|
|
4728
|
+
if (document.activeElement === lastElement) {
|
|
4729
|
+
e.preventDefault();
|
|
4730
|
+
firstElement.focus();
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4698
4733
|
}
|
|
4699
4734
|
};
|
|
4700
4735
|
if (isOpen) {
|
|
4701
|
-
|
|
4736
|
+
// Store the currently focused element to restore later
|
|
4737
|
+
previousActiveElement.current = document.activeElement;
|
|
4738
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
4702
4739
|
document.body.style.overflow = 'hidden';
|
|
4740
|
+
// Set initial focus to first focusable element
|
|
4741
|
+
// Use requestAnimationFrame to ensure the modal is rendered
|
|
4742
|
+
requestAnimationFrame(() => {
|
|
4743
|
+
const focusableElements = getFocusableElements();
|
|
4744
|
+
if (focusableElements.length > 0) {
|
|
4745
|
+
focusableElements[0].focus();
|
|
4746
|
+
}
|
|
4747
|
+
else if (modalRef.current) {
|
|
4748
|
+
// If no focusable elements, focus the modal container
|
|
4749
|
+
modalRef.current.focus();
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4703
4752
|
}
|
|
4704
4753
|
return () => {
|
|
4705
|
-
document.removeEventListener('keydown',
|
|
4754
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
4706
4755
|
document.body.style.overflow = 'unset';
|
|
4756
|
+
// Restore focus to the previously focused element
|
|
4757
|
+
if (previousActiveElement.current && typeof previousActiveElement.current.focus === 'function') {
|
|
4758
|
+
previousActiveElement.current.focus();
|
|
4759
|
+
}
|
|
4707
4760
|
};
|
|
4708
|
-
}, [isOpen, onClose, useBottomSheet]);
|
|
4761
|
+
}, [isOpen, onClose, useBottomSheet, getFocusableElements]);
|
|
4709
4762
|
// Track if mousedown originated on the backdrop
|
|
4710
4763
|
const handleBackdropMouseDown = (e) => {
|
|
4711
4764
|
if (e.target === e.currentTarget) {
|
|
@@ -4746,7 +4799,7 @@ function Modal({ isOpen, onClose, title, children, size = 'md', showCloseButton
|
|
|
4746
4799
|
return reactDom.createPortal(jsxRuntime.jsx(BottomSheet, { isOpen: isOpen, onClose: onClose, title: title, height: mobileHeight, showHandle: mobileShowHandle, showCloseButton: showCloseButton, children: children }), document.body);
|
|
4747
4800
|
}
|
|
4748
4801
|
// Render as standard modal on desktop
|
|
4749
|
-
const modalContent = (jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-ink-900 bg-opacity-50 backdrop-blur-sm animate-fade-in", onMouseDown: handleBackdropMouseDown, onClick: handleBackdropClick, children: jsxRuntime.jsxs("div", { ref: modalRef, className: `${sizeClasses$9[size]} w-full bg-white bg-subtle-grain rounded-xl shadow-2xl border border-paper-200 ${getAnimationClass()}`, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-paper-200", children: [jsxRuntime.jsx("h3", { id: titleId, className: "text-lg font-medium text-ink-900", children: title }), showCloseButton && (jsxRuntime.jsx("button", { onClick: onClose, className: "text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Close modal", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) }))] }), jsxRuntime.jsx("div", { className: `px-6 py-4 ${scrollable || maxHeight ? 'overflow-y-auto' : ''}`, style: {
|
|
4802
|
+
const modalContent = (jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-ink-900 bg-opacity-50 backdrop-blur-sm animate-fade-in", onMouseDown: handleBackdropMouseDown, onClick: handleBackdropClick, children: jsxRuntime.jsxs("div", { ref: modalRef, className: `${sizeClasses$9[size]} w-full bg-white bg-subtle-grain rounded-xl shadow-2xl border border-paper-200 ${getAnimationClass()}`, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, tabIndex: -1, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-paper-200", children: [jsxRuntime.jsx("h3", { id: titleId, className: "text-lg font-medium text-ink-900", children: title }), showCloseButton && (jsxRuntime.jsx("button", { onClick: onClose, className: "text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Close modal", children: jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" }) }))] }), jsxRuntime.jsx("div", { className: `px-6 py-4 ${scrollable || maxHeight ? 'overflow-y-auto' : ''}`, style: {
|
|
4750
4803
|
maxHeight: maxHeight || (scrollable ? 'calc(100vh - 200px)' : undefined),
|
|
4751
4804
|
}, children: children })] }) }));
|
|
4752
4805
|
return reactDom.createPortal(modalContent, document.body);
|
|
@@ -6040,6 +6093,68 @@ function HelpTooltip({ content, icon = 'help', size = 'md', position = 'top', cl
|
|
|
6040
6093
|
return (jsxRuntime.jsx(Tooltip, { content: content, position: position, children: jsxRuntime.jsx("span", { className: `inline-flex items-center justify-center text-ink-400 hover:text-ink-600 cursor-help transition-colors ${className}`, role: "button", "aria-label": "Help", tabIndex: 0, children: jsxRuntime.jsx(IconComponent, { className: sizeClasses$7[size] }) }) }));
|
|
6041
6094
|
}
|
|
6042
6095
|
|
|
6096
|
+
/**
|
|
6097
|
+
* SkipLink - Accessibility skip link for keyboard navigation
|
|
6098
|
+
*
|
|
6099
|
+
* A skip link allows keyboard users to bypass repetitive navigation
|
|
6100
|
+
* and jump directly to the main content. The link is visually hidden
|
|
6101
|
+
* until focused, making it invisible to mouse users while remaining
|
|
6102
|
+
* accessible to keyboard and screen reader users.
|
|
6103
|
+
*
|
|
6104
|
+
* Place this component at the very beginning of your page layout,
|
|
6105
|
+
* before any navigation elements.
|
|
6106
|
+
*
|
|
6107
|
+
* @example Basic usage
|
|
6108
|
+
* ```tsx
|
|
6109
|
+
* // In your layout component:
|
|
6110
|
+
* <SkipLink targetId="main-content" />
|
|
6111
|
+
* <Navigation />
|
|
6112
|
+
* <main id="main-content">
|
|
6113
|
+
* {children}
|
|
6114
|
+
* </main>
|
|
6115
|
+
* ```
|
|
6116
|
+
*
|
|
6117
|
+
* @example Custom text
|
|
6118
|
+
* ```tsx
|
|
6119
|
+
* <SkipLink targetId="content">Skip navigation</SkipLink>
|
|
6120
|
+
* ```
|
|
6121
|
+
*
|
|
6122
|
+
* @example Multiple skip links
|
|
6123
|
+
* ```tsx
|
|
6124
|
+
* <div>
|
|
6125
|
+
* <SkipLink targetId="main-content">Skip to main content</SkipLink>
|
|
6126
|
+
* <SkipLink targetId="search">Skip to search</SkipLink>
|
|
6127
|
+
* </div>
|
|
6128
|
+
* ```
|
|
6129
|
+
*/
|
|
6130
|
+
function SkipLink({ targetId, children = 'Skip to main content', className = '', }) {
|
|
6131
|
+
const handleClick = (e) => {
|
|
6132
|
+
e.preventDefault();
|
|
6133
|
+
const target = document.getElementById(targetId);
|
|
6134
|
+
if (target) {
|
|
6135
|
+
// Set tabindex to make the element focusable if it isn't already
|
|
6136
|
+
if (!target.hasAttribute('tabindex')) {
|
|
6137
|
+
target.setAttribute('tabindex', '-1');
|
|
6138
|
+
}
|
|
6139
|
+
target.focus();
|
|
6140
|
+
// Scroll the element into view
|
|
6141
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
6142
|
+
}
|
|
6143
|
+
};
|
|
6144
|
+
return (jsxRuntime.jsx("a", { href: `#${targetId}`, onClick: handleClick, className: `
|
|
6145
|
+
sr-only focus:not-sr-only
|
|
6146
|
+
focus:absolute focus:z-50
|
|
6147
|
+
focus:top-4 focus:left-4
|
|
6148
|
+
focus:px-4 focus:py-2
|
|
6149
|
+
focus:bg-accent-600 focus:text-white
|
|
6150
|
+
focus:rounded-lg focus:shadow-lg
|
|
6151
|
+
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400
|
|
6152
|
+
font-medium text-sm
|
|
6153
|
+
transition-none
|
|
6154
|
+
${className}
|
|
6155
|
+
`, children: children }));
|
|
6156
|
+
}
|
|
6157
|
+
|
|
6043
6158
|
function Popover({ trigger: triggerElement, children, placement = 'bottom', triggerMode = 'click', showArrow = true, offset = 8, open: controlledOpen, onOpenChange, closeOnClickOutside = true, closeOnEscape = true, showDelay = 0, hideDelay = 0, className = '', disabled = false, }) {
|
|
6044
6159
|
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
6045
6160
|
const [position, setPosition] = React.useState({ top: 0, left: 0 });
|
|
@@ -7654,7 +7769,7 @@ const Autocomplete = React.forwardRef(({ value, onChange, options = [], onSearch
|
|
|
7654
7769
|
${error
|
|
7655
7770
|
? 'border-error-500 focus:ring-error-400 focus:border-error-400'
|
|
7656
7771
|
: 'border-paper-300'}
|
|
7657
|
-
`, role: "combobox", "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Search' : undefined, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `autocomplete-option-${highlightedIndex}` : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : undefined, "aria-busy": loading }), clearable && value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: "absolute right-3 top-1/2 -translate-y-1/2 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Clear", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) }))] }), isOpen && filteredOptions.length > 0 && (jsxRuntime.jsx("div", { ref: dropdownRef, id: listboxId, className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg max-h-60 overflow-y-auto", role: "listbox", "aria-label": "Search results", children: filteredOptions.map((option, index) => (option.isHeader ? (
|
|
7772
|
+
`, role: "combobox", "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? 'Search' : undefined, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `autocomplete-option-${highlightedIndex}` : undefined, "aria-invalid": error ? 'true' : undefined, "aria-describedby": error ? errorId : undefined, "aria-required": required, "aria-busy": loading }), clearable && value && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: "absolute right-3 top-1/2 -translate-y-1/2 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Clear", children: jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) }))] }), isOpen && filteredOptions.length > 0 && (jsxRuntime.jsx("div", { ref: dropdownRef, id: listboxId, className: "absolute z-50 w-full mt-1 bg-white border border-paper-200 rounded-lg shadow-lg max-h-60 overflow-y-auto", role: "listbox", "aria-label": "Search results", children: filteredOptions.map((option, index) => (option.isHeader ? (
|
|
7658
7773
|
// Render section header (non-selectable)
|
|
7659
7774
|
jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs font-semibold text-ink-500 uppercase tracking-wide bg-paper-50 border-t border-paper-200 first:border-t-0 first:rounded-t-lg cursor-default", role: "presentation", children: option.label }, `header-${option.value}`)) : (
|
|
7660
7775
|
// Render selectable option
|
|
@@ -14041,6 +14156,10 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14041
14156
|
const tableContainerRef = React.useRef(null);
|
|
14042
14157
|
// Row hover state (for coordinating primary + secondary row highlighting)
|
|
14043
14158
|
const [hoveredRowKey, setHoveredRowKey] = React.useState(null);
|
|
14159
|
+
// Keyboard navigation state
|
|
14160
|
+
const [focusedCell, setFocusedCell] = React.useState(null);
|
|
14161
|
+
const [announcement, setAnnouncement] = React.useState('');
|
|
14162
|
+
const tableBodyRef = React.useRef(null);
|
|
14044
14163
|
// Temporary row highlight state (for flash animation)
|
|
14045
14164
|
const [flashingRows, setFlashingRows] = React.useState(new Set());
|
|
14046
14165
|
const flashTimeoutRef = React.useRef(null);
|
|
@@ -14106,6 +14225,11 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14106
14225
|
const currentDensity = densityClasses[density];
|
|
14107
14226
|
// Key extractor function - defined early for use in other functions
|
|
14108
14227
|
const getRowKey = keyExtractor || ((row) => String(row.id));
|
|
14228
|
+
// Calculate if there are any actions (for keyboard navigation column calculation)
|
|
14229
|
+
// This is computed early so it can be used in keyboard handlers
|
|
14230
|
+
const hasAnyActions = !!(onEdit || onDelete || actions.length > 0 ||
|
|
14231
|
+
expandedRowConfig?.edit || expandedRowConfig?.details ||
|
|
14232
|
+
expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length);
|
|
14109
14233
|
// Get row background class based on striping and highlighting
|
|
14110
14234
|
const getRowBackgroundClass = (item, index) => {
|
|
14111
14235
|
const classes = [];
|
|
@@ -14407,6 +14531,192 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14407
14531
|
const handleCollapseExpansion = () => {
|
|
14408
14532
|
setExpansionState(null);
|
|
14409
14533
|
};
|
|
14534
|
+
// Keyboard navigation handler
|
|
14535
|
+
const handleKeyboardNavigation = React.useCallback((e) => {
|
|
14536
|
+
if (!data.length)
|
|
14537
|
+
return;
|
|
14538
|
+
const totalRows = data.length;
|
|
14539
|
+
const totalCols = visibleColumns.length;
|
|
14540
|
+
// If no cell is focused, focus first data cell on first arrow key
|
|
14541
|
+
if (!focusedCell) {
|
|
14542
|
+
if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
|
14543
|
+
e.preventDefault();
|
|
14544
|
+
setFocusedCell({ row: 0, col: 0 });
|
|
14545
|
+
const colHeader = visibleColumns[0]?.header || 'first column';
|
|
14546
|
+
setAnnouncement(`Row 1, ${colHeader}`);
|
|
14547
|
+
return;
|
|
14548
|
+
}
|
|
14549
|
+
return;
|
|
14550
|
+
}
|
|
14551
|
+
const { row, col } = focusedCell;
|
|
14552
|
+
switch (e.key) {
|
|
14553
|
+
case 'ArrowDown':
|
|
14554
|
+
e.preventDefault();
|
|
14555
|
+
if (row < totalRows - 1) {
|
|
14556
|
+
const newRow = row + 1;
|
|
14557
|
+
setFocusedCell({ row: newRow, col });
|
|
14558
|
+
const rowItem = data[newRow];
|
|
14559
|
+
const colHeader = visibleColumns[col]?.header || '';
|
|
14560
|
+
const cellValue = rowItem[visibleColumns[col]?.key];
|
|
14561
|
+
setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || 'empty'}`);
|
|
14562
|
+
}
|
|
14563
|
+
break;
|
|
14564
|
+
case 'ArrowUp':
|
|
14565
|
+
e.preventDefault();
|
|
14566
|
+
if (row > 0) {
|
|
14567
|
+
const newRow = row - 1;
|
|
14568
|
+
setFocusedCell({ row: newRow, col });
|
|
14569
|
+
const rowItem = data[newRow];
|
|
14570
|
+
const colHeader = visibleColumns[col]?.header || '';
|
|
14571
|
+
const cellValue = rowItem[visibleColumns[col]?.key];
|
|
14572
|
+
setAnnouncement(`Row ${newRow + 1}, ${colHeader}: ${cellValue || 'empty'}`);
|
|
14573
|
+
}
|
|
14574
|
+
break;
|
|
14575
|
+
case 'ArrowRight':
|
|
14576
|
+
e.preventDefault();
|
|
14577
|
+
if (col < totalCols - 1) {
|
|
14578
|
+
const newCol = col + 1;
|
|
14579
|
+
setFocusedCell({ row, col: newCol });
|
|
14580
|
+
const rowItem = data[row];
|
|
14581
|
+
const colHeader = visibleColumns[newCol]?.header || '';
|
|
14582
|
+
const cellValue = rowItem[visibleColumns[newCol]?.key];
|
|
14583
|
+
setAnnouncement(`${colHeader}: ${cellValue || 'empty'}`);
|
|
14584
|
+
}
|
|
14585
|
+
break;
|
|
14586
|
+
case 'ArrowLeft':
|
|
14587
|
+
e.preventDefault();
|
|
14588
|
+
if (col > 0) {
|
|
14589
|
+
const newCol = col - 1;
|
|
14590
|
+
setFocusedCell({ row, col: newCol });
|
|
14591
|
+
const rowItem = data[row];
|
|
14592
|
+
const colHeader = visibleColumns[newCol]?.header || '';
|
|
14593
|
+
const cellValue = rowItem[visibleColumns[newCol]?.key];
|
|
14594
|
+
setAnnouncement(`${colHeader}: ${cellValue || 'empty'}`);
|
|
14595
|
+
}
|
|
14596
|
+
break;
|
|
14597
|
+
case 'Home':
|
|
14598
|
+
e.preventDefault();
|
|
14599
|
+
if (e.ctrlKey) {
|
|
14600
|
+
// Ctrl+Home: Go to first cell
|
|
14601
|
+
setFocusedCell({ row: 0, col: 0 });
|
|
14602
|
+
setAnnouncement(`First cell, Row 1, ${visibleColumns[0]?.header || ''}`);
|
|
14603
|
+
}
|
|
14604
|
+
else {
|
|
14605
|
+
// Home: Go to first cell in current row
|
|
14606
|
+
setFocusedCell({ row, col: 0 });
|
|
14607
|
+
const rowItem = data[row];
|
|
14608
|
+
const cellValue = rowItem[visibleColumns[0]?.key];
|
|
14609
|
+
setAnnouncement(`${visibleColumns[0]?.header || ''}: ${cellValue || 'empty'}`);
|
|
14610
|
+
}
|
|
14611
|
+
break;
|
|
14612
|
+
case 'End':
|
|
14613
|
+
e.preventDefault();
|
|
14614
|
+
if (e.ctrlKey) {
|
|
14615
|
+
// Ctrl+End: Go to last cell
|
|
14616
|
+
const lastRow = totalRows - 1;
|
|
14617
|
+
const lastCol = totalCols - 1;
|
|
14618
|
+
setFocusedCell({ row: lastRow, col: lastCol });
|
|
14619
|
+
setAnnouncement(`Last cell, Row ${lastRow + 1}, ${visibleColumns[lastCol]?.header || ''}`);
|
|
14620
|
+
}
|
|
14621
|
+
else {
|
|
14622
|
+
// End: Go to last cell in current row
|
|
14623
|
+
const lastCol = totalCols - 1;
|
|
14624
|
+
setFocusedCell({ row, col: lastCol });
|
|
14625
|
+
const rowItem = data[row];
|
|
14626
|
+
const cellValue = rowItem[visibleColumns[lastCol]?.key];
|
|
14627
|
+
setAnnouncement(`${visibleColumns[lastCol]?.header || ''}: ${cellValue || 'empty'}`);
|
|
14628
|
+
}
|
|
14629
|
+
break;
|
|
14630
|
+
case 'Enter':
|
|
14631
|
+
e.preventDefault();
|
|
14632
|
+
{
|
|
14633
|
+
const rowItem = data[row];
|
|
14634
|
+
const rowKey = getRowKey(rowItem);
|
|
14635
|
+
// Priority: Edit mode > Details mode > Row double-click handler
|
|
14636
|
+
if (onEdit) {
|
|
14637
|
+
onEdit(rowItem);
|
|
14638
|
+
setAnnouncement('Opening edit mode');
|
|
14639
|
+
}
|
|
14640
|
+
else if (expandedRowConfig?.edit) {
|
|
14641
|
+
handleExpansionWithMode(rowKey, 'edit');
|
|
14642
|
+
setAnnouncement('Opening inline edit');
|
|
14643
|
+
}
|
|
14644
|
+
else if (expandedRowConfig?.details) {
|
|
14645
|
+
handleExpansionWithMode(rowKey, 'details');
|
|
14646
|
+
setAnnouncement('Opening details view');
|
|
14647
|
+
}
|
|
14648
|
+
else if (onRowDoubleClick) {
|
|
14649
|
+
onRowDoubleClick(rowItem);
|
|
14650
|
+
setAnnouncement('Activating row');
|
|
14651
|
+
}
|
|
14652
|
+
else if (onRowClick) {
|
|
14653
|
+
onRowClick(rowItem);
|
|
14654
|
+
setAnnouncement('Row selected');
|
|
14655
|
+
}
|
|
14656
|
+
}
|
|
14657
|
+
break;
|
|
14658
|
+
case ' ':
|
|
14659
|
+
// Space: Toggle selection if selectable
|
|
14660
|
+
if (selectable) {
|
|
14661
|
+
e.preventDefault();
|
|
14662
|
+
const rowItem = data[row];
|
|
14663
|
+
const rowKey = getRowKey(rowItem);
|
|
14664
|
+
handleRowSelect(rowKey);
|
|
14665
|
+
const isNowSelected = !selectedRowsSet.has(rowKey);
|
|
14666
|
+
setAnnouncement(isNowSelected ? 'Row selected' : 'Row deselected');
|
|
14667
|
+
}
|
|
14668
|
+
break;
|
|
14669
|
+
case 'Escape':
|
|
14670
|
+
e.preventDefault();
|
|
14671
|
+
setFocusedCell(null);
|
|
14672
|
+
setAnnouncement('Table navigation exited');
|
|
14673
|
+
// Return focus to table container
|
|
14674
|
+
tableBodyRef.current?.closest('table')?.focus();
|
|
14675
|
+
break;
|
|
14676
|
+
case 'PageDown':
|
|
14677
|
+
e.preventDefault();
|
|
14678
|
+
{
|
|
14679
|
+
const jumpSize = 10;
|
|
14680
|
+
const newRow = Math.min(row + jumpSize, totalRows - 1);
|
|
14681
|
+
setFocusedCell({ row: newRow, col });
|
|
14682
|
+
const colHeader = visibleColumns[col]?.header || '';
|
|
14683
|
+
setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
|
|
14684
|
+
}
|
|
14685
|
+
break;
|
|
14686
|
+
case 'PageUp':
|
|
14687
|
+
e.preventDefault();
|
|
14688
|
+
{
|
|
14689
|
+
const jumpSize = 10;
|
|
14690
|
+
const newRow = Math.max(row - jumpSize, 0);
|
|
14691
|
+
setFocusedCell({ row: newRow, col });
|
|
14692
|
+
const colHeader = visibleColumns[col]?.header || '';
|
|
14693
|
+
setAnnouncement(`Row ${newRow + 1} of ${totalRows}, ${colHeader}`);
|
|
14694
|
+
}
|
|
14695
|
+
break;
|
|
14696
|
+
}
|
|
14697
|
+
}, [data, visibleColumns, focusedCell, selectable, expandedRowConfig, onEdit, onRowDoubleClick, onRowClick, getRowKey, handleExpansionWithMode, handleRowSelect, selectedRowsSet]);
|
|
14698
|
+
// Focus the appropriate cell when focusedCell changes
|
|
14699
|
+
React.useEffect(() => {
|
|
14700
|
+
if (focusedCell && tableBodyRef.current) {
|
|
14701
|
+
const { row, col } = focusedCell;
|
|
14702
|
+
const rows = tableBodyRef.current.querySelectorAll('tr[data-row-index]');
|
|
14703
|
+
const targetRow = rows[row];
|
|
14704
|
+
if (targetRow) {
|
|
14705
|
+
// Calculate actual column index including extra columns
|
|
14706
|
+
const hasSelectionCol = selectable;
|
|
14707
|
+
const hasExpandCol = (expandable || expandedRowConfig) && showExpandChevron;
|
|
14708
|
+
const hasActionsCol = hasAnyActions;
|
|
14709
|
+
const extraColsBefore = (hasSelectionCol ? 1 : 0) + (hasExpandCol ? 1 : 0) + (hasActionsCol ? 1 : 0);
|
|
14710
|
+
const cells = targetRow.querySelectorAll('td');
|
|
14711
|
+
const targetCell = cells[col + extraColsBefore];
|
|
14712
|
+
if (targetCell) {
|
|
14713
|
+
targetCell.focus();
|
|
14714
|
+
// Scroll into view if needed
|
|
14715
|
+
targetCell.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
14716
|
+
}
|
|
14717
|
+
}
|
|
14718
|
+
}
|
|
14719
|
+
}, [focusedCell, selectable, expandable, expandedRowConfig, showExpandChevron, hasAnyActions]);
|
|
14410
14720
|
// Handle column header click for sorting
|
|
14411
14721
|
const handleSort = (column) => {
|
|
14412
14722
|
if (!column.sortable || !onSortChange)
|
|
@@ -14491,7 +14801,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14491
14801
|
// Hover state for row pair (primary + secondary)
|
|
14492
14802
|
const isHovered = hoveredRowKey === rowKey;
|
|
14493
14803
|
const hoverClass = disableHover ? '' : (isHovered ? 'bg-paper-100' : '');
|
|
14494
|
-
|
|
14804
|
+
// Check if this row is keyboard-focused
|
|
14805
|
+
const isKeyboardFocused = focusedCell?.row === index;
|
|
14806
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("tr", { "data-row-index": index, className: `table-row-stable ${onRowDoubleClick || onRowClick || onEdit || expandedRowConfig?.edit || expandedRowConfig?.details || expandedRowConfig?.addRelated?.length || expandedRowConfig?.manageRelated?.length ? 'cursor-pointer' : ''} ${isSelected ? 'bg-accent-50 border-l-2 border-accent-500' : hoverClass || rowBgClass} ${borderClass} ${isKeyboardFocused ? 'ring-2 ring-inset ring-accent-400' : ''}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), onClick: () => onRowClick?.(item), onContextMenu: (e) => {
|
|
14495
14807
|
if (enableContextMenu && allActions.length > 0) {
|
|
14496
14808
|
e.preventDefault();
|
|
14497
14809
|
e.stopPropagation();
|
|
@@ -14569,7 +14881,9 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14569
14881
|
// Reduce left padding on first column when there are action buttons
|
|
14570
14882
|
const isFirstColumn = colIdx === 0;
|
|
14571
14883
|
const paddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
|
|
14572
|
-
|
|
14884
|
+
// Check if this cell is keyboard-focused
|
|
14885
|
+
const isCellFocused = focusedCell?.row === index && focusedCell?.col === colIdx;
|
|
14886
|
+
return (jsxRuntime.jsx("td", { className: `${currentDensity.cell} ${paddingClass} ${column.className || ''} ${bordered ? `border ${borderColor}` : ''} ${isCellFocused ? 'outline outline-2 outline-accent-500 outline-offset-[-2px]' : ''}`, style: getColumnStyle(column, dynamicWidth), tabIndex: isCellFocused ? 0 : -1, role: "gridcell", "aria-colindex": colIdx + 1, children: jsxRuntime.jsx("div", { className: `${currentDensity.text} leading-tight`, children: primaryContent }) }, `${item.id}-${columnKey}`));
|
|
14573
14887
|
})] }), hasSecondaryRow && (jsxRuntime.jsx("tr", { className: `secondary-row ${isSelected ? 'bg-accent-50 border-l-2 border-accent-500' : hoverClass || rowBgClass} border-b ${borderColor}`, onMouseEnter: () => !disableHover && setHoveredRowKey(rowKey), onMouseLeave: () => !disableHover && setHoveredRowKey(null), children: visibleColumns.map((column, colIdx) => {
|
|
14574
14888
|
const columnKey = String(column.key);
|
|
14575
14889
|
const dynamicWidth = columnWidths[columnKey];
|
|
@@ -14638,7 +14952,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14638
14952
|
})()] }, rowKey));
|
|
14639
14953
|
});
|
|
14640
14954
|
};
|
|
14641
|
-
const tableContent = (jsxRuntime.jsxs("div", { className: `bg-white rounded-lg shadow border-2 ${borderColor} ${virtualized ? 'overflow-hidden' : 'overflow-x-auto overflow-y-visible'} ${className}`, style: { position: 'relative' }, children: [loading && data.length > 0 && (jsxRuntime.jsx("div", { className: "absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center z-20", style: { backdropFilter: 'blur(2px)' }, children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", children: [jsxRuntime.jsx("div", { className: "loading-spinner", style: { width: '32px', height: '32px', borderWidth: '3px' } }), jsxRuntime.jsx("span", { className: "text-sm font-medium text-ink-600", children: "Loading..." })] }) })), jsxRuntime.jsxs("table", { className: `table-stable w-full ${bordered ? 'border-collapse' : ''}`, children: [jsxRuntime.jsxs("colgroup", { children: [selectable && jsxRuntime.jsx("col", { className: "w-12" }), ((expandable || expandedRowConfig) && showExpandChevron) && jsxRuntime.jsx("col", { className: "w-10" }), allActions.length > 0 && jsxRuntime.jsx("col", { style: { width: '28px' } }), visibleColumns.map((column, index) => {
|
|
14955
|
+
const tableContent = (jsxRuntime.jsxs("div", { className: `bg-white rounded-lg shadow border-2 ${borderColor} ${virtualized ? 'overflow-hidden' : 'overflow-x-auto overflow-y-visible'} ${className}`, style: { position: 'relative' }, children: [loading && data.length > 0 && (jsxRuntime.jsx("div", { className: "absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center z-20", style: { backdropFilter: 'blur(2px)' }, children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", children: [jsxRuntime.jsx("div", { className: "loading-spinner", style: { width: '32px', height: '32px', borderWidth: '3px' } }), jsxRuntime.jsx("span", { className: "text-sm font-medium text-ink-600", children: "Loading..." })] }) })), jsxRuntime.jsxs("table", { className: `table-stable w-full ${bordered ? 'border-collapse' : ''}`, role: "grid", "aria-label": "Data table", "aria-rowcount": data.length, "aria-colcount": visibleColumns.length, children: [jsxRuntime.jsxs("colgroup", { children: [selectable && jsxRuntime.jsx("col", { className: "w-12" }), ((expandable || expandedRowConfig) && showExpandChevron) && jsxRuntime.jsx("col", { className: "w-10" }), allActions.length > 0 && jsxRuntime.jsx("col", { style: { width: '28px' } }), visibleColumns.map((column, index) => {
|
|
14642
14956
|
const columnKey = String(column.key);
|
|
14643
14957
|
const dynamicWidth = columnWidths[columnKey];
|
|
14644
14958
|
return (jsxRuntime.jsx("col", { style: getColumnStyle(column, dynamicWidth) }, index));
|
|
@@ -14660,7 +14974,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14660
14974
|
const currentWidth = thRef.current?.offsetWidth || 100;
|
|
14661
14975
|
handleResizeStart(e, columnKey, currentWidth);
|
|
14662
14976
|
}, children: jsxRuntime.jsx("div", { className: "absolute right-0 top-0 bottom-0 w-1 bg-paper-300 group-hover:bg-accent-400 transition-colors" }) }))] }, columnKey));
|
|
14663
|
-
})] }) }), jsxRuntime.jsx("tbody", { className: "bg-white table-loading transition-opacity duration-200", children: loading && data.length === 0 ? (renderLoadingSkeleton()) : data.length === 0 ? (renderEmptyStateContent()) : (renderDataRows()) })] })] }));
|
|
14977
|
+
})] }) }), jsxRuntime.jsx("tbody", { ref: tableBodyRef, className: "bg-white table-loading transition-opacity duration-200", onKeyDown: handleKeyboardNavigation, tabIndex: 0, role: "rowgroup", "aria-label": "Table data", children: loading && data.length === 0 ? (renderLoadingSkeleton()) : data.length === 0 ? (renderEmptyStateContent()) : (renderDataRows()) })] })] }));
|
|
14664
14978
|
// Wrap in scrollable container if virtualized
|
|
14665
14979
|
const finalContent = virtualized ? (jsxRuntime.jsx("div", { ref: tableContainerRef, onScroll: handleScroll, style: { height: virtualHeight, overflow: 'auto' }, className: "rounded-lg", children: tableContent })) : tableContent;
|
|
14666
14980
|
// Calculate pagination values
|
|
@@ -14694,7 +15008,7 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
|
|
|
14694
15008
|
}
|
|
14695
15009
|
}, selectable: selectable, selectedRows: selectedRowsSet, onSelectionChange: onRowSelect ? (rows) => onRowSelect(rows) : undefined, keyExtractor: getRowKey, actions: allActions, onEdit: onEdit, onDelete: onDelete, className: className, cardClassName: cardClassName, gap: cardGap })) : null;
|
|
14696
15010
|
// Render with context menu
|
|
14697
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsxRuntime.jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({ isOpen: false, position: { x: 0, y: 0 }, item: null }) }))] }));
|
|
15011
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement }), renderPaginationControls(), shouldShowCardView ? cardViewContent : finalContent, contextMenuState.isOpen && contextMenuState.item && (jsxRuntime.jsx(Menu, { items: convertActionsToMenuItems(contextMenuState.item), position: contextMenuState.position, onClose: () => setContextMenuState({ isOpen: false, position: { x: 0, y: 0 }, item: null }) }))] }));
|
|
14698
15012
|
}
|
|
14699
15013
|
|
|
14700
15014
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
@@ -61480,6 +61794,7 @@ exports.SidebarGroup = SidebarGroup;
|
|
|
61480
61794
|
exports.Skeleton = Skeleton;
|
|
61481
61795
|
exports.SkeletonCard = SkeletonCard$1;
|
|
61482
61796
|
exports.SkeletonTable = SkeletonTable;
|
|
61797
|
+
exports.SkipLink = SkipLink;
|
|
61483
61798
|
exports.Slider = Slider;
|
|
61484
61799
|
exports.Spreadsheet = Spreadsheet;
|
|
61485
61800
|
exports.SpreadsheetReport = SpreadsheetReport;
|