@pk-design/react-tailwind 1.7.2 → 1.8.1

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.
Files changed (61) hide show
  1. package/dist/cjs/components/Button/Button.js +46 -28
  2. package/dist/cjs/components/Button/Button.js.map +1 -1
  3. package/dist/cjs/components/Checkbox/Checkbox.js +10 -4
  4. package/dist/cjs/components/Checkbox/Checkbox.js.map +1 -1
  5. package/dist/cjs/components/CheckboxGroup/CheckboxGroup.js +19 -13
  6. package/dist/cjs/components/CheckboxGroup/CheckboxGroup.js.map +1 -1
  7. package/dist/cjs/components/DataList/DataList.js +2 -2
  8. package/dist/cjs/components/DataList/DataList.js.map +1 -1
  9. package/dist/cjs/components/DataTable/DataTable.js +72 -40
  10. package/dist/cjs/components/DataTable/DataTable.js.map +1 -1
  11. package/dist/cjs/components/DataTable/DataTable.utils.js +2 -1
  12. package/dist/cjs/components/DataTable/DataTable.utils.js.map +1 -1
  13. package/dist/cjs/components/Input/Input.js +11 -8
  14. package/dist/cjs/components/Input/Input.js.map +1 -1
  15. package/dist/cjs/components/Pagination/Pagination.js +27 -15
  16. package/dist/cjs/components/Pagination/Pagination.js.map +1 -1
  17. package/dist/cjs/components/Radio/Radio.js +9 -5
  18. package/dist/cjs/components/Radio/Radio.js.map +1 -1
  19. package/dist/cjs/components/RadioGroup/RadioGroup.js +13 -6
  20. package/dist/cjs/components/RadioGroup/RadioGroup.js.map +1 -1
  21. package/dist/cjs/components/SelectBox/SelectBox.js +24 -25
  22. package/dist/cjs/components/SelectBox/SelectBox.js.map +1 -1
  23. package/dist/cjs/components/Textarea/Textarea.js +8 -5
  24. package/dist/cjs/components/Textarea/Textarea.js.map +1 -1
  25. package/dist/es/components/Button/Button.js +46 -28
  26. package/dist/es/components/Button/Button.js.map +1 -1
  27. package/dist/es/components/Checkbox/Checkbox.js +10 -4
  28. package/dist/es/components/Checkbox/Checkbox.js.map +1 -1
  29. package/dist/es/components/CheckboxGroup/CheckboxGroup.js +20 -14
  30. package/dist/es/components/CheckboxGroup/CheckboxGroup.js.map +1 -1
  31. package/dist/es/components/DataList/DataList.js +2 -2
  32. package/dist/es/components/DataList/DataList.js.map +1 -1
  33. package/dist/es/components/DataTable/DataTable.js +74 -42
  34. package/dist/es/components/DataTable/DataTable.js.map +1 -1
  35. package/dist/es/components/DataTable/DataTable.utils.js +2 -1
  36. package/dist/es/components/DataTable/DataTable.utils.js.map +1 -1
  37. package/dist/es/components/Input/Input.js +12 -9
  38. package/dist/es/components/Input/Input.js.map +1 -1
  39. package/dist/es/components/Pagination/Pagination.js +27 -15
  40. package/dist/es/components/Pagination/Pagination.js.map +1 -1
  41. package/dist/es/components/Radio/Radio.js +9 -5
  42. package/dist/es/components/Radio/Radio.js.map +1 -1
  43. package/dist/es/components/RadioGroup/RadioGroup.js +14 -7
  44. package/dist/es/components/RadioGroup/RadioGroup.js.map +1 -1
  45. package/dist/es/components/SelectBox/SelectBox.js +24 -25
  46. package/dist/es/components/SelectBox/SelectBox.js.map +1 -1
  47. package/dist/es/components/Textarea/Textarea.js +9 -6
  48. package/dist/es/components/Textarea/Textarea.js.map +1 -1
  49. package/dist/react-tailwind.cjs.js +269 -180
  50. package/dist/react-tailwind.cjs.js.map +1 -1
  51. package/dist/react-tailwind.es.js +269 -180
  52. package/dist/react-tailwind.es.js.map +1 -1
  53. package/dist/types/components/Button/Button.types.d.ts +5 -0
  54. package/dist/types/components/CheckboxGroup/CheckboxGroup.types.d.ts +9 -1
  55. package/dist/types/components/DataList/DataList.d.ts +1 -1
  56. package/dist/types/components/DataList/DataList.types.d.ts +8 -0
  57. package/dist/types/components/DataTable/DataTable.d.ts +1 -1
  58. package/dist/types/components/DataTable/DataTable.types.d.ts +46 -0
  59. package/dist/types/components/Pagination/Pagination.types.d.ts +21 -24
  60. package/dist/types/components/RadioGroup/RadioGroup.types.d.ts +8 -0
  61. package/package.json +1 -1
@@ -136,17 +136,17 @@ const THEMED_BTN_CLASS = {
136
136
  primary: {
137
137
  default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:bg-[var(--ui-primary-disabled)]',
138
138
  outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:text-[var(--ui-primary-disabled)] disabled:border-[var(--ui-primary-disabled)]',
139
- plain: 'text-[var(--ui-primary-text)] hover:bg-black/5 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:text-[var(--ui-primary-disabled)]',
139
+ plain: 'text-[var(--ui-primary-text)] hover:bg-blue-50/80 dark:hover:bg-blue-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:text-[var(--ui-primary-disabled)]',
140
140
  },
141
141
  danger: {
142
142
  default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:bg-[var(--ui-danger-disabled)]',
143
143
  outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)] disabled:border-[var(--ui-danger-disabled)]',
144
- plain: 'text-[var(--ui-danger-text)] hover:bg-black/5 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)]',
144
+ plain: 'text-[var(--ui-danger-text)] hover:bg-red-50/80 dark:hover:bg-red-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)]',
145
145
  },
146
146
  secondary: {
147
147
  default: 'text-gray-700 bg-white border border-[var(--ui-border)] hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:bg-gray-200 dark:disabled:bg-gray-700',
148
148
  outlined: 'text-gray-700 hover:text-white border border-[var(--ui-border)] hover:bg-gray-700 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-600 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400 disabled:border-[var(--ui-border-muted)]',
149
- plain: 'text-gray-700 hover:bg-black/5 dark:text-gray-200 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400',
149
+ plain: 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400',
150
150
  },
151
151
  success: {
152
152
  default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:bg-emerald-300 dark:disabled:bg-emerald-900',
@@ -186,54 +186,72 @@ const LOADER_TEXT_CLASS = {
186
186
  plain: 'text-amber-600 dark:text-amber-400',
187
187
  },
188
188
  };
