@pk-design/react-tailwind 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -450,9 +450,7 @@ const TableRowLoader = ({ colSize, rowSize }) => (jsxRuntime.jsx(jsxRuntime.Frag
450
450
  /* ----------------------------------------------
451
451
  * Table
452
452
  * ---------------------------------------------- */
453
- const Table = ({ children, layout = 'auto', className = '' }) => {
454
- return (jsxRuntime.jsx("table", { className: buildClassName('border-collapse w-full divide-y divide-gray-200 dark:divide-gray-700', TABLE_LAYOUT[layout], className), children: children }));
455
- };
453
+ const Table = ({ children, layout = 'auto', className = '', caption, }) => (jsxRuntime.jsxs("table", { className: buildClassName('border-collapse w-full divide-y divide-gray-200 dark:divide-gray-700', TABLE_LAYOUT[layout], className), children: [caption && (jsxRuntime.jsx("caption", { className: "mb-2 text-sm text-left text-gray-500 dark:text-gray-400", children: caption })), children] }));
456
454
  /* ----------------------------------------------
457
455
  * Head
458
456
  * ---------------------------------------------- */
@@ -460,7 +458,7 @@ const TableHead = ({ children, className = '' }) => (jsxRuntime.jsx("thead", { c
460
458
  /* ----------------------------------------------
461
459
  * Body
462
460
  * ---------------------------------------------- */
463
- const TableBody = ({ children, loading, colSize, rowSize = 5, className, }) => (jsxRuntime.jsx("tbody", { className: buildClassName('text-sm text-gray-700 dark:text-white divide-y divide-gray-200 dark:divide-gray-700', className), children: loading ? jsxRuntime.jsx(TableRowLoader, { colSize: colSize, rowSize: rowSize }) : children }));
461
+ const TableBody = ({ children, loading, colSize = 1, rowSize = 5, className, }) => (jsxRuntime.jsx("tbody", { className: buildClassName('text-sm text-gray-700 dark:text-white divide-y divide-gray-200 dark:divide-gray-700', className), children: loading ? jsxRuntime.jsx(TableRowLoader, { colSize: colSize, rowSize: rowSize }) : children }));
464
462
  /* ----------------------------------------------
465
463
  * Foot
466
464
  * ---------------------------------------------- */
@@ -473,7 +471,7 @@ const TableRow = ({ children, hoverable = false, className, ...props }) => (jsxR
473
471
  * Cells
474
472
  * ---------------------------------------------- */
475
473
  const TableCell = ({ children, align = 'left', className, ...props }) => (jsxRuntime.jsx("td", { ...props, className: buildClassName('px-3 py-3 first:pl-4 last:pr-4 bg-white dark:bg-gray-900 group-hover:bg-gray-50 group-hover:dark:bg-gray-800', TEXT_ALIGN[align], className), children: jsxRuntime.jsx("div", { className: "leading-normal", children: children }) }));
476
- const TableHeaderCell = ({ children, align = 'left', className, ...props }) => (jsxRuntime.jsx("th", { ...props, className: buildClassName('px-3 py-3 first:pl-4 last:pr-4 tracking-wider whitespace-nowrap text-gray-800 dark:text-gray-300', TEXT_ALIGN[align], className), children: jsxRuntime.jsx(BodyText, { as: "span", className: "leading-none", children: children }) }));
474
+ const TableHeaderCell = ({ children, align = 'left', className, ...props }) => (jsxRuntime.jsx("th", { ...props, className: buildClassName('px-3 py-3 first:pl-4 last:pr-4 tracking-wider whitespace-nowrap text-gray-800 dark:text-gray-300', TEXT_ALIGN[align], className), children: children }));
477
475
  /* ----------------------------------------------
478
476
  * Empty Row
479
477
  * ---------------------------------------------- */
@@ -723,9 +721,11 @@ const SortIconMap = {
723
721
  };
724
722
  const getSortIcon = (sort) => sort ? (SortIconMap[sort]) : (jsxRuntime.jsx(SvgArrowUpDown, { className: "size-4 text-gray-400 shrink-0 group-hover:text-gray-800 dark:group-hover:text-white" }));
725
723
 
726
- /**
727
- * Smart auto-sort helper (optional usage)
728
- */
724
+ const DENSITY_CELL_CLASS$2 = {
725
+ compact: 'px-2 py-1.5',
726
+ default: '',
727
+ spacious: 'px-4 py-4',
728
+ };
729
729
  const autoSortItems = (items, column, sort) => {
730
730
  const path = column.name;
731
731
  return [...items].sort((a, b) => {
@@ -741,7 +741,6 @@ const autoSortItems = (items, column, sort) => {
741
741
  const dB = new Date(valueB).getTime();
742
742
  return sort === 'asc' || sort === '1' ? dA - dB : dB - dA;
743
743
  }
744
- // String fallback
745
744
  const sA = String(valueA).toLowerCase();
746
745
  const sB = String(valueB).toLowerCase();
747
746
  if (sA < sB)
@@ -751,7 +750,7 @@ const autoSortItems = (items, column, sort) => {
751
750
  return 0;
752
751
  });
753
752
  };
754
- const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination = null, setPagination = () => { }, emptyMessage = 'No data found', containerClass = '', wrapperClass = '', layout = 'auto', }) => {
753
+ 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', }) {
755
754
  const internalSorting = React.useRef({});
756
755
  const [sortedItems, setSortedItems] = React.useState(items);
757
756
  React.useEffect(() => {
@@ -766,17 +765,38 @@ const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination =
766
765
  return;
767
766
  onSort ? onSort(column) : doAutoSort(column);
768
767
  };
769
- return (jsxRuntime.jsxs("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: [jsxRuntime.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: jsxRuntime.jsxs(Table, { className: "relative w-full max-w-full h-fit", layout: layout, children: [jsxRuntime.jsx(TableHead, { children: columns.map((column) => (jsxRuntime.jsx(TableHeaderCell, { align: column.headerAlign, style: { width: column.width || 'auto' }, className: buildClassName('bg-gray-50 dark:bg-gray-800', column.sticky === 'left' &&
768
+ const getKey = (item, idx) => {
769
+ var _a;
770
+ if (typeof rowKey === 'function')
771
+ return rowKey(item);
772
+ return (_a = item[rowKey]) !== null && _a !== void 0 ? _a : idx;
773
+ };
774
+ const getRowClass = (item, idx) => {
775
+ if (!rowClass)
776
+ return '';
777
+ if (typeof rowClass === 'function')
778
+ return rowClass(item, idx);
779
+ return rowClass;
780
+ };
781
+ const getCellClass = (column, item) => {
782
+ if (!column.cellClass)
783
+ return '';
784
+ if (typeof column.cellClass === 'function')
785
+ return column.cellClass(item);
786
+ return column.cellClass;
787
+ };
788
+ const densityCellClass = DENSITY_CELL_CLASS$2[density];
789
+ return (jsxRuntime.jsxs("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: [jsxRuntime.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: jsxRuntime.jsxs(Table, { className: buildClassName('relative w-full max-w-full h-fit', tableClass), layout: layout, children: [jsxRuntime.jsx(TableHead, { className: headClass, children: columns.map((column) => (jsxRuntime.jsx(TableHeaderCell, { align: column.headerAlign, style: { width: column.width || 'auto' }, className: buildClassName('bg-gray-50 dark:bg-gray-800', densityCellClass, column.sticky === 'left' &&
770
790
  'sticky left-0 z-10 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
771
- 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]'), children: jsxRuntime.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: [jsxRuntime.jsx(TextContent, { className: "shrink-0", children: column.label }), column.sortable ? getSortIcon(sorting[column.name]) : null] }) }, column.name))) }), jsxRuntime.jsx(TableBody, { loading: loading, colSize: columns.length, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && sortedItems.length === 0 ? (jsxRuntime.jsx(EmptyTableRow, { colSpan: columns.length, children: emptyMessage })) : (sortedItems.map((item) => (jsxRuntime.jsx(TableRow, { className: "group", children: columns.map((column) => {
791
+ 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]', column.headerClass), children: jsxRuntime.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: [jsxRuntime.jsx(TextContent, { className: "shrink-0", children: column.label }), column.sortable ? getSortIcon(sorting[column.name]) : null] }) }, column.name))) }), jsxRuntime.jsx(TableBody, { loading: loading, colSize: columns.length, rowSize: (pagination === null || pagination === void 0 ? void 0 : pagination.limit) || 5, children: !loading && sortedItems.length === 0 ? (jsxRuntime.jsx(EmptyTableRow, { colSpan: columns.length, children: emptyMessage })) : (sortedItems.map((item, idx) => (jsxRuntime.jsx(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: columns.map((column) => {
772
792
  var _a;
773
- return (jsxRuntime.jsx(TableCell, { align: column.align, className: buildClassName(column.sticky === 'left' &&
793
+ return (jsxRuntime.jsx(TableCell, { align: column.align, className: buildClassName(densityCellClass, striped && idx % 2 === 1 && 'bg-gray-50 dark:bg-gray-800/40', column.sticky === 'left' &&
774
794
  'sticky left-0 z-10 shadow-[2px_0_6px_rgba(0,0,0,0.06)]', column.sticky === 'right' &&
775
- 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]'), children: column.render
795
+ 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]', getCellClass(column, item)), children: column.render
776
796
  ? column.render(item)
777
797
  : ((_a = get(item, column.name)) !== null && _a !== void 0 ? _a : 'Not set') }, column.name));
778
- }) }, item.id)))) })] }) }), pagination && (jsxRuntime.jsx(Pagination, { ...pagination, className: "rounded-b-lg border border-t-0 border-gray-200 dark:border-gray-800", onChange: setPagination }))] }));
779
- };
798
+ }) }, getKey(item, idx))))) }), footerRow && jsxRuntime.jsx(TableFoot, { children: footerRow })] }) }), pagination && (jsxRuntime.jsx(Pagination, { ...pagination, className: "rounded-b-lg border border-t-0 border-gray-200 dark:border-gray-800", onChange: setPagination }))] }));
799
+ }
780
800
  DataTable.displayName = 'DataTable';
