@papernote/ui 1.10.16 → 1.10.17

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/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 inputId = id || `input-${Math.random().toString(36).substring(2, 9)}`;
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.jsx("label", { id: labelId, className: "label", children: label })), jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("select", { ref: nativeSelectRef, value: value || '', onChange: (e) => onChange?.(e.target.value), disabled: disabled, className: `
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.jsx("label", { id: labelId, className: "label", children: label })), jsxRuntime.jsx("div", { ref: selectRef, className: "relative", children: jsxRuntime.jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `
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 escape
4695
- const handleEscape = (e) => {
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
- document.addEventListener('keydown', handleEscape);
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', handleEscape);
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);
@@ -7654,7 +7707,7 @@ const Autocomplete = React.forwardRef(({ value, onChange, options = [], onSearch
7654
7707
  ${error
7655
7708
  ? 'border-error-500 focus:ring-error-400 focus:border-error-400'
7656
7709
  : '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 ? (
7710
+ `, 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
7711
  // Render section header (non-selectable)
7659
7712
  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
7713
  // Render selectable option