189
- /**
190
- * Generate size classes.
191
- */
192
- const getBtnSizeClasses = (size, iconOnly = false) => {
193
- switch (size) {
194
- case 'xs':
195
- return iconOnly ? 'h-7 w-7 p-1 rounded-sm text-xs' : 'h-7 px-2.5 rounded-sm gap-1 text-xs';
196
- case 'sm':
197
- return iconOnly ? 'h-8 w-8 p-1.5 rounded text-sm' : 'h-8 px-3 rounded gap-1.5 text-sm';
198
- case 'md':
199
- return iconOnly ? 'h-9 w-9 p-2 rounded-md text-sm' : 'h-9 px-4 rounded-md gap-2 text-sm';
200
- case 'lg':
201
- return iconOnly
202
- ? 'h-10 w-10 p-2.5 rounded-lg text-base'
203
- : 'h-10 px-5 rounded-lg gap-2 text-base';
204
- }
189
+ const SIZE_CLASSES = {
190
+ xs: { normal: 'h-7 px-2.5 rounded-sm gap-1 text-xs', iconOnly: 'h-7 w-7 p-1 rounded-sm text-xs' },
191
+ sm: { normal: 'h-8 px-3 rounded gap-1.5 text-sm', iconOnly: 'h-8 w-8 p-1.5 rounded text-sm' },
192
+ md: { normal: 'h-9 px-4 rounded-md gap-2 text-sm', iconOnly: 'h-9 w-9 p-2 rounded-md text-sm' },
193
+ lg: {
194
+ normal: 'h-10 px-5 rounded-lg gap-2 text-base',
195
+ iconOnly: 'h-10 w-10 p-2.5 rounded-lg text-base',
196
+ },
197
+ };
198
+ const LOADER_SIZE = {
199
+ xs: 'xs',
200
+ sm: 'xs',
201
+ md: 'sm',
202
+ lg: 'sm',
205
203
  };
206
204
  /**
207
205
  * Base shared tailwind classes.
208
206
  */
209
- const BASE_BTN_CLASS = 'font-medium flex items-center justify-center shrink-0 cursor-pointer ' +
207
+ const BASE_BTN_CLASS = 'font-medium inline-flex items-center justify-center shrink-0 cursor-pointer select-none ' +
210
208
  'disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-75 ' +
211
209
  'active:scale-[0.98] transition-all duration-150 ease-out focus:outline-none';
212
210
  const Button = forwardRef(({ as, type, ...props }, ref) => {
213
- const { className = '', children, theme = 'primary', variant = 'default', size = 'md', leftIcon, rightIcon, fullWidth = false, loading = false, loadingText = '', rounded = false, disabled, iconOnly = false, noOutlineOnFocus = false, ...restProps } = props;
211
+ var _a, _b, _c, _d;
212
+ const { className = '', children, theme = 'primary', variant = 'default', size = 'md', leftIcon, rightIcon, fullWidth = false, loading = false, loadingText = '', rounded = false, disabled, iconOnly = false, noOutlineOnFocus = false, active, onClick, ...restProps } = props;
214
213
  const Component = as || 'button';
214
+ const isNativeButton = Component === 'button';
215
215
  // Accessibility guard for icon-only buttons
216
216
  if (iconOnly && !restProps['aria-label']) {
217
217
  console.warn('[ui] <Button iconOnly> requires aria-label for accessibility.');
218
218
  }
219
- const resolvedDisabled = disabled || loading;
219
+ const resolvedDisabled = Boolean(disabled || loading);
220
220
  // Clean invalid props when not rendering a real button
221
221
  const { type: _ignoreType, disabled: _ignoreDisabled, ...cleanRest } = restProps;
222
- const buttonSpecificProps = Component === 'button'
222
+ const handleClick = (e) => {
223
+ if (resolvedDisabled) {
224
+ e.preventDefault();
225
+ e.stopPropagation();
226
+ return;
227
+ }
228
+ onClick === null || onClick === void 0 ? void 0 : onClick(e);
229
+ };
230
+ const elementProps = isNativeButton
223
231
  ? {
224
232
  type: type !== null && type !== void 0 ? type : 'button',
225
233
  disabled: resolvedDisabled,
234
+ onClick: handleClick,
226
235
  }
227
- : {};
228
- const classes = useMemo(() => buildClassName(BASE_BTN_CLASS, THEMED_BTN_CLASS[theme][variant], getBtnSizeClasses(size, iconOnly), !noOutlineOnFocus && 'focus:outline focus:outline-2 focus:outline-offset-2', rounded && 'rounded-full', fullWidth && 'w-full', variant === 'default' && 'shadow-xs hover:shadow-none disabled:shadow-none', className), [theme, variant, size, iconOnly, rounded, fullWidth, noOutlineOnFocus, className]);
236
+ : {
237
+ role: (_a = cleanRest.role) !== null && _a !== void 0 ? _a : 'button',
238
+ tabIndex: resolvedDisabled ? -1 : ((_b = cleanRest.tabIndex) !== null && _b !== void 0 ? _b : 0),
239
+ 'aria-disabled': resolvedDisabled ? true : undefined,
240
+ onClick: handleClick,
241
+ };
242
+ const sizeObj = SIZE_CLASSES[size] || SIZE_CLASSES.md;
243
+ const classes = useMemo(() => {
244
+ var _a;
245
+ return buildClassName(BASE_BTN_CLASS, ((_a = THEMED_BTN_CLASS[theme]) === null || _a === void 0 ? void 0 : _a[variant]) || THEMED_BTN_CLASS.primary.default, iconOnly ? sizeObj.iconOnly : sizeObj.normal, !noOutlineOnFocus && 'focus-visible:ring-2 focus-visible:ring-offset-2', rounded && 'rounded-full', fullWidth && 'w-full', variant === 'default' && 'shadow-xs hover:shadow-none disabled:shadow-none', active && 'ring-2 ring-offset-2 ring-current', className);
246
+ }, [theme, variant, sizeObj, iconOnly, noOutlineOnFocus, rounded, fullWidth, active, className]);
247
+ const loaderIcon = (jsx(Loader, { "data-testid": "btn-loader-icon", size: LOADER_SIZE[size] || 'xs', className: buildClassName((_c = LOADER_TEXT_CLASS[theme]) === null || _c === void 0 ? void 0 : _c[variant]) }));
229
248
  /**
230
249
  * SPECIAL CASE: iconOnly + loading → show spinner only
231
250
  */
232
251
  if (loading && iconOnly) {
233
- return (jsx(Component, { ref: ref, className: classes, ...buttonSpecificProps, ...cleanRest, children: jsx(Loader, { "data-testid": "btn-loader-icon", size: "xs", className: buildClassName(LOADER_TEXT_CLASS[theme][variant]) }) }));
252
+ return (jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...elementProps, ...cleanRest, children: loaderIcon }));
234
253
  }
235
- const loaderIcon = (jsx(Loader, { "data-testid": "btn-loader-icon", size: "xs", className: buildClassName(LOADER_TEXT_CLASS[theme][variant]) }));
236
- return (jsx(Component, { ref: ref, className: classes, ...buttonSpecificProps, ...cleanRest, children: loading && !loadingText ? (jsxs("span", { className: "flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, children] })) : loading && loadingText ? (jsxs("span", { className: "flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, jsx(TextContent, { className: buildClassName(LOADER_TEXT_CLASS[theme][variant]), children: loadingText })] })) : (jsxs(Fragment, { children: [leftIcon && jsx("span", { className: "shrink-0 flex items-center", children: leftIcon }), children, rightIcon && jsx("span", { className: "shrink-0 flex items-center", children: rightIcon })] })) }));
254
+ return (jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...elementProps, ...cleanRest, children: loading ? (jsxs("span", { className: "inline-flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, loadingText ? (jsx(TextContent, { className: buildClassName((_d = LOADER_TEXT_CLASS[theme]) === null || _d === void 0 ? void 0 : _d[variant]), children: loadingText })) : (children)] })) : (jsxs(Fragment, { children: [leftIcon && jsx("span", { className: "shrink-0 inline-flex items-center", children: leftIcon }), children, rightIcon && jsx("span", { className: "shrink-0 inline-flex items-center", children: rightIcon })] })) }));
237
255
  });
238
256
  Button.displayName = 'Button';
239
257
 
@@ -880,6 +898,34 @@ const Label = forwardRef((props, ref) => {
880
898
  });
881
899
  Label.displayName = 'Label';
882
900
 
901
+ /**
902
+ * Checks if a value is an array.
903
+ * Equivalent to lodash's _.isArray.
904
+ */
905
+ function isArray(value) {
906
+ return Array.isArray(value);
907
+ }
908
+
909
+ /**
910
+ * Checks if a value is empty.
911
+ * Works for arrays, objects, strings, Maps, and Sets.
912
+ * Equivalent to lodash's _.isEmpty.
913
+ */
914
+ function isEmpty(value) {
915
+ if (value == null)
916
+ return true; // null or undefined
917
+ if (typeof value === 'string' || isArray(value)) {
918
+ return value.length === 0;
919
+ }
920
+ if (value instanceof Map || value instanceof Set) {
921
+ return value.size === 0;
922
+ }
923
+ if (typeof value === 'object') {
924
+ return Object.keys(value).length === 0;
925
+ }
926
+ return false; // for numbers, booleans, functions, etc.
927
+ }
928
+
883
929
  const sizeStyles = {
884
930
  sm: {
885
931
  box: 'size-3.5',
@@ -902,7 +948,7 @@ const sizeStyles = {
902
948
  };
903
949
  const Checkbox = forwardRef((props, ref) => {
904
950
  var _a;
905
- const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, helperText, disabled = false, indeterminate = false, size = 'md', variant = 'default', description, className = '', checked, onChange, ...rest } = props;
951
+ const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, helperText, disabled = false, indeterminate = false, size = 'md', variant = 'default', description, className = '', checked, onChange, required, 'aria-describedby': ariaDescribedBy, ...rest } = props;
906
952
  const generatedId = useId();
907
953
  const checkboxId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : generatedId;
908
954
  const innerRef = useRef(null);
@@ -914,6 +960,11 @@ const Checkbox = forwardRef((props, ref) => {
914
960
  }, [indeterminate]);
915
961
  const currentSize = sizeStyles[size] || sizeStyles.md;
916
962
  const isCard = variant === 'card';
963
+ const hasError = !isEmpty(error);
964
+ const errorId = checkboxId && hasError ? `${checkboxId}-error` : undefined;
965
+ const helperId = checkboxId && helperText && !hasError ? `${checkboxId}-helper` : undefined;
966
+ const descId = checkboxId && description ? `${checkboxId}-desc` : undefined;
967
+ const describedBy = [ariaDescribedBy, errorId, helperId, descId].filter(Boolean).join(' ') || undefined;
917
968
  return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsx("div", { className: buildClassName(isCard
918
969
  ? 'rounded-xl border p-3.5 transition-all duration-150 relative'
919
970
  : 'flex items-center', isCard &&
@@ -921,16 +972,16 @@ const Checkbox = forwardRef((props, ref) => {
921
972
  ? 'border-[var(--ui-border-muted)] bg-gray-50/50 dark:bg-gray-900/40 opacity-70'
922
973
  : checked || indeterminate
923
974
  ? 'border-blue-500/80 bg-blue-50/30 dark:bg-blue-950/20 dark:border-blue-500/60 shadow-xs'
924
- : 'border-[var(--ui-border)] bg-white dark:bg-gray-900 hover:border-gray-300 dark:hover:border-gray-700 shadow-2xs'), containerClass), children: jsxs("label", { htmlFor: checkboxId, className: buildClassName('inline-flex relative select-none w-full', description ? 'items-start' : 'items-center', disabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'), children: [jsxs("div", { className: "relative inline-flex items-center justify-center shrink-0", children: [jsx("input", { ref: innerRef, ...rest, id: checkboxId, name: name, type: "checkbox", disabled: disabled, checked: checked, onChange: onChange, "aria-checked": indeterminate ? 'mixed' : checked, className: buildClassName('peer cursor-pointer transition-all appearance-none rounded', currentSize.box, 'border focus:ring-2 focus:ring-offset-2', 'checked:bg-blue-600 checked:border-blue-600 dark:checked:bg-blue-600 dark:checked:border-blue-600', 'focus:ring-[var(--ui-focus-ring)] dark:focus:ring-offset-gray-900', disabled
975
+ : 'border-[var(--ui-border)] bg-white dark:bg-gray-900 hover:border-gray-300 dark:hover:border-gray-700 shadow-2xs'), containerClass), children: jsxs("label", { htmlFor: checkboxId, className: buildClassName('inline-flex relative select-none w-full', description ? 'items-start' : 'items-center', disabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'), children: [jsxs("div", { className: "relative inline-flex items-center justify-center shrink-0", children: [jsx("input", { ref: innerRef, ...rest, id: checkboxId, name: name, type: "checkbox", disabled: disabled, checked: checked, onChange: onChange, required: required, "aria-required": required ? true : undefined, "aria-invalid": hasError ? true : undefined, "aria-describedby": describedBy, "aria-checked": indeterminate ? 'mixed' : checked, className: buildClassName('peer cursor-pointer transition-all appearance-none rounded', currentSize.box, 'border focus:ring-2 focus:ring-offset-2', 'checked:bg-blue-600 checked:border-blue-600 dark:checked:bg-blue-600 dark:checked:border-blue-600', 'focus:ring-[var(--ui-focus-ring)] dark:focus:ring-offset-gray-900', disabled
925
976
  ? 'border-[var(--ui-border-muted)] bg-gray-100 dark:bg-gray-800 pointer-events-none'
926
977
  : 'bg-white dark:bg-gray-700 border-[var(--ui-border)] hover:border-gray-400 dark:hover:border-gray-500', indeterminate &&
927
978
  '!bg-blue-600 !border-blue-600 dark:!bg-blue-600 dark:!border-blue-600', className) }), !indeterminate && (jsx("span", { className: buildClassName('absolute inset-0 flex items-center justify-center text-white pointer-events-none transition-opacity', checked !== undefined
928
979
  ? checked
929
980
  ? 'opacity-100'
930
981
  : 'opacity-0'
931
- : 'opacity-0 peer-checked:opacity-100'), children: jsx("svg", { className: currentSize.svg, viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) })), indeterminate && (jsx("span", { className: "absolute inset-0 flex items-center justify-center text-white pointer-events-none opacity-100 z-10", children: jsx("svg", { className: currentSize.svg, viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M4 10a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1z", clipRule: "evenodd" }) }) }))] }), (label || description) && (jsxs("div", { className: "ml-2.5 flex flex-col justify-center min-w-0 flex-1", children: [label && (jsx(Label, { htmlFor: checkboxId, className: buildClassName(disabled
982
+ : 'opacity-0 peer-checked:opacity-100'), children: jsx("svg", { className: currentSize.svg, viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) })), indeterminate && (jsx("span", { className: "absolute inset-0 flex items-center justify-center text-white pointer-events-none opacity-100 z-10", children: jsx("svg", { className: currentSize.svg, viewBox: "0 0 20 20", fill: "currentColor", children: jsx("path", { fillRule: "evenodd", d: "M4 10a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1z", clipRule: "evenodd" }) }) }))] }), (label || description) && (jsxs("div", { className: "ml-2.5 flex flex-col justify-center min-w-0 flex-1", children: [label && (jsx(Label, { htmlFor: checkboxId, "aria-required": required, className: buildClassName(disabled
932
983
  ? 'cursor-not-allowed text-gray-400 dark:text-gray-500'
933
- : 'cursor-pointer text-gray-800 dark:text-gray-200 font-medium', currentSize.text, labelClass), children: label })), description && (jsx("span", { className: buildClassName('mt-0.5 text-gray-500 dark:text-gray-400 font-normal leading-normal', currentSize.subtext), children: description }))] }))] }) }), (error || helperText) && (jsx("div", { className: "mt-1.5 ml-0.5", children: error ? (jsx(TextContent, { error: true, small: true, children: error })) : (jsx(TextContent, { muted: true, small: true, children: helperText })) }))] }));
984
+ : 'cursor-pointer text-gray-800 dark:text-gray-200 font-medium', currentSize.text, labelClass), children: label })), description && (jsx("span", { id: descId, className: buildClassName('mt-0.5 text-gray-500 dark:text-gray-400 font-normal leading-normal', currentSize.subtext), children: description }))] }))] }) }), (error || helperText) && (jsx("div", { className: "mt-1.5 ml-0.5", children: error ? (jsx(TextContent, { error: true, small: true, id: errorId, children: error })) : (jsx(TextContent, { muted: true, small: true, id: helperId, children: helperText })) }))] }));
934
985
  });
935
986
  Checkbox.displayName = 'Checkbox';
936
987
 
@@ -941,25 +992,26 @@ const columnClasses = {
941
992
  4: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3',
942
993
  };
943
994
  const CheckboxGroup = (props) => {
944
- const { name, options, label, labelClass = '', labelWrapperClass = '', labelHint, containerClass = '', value: controlledValue, defaultValue, onChange, inline = false, columns, size = 'md', variant = 'default', showSelectAll = false, selectAllLabel = 'Select All', error, helperText, disabled = false, } = props;
995
+ const { name, options, label, labelClass = '', labelWrapperClass = '', labelHint, containerClass = '', value: controlledValue, defaultValue, onChange, inline = false, columns, size = 'md', variant = 'default', showSelectAll = false, selectAllLabel = 'Select All', error, helperText, disabled = false, required, 'aria-describedby': ariaDescribedBy, } = props;
945
996
  const groupId = useId();
946
997
  const [internalValue, setInternalValue] = useState(defaultValue || []);
947
998
  const isControlled = controlledValue !== undefined;
948
- const currentValue = isControlled ? controlledValue : internalValue;
999
+ const currentValue = isControlled ? controlledValue || [] : internalValue;
949
1000
  const items = useMemo(() => options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt), [options]);
