@pk-design/react-tailwind 1.1.4 → 1.1.6

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.
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import * as React from 'react';
4
- import React__default, { forwardRef, useMemo, useState, useId, useEffect, useRef, Fragment as Fragment$1, useCallback, useImperativeHandle, createContext, useContext, memo, useLayoutEffect, createElement } from 'react';
4
+ import React__default, { forwardRef, useMemo, useId, useRef, useState, useEffect, Fragment as Fragment$1, useCallback, useImperativeHandle, createContext, useContext, memo, useLayoutEffect, createElement } from 'react';
5
5
  import { Dialog, DialogBackdrop, DialogPanel, DialogTitle, Menu, MenuButton, MenuItems, MenuItem, RadioGroup as RadioGroup$1, Radio as Radio$1, Combobox, ComboboxInput, ComboboxButton, ComboboxOptions, ComboboxOption, TabGroup, TabList, Tab, TabPanels, TabPanel, Transition } from '@headlessui/react';
6
6
 
7
7
  const buildClassName = (...classes) => twMerge(classes.filter(Boolean));
@@ -223,7 +223,7 @@ const TypeClassMap = {
223
223
  const Alert = (props) => {
224
224
  const { type = 'info', className = '', message, children, removable = false, onRemove = () => { }, ...restProps } = props;
225
225
  const Icon = AlertTypeIconMap[type] || SvgInfo;
226
- return (jsxs("div", { className: buildClassName(className, 'flex items-center justify-between rounded-md p-3 text-sm ring-1 ring-inset gap-2', TypeClassMap[type]), ...restProps, children: [jsxs("span", { className: "inline-flex items-center gap-2", children: [jsx(Icon, { className: "size-5", "data-testid": `alert-${type}-icon` }), jsx("span", { className: "pr-2 inline-flex items-center", children: message || children })] }), removable && (jsx(Button, { iconOnly: true, size: "xs", variant: "plain", theme: "secondary", className: "hover:bg-black/5 text-gray-600 dark:hover:bg-black/5 dark:text-gray-600", onClick: () => onRemove(), "data-testid": "alert-remove-btn", children: jsx(SvgX, { className: "size-4.5", "data-testid": "alert-remove-icon" }) }))] }));
226
+ return (jsxs("div", { className: buildClassName(className, 'flex items-center justify-between rounded-md p-3 text-sm ring-1 ring-inset gap-2', TypeClassMap[type]), ...restProps, children: [jsxs("span", { className: "inline-flex items-center gap-2", children: [jsx(Icon, { className: "size-5", "data-testid": `alert-${type}-icon` }), jsx("span", { className: "pr-2 inline-flex items-center", children: message || children })] }), removable && (jsx(Button, { iconOnly: true, "aria-label": "Dismiss alert", size: "xs", variant: "plain", theme: "secondary", className: "hover:bg-black/5 text-gray-600 dark:hover:bg-black/5 dark:text-gray-600", onClick: () => onRemove(), "data-testid": "alert-remove-btn", children: jsx(SvgX, { className: "size-4.5", "data-testid": "alert-remove-icon" }) }))] }));
227
227
  };
228
228
  Alert.displayName = 'Alert';
229
229
 
@@ -236,7 +236,12 @@ const ThemeClassMap = {
236
236
  };
237
237
  const Badge = forwardRef((props, ref) => {
238
238
  const { theme = 'info', className = '', children, removable = false, rounded = false, onRemove = () => { }, ...restProps } = props;
239
- return (jsxs(TextContent, { ref: ref, className: buildClassName(className, 'inline-flex items-center pl-3 py-1 text-xs font-medium ring-1 ring-inset', ThemeClassMap[theme], rounded ? 'rounded-full' : 'rounded-md'), "data-testid": `badge-${theme}`, ...restProps, children: [jsx(TextContent, { className: "pr-3 inline-flex items-center", children: children }), removable ? (jsx(SvgX, { className: "size-5 px-0.5 mr-1 hover:bg-slate-200 dark:hover:bg-white/10 rounded-sm cursor-pointer text-gray-600 dark:text-gray-300", role: "button", onClick: () => onRemove() })) : null] }));
239
+ return (jsxs(TextContent, { ref: ref, className: buildClassName(className, 'inline-flex items-center pl-3 py-1 text-xs font-medium ring-1 ring-inset', ThemeClassMap[theme], rounded ? 'rounded-full' : 'rounded-md'), "data-testid": `badge-${theme}`, ...restProps, children: [jsx(TextContent, { className: "pr-3 inline-flex items-center", children: children }), removable ? (jsx(SvgX, { className: "size-5 px-0.5 mr-1 hover:bg-slate-200 dark:hover:bg-white/10 rounded-sm cursor-pointer text-gray-600 dark:text-gray-300", role: "button", tabIndex: 0, "aria-label": "Remove", onClick: () => onRemove(), onKeyDown: (e) => {
240
+ if (e.key === 'Enter' || e.key === ' ') {
241
+ e.preventDefault();
242
+ onRemove();
243
+ }
244
+ } })) : null] }));
240
245
  });
241
246
  Badge.displayName = 'Badge';
242
247
 
@@ -340,7 +345,7 @@ var SvgSlash = function SvgSlash(props) {
340
345
  const Breadcrumb = ({ items, render }) => {
341
346
  const count = items.length;
342
347
  const isLast = (index) => index === count - 1;
343
- return (jsx("div", { className: "flex flex-wrap items-center gap-2", "data-testid": "breadcrumb-container", children: items.map((item, index) => (jsxs("span", { className: "inline-flex gap-2 items-center dark:text-white", "data-testid": `breadcrumb-item-${item.key}`, children: [jsx("span", { className: "font-medium", children: isLast(index) ? item.text : render(item) }), !isLast(index) && (jsx(SvgSlash, { "data-testid": `breadcrumb-divider-${index}`, className: "size-4" }))] }, item.key))) }));
348
+ return (jsx("nav", { "aria-label": "Breadcrumb", className: "flex flex-wrap items-center gap-2", "data-testid": "breadcrumb-container", children: items.map((item, index) => (jsxs("span", { className: "inline-flex gap-2 items-center dark:text-white", "data-testid": `breadcrumb-item-${item.key}`, children: [jsx("span", { className: "font-medium", ...(isLast(index) ? { 'aria-current': 'page' } : {}), children: isLast(index) ? item.text : render(item) }), !isLast(index) && (jsx(SvgSlash, { "data-testid": `breadcrumb-divider-${index}`, className: "size-4" }))] }, item.key))) }));
344
349
  };
345
350
  Breadcrumb.displayName = 'Breadcrumb';
346
351
 
@@ -367,7 +372,8 @@ const Checkbox = forwardRef((props, ref) => {
367
372
  const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '',
368
373
  // Checkbox-only props removed from rest
369
374
  ...rest } = props;
370
- const checkboxId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : 'checkbox-' + Math.random();
375
+ const generatedId = useId();
376
+ const checkboxId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : generatedId;
371
377
  return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-center', containerClass), children: [jsxs("label", { htmlFor: checkboxId, className: buildClassName('inline-flex items-center relative select-none', disabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'), children: [jsx("input", { ref: ref, ...rest, id: checkboxId, name: name, type: "checkbox", disabled: disabled, "aria-checked": rest.checked, className: buildClassName('peer size-4.5 cursor-pointer transition-all appearance-none rounded', 'border focus:ring-2 focus:ring-offset-2', 'checked:bg-blue-600 checked:border-blue-600', 'focus:ring-blue-500 dark:focus:ring-offset-gray-900', disabled
372
378
  ? 'border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 pointer-events-none'
373
379
  : 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500 hover:shadow-md', className) }), jsx("span", { className: "absolute text-white opacity-0 peer-checked:opacity-100 \n top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2", children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3.25 w-3.25", viewBox: "0 0 20 20", fill: "currentColor", stroke: "currentColor", strokeWidth: "1", 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" }) }) })] }), label && (jsx(Label, { htmlFor: checkboxId, className: buildClassName(disabled
@@ -378,15 +384,8 @@ Checkbox.displayName = 'Checkbox';
378
384
 
379
385
  const CheckboxGroup = (props) => {
380
386
  const { name, options, label, labelClass = '', labelWrapperClass = '', labelHint, containerClass = '', value = [], onChange, inline = false, } = props;
381
- const [items, setItems] = useState([]);
382
- const groupId = useId(); // unique per group
383
- /**
384
- * Normalize options (strings → {label, value})
385
- */
386
- useEffect(() => {
387
- const normalized = options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt);
388
- setItems(normalized);
389
- }, [options]);
387
+ const groupId = useId();
388
+ const items = useMemo(() => options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt), [options]);
390
389
  /**
391
390
  * Group-level change handler. Converts raw checkbox change events
392
391
  * into a typed updated values array.
@@ -537,14 +536,14 @@ const Pagination = ({ page: currentPage, limit, total, onChange, className, elli
537
536
  onChange({ page: nextVal });
538
537
  };
539
538
  if (ellipsis) {
540
- 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-next-btn", children: "Prev" }) }), range.map((page, idx) => {
539
+ 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) => {
541
540
  if (page === DOTS) {
542
541
  return (jsx("li", { "data-testid": `page-${page}`, className: "px-2 py-1 text-gray-400", children: "\u2026" }, `${page}-${idx}`));
543
542
  }
544
543
  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}`));
545
- }), jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-prev-btn", children: "Next" }) })] })] })] }));
544
+ }), jsx("li", { children: jsx(Button, { size: "xs", theme: "secondary", variant: "plain", disabled: isLastPage, onClick: () => handlePageChange(currentPage + 1), "data-testid": "ellipsis-next-btn", children: "Next" }) })] })] })] }));
546
545
  }
547
- 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": "next-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": "prev-btn", children: [jsx("span", { className: "sr-only", children: "Next" }), jsx(SvgChevronRight, { className: "size-4" })] })] })] })] }));
546
+ 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" })] })] })] })] }));
548
547
  };
549
548
 
550
549
  function get(obj, path, defaultValue) {
@@ -733,9 +732,13 @@ const autoSortItems = (items, column, sort) => {
733
732
  };
734
733
  const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination = null, setPagination = () => { }, emptyMessage = 'No data found', containerClass = '', wrapperClass = '', layout = 'auto', }) => {
735
734
  const internalSorting = useRef({});
735
+ const [sortedItems, setSortedItems] = useState(items);
736
+ useEffect(() => {
737
+ setSortedItems(items);
738
+ }, [items]);
736
739
  const doAutoSort = (column) => {
737
740
  internalSorting.current = updateSortQuery(internalSorting.current, column.name, column.type);
738
- autoSortItems(items, column, internalSorting.current[column.name]);
741
+ setSortedItems(autoSortItems(items, column, internalSorting.current[column.name]));
739
742
  };
740
743
  const handleSort = (column) => {
741
744
  if (!column.sortable)
@@ -744,7 +747,7 @@ const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination =
744
747
  };
745
748
  return (jsxs("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: [jsx("div", { className: buildClassName('overflow-x-auto overflow-y-hidden rounded-lg border border-gray-200 dark:border-gray-800', wrapperClass, pagination && 'rounded-b-none border-b-0'), children: jsxs(Table, { className: "relative w-full max-w-full h-fit", layout: layout, children: [jsx(TableHead, { children: columns.map((column) => (jsx(TableHeaderCell, { align: column.headerAlign, style: { width: column.width || 'auto' }, className: buildClassName('bg-gray-50 dark:bg-gray-800', column.sticky === 'left' &&
746
749
  'sticky left-0 z-10 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
747
- 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]'), 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: columns.length, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && items.length === 0 ? (jsx(EmptyTableRow, { colSpan: columns.length, children: emptyMessage })) : (items.map((item) => (jsx(TableRow, { className: "group", children: columns.map((column) => {
750
+ 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]'), 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: columns.length, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && sortedItems.length === 0 ? (jsx(EmptyTableRow, { colSpan: columns.length, children: emptyMessage })) : (sortedItems.map((item) => (jsx(TableRow, { className: "group", children: columns.map((column) => {
748
751
  var _a;
749
752
  return (jsx(TableCell, { align: column.align, className: buildClassName(column.sticky === 'left' &&
750
753
  'sticky left-0 z-10 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
@@ -858,7 +861,7 @@ Input.displayName = 'Input';
858
861
  const PASSWORD_TYPE = 'password';
859
862
  const PASSWORD_PLACEHOLDER = '••••••••••';
860
863
  const PasswordInput = (props) => {
861
- const { type, placeholder, ...inputProps } = props;
864
+ const { ...inputProps } = props;
862
865
  const [showPassword, setShowPassword] = useState(false);
863
866
  return (jsx(Input, { ...inputProps, type: showPassword ? 'text' : PASSWORD_TYPE, placeholder: PASSWORD_PLACEHOLDER, rightGroup: jsx(Button, { theme: "secondary", variant: "plain", size: "xs", className: "px-1 mr-1", type: "button", onClick: () => setShowPassword(() => !showPassword), tabIndex: -1, children: showPassword ? (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", className: "size-4", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" }) })) : (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", className: "size-4", children: [jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" }), jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" })] })) }) }));
864
867
  };
@@ -868,8 +871,9 @@ const Radio = forwardRef((props, ref) => {
868
871
  const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '',
869
872
  // RadioButton-only props removed from rest
870
873
  ...rest } = props;
871
- const radioId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : 'checkbox-' + Math.random();
872
- return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-center gap-2', containerClass), children: [jsx("div", { className: "grid place-items-center", children: jsx("input", { ref: ref, ...rest, id: radioId, name: name, type: "radio", disabled: disabled, "aria-selected": rest.checked, className: buildClassName('peer transition-all col-start-1 row-start-1 appearance-none shrink-0 size-4.5 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'focus:ring-blue-500 checked:focus:ring-blue-600 dark:focus:ring-offset-gray-800', disabled
874
+ const generatedId = useId();
875
+ const radioId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : generatedId;
876
+ return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-center gap-2', containerClass), children: [jsx("div", { className: "grid place-items-center", 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 size-4.5 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'focus:ring-blue-500 checked:focus:ring-blue-600 dark:focus:ring-offset-gray-800', disabled
873
877
  ? 'border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 pointer-events-none'
874
878
  : buildClassName('bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500', 'checked:border-blue-600 checked:hover:border-blue-600 dark:checked:border-blue-600 dark:checked:hover:border-blue-600'), className) }) }), label && (jsx(Label, { htmlFor: radioId, className: buildClassName(disabled
875
879
  ? 'cursor-not-allowed text-gray-400 dark:text-gray-500'
@@ -878,16 +882,9 @@ const Radio = forwardRef((props, ref) => {
878
882
 
879
883
  const RadioGroup = (props) => {
880
884
  const { name, label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, row, error, showErrorMessage, onChange, } = props;
881
- const [items, setItems] = useState([]);
882
- useEffect(() => {
883
- setItems(() => {
884
- return options === null || options === void 0 ? void 0 : options.map((option) => {
885
- if (typeof option === 'string') {
886
- return { label: option, value: option };
887
- }
888
- return option;
889
- });
890
- });
885
+ const items = useMemo(() => {
886
+ var _a;
887
+ return (_a = options === null || options === void 0 ? void 0 : options.map((option) => typeof option === 'string' ? { label: option, value: option } : option)) !== null && _a !== void 0 ? _a : [];
891
888
  }, [options]);
892
889
  return (jsxs("div", { className: buildClassName(containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-4', labelWrapperClass), children: [jsx(Label, { className: labelClass, children: label }), labelHint] })), jsx("div", { className: buildClassName('flex gap-4', row ? 'flex-row items-center' : 'flex-col items-start'), children: items.map((item) => (jsx(Radio, { name: name, id: `${name}-${item.value}`, label: item.label, value: item.value, checked: value === item.value, onChange: onChange }, item.value))) }), showErrorMessage && error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) }))] }));
893
890
  };
@@ -981,35 +978,53 @@ const RangeSlider = ({ label, labelHint, labelClass = '', containerClass = '', c
981
978
  const activeThumbRef = useRef(null);
982
979
  // State for UI styling
983
980
  const [activeThumbState, setActiveThumbState] = useState(null);
981
+ // Keep prop values in refs so drag handlers always use latest values (no stale closure)
982
+ const valueMinRef = useRef(valueMin);
983
+ const valueMaxRef = useRef(valueMax);
984
+ const onChangeRef = useRef(onChange);
985
+ valueMinRef.current = valueMin;
986
+ valueMaxRef.current = valueMax;
987
+ onChangeRef.current = onChange;
984
988
  const hasError = !isEmpty(error);
985
989
  const percent = (v) => ((v - min) / (max - min)) * 100;
986
- // ---------------------------------------------
987
- // Pointer Move (drag)
988
- // ---------------------------------------------
989
- const pointerMoveHandler = (e) => {
990
+ // Stable handler references registered on window — these never change identity
991
+ // so removeEventListener always finds the right listener to remove.
992
+ const stablePointerMoveRef = useRef((e) => {
990
993
  if (!trackRef.current || !activeThumbRef.current)
991
994
  return;
992
995
  const rect = trackRef.current.getBoundingClientRect();
993
996
  const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
994
997
  const rawVal = min + (x / rect.width) * (max - min);
995
998
  if (activeThumbRef.current === 'min') {
996
- onChange(Math.min(rawVal, valueMax - 1), valueMax);
999
+ onChangeRef.current(Math.min(rawVal, valueMaxRef.current - 1), valueMaxRef.current);
997
1000
  }
998
1001
  else {
999
- onChange(valueMin, Math.max(rawVal, valueMin + 1));
1002
+ onChangeRef.current(valueMinRef.current, Math.max(rawVal, valueMinRef.current + 1));
1000
1003
  }
1001
- };
1002
- const pointerUpHandler = () => {
1004
+ });
1005
+ const stablePointerUpRef = useRef(() => {
1003
1006
  activeThumbRef.current = null;
1004
1007
  setActiveThumbState(null);
1005
- window.removeEventListener('pointermove', pointerMoveHandler);
1006
- window.removeEventListener('pointerup', pointerUpHandler);
1007
- };
1008
- const startDrag = (thumb) => (e) => {
1008
+ window.removeEventListener('pointermove', stablePointerMoveRef.current);
1009
+ window.removeEventListener('pointerup', stablePointerUpRef.current);
1010
+ });
1011
+ // Remove listeners on unmount in case a drag is in progress
1012
+ useEffect(() => {
1013
+ const move = stablePointerMoveRef.current;
1014
+ const up = stablePointerUpRef.current;
1015
+ return () => {
1016
+ window.removeEventListener('pointermove', move);
1017
+ window.removeEventListener('pointerup', up);
1018
+ };
1019
+ }, []);
1020
+ // ---------------------------------------------
1021
+ // Pointer Move (drag)
1022
+ // ---------------------------------------------
1023
+ const startDrag = (thumb) => (_e) => {
1009
1024
  activeThumbRef.current = thumb;
1010
1025
  setActiveThumbState(thumb);
1011
- window.addEventListener('pointermove', pointerMoveHandler);
1012
- window.addEventListener('pointerup', pointerUpHandler);
1026
+ window.addEventListener('pointermove', stablePointerMoveRef.current);
1027
+ window.addEventListener('pointerup', stablePointerUpRef.current);
1013
1028
  };
1014
1029
  // ---------------------------------------------
1015
1030
  // Click Track to move closest thumb
@@ -1071,6 +1086,9 @@ const useDebounce = (value, delay) => {
1071
1086
  };
1072
1087
  const useDebounceFn = (fn, ms) => {
1073
1088
  const timeout = useRef(null);
1089
+ useEffect(() => {
1090
+ return () => clearTimeout(timeout.current);
1091
+ }, []);
1074
1092
  return useCallback((...args) => {
1075
1093
  clearTimeout(timeout.current);
1076
1094
  timeout.current = setTimeout(() => fn(...args), ms);
@@ -1235,7 +1253,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
1235
1253
  console.error('onAdd handler error', err);
1236
1254
  }
1237
1255
  }, [onAdd, handleOnChangeInternal]);
1238
- const handleSearch = async (searchString) => {
1256
+ const handleSearch = useCallback(async (searchString) => {
1239
1257
  var _a;
1240
1258
  try {
1241
1259
  setOptionsLoading(true);
@@ -1247,11 +1265,11 @@ const SelectBox = forwardRef((props, forwardedRef) => {
1247
1265
  finally {
1248
1266
  setOptionsLoading(false);
1249
1267
  }
1250
- };
1268
+ }, [props.onSearch]);
1251
1269
  useEffect(() => {
1252
1270
  if (async && debouncedQuery)
1253
1271
  handleSearch(debouncedQuery);
1254
- }, [debouncedQuery, async]);
1272
+ }, [debouncedQuery, async, handleSearch]);
1255
1273
  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] })), jsxs(Combobox, { multiple: multiple, immediate: immediate, name: name, value: selected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, children: [multiple ? (jsxs("div", { className: buildClassName(inputClass, 'flex flex-wrap gap-2 peer relative'), ref: forwardedRef, children: [isArray(selected) &&
1256
1274
  (selected === null || selected === void 0 ? void 0 : selected.length) > 0 &&
1257
1275
  selected.map((option, idx) => (jsx(Badge, { theme: "secondary", 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", "aria-label": name, value: query, autoComplete: "off", placeholder: (selected === null || selected === void 0 ? void 0 : selected.length) ? '' : placeholder, onChange: (e) => setQuery(e.target.value), id: id, ref: inputRef, onKeyDown: (e) => {
@@ -1364,7 +1382,11 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1364
1382
  timeoutRefs.current.delete(id);
1365
1383
  }
1366
1384
  };
1367
- const scheduleClose = (toast) => {
1385
+ const closeToast = useCallback((id) => {
1386
+ clearTimer(id);
1387
+ setToasts((prev) => prev.filter((t) => t.id !== id));
1388
+ }, []);
1389
+ const scheduleClose = useCallback((toast) => {
1368
1390
  if (!toast.autoClose)
1369
1391
  return;
1370
1392
  clearTimer(toast.id);
@@ -1372,7 +1394,11 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1372
1394
  closeToast(toast.id);
1373
1395
  }, toast.duration);
1374
1396
  timeoutRefs.current.set(toast.id, timeout);
1375
- };
1397
+ }, [closeToast]);
1398
+ useEffect(() => {
1399
+ const refs = timeoutRefs.current;
1400
+ return () => refs.forEach((id) => clearTimeout(id));
1401
+ }, []);
1376
1402
  const showToast = useCallback((message, options) => {
1377
1403
  var _a, _b, _c, _d;
1378
1404
  const id = ++toastCounter;
@@ -1391,11 +1417,7 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1391
1417
  return updated;
1392
1418
  });
1393
1419
  scheduleClose(newToast);
1394
- }, []);
1395
- const closeToast = useCallback((id) => {
1396
- clearTimer(id);
1397
- setToasts((prev) => prev.filter((t) => t.id !== id));
1398
- }, []);
1420
+ }, [scheduleClose]);
1399
1421
  return (jsxs(ToastContext.Provider, { value: { showToast, closeToast }, children: [children, jsx("div", { className: buildClassName('fixed z-50 flex flex-col gap-3 pointer-events-none', placementClass[placement]), children: toasts.map((toast, index) => (jsx("div", { className: "pointer-events-auto", onMouseEnter: () => toast.pauseOnHover && clearTimer(toast.id), onMouseLeave: () => toast.pauseOnHover && scheduleClose(toast), children: jsx(Toast, { ...toast, isTop: placement.startsWith('top'), onClose: () => closeToast(toast.id) }) }, toast.id))) })] }));
1400
1422
  };
1401
1423
 
@@ -2094,5 +2116,11 @@ const useIsTablet = () => {
2094
2116
  return isTablet;
2095
2117
  };
2096
2118
 
2097
- export { Alert, Badge, Banner, BodyText, Breadcrumb, Button, Card, Checkbox, CheckboxGroup, DOTS, DataTable, DetailedInformation, Dot, Drawer, Dropdown, EmptyState, EmptyTableRow, HeadingText, Input, Label, Loader, PASSWORD_PLACEHOLDER, PASSWORD_TYPE, Pagination, PasswordInput, Radio, RadioGroup, RadioSwitch, RangeInput, RangeSlider, SelectBox, SortIconMap, Table, TableBody, TableCell, TableFoot, TableHead, TableHeaderCell, TableRow, Tabs, TextContent, Textarea, Toast, ToastProvider, VirtualizedDataTable, buildClassName, buildInputClass, get, getSortIcon, isArray, isEmpty, nextSortDirection, updateSortQuery, useDebounce, useDebounceFn, useIsMobile, useIsTablet, usePagination, useToast };
2119
+ const useIsTabletOrMobile = () => {
2120
+ const isMobile = useIsMobile();
2121
+ const isTablet = useIsTablet();
2122
+ return isMobile || isTablet;
2123
+ };
2124
+
2125
+ export { Alert, Badge, Banner, BodyText, Breadcrumb, Button, Card, Checkbox, CheckboxGroup, DOTS, DataTable, DetailedInformation, Dot, Drawer, Dropdown, EmptyState, EmptyTableRow, HeadingText, Input, Label, Loader, PASSWORD_PLACEHOLDER, PASSWORD_TYPE, Pagination, PasswordInput, Radio, RadioGroup, RadioSwitch, RangeInput, RangeSlider, SelectBox, SortIconMap, Table, TableBody, TableCell, TableFoot, TableHead, TableHeaderCell, TableRow, Tabs, TextContent, Textarea, Toast, ToastProvider, VirtualizedDataTable, buildClassName, buildInputClass, get, getSortIcon, isArray, isEmpty, nextSortDirection, updateSortQuery, useDebounce, useDebounceFn, useIsMobile, useIsTablet, useIsTabletOrMobile, usePagination, useToast };
2098
2126
  //# sourceMappingURL=react-tailwind.es.js.map