781
801
 
782
802
  const Title = ({ children, className }) => (jsxRuntime.jsx("h1", { className: buildClassName('text-3xl font-bold', className), children: children }));
@@ -1302,35 +1322,48 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
1302
1322
  }, "data-testid": "combobox-input" }), jsxRuntime.jsx(react.ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5", "data-testid": "combobox-button", children: jsxRuntime.jsx(SvgChevronDown, { className: chevronIconClass }) })] })) : (jsxRuntime.jsxs("div", { className: "relative", ref: forwardedRef, children: [jsxRuntime.jsx(react.ComboboxInput, { "aria-label": name, displayValue: (option) => (option ? getDisplayValue(option) : null), autoComplete: "off", placeholder: placeholder, onChange: (event) => setQuery(event.target.value), className: inputClass, disabled: disabled, id: id, readOnly: !searchable, ref: inputRef, "data-testid": "combobox-input" }), allowClear && !isEmpty(selected) && (jsxRuntime.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: jsxRuntime.jsx(SvgX, { className: "size-4 stroke-gray-500" }) })), jsxRuntime.jsx(react.ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5", "data-testid": "combobox-button", children: jsxRuntime.jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxRuntime.jsx(react.ComboboxOptions, { anchor: { to: 'bottom', gap: 5 }, as: "div", className: buildClassName('w-[var(--input-width)] rounded-lg border border-gray-200 dark:border-white/5 bg-white dark:bg-gray-800 p-1 [--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: optionsLoading ? (jsxRuntime.jsx("div", { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsxRuntime.jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsxRuntime.jsx("div", { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxRuntime.jsxs("div", { children: [filteredOptions.map((option) => (jsxRuntime.jsx(react.ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-black/3 dark:data-[focus]:bg-white/10 data-disabled:opacity-50 cursor-pointer data-disabled:pointer-events-none", children: jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 dark:text-white group-data-[selected]:text-blue-600", children: [jsxRuntime.jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsxRuntime.jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] }) }, getValue(option)))), filteredOptions.length === 0 && query && !optionsLoading && (jsxRuntime.jsxs("div", { "data-testid": "no-result-found", className: "flex items-center gap-2 py-1.5 px-3 select-none text-gray-500 dark:text-gray-400", children: ["No results found for ", jsxRuntime.jsx("span", { className: "font-semibold", children: query })] })), allowAdd && (jsxRuntime.jsx(react.ComboboxOption, { value: query, className: "group rounded-lg py-1.5 px-3 cursor-pointer text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700", onClick: () => void handleAddAndSelect(query), children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx(SvgCirclePlus, { className: "size-4.5" }), jsxRuntime.jsxs(TextContent, { children: [addNewText, " \"", query, "\""] })] }) }))] })) })] }), showErrorMessage && error && (jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx(TextContent, { error: true, small: true, children: error }) }))] }));
1303
1323
  });
1304
1324
 
1305
- const Tabs = ({ tabs, defaultIndex = 0, variant = 'underline', className = '', listClass = '', panelClass = '', }) => {
1306
- return (jsxRuntime.jsxs(react.TabGroup, { defaultIndex: defaultIndex, className: className, children: [jsxRuntime.jsx(react.TabList, { className: buildClassName('flex gap-2', listClass), children: tabs.map((tab, idx) => (jsxRuntime.jsx(react.Tab, { as: React.Fragment, children: ({ selected }) => (jsxRuntime.jsx("button", { className: buildTabClass({ selected, variant }), children: tab.label })) }, idx))) }), jsxRuntime.jsx(react.TabPanels, { className: buildClassName('mt-3', panelClass), children: tabs.map((tab, idx) => (jsxRuntime.jsx(react.TabPanel, { className: "rounded-lg bg-white dark:bg-gray-800 p-4 shadow-sm", children: tab.content }, idx))) })] }));
1325
+ const Tabs = ({ tabs, defaultIndex = 0, selectedIndex, onChange, variant = 'underline', orientation = 'horizontal', stretch = false, className = '', listClass = '', panelsClass = '', }) => {
1326
+ const isVertical = orientation === 'vertical';
1327
+ return (jsxRuntime.jsxs(react.TabGroup, { defaultIndex: defaultIndex, selectedIndex: selectedIndex, onChange: onChange, vertical: isVertical, className: buildClassName(isVertical && 'flex gap-4', stretch && !isVertical && 'w-full', className), children: [jsxRuntime.jsx(react.TabList, { className: buildClassName(isVertical ? 'flex flex-col gap-1' : 'flex gap-2', stretch && !isVertical && 'w-full', listClass), children: tabs.map((tab, idx) => {
1328
+ var _a;
1329
+ return (jsxRuntime.jsx(react.Tab, { disabled: tab.disabled, as: React.Fragment, children: ({ selected }) => (jsxRuntime.jsxs("button", { disabled: tab.disabled, className: buildTabClass({
1330
+ selected,
1331
+ variant,
1332
+ stretch,
1333
+ isVertical,
1334
+ tabClass: tab.tabClass,
1335
+ }), children: [tab.icon && jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: tab.icon }), tab.label] })) }, (_a = tab.id) !== null && _a !== void 0 ? _a : idx));
1336
+ }) }), jsxRuntime.jsx(react.TabPanels, { className: buildClassName(isVertical ? 'flex-1' : 'mt-3', panelsClass), children: tabs.map((tab, idx) => {
1337
+ var _a;
1338
+ return (jsxRuntime.jsx(react.TabPanel, { className: tab.panelClass !== undefined
1339
+ ? tab.panelClass
1340
+ : 'rounded-lg bg-white dark:bg-gray-800 p-4 shadow-sm', children: tab.content }, (_a = tab.id) !== null && _a !== void 0 ? _a : idx));
1341
+ }) })] }));
1307
1342
  };
1308
- /**
1309
- * Build tab styles based on variant + active state
1310
- */
1311
- function buildTabClass({ selected, variant, }) {
1343
+ function buildTabClass({ selected, variant, stretch, isVertical, tabClass, }) {
1344
+ const base = buildClassName('inline-flex items-center gap-1.5 text-sm font-medium transition focus-visible:outline-none', 'disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none', stretch && !isVertical && 'flex-1 justify-center');
1345
+ let variantClass;
1312
1346
  switch (variant) {
1313
- /**
1314
- * UNDERLINE (Default)
1315
- */
1316
1347
  case 'underline':
1317
- return buildClassName('px-3 py-1.5 text-sm font-medium transition relative', 'text-gray-600 dark:text-gray-300', 'hover:text-gray-900 dark:hover:text-white', selected &&
1318
- 'text-gray-900 dark:text-white after:absolute after:left-0 after:right-0 after:-bottom-1 after:h-0.5 after:bg-[var(--ui-primary)]');
1319
- /**
1320
- * SEGMENTED
1321
- */
1348
+ variantClass = isVertical
1349
+ ? buildClassName('px-3 py-2 w-full text-left border-l-2', selected
1350
+ ? 'border-[var(--ui-primary)] text-gray-900 dark:text-white'
1351
+ : 'border-transparent text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:border-gray-300 dark:hover:border-gray-600')
1352
+ : buildClassName('px-3 py-1.5 relative text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white', selected &&
1353
+ 'text-gray-900 dark:text-white after:absolute after:z-10 after:left-0 after:right-0 after:-bottom-1 after:h-0.5 after:bg-[var(--ui-primary)]');
1354
+ break;
1322
1355
  case 'segmented':
1323
- return buildClassName('px-3 py-1.5 text-sm font-medium rounded-lg transition', selected
1356
+ variantClass = buildClassName('px-3 py-1.5 rounded-lg', selected
1324
1357
  ? 'bg-[var(--ui-primary)] text-white'
1325
1358
  : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700');
1326
- /**
1327
- * SOLID
1328
- */
1359
+ break;
1329
1360
  case 'solid':
1330
- return buildClassName('px-3 py-1.5 text-sm font-medium rounded-md transition', selected
1361
+ variantClass = buildClassName('px-3 py-1.5 rounded-md', selected
1331
1362
  ? 'bg-gray-900 dark:bg-gray-700 text-white'
1332
1363
  : 'bg-gray-200 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-700');
1364
+ break;
1333
1365
  }
1366
+ return buildClassName(base, variantClass, tabClass);
1334
1367
  }
1335
1368
 
1336
1369
  const Textarea = React.forwardRef((props, ref) => {
@@ -1435,33 +1468,61 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1435
1468
  return (jsxRuntime.jsxs(ToastContext.Provider, { value: { showToast, closeToast }, children: [children, jsxRuntime.jsx("div", { className: buildClassName('fixed z-50 flex flex-col gap-3 pointer-events-none', placementClass[placement]), children: toasts.map((toast, index) => (jsxRuntime.jsx("div", { className: "pointer-events-auto", onMouseEnter: () => toast.pauseOnHover && clearTimer(toast.id), onMouseLeave: () => toast.pauseOnHover && scheduleClose(toast), children: jsxRuntime.jsx(Toast, { ...toast, isTop: placement.startsWith('top'), onClose: () => closeToast(toast.id) }) }, toast.id))) })] }));
1436
1469
  };
1437
1470
 
1438
- const VirtualizedRow = ({ index, style, data }) => {
1439
- const { items, columns, gridTemplate } = data;
1471
+ const DENSITY_CELL_CLASS$1 = {
1472
+ compact: 'px-2 py-1.5',
1473
+ default: 'px-3 py-3',
1474
+ spacious: 'px-4 py-4',
1475
+ };
1476
+ const VirtualizedRow = ({ index, style, data, }) => {
1477
+ const { items, columns, gridTemplate, onRowClick, rowClass, density = 'default', striped } = data;
1440
1478
  const item = items[index];
1441
- return (jsxRuntime.jsx("div", { role: "row", className: "grid border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 group", style: {
1479
+ const isStriped = striped && index % 2 === 1;
1480
+ const resolvedRowClass = !rowClass
1481
+ ? ''
1482
+ : typeof rowClass === 'function'
1483
+ ? rowClass(item, index)
1484
+ : rowClass;
1485
+ const getCellClass = (col) => {
1486
+ if (!col.cellClass)
1487
+ return '';
1488
+ if (typeof col.cellClass === 'function')
1489
+ return col.cellClass(item);
1490
+ return col.cellClass;
1491
+ };
1492
+ const densityCellClass = DENSITY_CELL_CLASS$1[density];
1493
+ return (jsxRuntime.jsx("div", { role: "row", className: buildClassName('grid border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 group', onRowClick && 'cursor-pointer', isStriped && 'bg-gray-50 dark:bg-gray-800/40', resolvedRowClass), style: {
1442
1494
  ...style,
1443
1495
  display: 'grid',
1444
1496
  gridTemplateColumns: gridTemplate,
1445
1497
  minWidth: 'fit-content',
1446
- }, children: columns.map((col) => {
1498
+ }, onClick: onRowClick ? () => onRowClick(item, index) : undefined, children: columns.map((col) => {
1447
1499
  var _a;
1448
1500
  const content = col.render ? col.render(item) : ((_a = get(item, col.name)) !== null && _a !== void 0 ? _a : 'Not set');
1449
- return (jsxRuntime.jsx("div", { role: "cell", className: buildClassName('px-3 py-3 whitespace-nowrap border-r last:border-r-0 border-gray-200 dark:border-gray-700', col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.sticky === 'left' &&
1501
+ return (jsxRuntime.jsx("div", { role: "cell", className: buildClassName('whitespace-nowrap border-r last:border-r-0 border-gray-200 dark:border-gray-700', densityCellClass, col.align === 'center' && 'text-center', col.align === 'right' && 'text-right', col.sticky === 'left' &&
1450
1502
  'sticky left-0 z-10 bg-white dark:bg-gray-900 shadow-[2px_0_4px_rgba(0,0,0,0.05)]', col.sticky === 'right' &&
1451
- 'sticky right-0 z-10 bg-white dark:bg-gray-900 shadow-[-2px_0_4px_rgba(0,0,0,0.05)]'), children: content }, col.name));
1503
+ 'sticky right-0 z-10 bg-white dark:bg-gray-900 shadow-[-2px_0_4px_rgba(0,0,0,0.05)]', isStriped && 'bg-gray-50 dark:bg-gray-800/40', getCellClass(col)), children: content }, col.name));
1452
1504
  }) }));
1453
1505
  };
1454
1506
 
1455
- /* -------------------------------------------------------
1456
- * Convert column definitions into CSS grid template
1457
- * ------------------------------------------------------- */
1458
- const useGridTemplate = (columns) => React.useMemo(() => columns.map((col) => (col.width ? `${col.width}px` : '1fr')).join(' '), [columns]);
1459
- const VirtualizedDataTable = ({ items, columns, sorting = {}, onSort, loading = false, emptyMessage = 'No data available', containerClass = '', wrapperClass = '', rowHeight = 48, maxHeight = 400, }) => {
1507
+ const DENSITY_CELL_CLASS = {
1508
+ compact: 'px-2 py-1.5',
1509
+ default: 'px-3 py-3',
1510
+ spacious: 'px-4 py-4',
1511
+ };
1512
+ const columnWidth = (col) => {
1513
+ if (!col.width)
1514
+ return '1fr';
1515
+ if (typeof col.width === 'number')
1516
+ return `${col.width}px`;
1517
+ return col.width;
1518
+ };
1519
+ function VirtualizedDataTable({ items, columns, sorting = {}, onSort, loading = false, emptyMessage = 'No data available', containerClass = '', wrapperClass = '', onRowClick, rowClass, density = 'default', striped = false, rowHeight = 48, maxHeight = 400, overscanCount = 3, }) {
1460
1520
  const scrollRef = React.useRef(null);
1461
1521
  const outerRef = React.useRef(null);
1462
1522
  const headerRef = React.useRef(null);
1463
- const gridTemplate = useGridTemplate(columns);
1464
- /** Sync horizontal scroll of the virtualized body → header */
1523
+ const gridTemplate = React.useMemo(() => columns.map((col) => columnWidth(col)).join(' '), [columns]);
1524
+ const densityCellClass = DENSITY_CELL_CLASS[density];
1525
+ const skeletonRows = Math.max(1, Math.floor(maxHeight / rowHeight));
1465
1526
  React.useEffect(() => {
1466
1527
  const el = outerRef.current;
1467
1528
  if (!el)
@@ -1477,35 +1538,31 @@ const VirtualizedDataTable = ({ items, columns, sorting = {}, onSort, loading =
1477
1538
  if (column.sortable)
1478
1539
  onSort === null || onSort === void 0 ? void 0 : onSort(column);
1479
1540
  }, [onSort]);
1541
+ const itemData = React.useMemo(() => ({ items, columns, gridTemplate, onRowClick, rowClass, density, striped }), [items, columns, gridTemplate, onRowClick, rowClass, density, striped]);
1480
1542
  /* -------------------------------------------------------
1481
- * RENDER: Header row (real DOM for accessibility)
1543
+ * Header
1482
1544
  * ------------------------------------------------------- */
1483
- const Header = (jsxRuntime.jsx("div", { ref: headerRef, className: "overflow-hidden w-full", children: jsxRuntime.jsx("div", { className: "grid bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700", style: {
1484
- gridTemplateColumns: gridTemplate,
1485
- minWidth: 'fit-content',
1486
- }, children: columns.map((col) => (jsxRuntime.jsx("div", { role: "columnheader", className: buildClassName('px-3 py-3 font-semibold text-sm whitespace-nowrap flex items-center', 'border-r last:border-r-0 border-gray-200 dark:border-gray-700', col.sticky === 'left' &&
1545
+ const Header = (jsxRuntime.jsx("div", { ref: headerRef, className: "overflow-hidden w-full", children: jsxRuntime.jsx("div", { className: "grid bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700", style: { gridTemplateColumns: gridTemplate, minWidth: 'fit-content' }, children: columns.map((col) => (jsxRuntime.jsx("div", { role: "columnheader", className: buildClassName('font-semibold text-sm whitespace-nowrap flex items-center', densityCellClass, 'border-r last:border-r-0 border-gray-200 dark:border-gray-700', col.sticky === 'left' &&
1487
1546
  'sticky left-0 z-20 bg-gray-50 dark:bg-gray-800 shadow-[2px_0_4px_rgba(0,0,0,0.05)]', col.sticky === 'right' &&
1488
- 'sticky right-0 z-20 bg-gray-50 dark:bg-gray-800 shadow-[-2px_0_4px_rgba(0,0,0,0.05)]'), children: jsxRuntime.jsxs(TextContent, { role: col.sortable ? 'button' : undefined, className: buildClassName(col.sortable && 'inline-flex items-center gap-2 cursor-pointer select-none'), onClick: () => handleSort(col), children: [col.label, col.sortable ? getSortIcon(sorting[col.name]) : null] }) }, col.name))) }) }));
1547
+ 'sticky right-0 z-20 bg-gray-50 dark:bg-gray-800 shadow-[-2px_0_4px_rgba(0,0,0,0.05)]', col.headerClass), children: jsxRuntime.jsxs(TextContent, { role: col.sortable ? 'button' : undefined, className: buildClassName(col.sortable && 'inline-flex items-center gap-2 cursor-pointer select-none'), onClick: () => handleSort(col), children: [col.label, col.sortable ? getSortIcon(sorting[col.name]) : null] }) }, col.name))) }) }));
1548
+ const wrapperCls = buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass);
1489
1549
  /* -------------------------------------------------------
1490
- * LOADING STATE (non-virtualized placeholder)
1550
+ * Loading state
1491
1551
  * ------------------------------------------------------- */
1492
1552
  if (loading) {
1493
- return (jsxRuntime.jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxRuntime.jsxs("div", { className: buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass), children: [Header, jsxRuntime.jsx("div", { style: { height: maxHeight }, className: "flex flex-col", children: [...Array(6)].map((_, idx) => (jsxRuntime.jsx("div", { "data-testid": "skeleton-row", className: "grid animate-pulse bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800", style: {
1494
- gridTemplateColumns: gridTemplate,
1495
- height: rowHeight,
1496
- }, children: columns.map((col) => (jsxRuntime.jsx("div", { className: "px-3 py-3", children: jsxRuntime.jsx("div", { className: "h-4 w-3/4 bg-gray-300 dark:bg-gray-700 rounded" }) }, col.name))) }, idx))) })] }) }));
1553
+ return (jsxRuntime.jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxRuntime.jsxs("div", { className: wrapperCls, children: [Header, jsxRuntime.jsx("div", { style: { height: maxHeight }, className: "flex flex-col", children: Array.from({ length: skeletonRows }).map((_, idx) => (jsxRuntime.jsx("div", { "data-testid": "skeleton-row", className: "grid animate-pulse bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800", style: { gridTemplateColumns: gridTemplate, height: rowHeight }, children: columns.map((col) => (jsxRuntime.jsx("div", { className: densityCellClass, children: jsxRuntime.jsx("div", { className: "h-4 w-3/4 bg-gray-300 dark:bg-gray-700 rounded" }) }, col.name))) }, idx))) })] }) }));
1497
1554
  }
1498
1555
  /* -------------------------------------------------------
1499
- * EMPTY STATE (non-virtualized → single row)
1556
+ * Empty state
1500
1557
  * ------------------------------------------------------- */
1501
- if (!loading && items.length === 0) {
1502
- return (jsxRuntime.jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxRuntime.jsxs("div", { className: buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass), children: [Header, jsxRuntime.jsx("div", { className: "flex items-center justify-center text-gray-500 dark:text-gray-300", style: { height: maxHeight }, children: emptyMessage })] }) }));
1558
+ if (items.length === 0) {
1559
+ return (jsxRuntime.jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxRuntime.jsxs("div", { className: wrapperCls, children: [Header, jsxRuntime.jsx("div", { className: "flex items-center justify-center text-gray-500 dark:text-gray-300", style: { height: maxHeight }, children: emptyMessage })] }) }));
1503
1560
  }
1504
1561
  /* -------------------------------------------------------
1505
- * MAIN (virtualized body)
1562
+ * Virtualized body
1506
1563
  * ------------------------------------------------------- */
1507
- return (jsxRuntime.jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxRuntime.jsxs("div", { className: buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass), children: [Header, jsxRuntime.jsx(reactWindow.FixedSizeList, { ref: scrollRef, outerRef: outerRef, height: maxHeight, width: "100%", itemCount: items.length, itemSize: rowHeight, itemData: { items, columns, gridTemplate }, children: VirtualizedRow })] }) }));
1508
- };
1564
+ return (jsxRuntime.jsx("div", { className: buildClassName('w-full rounded-lg relative', containerClass), children: jsxRuntime.jsxs("div", { className: wrapperCls, children: [Header, jsxRuntime.jsx(reactWindow.FixedSizeList, { ref: scrollRef, outerRef: outerRef, height: maxHeight, width: "100%", itemCount: items.length, itemSize: rowHeight, itemData: itemData, overscanCount: overscanCount, children: VirtualizedRow })] }) }));
1565
+ }
1509
1566
 
1510
1567
  const useIsMobile = () => {
1511
1568
  const [isMobile, setIsMobile] = React.useState(false);