950
1001
  const enabledItems = useMemo(() => items.filter((i) => !i.disabled && !disabled), [items, disabled]);
1002
+ const currentValueKeySet = useMemo(() => new Set(currentValue), [currentValue]);
951
1003
  // Select All State calculation
952
1004
  const allSelected = useMemo(() => {
953
1005
  if (!enabledItems.length)
954
1006
  return false;
955
- return enabledItems.every((item) => currentValue.includes(item.value));
956
- }, [enabledItems, currentValue]);
1007
+ return enabledItems.every((item) => currentValueKeySet.has(item.value));
1008
+ }, [enabledItems, currentValueKeySet]);
957
1009
  const isIndeterminate = useMemo(() => {
958
1010
  if (allSelected)
959
1011
  return false;
960
- return enabledItems.some((item) => currentValue.includes(item.value));
961
- }, [allSelected, enabledItems, currentValue]);
962
- const handleSelectAllChange = (e) => {
1012
+ return enabledItems.some((item) => currentValueKeySet.has(item.value));
1013
+ }, [allSelected, enabledItems, currentValueKeySet]);
1014
+ const handleSelectAllChange = useCallback((e) => {
963
1015
  let updated;
964
1016
  if (allSelected || isIndeterminate) {
965
1017
  const enabledValues = new Set(enabledItems.map((i) => i.value));
@@ -974,8 +1026,8 @@ const CheckboxGroup = (props) => {
974
1026
  setInternalValue(updated);
975
1027
  }
976
1028
  onChange === null || onChange === void 0 ? void 0 : onChange(updated, e);
977
- };
978
- const handleGroupChange = (e) => {
1029
+ }, [allSelected, isIndeterminate, enabledItems, currentValue, isControlled, onChange]);
1030
+ const handleGroupChange = useCallback((e) => {
979
1031
  const raw = e.target.value;
980
1032
  const checked = e.target.checked;
981
1033
  const matched = items.find((i) => String(i.value) === raw);
@@ -991,16 +1043,21 @@ const CheckboxGroup = (props) => {
991
1043
  setInternalValue(updated);
992
1044
  }
993
1045
  onChange === null || onChange === void 0 ? void 0 : onChange(updated, e);
994
- };
1046
+ }, [items, currentValue, isControlled, onChange]);
1047
+ const hasError = Boolean(error);
1048
+ const labelId = label ? `${groupId}-label` : undefined;
1049
+ const errorId = groupId && hasError ? `${groupId}-error` : undefined;
1050
+ const helperId = groupId && helperText && !hasError ? `${groupId}-helper` : undefined;
1051
+ const describedBy = [ariaDescribedBy, errorId, helperId].filter(Boolean).join(' ') || undefined;
995
1052
  const layoutClass = columns
996
1053
  ? columnClasses[columns]
997
1054
  : inline
998
1055
  ? 'flex flex-wrap gap-3'
999
1056
  : 'flex flex-col gap-3';
1000
- return (jsxs("div", { className: buildClassName('mb-3 space-y-3', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, children: label }), labelHint && (jsx(BodyText, { muted: true, small: true, children: labelHint }))] })), showSelectAll && items.length > 0 && (jsx("div", { className: "pb-1 border-b border-[var(--ui-border)]", children: jsx(Checkbox, { id: `${groupId}-select-all`, label: selectAllLabel, size: size, variant: variant, checked: allSelected, indeterminate: isIndeterminate, disabled: disabled || enabledItems.length === 0, onChange: handleSelectAllChange }) })), jsx("div", { className: layoutClass, children: items.map((item, index) => {
1057
+ return (jsxs("div", { role: "group", "aria-labelledby": labelId, "aria-describedby": describedBy, className: buildClassName('mb-3 space-y-3', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { id: labelId, className: labelClass, "aria-required": required, children: label }), labelHint && (jsx(BodyText, { muted: true, small: true, children: labelHint }))] })), showSelectAll && items.length > 0 && (jsx("div", { className: "pb-1 border-b border-[var(--ui-border)]", children: jsx(Checkbox, { id: `${groupId}-select-all`, label: selectAllLabel, size: size, variant: variant, checked: allSelected, indeterminate: isIndeterminate, disabled: disabled || enabledItems.length === 0, onChange: handleSelectAllChange }) })), jsx("div", { className: layoutClass, children: items.map((item, index) => {
1001
1058
  const key = typeof item.value === 'string' || typeof item.value === 'number' ? item.value : index;
1002
- return (jsx(Checkbox, { name: name, id: `${groupId}-${String(item.value)}`, label: item.label, description: item.description, value: String(item.value), size: size, variant: variant, disabled: disabled || item.disabled, checked: currentValue.includes(item.value), onChange: handleGroupChange }, key));
1003
- }) }), (error || helperText) && (jsx("div", { className: "mt-1.5 ml-0.5", children: error ? (jsx(TextContent, { error: true, small: true, children: error })) : (jsx(TextContent, { muted: true, small: true, children: helperText })) }))] }));
1059
+ return (jsx(Checkbox, { name: name, id: `${groupId}-${String(item.value)}`, label: item.label, description: item.description, value: String(item.value), size: size, variant: variant, disabled: disabled || item.disabled, checked: currentValueKeySet.has(item.value), onChange: handleGroupChange }, key));
1060
+ }) }), (error || helperText) && (jsx("div", { className: "mt-1.5 ml-0.5", children: error ? (jsx(TextContent, { error: true, small: true, id: errorId, children: error })) : (jsx(TextContent, { muted: true, small: true, id: helperId, children: helperText })) }))] }));
1004
1061
  };
1005
1062
  CheckboxGroup.displayName = 'CheckboxGroup';
1006
1063
 
@@ -1094,27 +1151,39 @@ var SvgChevronRight = function SvgChevronRight(props) {
1094
1151
  })));
1095
1152
  };
1096
1153
 
1097
- const Pagination = ({ page: currentPage, limit, total, onChange, className, ellipsis = true, // Default to showing ellipsis
1098
- }) => {
1099
- const pages = new Array(Math.ceil(total / limit)).fill(null).map((_, i) => i + 1);
1100
- const isFirstPage = currentPage === 1;
1101
- const isLastPage = total === 0 || pages.length === currentPage;
1102
- const range = usePagination({ totalPages: pages.length, currentPage });
1154
+ const Pagination = (props) => {
1155
+ var _a, _b, _c, _d, _e, _f;
1156
+ const { className, ellipsis = true, onChange, isMobile, showSummary = true, showPageNumbers = true, } = props;
1157
+ const currentPage = (_c = (_b = (_a = props.page) !== null && _a !== void 0 ? _a : props.current) !== null && _b !== void 0 ? _b : props.currentPage) !== null && _c !== void 0 ? _c : 1;
1158
+ const limit = (_e = (_d = props.limit) !== null && _d !== void 0 ? _d : props.pageSize) !== null && _e !== void 0 ? _e : 10;
1159
+ const total = (_f = props.total) !== null && _f !== void 0 ? _f : 0;
1160
+ const totalPages = Math.max(1, Math.ceil(total / limit));
1161
+ const isFirstPage = currentPage <= 1;
1162
+ const isLastPage = total === 0 || currentPage >= totalPages;
1163
+ const range = usePagination({ totalPages, currentPage });
1103
1164
  const handlePageChange = (nextVal) => {
1104
- if (nextVal === currentPage)
1165
+ if (nextVal === currentPage || nextVal < 1 || nextVal > totalPages)
1105
1166
  return;
1106
1167
  onChange({ page: nextVal });
1107
1168
  };
1108
- if (ellipsis) {
1109
- return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6', className), children: [jsxs("div", { className: "flex flex-1 justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "ellipsis-mobile-prev-btn", children: [jsx("span", { children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-mobile-next-btn", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: total === 0 ? 0 : Math.max(currentPage * limit - limit + 1, 1) }), "to", jsx("span", { className: "font-medium", children: Math.min(currentPage * limit, total) }), "of", jsx("span", { className: "font-medium", children: total }), "results"] }), jsxs("ul", { className: "flex items-center space-x-2 text-sm", children: [jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "ellipsis-prev-btn", children: "Prev" }) }), range.map((page, idx) => {
1110
- if (page === DOTS) {
1111
- return (jsx("li", { "data-testid": `page-${page}`, className: "px-2 py-1 text-gray-400", children: "\u2026" }, `${page}-${idx}`));
1112
- }
1113
- return (jsx("li", { children: jsx(Button, { size: "xs", theme: page === currentPage ? 'primary' : 'secondary', variant: page === currentPage ? 'default' : 'plain', "aria-current": page === currentPage ? 'page' : undefined, onClick: () => handlePageChange(page), children: page }) }, `${page}-${idx}`));
1114
- }), jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-next-btn", children: "Next" }) })] })] })] }));
1169
+ const startResult = total === 0 ? 0 : Math.max((currentPage - 1) * limit + 1, 1);
1170
+ const endResult = Math.min(currentPage * limit, total);
1171
+ // Explicit Mobile UX (when isMobile=true)
1172
+ if (isMobile) {
1173
+ return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6 rounded-xl border border-[var(--ui-border-muted)] w-full', className), children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": ellipsis ? 'ellipsis-mobile-prev-btn' : 'mobile-prev-btn', className: "flex items-center gap-1 font-medium", children: [jsx(SvgChevronLeft, { className: "size-4" }), jsx("span", { children: "Previous" })] }), jsxs("span", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300", children: ["Page ", currentPage, " of ", totalPages] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": ellipsis ? 'ellipsis-mobile-next-btn' : 'mobile-next-btn', className: "flex items-center gap-1 font-medium", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }));
1115
1174
  }
1116
- return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6', className), children: [jsxs("div", { className: "flex flex-1 justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "mobile-prev-btn", children: [jsx("span", { children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "mobile-next-btn", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: total === 0 ? 0 : Math.max(currentPage * limit - limit + 1, 1) }), "to", jsx("span", { className: "font-medium", children: Math.min(currentPage * limit, total) }), "of", jsx("span", { className: "font-medium", children: total }), "results"] }), jsxs("nav", { className: "isolate inline-flex items-center rounded-md", "aria-label": "Pagination", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": "prev-btn", children: [jsx("span", { className: "sr-only", children: "Previous" }), jsx(SvgChevronLeft, { className: "size-4" })] }), pages.map((page) => (jsx(Button, { size: "xs", theme: page === currentPage ? 'primary' : 'secondary', variant: page === currentPage ? 'default' : 'plain', "aria-current": page === currentPage ? 'page' : undefined, onClick: () => handlePageChange(page), children: jsx("span", { className: "size-4 block", children: page }) }, page))), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "next-btn", children: [jsx("span", { className: "sr-only", children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] })] })] }));
1175
+ // Standard Responsive View
1176
+ return (jsxs("div", { "data-testid": "pagination-container", className: buildClassName('flex items-center justify-between bg-white dark:bg-gray-800 px-4 py-3 sm:px-6 rounded-xl border border-[var(--ui-border-muted)] w-full', className), children: [jsxs("div", { className: "flex flex-1 items-center justify-between sm:hidden", children: [jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": ellipsis ? 'ellipsis-mobile-prev-btn' : 'mobile-prev-btn', className: "flex items-center gap-1", children: [jsx(SvgChevronLeft, { className: "size-4" }), jsx("span", { children: "Previous" })] }), jsxs("span", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300", children: ["Page ", currentPage, " of ", totalPages] }), jsxs(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": ellipsis ? 'ellipsis-mobile-next-btn' : 'mobile-next-btn', className: "flex items-center gap-1", children: [jsx("span", { children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] }), jsxs("div", { className: "hidden sm:flex sm:flex-1 sm:items-center sm:justify-between", children: [showSummary && (jsxs("p", { "data-testid": "pagination-summary", className: "text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1", children: ["Showing", jsx("span", { className: "font-medium", children: startResult }), "to", jsx("span", { className: "font-medium", children: endResult }), "of", jsx("span", { className: "font-medium", children: total }), "results"] })), showPageNumbers && (jsxs("ul", { className: "flex items-center space-x-2 text-sm", children: [jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isFirstPage, onClick: () => handlePageChange(currentPage - 1), "data-testid": ellipsis ? 'ellipsis-prev-btn' : 'prev-btn', children: ellipsis ? 'Prev' : 'Previous' }) }), ellipsis
1177
+ ? range.map((pageItem, idx) => {
1178
+ if (pageItem === DOTS) {
1179
+ return (jsx("li", { "data-testid": `page-${pageItem}`, className: "px-2 py-1 text-gray-400", children: "\u2026" }, `${pageItem}-${idx}`));
1180
+ }
1181
+ const pageNum = Number(pageItem);
1182
+ return (jsx("li", { children: jsx(Button, { size: "xs", theme: pageNum === currentPage ? 'primary' : 'secondary', variant: pageNum === currentPage ? 'default' : 'plain', "aria-current": pageNum === currentPage ? 'page' : undefined, onClick: () => handlePageChange(pageNum), children: pageNum }) }, `${pageItem}-${idx}`));
1183
+ })
1184
+ : Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNum) => (jsx("li", { children: jsx(Button, { size: "xs", theme: pageNum === currentPage ? 'primary' : 'secondary', variant: pageNum === currentPage ? 'default' : 'plain', "aria-current": pageNum === currentPage ? 'page' : undefined, onClick: () => handlePageChange(pageNum), children: jsx("span", { className: "size-4 block", children: pageNum }) }) }, pageNum))), jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": ellipsis ? 'ellipsis-next-btn' : 'next-btn', children: ellipsis ? 'Next' : 'Next' }) })] }))] })] }));
1117
1185
  };
1186
+ Pagination.displayName = 'Pagination';
1118
1187
 
1119
1188
  function get(obj, path, defaultValue) {
1120
1189
  var _a;
@@ -1201,9 +1270,10 @@ const useRowSelection = ({ items, getKey, selectedRowKeys, defaultSelectedRowKey
1201
1270
  emit(Array.from(new Set([...keys, ...selectableKeySet])));
1202
1271
  }
1203
1272
  }, [allSelected, selectableEntries, keys, emit]);
1273
+ const isSelected = useCallback((key) => keySet.has(key), [keySet]);
1204
1274
  return {
1205
1275
  selectedKeys: keys,
1206
- isSelected: (key) => keySet.has(key),
1276
+ isSelected,
1207
1277
  toggleRow,
1208
1278
  toggleAll,
1209
1279
  allSelected,
@@ -1314,6 +1384,8 @@ const SortIconMap = {
1314
1384
  };
1315
1385
  const getSortIcon = (sort) => sort ? (SortIconMap[sort]) : (jsx(SvgArrowUpDown, { className: "size-4 text-gray-400 shrink-0 group-hover:text-gray-800 dark:group-hover:text-white" }));
1316
1386
 
1387
+ const OVERFLOW_REGEX = /\boverflow-(x-|y-)?(auto|hidden|scroll|visible|clip)\b/;
1388
+ const NOOP$1 = () => { };
1317
1389
  const DENSITY_CELL_CLASS$2 = {
1318
1390
  compact: 'px-2 py-1.5',
1319
1391
  default: '',
@@ -1343,41 +1415,33 @@ const autoSortItems = (items, column, sort) => {
1343
1415
  return 0;
1344
1416
  });
1345
1417
  };
1346
- function DataTable({ items, columns, sorting = {}, onSort, loading, pagination, setPagination = () => { }, emptyMessage = 'No data found', containerClass = '', wrapperClass = '', tableClass = '', headClass = '', footerRow, rowKey = 'id', onRowClick, rowClass, density = 'default', striped = false, layout = 'auto', selectable = false, selectedRowKeys, defaultSelectedRowKeys, onSelectionChange, isRowSelectable, stickyHeader = true, stickyPagination = true, paginationContainerClass, }) {
1347
- const internalSorting = useRef({});
1348
- const [sortedItems, setSortedItems] = useState(items);
1349
- useEffect(() => {
1350
- setSortedItems(items);
1351
- }, [items]);
1352
- const doAutoSort = (column) => {
1353
- internalSorting.current = updateSortQuery(internalSorting.current, column.name, column.type);
1354
- setSortedItems(autoSortItems(items, column, internalSorting.current[column.name]));
1355
- };
1356
- const handleSort = (column) => {
1418
+ function DataTable({ items, columns, sorting = {}, onSort, loading, pagination, setPagination = NOOP$1, emptyMessage = 'No data found', containerClass = '', wrapperClass = '', tableClass = '', headClass = '', footerRow, rowKey = 'id', onRowClick, rowClass, density = 'default', striped = false, layout = 'auto', selectable = false, selectedRowKeys, defaultSelectedRowKeys, onSelectionChange, isRowSelectable, stickyHeader = true, stickyPagination = true, rowHoverActions, renderRowHoverActions, rowHoverActionHeader, rowHoverActionMode = 'overlay', rowHoverActionClass, paginationClass, paginationContainerClass, paginationProps, renderPagination, }) {
1419
+ const [internalSortingState, setInternalSortingState] = useState({});
1420
+ const handleSort = useCallback((column) => {
1357
1421
  if (!column.sortable)
1358
1422
  return;
1359
- onSort ? onSort(column) : doAutoSort(column);
1360
- };
1361
- const getKey = (item, idx) => {
1362
- var _a;
1363
- if (typeof rowKey === 'function')
1364
- return rowKey(item);
1365
- return (_a = item[rowKey]) !== null && _a !== void 0 ? _a : idx;
1366
- };
1367
- const getRowClass = (item, idx) => {
1368
- if (!rowClass)
1369
- return '';
1370
- if (typeof rowClass === 'function')
1371
- return rowClass(item, idx);
1372
- return rowClass;
1373
- };
1374
- const getCellClass = (column, item) => {
1375
- if (!column.cellClass)
1376
- return '';
1377
- if (typeof column.cellClass === 'function')
1378
- return column.cellClass(item);
1379
- return column.cellClass;
1380
- };
1423
+ if (onSort) {
1424
+ onSort(column);
1425
+ }
1426
+ else {
1427
+ setInternalSortingState((prev) => updateSortQuery(prev, column.name, column.type));
1428
+ }
1429
+ }, [onSort]);
1430
+ const activeSorting = useMemo(() => ({ ...sorting, ...internalSortingState }), [sorting, internalSortingState]);
1431
+ const sortedItems = useMemo(() => {
1432
+ if (onSort)
1433
+ return items;
1434
+ const activeSortKey = Object.keys(activeSorting)[0];
1435
+ if (!activeSortKey)
1436
+ return items;
1437
+ const column = columns.find((c) => c.name === activeSortKey);
1438
+ if (!column)
1439
+ return items;
1440
+ return autoSortItems(items, column, activeSorting[activeSortKey]);
1441
+ }, [items, columns, onSort, activeSorting]);
1442
+ const getKey = useCallback((item, idx) => { var _a; return typeof rowKey === 'function' ? rowKey(item) : ((_a = item[rowKey]) !== null && _a !== void 0 ? _a : idx); }, [rowKey]);
1443
+ const getRowClass = useCallback((item, idx) => typeof rowClass === 'function' ? rowClass(item, idx) : rowClass || '', [rowClass]);
1444
+ const getCellClass = useCallback((column, item) => typeof column.cellClass === 'function' ? column.cellClass(item) : column.cellClass || '', []);
1381
1445
  const { isSelected, toggleRow, toggleAll, allSelected, someSelected } = useRowSelection({
1382
1446
  items: sortedItems,
1383
1447
  getKey,
@@ -1386,15 +1450,50 @@ function DataTable({ items, columns, sorting = {}, onSort, loading, pagination,
1386
1450
  onSelectionChange,
1387
1451
  isRowSelectable,
1388
1452
  });
1453
+ const hasRowHoverActions = Boolean(rowHoverActions || renderRowHoverActions);
1454
+ const isInlineMode = hasRowHoverActions && rowHoverActionMode === 'inline';
1455
+ const isOverlayMode = hasRowHoverActions && rowHoverActionMode !== 'inline';
1389
1456
  const densityCellClass = DENSITY_CELL_CLASS$2[density];
1390
- const colSize = columns.length + (selectable ? 1 : 0);
1391
- const wrapperHasOverflow = /\boverflow-(x-|y-)?(auto|hidden|scroll|visible|clip)\b/.test(wrapperClass);
1457
+ const colSize = columns.length + (selectable ? 1 : 0) + (isInlineMode ? 1 : 0);
1458
+ const wrapperHasOverflow = OVERFLOW_REGEX.test(wrapperClass);
1459
+ const renderActionsContent = useCallback((item, idx) => {
1460
+ if (renderRowHoverActions)
1461
+ return renderRowHoverActions(item, idx);
1462
+ if (!rowHoverActions)
1463
+ return null;
1464
+ return rowHoverActions(item, idx).map((act) => {
1465
+ const isDisabled = typeof act.disabled === 'function' ? act.disabled(item) : act.disabled;
1466
+ const isIconOnly = Boolean(act.icon && !act.label);
1467
+ return (jsxs(Button, { size: "xs", theme: act.theme || 'secondary', variant: act.variant || 'plain', disabled: isDisabled, title: act.title || (typeof act.label === 'string' ? act.label : undefined), className: buildClassName(isIconOnly
1468
+ ? 'p-1.5 rounded-full hover:bg-black/5 dark:hover:bg-white/10'
1469
+ : 'px-2 py-1', act.className), onClick: (e) => {
1470
+ e.stopPropagation();
1471
+ act.onClick(item, idx, e);
1472
+ }, children: [act.icon && jsx("span", { className: "shrink-0", children: act.icon }), act.label && jsx("span", { children: act.label })] }, act.id));
1473
+ });
1474
+ }, [renderRowHoverActions, rowHoverActions]);
1392
1475
  return (jsxs("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: [jsx("div", { className: buildClassName(!wrapperHasOverflow && 'overflow-x-auto', 'rounded-lg border border-[var(--ui-border)]', wrapperClass, pagination && 'rounded-b-none border-b-0'), children: jsxs(Table, { className: buildClassName('relative w-full max-w-full h-fit', tableClass), layout: layout, children: [jsxs(TableHead, { className: headClass, children: [selectable && (jsx(TableHeaderCell, { className: buildClassName('bg-gray-50 dark:bg-gray-800 w-px', densityCellClass, stickyHeader && 'sticky top-0 z-20 shadow-[0_1px_2px_rgba(0,0,0,0.06)]'), children: jsx(Checkbox, { "aria-label": "Select all rows", checked: allSelected, ref: (el) => {
1393
1476
  if (el)
1394
1477
  el.indeterminate = someSelected;
1395
- }, onChange: toggleAll }) })), columns.map((column) => (jsx(TableHeaderCell, { align: column.headerAlign, style: { width: column.width || 'auto' }, className: buildClassName('bg-gray-50 dark:bg-gray-800', densityCellClass, stickyHeader && 'sticky top-0 z-20 shadow-[0_1px_2px_rgba(0,0,0,0.06)]', column.sticky === 'left' &&
1396
- 'sticky left-0 z-30 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
1397
- 'sticky right-0 z-30 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]', column.headerClass), children: jsxs(TextContent, { role: column.sortable ? 'button' : undefined, className: buildClassName('group', column.sortable && 'inline-flex items-center gap-2 cursor-pointer select-none'), onClick: () => handleSort(column), children: [jsx(TextContent, { className: "shrink-0", children: column.label }), column.sortable ? getSortIcon(sorting[column.name]) : null] }) }, column.name)))] }), jsx(TableBody, { loading: loading, colSize: colSize, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && sortedItems.length === 0 ? (jsx(EmptyTableRow, { colSpan: colSize, children: emptyMessage })) : (sortedItems.map((item, idx) => {
1478
+ }, onChange: toggleAll }) })), columns.map((column) => {
1479
+ const sortState = activeSorting[column.name];
1480
+ const ariaSortValue = column.sortable
1481
+ ? sortState === 'asc' || sortState === '1'
1482
+ ? 'ascending'
1483
+ : sortState === 'desc' || sortState === '-1'
1484
+ ? 'descending'
1485
+ : 'none'
1486
+ : undefined;
1487
+ return (jsx(TableHeaderCell, { align: column.headerAlign, style: { width: column.width || 'auto' }, "aria-sort": ariaSortValue, className: buildClassName('bg-gray-50 dark:bg-gray-800', densityCellClass, stickyHeader && 'sticky top-0 z-20 shadow-[0_1px_2px_rgba(0,0,0,0.06)]', column.sticky === 'left' &&
1488
+ 'sticky left-0 z-30 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
1489
+ 'sticky right-0 z-30 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]', column.headerClass), children: jsxs(TextContent, { role: column.sortable ? 'button' : undefined, tabIndex: column.sortable ? 0 : undefined, "aria-label": column.sortable ? `Sort by ${column.label}` : undefined, className: buildClassName('group', column.sortable &&
1490
+ 'inline-flex items-center gap-2 cursor-pointer select-none focus:outline-none focus-visible:ring-1 focus-visible:ring-blue-500 rounded'), onClick: () => handleSort(column), onKeyDown: (e) => {
1491
+ if (column.sortable && (e.key === 'Enter' || e.key === ' ')) {
1492
+ e.preventDefault();
1493
+ handleSort(column);
1494
+ }
1495
+ }, children: [jsx(TextContent, { className: "shrink-0", children: column.label }), column.sortable ? getSortIcon(activeSorting[column.name]) : null] }) }, column.name));
1496
+ }), isInlineMode && (jsx(TableHeaderCell, { align: "right", className: buildClassName('bg-gray-50 dark:bg-gray-800 text-right w-px', densityCellClass, stickyHeader && 'sticky top-0 z-20 shadow-[0_1px_2px_rgba(0,0,0,0.06)]'), children: rowHoverActionHeader !== null && rowHoverActionHeader !== void 0 ? rowHoverActionHeader : '' })), isOverlayMode && (jsx(TableHeaderCell, { align: "right", style: { padding: 0, width: 0, minWidth: 0, maxWidth: 0 }, className: buildClassName('sticky right-0 z-20 !p-0 !pr-0 !pl-0 !py-0 w-0 max-w-0 min-w-0 border-0 bg-gray-50 dark:bg-gray-800', stickyHeader && 'top-0'), children: null }))] }), jsx(TableBody, { loading: loading, colSize: colSize, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && sortedItems.length === 0 ? (jsx(EmptyTableRow, { colSpan: colSize, children: emptyMessage })) : (sortedItems.map((item, idx) => {
1398
1497
  const key = getKey(item, idx);
1399
1498
  const rowSelectable = !isRowSelectable || isRowSelectable(item);
1400
1499
  return (jsxs(TableRow, { className: buildClassName('group', onRowClick && 'cursor-pointer', striped && idx % 2 === 1 && 'bg-gray-50 dark:bg-gray-800/40', getRowClass(item, idx)), onClick: onRowClick ? () => onRowClick(item, idx) : undefined, children: [selectable && (jsx(TableCell, { className: buildClassName(densityCellClass, striped && idx % 2 === 1 && 'bg-gray-50 dark:bg-gray-800/40'), children: jsx("span", { onClick: (e) => e.stopPropagation(), children: jsx(Checkbox, { "aria-label": "Select row", checked: isSelected(key), disabled: !rowSelectable, onChange: () => toggleRow(key) }) }) })), columns.map((column) => {
@@ -1404,8 +1503,10 @@ function DataTable({ items, columns, sorting = {}, onSort, loading, pagination,
1404
1503
  'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]', getCellClass(column, item)), children: column.render
1405
1504
  ? column.render(item)
1406
1505
  : ((_a = get(item, column.name)) !== null && _a !== void 0 ? _a : 'Not set') }, column.name));
1407
- })] }, key));
1408
- })) }), footerRow && jsx(TableFoot, { children: footerRow })] }) }), pagination && (jsx(Pagination, { ...pagination, className: buildClassName('rounded-b-lg border border-[var(--ui-border)]', stickyPagination && 'sticky bottom-0 z-20 bg-white dark:bg-gray-800', paginationContainerClass), onChange: setPagination }))] }));
1506
+ }), isInlineMode && (jsx(TableCell, { align: "right", className: buildClassName(densityCellClass, striped && idx % 2 === 1 && 'bg-gray-50 dark:bg-gray-800/40'), onClick: (e) => e.stopPropagation(), children: jsx("div", { className: buildClassName('opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-150 flex items-center justify-end gap-1.5', rowHoverActionClass), "data-testid": `row-hover-actions-${key}`, children: renderActionsContent(item, idx) }) })), isOverlayMode && (jsx(TableCell, { align: "right", style: { padding: 0, width: 0, minWidth: 0, maxWidth: 0 }, className: "sticky right-0 z-20 !p-0 !pr-0 !pl-0 !py-0 w-0 max-w-0 min-w-0 border-0 overflow-visible text-right", onClick: (e) => e.stopPropagation(), children: jsx("div", { className: buildClassName('absolute right-0 top-0 bottom-0 h-full opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-150 flex items-center justify-end gap-1 px-3 z-30 pointer-events-none group-hover:pointer-events-auto whitespace-nowrap', striped && idx % 2 === 1
1507
+ ? 'bg-gray-100 dark:bg-gray-800'
1508
+ : 'bg-white group-hover:bg-gray-50 dark:bg-gray-900 dark:group-hover:bg-gray-800', rowHoverActionClass), "data-testid": `row-hover-actions-${key}`, children: renderActionsContent(item, idx) }) }))] }, key));
1509
+ })) }), footerRow && jsx(TableFoot, { children: footerRow })] }) }), pagination && (jsx("div", { className: buildClassName(stickyPagination && 'sticky bottom-0 z-20', paginationContainerClass), children: renderPagination ? (renderPagination(pagination, setPagination)) : (jsx(Pagination, { ...pagination, ...paginationProps, className: buildClassName('rounded-t-none rounded-b-lg border border-[var(--ui-border)] bg-white dark:bg-gray-800', paginationClass, paginationProps === null || paginationProps === void 0 ? void 0 : paginationProps.className), onChange: setPagination })) }))] }));
1409
1510
  }
1410
1511
  DataTable.displayName = 'DataTable';
1411
1512
 
@@ -2034,20 +2135,20 @@ function buildInputClass(className, { isFocusWithin = false, disabled = false, h
2034
2135
  : 'focus:outline-[var(--ui-focus-ring)] focus:outline-2 focus:-outline-offset-2', className);
2035
2136
  }
2036
2137
  const Input = forwardRef((props, ref) => {
2037
- const { name, id = name, type, label, labelHint, className, labelClass, labelWrapperClass, containerClass, error, showErrorMessage = true, rightGroup, leftGroup, prefix, suffix, clearable = false, onClear, showCount = false, helperText, size = 'md', value, defaultValue, onChange, maxLength, ...inputProps } = props;
2138
+ const { name, id = name, type, label, labelHint, className, labelClass, labelWrapperClass, containerClass, error, showErrorMessage = true, rightGroup, leftGroup, prefix, suffix, clearable = false, onClear, showCount = false, helperText, size = 'md', value, defaultValue, onChange, maxLength, required, 'aria-describedby': ariaDescribedBy, ...inputProps } = props;
2038
2139
  const innerRef = useRef(null);
2039
2140
  useImperativeHandle(ref, () => innerRef.current);
2040
2141
  const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''));
2041
2142
  const isControlled = value !== undefined;
2042
2143
  const currentVal = isControlled ? String(value !== null && value !== void 0 ? value : '') : uncontrolledValue;
2043
2144
  const charLength = currentVal.length;
2044
- const handleInputChange = (e) => {
2145
+ const handleInputChange = useCallback((e) => {
2045
2146
  if (!isControlled) {
2046
2147
  setUncontrolledValue(e.target.value);
2047
2148
  }
2048
2149
  onChange === null || onChange === void 0 ? void 0 : onChange(e);
2049
- };
2050
- const handleClear = () => {
2150
+ }, [isControlled, onChange]);
2151
+ const handleClear = useCallback(() => {
2051
2152
  if (innerRef.current) {
2052
2153
  innerRef.current.value = '';
2053
2154
  const event = new Event('input', {
@@ -2060,18 +2161,21 @@ const Input = forwardRef((props, ref) => {
2060
2161
  onChange === null || onChange === void 0 ? void 0 : onChange(event);
2061
2162
  }
2062
2163
  onClear === null || onClear === void 0 ? void 0 : onClear();
2063
- };
2164
+ }, [isControlled, onChange, onClear]);
2064
2165
  const hasError = Boolean(error);
2065
2166
  const currentSize = sizeClasses$4[size] || sizeClasses$4.md;
2167
+ const errorId = id && hasError && showErrorMessage ? `${id}-error` : undefined;
2168
+ const helperId = id && helperText && !hasError ? `${id}-helper` : undefined;
2169
+ const describedBy = [ariaDescribedBy, errorId, helperId].filter(Boolean).join(' ') || undefined;
2066
2170
  const showClearButton = clearable && charLength > 0 && !inputProps.disabled;
2067
- const actualRightGroup = rightGroup !== null && rightGroup !== void 0 ? rightGroup : (showClearButton ? (jsx("button", { type: "button", onClick: handleClear, className: "p-1 rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors cursor-pointer", title: "Clear text", tabIndex: -1, children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", className: currentSize.icon, children: jsx("path", { d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" }) }) })) : null);
2171
+ const actualRightGroup = rightGroup !== null && rightGroup !== void 0 ? rightGroup : (showClearButton ? (jsx("button", { type: "button", onClick: handleClear, className: "p-1 rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors cursor-pointer", "aria-label": "Clear input text", title: "Clear text", tabIndex: -1, children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", className: currentSize.icon, children: jsx("path", { d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" }) }) })) : null);
2068
2172
  const hasRightGroup = actualRightGroup !== null && actualRightGroup !== undefined;
2069
2173
  const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
2070
- return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-1.5', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs("div", { className: "flex rounded-lg shadow-2xs", children: [prefix && (jsx("span", { className: "inline-flex items-center px-3 rounded-l-lg border border-r-0 border-[var(--ui-border)] bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs font-medium shrink-0", children: prefix })), jsxs("div", { className: "relative flex-1 min-w-0", children: [hasLeftGroup && (jsx("span", { className: buildClassName('absolute top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), jsx("input", { ref: innerRef, autoComplete: "off", ...inputProps, value: value, defaultValue: defaultValue, onChange: handleInputChange, maxLength: maxLength, name: name, id: id, type: type, className: buildClassName(buildInputClass(className, {
2174
+ return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-1.5', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs("div", { className: "flex rounded-lg shadow-2xs", children: [prefix && (jsx("span", { className: "inline-flex items-center px-3 rounded-l-lg border border-r-0 border-[var(--ui-border)] bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs font-medium shrink-0", children: prefix })), jsxs("div", { className: "relative flex-1 min-w-0", children: [hasLeftGroup && (jsx("span", { className: buildClassName('absolute top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), jsx("input", { ref: innerRef, autoComplete: "off", ...inputProps, value: value, defaultValue: defaultValue, onChange: handleInputChange, maxLength: maxLength, name: name, id: id, type: type, required: required, "aria-invalid": hasError ? true : undefined, "aria-describedby": describedBy, className: buildClassName(buildInputClass(className, {
2071
2175
  disabled: inputProps.disabled,
2072
2176
  hasError,
2073
2177
  size,
2074
- }), prefix ? '!rounded-l-none' : '', suffix ? '!rounded-r-none' : '', hasRightGroup ? (size === 'lg' ? 'pe-12' : size === 'sm' ? 'pe-8' : 'pe-10') : '', hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : '') }), hasRightGroup && (jsx("span", { className: buildClassName('absolute end-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: actualRightGroup }))] }), suffix && (jsx("span", { className: "inline-flex items-center px-3 rounded-r-lg border border-l-0 border-[var(--ui-border)] bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs font-medium shrink-0", children: suffix }))] }), (showErrorMessage && error) || helperText || showCount ? (jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2 text-xs", children: [jsx("div", { className: "min-w-0 flex-1", children: showErrorMessage && error ? (jsx(TextContent, { error: true, small: true, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, children: helperText })) : null }), showCount && (jsxs("span", { className: buildClassName('text-[11px] font-medium shrink-0 ml-auto', maxLength && charLength >= maxLength
2178
+ }), prefix ? '!rounded-l-none' : '', suffix ? '!rounded-r-none' : '', hasRightGroup ? (size === 'lg' ? 'pe-12' : size === 'sm' ? 'pe-8' : 'pe-10') : '', hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : '') }), hasRightGroup && (jsx("span", { className: buildClassName('absolute end-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: actualRightGroup }))] }), suffix && (jsx("span", { className: "inline-flex items-center px-3 rounded-r-lg border border-l-0 border-[var(--ui-border)] bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs font-medium shrink-0", children: suffix }))] }), (showErrorMessage && error) || helperText || showCount ? (jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2 text-xs", children: [jsx("div", { className: "min-w-0 flex-1", children: showErrorMessage && error ? (jsx(TextContent, { error: true, small: true, id: errorId, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, id: helperId, children: helperText })) : null }), showCount && (jsxs("span", { className: buildClassName('text-[11px] font-medium shrink-0 ml-auto', maxLength && charLength >= maxLength
2075
2179
  ? 'text-red-500 font-semibold'
2076
2180
  : 'text-gray-400 dark:text-gray-500'), children: [charLength, maxLength ? ` / ${maxLength}` : ''] }))] })) : null] }));
2077
2181
  });
@@ -2296,16 +2400,20 @@ const THEME_MAP = {
2296
2400
  danger: 'checked:border-[var(--ui-danger)] checked:hover:border-[var(--ui-danger)] focus:ring-[var(--ui-danger-ring)]',
2297
2401
  };
2298
2402
  const Radio = forwardRef((props, ref) => {
2299
- const { id, name, label, description, size = 'md', theme = 'primary', labelClass = '', descriptionClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '', ...rest } = props;
2403
+ const { id, name, label, description, size = 'md', theme = 'primary', labelClass = '', descriptionClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '', required, 'aria-describedby': ariaDescribedBy, ...rest } = props;
2300
2404
  const generatedId = useId();
2301
2405
  const radioId = id !== null && id !== void 0 ? id : (name && rest.value ? `${name}-${rest.value}` : generatedId);
2302
- return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-start gap-2.5', containerClass), children: [jsx("div", { className: "grid place-items-center shrink-0 pt-0.5", children: jsx("input", { ref: ref, ...rest, id: radioId, name: name, type: "radio", disabled: disabled, "aria-checked": rest.checked, className: buildClassName('peer transition-all col-start-1 row-start-1 appearance-none shrink-0 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'dark:focus:ring-offset-gray-800', SIZE_MAP[size], THEME_MAP[theme], disabled
2406
+ const hasError = Boolean(error);
2407
+ const errorId = radioId && hasError ? `${radioId}-error` : undefined;
2408
+ const descId = radioId && description ? `${radioId}-desc` : undefined;
2409
+ const describedBy = [ariaDescribedBy, errorId, descId].filter(Boolean).join(' ') || undefined;
2410
+ return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-start gap-2.5', containerClass), children: [jsx("div", { className: "grid place-items-center shrink-0 pt-0.5", children: jsx("input", { ref: ref, ...rest, id: radioId, name: name, type: "radio", disabled: disabled, required: required, "aria-required": required ? true : undefined, "aria-invalid": hasError ? true : undefined, "aria-describedby": describedBy, "aria-checked": rest.checked, className: buildClassName('peer transition-all col-start-1 row-start-1 appearance-none shrink-0 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'dark:focus:ring-offset-gray-800', SIZE_MAP[size], THEME_MAP[theme], disabled
2303
2411
  ? 'border-[var(--ui-border-muted)] bg-gray-100 dark:bg-gray-800 pointer-events-none'
2304
- : 'bg-white dark:bg-gray-700 border-[var(--ui-border)] hover:border-gray-400 dark:hover:border-gray-500 cursor-pointer', className) }) }), (label || description) && (jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [label && (jsx(Label, { htmlFor: radioId, className: buildClassName(disabled
2412
+ : 'bg-white dark:bg-gray-700 border-[var(--ui-border)] hover:border-gray-400 dark:hover:border-gray-500 cursor-pointer', className) }) }), (label || description) && (jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [label && (jsx(Label, { htmlFor: radioId, "aria-required": required, className: buildClassName(disabled
2305
2413
  ? 'cursor-not-allowed text-gray-400 dark:text-gray-500'
2306
- : 'cursor-pointer text-gray-700 dark:text-gray-200 font-medium', labelClass), children: label })), description && (jsx("p", { className: buildClassName('text-xs leading-normal', disabled
2414
+ : 'cursor-pointer text-gray-700 dark:text-gray-200 font-medium', labelClass), children: label })), description && (jsx("p", { id: descId, className: buildClassName('text-xs leading-normal', disabled
2307
2415
  ? 'text-gray-300 dark:text-gray-600'
2308
- : 'text-gray-500 dark:text-gray-400', descriptionClass), children: description }))] }))] }), error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) }))] }));
2416
+ : 'text-gray-500 dark:text-gray-400', descriptionClass), children: description }))] }))] }), error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, id: errorId, children: error }) }))] }));
2309
2417
  });
2310
2418
  Radio.displayName = 'Radio';
2311
2419
 
@@ -2316,7 +2424,9 @@ const COLUMN_GRID_MAP = {
2316
2424
  4: 'grid-cols-1 md:grid-cols-4',
2317
2425
  };
2318
2426
  const RadioGroup = (props) => {
2319
- const { name, variant = 'default', size = 'md', theme = 'primary', label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, columns, disabled = false, row, error, showErrorMessage = true, onChange, } = props;
2427
+ const { name, variant = 'default', size = 'md', theme = 'primary', label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, columns, disabled = false, row, error, showErrorMessage = true, onChange, required, 'aria-describedby': ariaDescribedBy, } = props;
2428
+ const groupId = useId();
2429
+ const groupName = name || groupId;
2320
2430
  const items = useMemo(() => {
2321
2431
  var _a;
2322
2432
  return (_a = options === null || options === void 0 ? void 0 : options.map((option) => typeof option === 'string'
@@ -2330,21 +2440,26 @@ const RadioGroup = (props) => {
2330
2440
  : option)) !== null && _a !== void 0 ? _a : [];
2331
2441
  }, [options]);
2332
2442
  const isCards = variant === 'cards';
2333
- return (jsxs("div", { className: buildClassName(containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-3', labelWrapperClass), children: [jsx(Label, { className: labelClass, children: label }), labelHint] })), jsx("div", { className: buildClassName(columns
2443
+ const hasError = Boolean(error);
2444
+ const labelId = label ? `${groupId}-label` : undefined;
2445
+ const errorId = groupId && hasError && showErrorMessage ? `${groupId}-error` : undefined;
2446
+ const describedBy = [ariaDescribedBy, errorId].filter(Boolean).join(' ') || undefined;
2447
+ return (jsxs("div", { role: "radiogroup", "aria-labelledby": labelId, "aria-describedby": describedBy, className: buildClassName(containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-3', labelWrapperClass), children: [jsx(Label, { id: labelId, className: labelClass, "aria-required": required, children: label }), labelHint] })), jsx("div", { className: buildClassName(columns
2334
2448
  ? buildClassName('grid gap-3', COLUMN_GRID_MAP[columns])
2335
2449
  : row || isCards
2336
2450
  ? 'flex flex-wrap gap-3'
2337
2451
  : 'flex flex-col gap-3'), children: items.map((item) => {
2338
2452
  const isSelected = value === item.value;
2339
2453
  const isItemDisabled = disabled || Boolean(item.disabled);
2454
+ const radioItemId = `${groupName}-${item.value}`;
2340
2455
  if (isCards) {
2341
- return (jsxs("label", { htmlFor: `${name}-${item.value}`, className: buildClassName('relative flex items-start gap-3 p-4 rounded-xl border transition-all cursor-pointer select-none min-w-[200px] flex-1', isSelected
2456
+ return (jsxs("label", { htmlFor: radioItemId, className: buildClassName('relative flex items-start gap-3 p-4 rounded-xl border transition-all cursor-pointer select-none min-w-[200px] flex-1', 'focus-within:ring-2 focus-within:ring-[var(--ui-primary-ring)] focus-within:border-[var(--ui-primary)]', isSelected
2342
2457
  ? 'border-[var(--ui-primary)] bg-blue-50/40 dark:bg-blue-950/30 ring-2 ring-[var(--ui-primary-ring)]/30'
2343
2458
  : 'border-[var(--ui-border)] bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600', isItemDisabled &&
2344
- 'opacity-50 cursor-not-allowed pointer-events-none bg-gray-50 dark:bg-gray-800/50'), children: [jsx(Radio, { name: name, id: `${name}-${item.value}`, value: item.value, "aria-label": item.label, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange, containerClass: "pt-0.5" }), jsxs("div", { className: "flex-1 min-w-0", children: [jsxs("div", { className: "flex items-center gap-2", children: [item.icon && (jsx("span", { className: "shrink-0 text-gray-500 dark:text-gray-400", children: item.icon })), jsx("span", { className: "text-sm font-semibold text-gray-900 dark:text-white truncate", children: item.label })] }), item.description && (jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400 font-normal leading-relaxed", children: item.description }))] })] }, item.value));
2459
+ 'opacity-50 cursor-not-allowed pointer-events-none bg-gray-50 dark:bg-gray-800/50'), children: [jsx(Radio, { name: groupName, id: radioItemId, value: item.value, "aria-label": item.label, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange, containerClass: "pt-0.5" }), jsxs("div", { className: "flex-1 min-w-0", children: [jsxs("div", { className: "flex items-center gap-2", children: [item.icon && (jsx("span", { className: "shrink-0 text-gray-500 dark:text-gray-400", children: item.icon })), jsx("span", { className: "text-sm font-semibold text-gray-900 dark:text-white truncate", children: item.label })] }), item.description && (jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400 font-normal leading-relaxed", children: item.description }))] })] }, item.value));
2345
2460
  }
2346
- return (jsx(Radio, { name: name, id: `${name}-${item.value}`, label: item.label, description: item.description, value: item.value, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange }, item.value));
2347
- }) }), showErrorMessage && error && (jsx("div", { className: "mt-1.5", children: jsx(TextContent, { error: true, small: true, children: error }) }))] }));
2461
+ return (jsx(Radio, { name: groupName, id: radioItemId, label: item.label, description: item.description, value: item.value, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange }, item.value));
2462
+ }) }), showErrorMessage && error && (jsx("div", { className: "mt-1.5", children: jsx(TextContent, { error: true, small: true, id: errorId, children: error }) }))] }));
2348
2463
  };
2349
2464
  RadioGroup.displayName = 'RadioGroup';
2350
2465
 
@@ -2456,34 +2571,6 @@ const RadioSwitch = ({ label, items, selected, size = 'md', theme = 'primary', d
2456
2571
  };
2457
2572
  RadioSwitch.displayName = 'RadioSwitch';
2458
2573
 
2459
- /**
2460
- * Checks if a value is an array.
2461
- * Equivalent to lodash's _.isArray.
2462
- */
2463
- function isArray(value) {
2464
- return Array.isArray(value);
2465
- }
2466
-
2467
- /**
2468
- * Checks if a value is empty.
2469
- * Works for arrays, objects, strings, Maps, and Sets.
2470
- * Equivalent to lodash's _.isEmpty.
2471
- */
2472
- function isEmpty(value) {
2473
- if (value == null)
2474
- return true; // null or undefined
2475
- if (typeof value === 'string' || isArray(value)) {
2476
- return value.length === 0;
2477
- }
2478
- if (value instanceof Map || value instanceof Set) {
2479
- return value.size === 0;
2480
- }
2481
- if (typeof value === 'object') {
2482
- return Object.keys(value).length === 0;
2483
- }
2484
- return false; // for numbers, booleans, functions, etc.
2485
- }
2486
-
2487
2574
  const sizeClasses$3 = {
2488
2575
  sm: {
2489
2576
  track: 'h-1.5',
@@ -3466,7 +3553,7 @@ const parseNumericValue = (val) => {
3466
3553
  const cleaned = String(val).replace(/[^0-9.-]/g, '');
3467
3554
  return cleaned !== '' ? Number(cleaned) : NaN;
3468
3555
  };
3469
- function DataList({ items, columns, renderItem, searchable = false, searchQuery: externalSearchQuery, onSearchChange, searchFields, filterFields, selectedFilters: externalFilters, onFilterChange, sorting: externalSorting, onSortChange, swipeableActions, rowKey = 'id', selectable = false, selectedKeys = [], onSelectionChange, onItemClick, loading = false, emptyMessage = 'No items found', pagination, setPagination, containerClass = '', itemClass = '', }) {
3556
+ function DataList({ items, columns, renderItem, searchable = false, searchQuery: externalSearchQuery, onSearchChange, searchFields, filterFields, selectedFilters: externalFilters, onFilterChange, sorting: externalSorting, onSortChange, swipeableActions, rowKey = 'id', selectable = false, selectedKeys = [], onSelectionChange, onItemClick, loading = false, emptyMessage = 'No items found', pagination, setPagination, paginationClass, paginationContainerClass, paginationProps, renderPagination, containerClass = '', itemClass = '', }) {
3470
3557
  // Internal Search State
3471
3558
  const [internalSearchQuery, setInternalSearchQuery] = useState('');
3472
3559
  const searchQuery = externalSearchQuery !== undefined ? externalSearchQuery : internalSearchQuery;
@@ -3714,10 +3801,12 @@ function DataList({ items, columns, renderItem, searchable = false, searchQuery:
3714
3801
  return (jsx(SwipeableRow, { leftActions: actions.leftActions, rightActions: actions.rightActions, children: contentNode }, key));
3715
3802
  }
3716
3803
  return jsx("div", { children: contentNode }, key);
3717
- })) }), pagination && pagination.total > 0 && (jsx("div", { className: "pt-2 flex justify-center", children: jsx(Pagination, { ...pagination, onChange: (newPag) => setPagination === null || setPagination === void 0 ? void 0 : setPagination(newPag) }) }))] }));
3804
+ })) }), pagination && pagination.total > 0 && (jsx("div", { className: buildClassName('pt-2 flex justify-center w-full', paginationClass, paginationContainerClass), children: renderPagination ? (renderPagination(pagination, setPagination)) : (jsx(Pagination, { ...pagination, ...paginationProps, onChange: (newPag) => setPagination === null || setPagination === void 0 ? void 0 : setPagination(newPag) })) }))] }));
3718
3805
  }
3719
3806
  DataList.displayName = 'DataList';
3720
3807
 
3808
+ const NOOP = () => { };
3809
+ const EMPTY_ARRAY = [];
3721
3810
  const sizeClasses$1 = {
3722
3811
  sm: {
3723
3812
  input: 'px-2.5 py-1.5 text-xs leading-5 min-h-[32px]',
@@ -3739,7 +3828,7 @@ const sizeClasses$1 = {
3739
3828
  },
3740
3829
  };
3741
3830
  const SelectBox = forwardRef((props, forwardedRef) => {
3742
- const { labelKey = 'label', valueKey = 'value', name, id = name, options = [], groups, selected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange = () => { }, placeholder, dropdownContainerClass = 'z-20', multiple = false, maxSelection, showSelectAll = false, immediate = true, showErrorMessage = true, error = null, helperText, leftGroup, renderOption, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = () => { }, allowClear = false, allowFreeText = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
3831
+ const { labelKey = 'label', valueKey = 'value', name, id = name, options = EMPTY_ARRAY, groups, selected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange = NOOP, placeholder, dropdownContainerClass = 'z-20', multiple = false, maxSelection, showSelectAll = false, immediate = true, showErrorMessage = true, error = null, helperText, leftGroup, renderOption, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = NOOP, allowClear = false, allowFreeText = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
3743
3832
  if (props.asBottomSheet || props.mobileMode === 'sheet') {
3744
3833
  return jsx(MobilePicker, { ...props });
3745
3834
  }
@@ -3757,7 +3846,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3757
3846
  const shouldShowTypeToSearch = async && !query && !optionsLoading && allOptions.length === 0;
3758
3847
  const currentSize = sizeClasses$1[size] || sizeClasses$1.md;
3759
3848
  const baseInputClass = useMemo(() => buildInputClass('', { disabled, hasError, size }), [disabled, hasError, size]);
3760
- const chevronIconClass = buildClassName(currentSize.icon, 'stroke-gray-500 group-data-[hover]:stroke-gray-800', 'dark:stroke-gray-300 dark:group-data-[hover]:stroke-gray-100', disabled && 'dark:stroke-gray-600 stroke-gray-400');
3849
+ const chevronIconClass = useMemo(() => buildClassName(currentSize.icon, 'stroke-gray-500 group-data-[hover]:stroke-gray-800', 'dark:stroke-gray-300 dark:group-data-[hover]:stroke-gray-100', disabled && 'dark:stroke-gray-600 stroke-gray-400'), [currentSize.icon, disabled]);
3761
3850
  const inputRef = useRef(null);
3762
3851
  const containerRef = useRef(null);
3763
3852
  useImperativeHandle(forwardedRef, () => inputRef.current, [inputRef]);
@@ -3833,7 +3922,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3833
3922
  maxSelection,
3834
3923
  onChange,
3835
3924
  ]);
3836
- const removeSelection = (idx) => {
3925
+ const removeSelection = useCallback((idx) => {
3837
3926
  var _a;
3838
3927
  if (!isArray(selected))
3839
3928
  return;
@@ -3841,7 +3930,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3841
3930
  next.splice(idx, 1);
3842
3931
  onChange(next);
3843
3932
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
3844
- };
3933
+ }, [selected, onChange]);
3845
3934
  const handleOnChangeInternal = useCallback((value) => {
3846
3935
  if (value === null) {
3847
3936
  onChange(multiple ? [] : null);
@@ -3885,9 +3974,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3885
3974
  }
3886
3975
  }, [onAdd, handleOnChangeInternal]);
3887
3976
  const onSearchRef = useRef(props.onSearch);
3888
- useEffect(() => {
3889
- onSearchRef.current = props.onSearch;
3890
- }, [props.onSearch]);
3977
+ onSearchRef.current = props.onSearch;
3891
3978
  const handleSearch = useCallback(async (searchString) => {
3892
3979
  var _a;
3893
3980
  try {
@@ -3919,19 +4006,27 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3919
4006
  return selected !== null && selected !== void 0 ? selected : null;
3920
4007
  }
3921
4008
  }, [multiple, selected]);
4009
+ const selectedValueSet = useMemo(() => {
4010
+ if (!normalizedSelected)
4011
+ return new Set();
4012
+ if (Array.isArray(normalizedSelected)) {
4013
+ return new Set(normalizedSelected.map((s) => getValue(s)));
4014
+ }
4015
+ return new Set([getValue(normalizedSelected)]);
4016
+ }, [normalizedSelected, getValue]);
3922
4017
  const isAllSelected = useMemo(() => {
3923
4018
  if (!multiple || !Array.isArray(normalizedSelected) || !allOptions.length)
3924
4019
  return false;
3925
4020
  return allOptions.length === normalizedSelected.length;
3926
4021
  }, [multiple, normalizedSelected, allOptions]);
3927
- const handleToggleSelectAll = () => {
4022
+ const handleToggleSelectAll = useCallback(() => {
3928
4023
  if (isAllSelected) {
3929
4024
  onChange([]);
3930
4025
  }
3931
4026
  else {
3932
4027
  onChange(allOptions);
3933
4028
  }
3934
- };
4029
+ }, [isAllSelected, onChange, allOptions]);
3935
4030
  const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
3936
4031
  const hasFreeTextOption = Boolean(allowFreeText &&
3937
4032
  query.trim() &&
@@ -3940,6 +4035,11 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3940
4035
  const hasGroupOptions = Boolean(filteredGroups && filteredGroups.length > 0);
3941
4036
  const hasFlatOptions = filteredOptions.length > 0;
3942
4037
  const hasAnyOptionsToDisplay = hasFlatOptions || hasGroupOptions || hasFreeTextOption || hasAddOption;
4038
+ const renderOptionItem = (option) => {
4039
+ const optVal = getValue(option);
4040
+ const isOptSelected = selectedValueSet.has(optVal);
4041
+ return (jsx(ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-gray-100 dark:data-[focus]:bg-white/10 data-disabled:opacity-50 cursor-pointer data-disabled:pointer-events-none", children: renderOption ? (renderOption(option, Boolean(isOptSelected))) : (jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
4042
+ };
3943
4043
  return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs(Combobox, { multiple: multiple, immediate: immediate, name: name, value: normalizedSelected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, by: (a, b) => (a && b ? getValue(a) === getValue(b) : a === b), children: [multiple ? (jsxs("div", { className: buildClassName(baseInputClass, 'flex flex-wrap gap-2 peer relative', hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), ref: forwardedRef, children: [hasLeftGroup && (jsx("span", { className: buildClassName('absolute left-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), isArray(normalizedSelected) &&
3944
4044
  (normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) > 0 &&
3945
4045
  normalizedSelected.map((option, idx) => (jsx(Badge, { theme: "secondary", size: currentSize.badgeSize, removable: !disabled, onRemove: () => removeSelection(idx), children: getDisplayValue(option) }, `${getValue(option)}-${idx}`))), jsx(ComboboxInput, { className: "outline-none flex-1 dark:placeholder-gray-400 placeholder-gray-400 bg-transparent", "aria-label": name, value: query, autoComplete: "off", placeholder: (normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) ? '' : placeholder, onChange: (e) => {
@@ -3983,23 +4083,9 @@ const SelectBox = forwardRef((props, forwardedRef) => {
3983
4083
  }, className: buildClassName(baseInputClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), disabled: disabled, id: id, readOnly: !searchable, ref: inputRef, "data-testid": "combobox-input" }), allowClear && !isEmpty(normalizedSelected) && (jsx(Button, { size: "xs", iconOnly: true, "aria-label": "Clear selection", theme: "secondary", variant: "plain", className: "group absolute inset-y-0 right-8 h-6 top-2", onClick: () => handleOnChangeInternal(multiple ? [] : null), disabled: disabled, "data-testid": "combobox-clear-button", noOutlineOnFocus: true, children: jsx(SvgX, { className: "size-4 stroke-gray-500 dark:stroke-gray-300" }) })), jsx(ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5 flex items-center justify-center", "data-testid": "combobox-button", children: jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxs(ComboboxOptions, { anchor: { to: 'bottom', gap: 5 }, as: "div", modal: modalDropdown, className: buildClassName('w-[var(--input-width)] rounded-lg border border-[var(--ui-border)] bg-white dark:bg-gray-800 p-1 shadow-lg max-h-80 overflow-y-auto z-50 [--anchor-gap:var(--spacing-1)]', '[--anchor-max-height:20rem]', 'origin-top border transition duration-200 ease-[cubic-bezier(0.4, 0.0, 0.2, 1)] data-[closed]:translate-y-[-15%] data-[closed]:opacity-0', dropdownContainerClass), transition: true, "data-testid": "combobox-options", children: [multiple && showSelectAll && allOptions.length > 0 && (jsxs("div", { className: "pb-1 mb-1 border-b border-[var(--ui-border-muted)] px-3 pt-1 flex items-center justify-between", children: [jsx("button", { type: "button", onClick: handleToggleSelectAll, className: "text-xs font-semibold text-blue-600 dark:text-blue-400 hover:underline cursor-pointer", children: isAllSelected ? 'Deselect All' : 'Select All' }), jsxs("span", { className: "text-[11px] font-medium text-gray-400", children: [Array.isArray(normalizedSelected) ? normalizedSelected.length : 0, " of", ' ', allOptions.length] })] })), optionsLoading ? (jsx(BodyText, { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsx(BodyText, { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxs("div", { children: [allowFreeText &&
3984
4084
  query.trim() &&
3985
4085
  !allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }) && (jsx(ComboboxOption, { value: createFreeTextOption(query.trim()), className: "group flex items-center gap-2 rounded-lg py-1.5 px-3 select-none text-sm data-[focus]:bg-blue-50 dark:data-[focus]:bg-blue-950/40 text-blue-600 dark:text-blue-400 font-medium cursor-pointer w-full", "data-testid": "freetext-option", children: jsxs("span", { children: ["Use \"", query.trim(), "\""] }) }, `freetext-${query.trim()}`)), filteredGroups
3986
- ? filteredGroups.map((grp, gIdx) => (jsxs("div", { children: [jsx("div", { className: "text-[11px] font-bold uppercase tracking-wider text-gray-400 dark:text-gray-500 px-3 py-1 select-none", children: grp.group }), grp.options.map((option) => {
3987
- const optVal = getValue(option);
3988
- const isOptSelected = multiple
3989
- ? isArray(normalizedSelected) &&
3990
- normalizedSelected.some((s) => getValue(s) === optVal)
3991
- : normalizedSelected && getValue(normalizedSelected) === optVal;
3992
- return (jsx(ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-gray-100 dark:data-[focus]:bg-white/10 data-disabled:opacity-50 cursor-pointer data-disabled:pointer-events-none", children: renderOption ? (renderOption(option, Boolean(isOptSelected))) : (jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
3993
- })] }, gIdx)))
4086
+ ? filteredGroups.map((grp, gIdx) => (jsxs("div", { children: [jsx("div", { className: "text-[11px] font-bold uppercase tracking-wider text-gray-400 dark:text-gray-500 px-3 py-1 select-none", children: grp.group }), grp.options.map(renderOptionItem)] }, gIdx)))
3994
4087
  : /* Render Standard Flat Options */
3995
- filteredOptions.map((option) => {
3996
- const optVal = getValue(option);
3997
- const isOptSelected = multiple
3998
- ? isArray(normalizedSelected) &&
3999
- normalizedSelected.some((s) => getValue(s) === optVal)
4000
- : normalizedSelected && getValue(normalizedSelected) === optVal;
4001
- return (jsx(ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-gray-100 dark:data-[focus]:bg-white/10 data-disabled:opacity-50 cursor-pointer data-disabled:pointer-events-none", children: renderOption ? (renderOption(option, Boolean(isOptSelected))) : (jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
4002
- }), !hasAnyOptionsToDisplay && Boolean(noOptionsText) && (jsxs("div", { "data-testid": "no-result-found", className: "flex items-center gap-2 py-1.5 px-3 select-none", children: [jsx(TextContent, { muted: true, small: true, children: noOptionsText }), Boolean(query.trim()) && (jsxs(TextContent, { strong: true, small: true, children: ["\"", query.trim(), "\""] }))] })), !allowFreeText && allowAdd && Boolean(query.trim()) && (jsx(ComboboxOption, { value: query, className: "group rounded-lg py-1.5 px-3 cursor-pointer text-gray-700 dark:text-gray-300 data-[focus]:bg-gray-100 dark:data-[focus]:bg-gray-700", onClick: () => void handleAddAndSelect(query), children: jsxs("div", { className: "flex items-center gap-2", children: [jsx(SvgCirclePlus, { className: "size-4.5" }), jsxs(TextContent, { children: [addNewText, " \"", query, "\""] })] }) }, `add-${query.trim()}`))] }))] })] }), showErrorMessage && error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) })), !error && helperText && (jsx("div", { className: "mt-1", children: jsx(TextContent, { muted: true, small: true, children: helperText }) }))] }));
4088
+ filteredOptions.map(renderOptionItem), !hasAnyOptionsToDisplay && Boolean(noOptionsText) && (jsxs("div", { "data-testid": "no-result-found", className: "flex items-center gap-2 py-1.5 px-3 select-none", children: [jsx(TextContent, { muted: true, small: true, children: noOptionsText }), Boolean(query.trim()) && (jsxs(TextContent, { strong: true, small: true, children: ["\"", query.trim(), "\""] }))] })), !allowFreeText && allowAdd && Boolean(query.trim()) && (jsx(ComboboxOption, { value: query, className: "group rounded-lg py-1.5 px-3 cursor-pointer text-gray-700 dark:text-gray-300 data-[focus]:bg-gray-100 dark:data-[focus]:bg-gray-700", onClick: () => void handleAddAndSelect(query), children: jsxs("div", { className: "flex items-center gap-2", children: [jsx(SvgCirclePlus, { className: "size-4.5" }), jsxs(TextContent, { children: [addNewText, " \"", query, "\""] })] }) }, `add-${query.trim()}`))] }))] })] }), showErrorMessage && error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) })), !error && helperText && (jsx("div", { className: "mt-1", children: jsx(TextContent, { muted: true, small: true, children: helperText }) }))] }));
4003
4089
  });
4004
4090
  SelectBox.displayName = 'SelectBox';
4005
4091
 
@@ -4353,7 +4439,7 @@ const resizeClasses = {
4353
4439
  both: 'resize',
4354
4440
  };
4355
4441
  const Textarea = forwardRef((props, ref) => {
4356
- const { name, id = name, label = null, labelHint = null, className = '', labelClass = '', labelWrapperClass = '', containerClass = '', error, showErrorMessage = true, rightGroup = null, leftGroup = null, size = 'md', autoSize = false, showCount = false, helperText, resize = 'vertical', value, defaultValue, onChange, maxLength, ...inputProps } = props;
4442
+ const { name, id = name, label = null, labelHint = null, className = '', labelClass = '', labelWrapperClass = '', containerClass = '', error, showErrorMessage = true, rightGroup = null, leftGroup = null, size = 'md', autoSize = false, showCount = false, helperText, resize = 'vertical', value, defaultValue, onChange, maxLength, required, 'aria-describedby': ariaDescribedBy, ...inputProps } = props;
4357
4443
  const innerRef = useRef(null);
4358
4444
  useImperativeHandle(ref, () => innerRef.current);
4359
4445
  const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''));
@@ -4367,19 +4453,22 @@ const Textarea = forwardRef((props, ref) => {
4367
4453
  innerRef.current.style.height = `${innerRef.current.scrollHeight}px`;
4368
4454
  }
4369
4455
  }, [autoSize, currentVal]);
4370
- const handleTextareaChange = (e) => {
4456
+ const handleTextareaChange = useCallback((e) => {
4371
4457
  if (!isControlled) {
4372
4458
  setUncontrolledValue(e.target.value);
4373
4459
  }
4374
4460
  onChange === null || onChange === void 0 ? void 0 : onChange(e);
4375
- };
4461
+ }, [isControlled, onChange]);
4376
4462
  const hasError = !isEmpty(error);
4377
4463
  const hasRightGroup = rightGroup != null && rightGroup !== undefined;
4378
4464
  const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
4379
4465
  const currentSize = sizeClasses[size] || sizeClasses.md;
4380
- return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-1.5', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs("div", { className: "relative", children: [hasLeftGroup && (jsx("span", { className: "absolute top-2.5 left-3 flex items-center justify-center text-gray-400 dark:text-gray-500", children: leftGroup })), jsx("textarea", { ref: innerRef, autoComplete: "off", ...inputProps, value: value, defaultValue: defaultValue, onChange: handleTextareaChange, maxLength: maxLength, name: name, id: id, className: buildClassName('transition-all duration-150', 'block w-full rounded-lg bg-white', currentSize.textarea, autoSize ? 'resize-none overflow-hidden' : resizeClasses[resize], 'text-gray-900 outline outline-1 -outline-offset-1', hasError
4466
+ const errorId = id && hasError && showErrorMessage ? `${id}-error` : undefined;
4467
+ const helperId = id && helperText && !hasError ? `${id}-helper` : undefined;
4468
+ const describedBy = [ariaDescribedBy, errorId, helperId].filter(Boolean).join(' ') || undefined;
4469
+ return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-1.5', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs("div", { className: "relative", children: [hasLeftGroup && (jsx("span", { className: "absolute top-2.5 left-3 flex items-center justify-center text-gray-400 dark:text-gray-500", children: leftGroup })), jsx("textarea", { ref: innerRef, autoComplete: "off", ...inputProps, value: value, defaultValue: defaultValue, onChange: handleTextareaChange, maxLength: maxLength, name: name, id: id, required: required, "aria-invalid": hasError ? true : undefined, "aria-describedby": describedBy, className: buildClassName('transition-all duration-150', 'block w-full rounded-lg bg-white', currentSize.textarea, autoSize ? 'resize-none overflow-hidden' : resizeClasses[resize], 'text-gray-900 outline outline-1 -outline-offset-1', hasError
4381
4470
  ? '!outline-red-500 !ring-1 !ring-red-500/50 dark:!outline-red-500'
4382
- : 'outline-[var(--ui-border)]', 'focus:outline-[var(--ui-focus-ring)] focus:outline-2 focus:-outline-offset-2', 'dark:bg-gray-700 dark:placeholder-gray-400 placeholder:text-gray-400 dark:text-white', 'disabled:pointer-events-none disabled:bg-gray-100 disabled:outline-[var(--ui-border-muted)]', 'dark:disabled:bg-gray-800 dark:disabled:text-gray-600 dark:disabled:placeholder-gray-600 dark:disabled:outline-[var(--ui-border-muted)]', hasRightGroup ? 'pe-10' : '', hasLeftGroup ? 'ps-10' : '', className) }), hasRightGroup && (jsx("span", { className: "absolute end-3 top-2.5 flex items-center justify-center text-gray-400 dark:text-gray-500", children: rightGroup }))] }), (showErrorMessage && error) || helperText || showCount ? (jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2 text-xs", children: [jsx("div", { className: "min-w-0 flex-1", children: showErrorMessage && error ? (jsx(TextContent, { error: true, small: true, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, children: helperText })) : null }), showCount && (jsxs("span", { className: buildClassName('text-[11px] font-medium shrink-0 ml-auto', maxLength && charLength >= maxLength
4471
+ : 'outline-[var(--ui-border)]', 'focus:outline-[var(--ui-focus-ring)] focus:outline-2 focus:-outline-offset-2', 'dark:bg-gray-700 dark:placeholder-gray-400 placeholder:text-gray-400 dark:text-white', 'disabled:pointer-events-none disabled:bg-gray-100 disabled:outline-[var(--ui-border-muted)]', 'dark:disabled:bg-gray-800 dark:disabled:text-gray-600 dark:disabled:placeholder-gray-600 dark:disabled:outline-[var(--ui-border-muted)]', hasRightGroup ? 'pe-10' : '', hasLeftGroup ? 'ps-10' : '', className) }), hasRightGroup && (jsx("span", { className: "absolute end-3 top-2.5 flex items-center justify-center text-gray-400 dark:text-gray-500", children: rightGroup }))] }), (showErrorMessage && error) || helperText || showCount ? (jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2 text-xs", children: [jsx("div", { className: "min-w-0 flex-1", children: showErrorMessage && error ? (jsx(TextContent, { error: true, small: true, id: errorId, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, id: helperId, children: helperText })) : null }), showCount && (jsxs("span", { className: buildClassName('text-[11px] font-medium shrink-0 ml-auto', maxLength && charLength >= maxLength
4383
4472
  ? 'text-red-500 font-semibold'
4384
4473
  : 'text-gray-400 dark:text-gray-500'), children: [charLength, maxLength ? ` / ${maxLength}` : ''] }))] })) : null] }));
4385
4474
  });