@pk-design/react-tailwind 1.3.0 → 1.4.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.
@@ -244,7 +244,7 @@ const TypeClassMap = {
244
244
  const Alert = (props) => {
245
245
  const { type = 'info', className = '', message, children, removable = false, onRemove = () => { }, ...restProps } = props;
246
246
  const Icon = AlertTypeIconMap[type] || SvgInfo;
247
- return (jsxRuntime.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: [jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2", children: [jsxRuntime.jsx(Icon, { className: "size-5", "data-testid": `alert-${type}-icon` }), jsxRuntime.jsx("span", { className: "pr-2 inline-flex items-center", children: message || children })] }), removable && (jsxRuntime.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: jsxRuntime.jsx(SvgX, { className: "size-4.5", "data-testid": "alert-remove-icon" }) }))] }));
247
+ return (jsxRuntime.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: [jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2", children: [jsxRuntime.jsx(Icon, { className: "size-5", "data-testid": `alert-${type}-icon` }), jsxRuntime.jsx("span", { className: "pr-2 inline-flex items-center", children: message || children })] }), removable && (jsxRuntime.jsx(Button, { iconOnly: true, "aria-label": "Dismiss alert", size: "xs", variant: "plain", theme: "secondary", className: "hover:bg-black/10 dark:hover:bg-white/10", onClick: () => onRemove(), "data-testid": "alert-remove-btn", children: jsxRuntime.jsx(SvgX, { className: "size-4.5", "data-testid": "alert-remove-icon" }) }))] }));
248
248
  };
249
249
  Alert.displayName = 'Alert';
250
250
 
@@ -257,7 +257,7 @@ const ThemeClassMap = {
257
257
  };
258
258
  const Badge = React.forwardRef((props, ref) => {
259
259
  const { theme = 'info', className = '', children, removable = false, rounded = false, onRemove = () => { }, ...restProps } = props;
260
- return (jsxRuntime.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: [jsxRuntime.jsx(TextContent, { className: "pr-3 inline-flex items-center", children: children }), removable ? (jsxRuntime.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) => {
260
+ return (jsxRuntime.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: [jsxRuntime.jsx(TextContent, { className: "pr-3 inline-flex items-center", children: children }), removable ? (jsxRuntime.jsx(SvgX, { className: "size-5 px-0.5 mr-1 hover:bg-black/10 dark:hover:bg-white/10 rounded-sm cursor-pointer", role: "button", tabIndex: 0, "aria-label": "Remove", onClick: () => onRemove(), onKeyDown: (e) => {
261
261
  if (e.key === 'Enter' || e.key === ' ') {
262
262
  e.preventDefault();
263
263
  onRemove();
@@ -309,13 +309,11 @@ var SvgCircleCheck = function SvgCircleCheck(props) {
309
309
  };
310
310
 
311
311
  const sizeMap = {
312
- 4: `size-4`,
313
- 5: `size-5`,
314
- 6: `size-6`,
315
- 7: `size-7`,
316
- 8: `size-8`,
317
- 9: `size-9`,
318
- 10: `size-10`,
312
+ xs: 'size-4',
313
+ sm: 'size-5',
314
+ md: 'size-6',
315
+ lg: 'size-8',
316
+ xl: 'size-10',
319
317
  };
320
318
  const IconMap = {
321
319
  info: SvgInfo,
@@ -324,7 +322,7 @@ const IconMap = {
324
322
  warning: SvgTriangleAlert,
325
323
  };
326
324
  const Banner = React.forwardRef((props, ref) => {
327
- const { type = 'info', iconSize = 5, children, ...restProps } = props;
325
+ const { type = 'info', iconSize = 'sm', children, ...restProps } = props;
328
326
  const className = buildClassName('text-sm banner rounded-md flex w-full p-3 items-center', type === 'info' ? 'bg-blue-50 text-blue-700' : '', type === 'error' ? 'bg-red-50 text-red-700' : '', type === 'success' ? 'bg-green-50 text-green-700' : '', type === 'warning' ? 'bg-orange-50 text-orange-700' : '');
329
327
  const renderIcon = () => {
330
328
  const Icon = IconMap[type];
@@ -450,9 +448,7 @@ const TableRowLoader = ({ colSize, rowSize }) => (jsxRuntime.jsx(jsxRuntime.Frag
450
448
  /* ----------------------------------------------
451
449
  * Table
452
450
  * ---------------------------------------------- */
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
- };
451
+ 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
452
  /* ----------------------------------------------
457
453
  * Head
458
454
  * ---------------------------------------------- */
@@ -460,7 +456,7 @@ const TableHead = ({ children, className = '' }) => (jsxRuntime.jsx("thead", { c
460
456
  /* ----------------------------------------------
461
457
  * Body
462
458
  * ---------------------------------------------- */
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 }));
459
+ 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
460
  /* ----------------------------------------------
465
461
  * Foot
466
462
  * ---------------------------------------------- */
@@ -473,7 +469,7 @@ const TableRow = ({ children, hoverable = false, className, ...props }) => (jsxR
473
469
  * Cells
474
470
  * ---------------------------------------------- */
475
471
  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 }) }));
472
+ 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
473
  /* ----------------------------------------------
478
474
  * Empty Row
479
475
  * ---------------------------------------------- */
@@ -723,9 +719,11 @@ const SortIconMap = {
723
719
  };
724
720
  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
721
 
726
- /**
727
- * Smart auto-sort helper (optional usage)
728
- */
722
+ const DENSITY_CELL_CLASS$2 = {
723
+ compact: 'px-2 py-1.5',
724
+ default: '',
725
+ spacious: 'px-4 py-4',
726
+ };
729
727
  const autoSortItems = (items, column, sort) => {
730
728
  const path = column.name;
731
729
  return [...items].sort((a, b) => {
@@ -741,7 +739,6 @@ const autoSortItems = (items, column, sort) => {
741
739
  const dB = new Date(valueB).getTime();
742
740
  return sort === 'asc' || sort === '1' ? dA - dB : dB - dA;
743
741
  }
744
- // String fallback
745
742
  const sA = String(valueA).toLowerCase();
746
743
  const sB = String(valueB).toLowerCase();
747
744
  if (sA < sB)
@@ -751,7 +748,7 @@ const autoSortItems = (items, column, sort) => {
751
748
  return 0;
752
749
  });
753
750
  };
754
- const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination = null, setPagination = () => { }, emptyMessage = 'No data found', containerClass = '', wrapperClass = '', layout = 'auto', }) => {
751
+ 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
752
  const internalSorting = React.useRef({});
756
753
  const [sortedItems, setSortedItems] = React.useState(items);
757
754
  React.useEffect(() => {
@@ -766,17 +763,38 @@ const DataTable = ({ items, columns, sorting = {}, onSort, loading, pagination =
766
763
  return;
767
764
  onSort ? onSort(column) : doAutoSort(column);
768
765
  };
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' &&
766
+ const getKey = (item, idx) => {
767
+ var _a;
768
+ if (typeof rowKey === 'function')
769
+ return rowKey(item);
770
+ return (_a = item[rowKey]) !== null && _a !== void 0 ? _a : idx;
771
+ };
772
+ const getRowClass = (item, idx) => {
773
+ if (!rowClass)
774
+ return '';
775
+ if (typeof rowClass === 'function')
776
+ return rowClass(item, idx);
777
+ return rowClass;
778
+ };
779
+ const getCellClass = (column, item) => {
780
+ if (!column.cellClass)
781
+ return '';
782
+ if (typeof column.cellClass === 'function')
783
+ return column.cellClass(item);
784
+ return column.cellClass;
785
+ };
786
+ const densityCellClass = DENSITY_CELL_CLASS$2[density];
787
+ 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
788
  '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) => {
789
+ '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
790
  var _a;
773
- return (jsxRuntime.jsx(TableCell, { align: column.align, className: buildClassName(column.sticky === 'left' &&
791
+ 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
792
  '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
793
+ 'sticky right-0 z-10 shadow-[-2px_0_6px_rgba(0,0,0,0.06)]', getCellClass(column, item)), children: column.render
776
794
  ? column.render(item)
777
795
  : ((_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
- };
796
+ }) }, 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 }))] }));
797
+ }
780
798
  DataTable.displayName = 'DataTable';
781
799
 
782
800
  const Title = ({ children, className }) => (jsxRuntime.jsx("h1", { className: buildClassName('text-3xl font-bold', className), children: children }));
@@ -1448,33 +1466,61 @@ const ToastProvider = ({ children, placement = 'top-center' }) => {
1448
1466
  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))) })] }));
1449
1467
  };
1450
1468
 
1451
- const VirtualizedRow = ({ index, style, data }) => {
1452
- const { items, columns, gridTemplate } = data;
1469
+ const DENSITY_CELL_CLASS$1 = {
1470
+ compact: 'px-2 py-1.5',
1471
+ default: 'px-3 py-3',
1472
+ spacious: 'px-4 py-4',
1473
+ };
1474
+ const VirtualizedRow = ({ index, style, data, }) => {
1475
+ const { items, columns, gridTemplate, onRowClick, rowClass, density = 'default', striped } = data;
1453
1476
  const item = items[index];
1454
- 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: {
1477
+ const isStriped = striped && index % 2 === 1;
1478
+ const resolvedRowClass = !rowClass
1479
+ ? ''
1480
+ : typeof rowClass === 'function'
1481
+ ? rowClass(item, index)
1482
+ : rowClass;
1483
+ const getCellClass = (col) => {
1484
+ if (!col.cellClass)
1485
+ return '';
1486
+ if (typeof col.cellClass === 'function')
1487
+ return col.cellClass(item);
1488
+ return col.cellClass;
1489
+ };
1490
+ const densityCellClass = DENSITY_CELL_CLASS$1[density];
1491
+ 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: {
1455
1492
  ...style,
1456
1493
  display: 'grid',
1457
1494
  gridTemplateColumns: gridTemplate,
1458
1495
  minWidth: 'fit-content',
1459
- }, children: columns.map((col) => {
1496
+ }, onClick: onRowClick ? () => onRowClick(item, index) : undefined, children: columns.map((col) => {
1460
1497
  var _a;
1461
1498
  const content = col.render ? col.render(item) : ((_a = get(item, col.name)) !== null && _a !== void 0 ? _a : 'Not set');
1462
- 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' &&
1499
+ 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' &&
1463
1500
  'sticky left-0 z-10 bg-white dark:bg-gray-900 shadow-[2px_0_4px_rgba(0,0,0,0.05)]', col.sticky === 'right' &&
1464
- '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));
1501
+ '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));
1465
1502
  }) }));
1466
1503
  };
1467
1504
 
1468
- /* -------------------------------------------------------
1469
- * Convert column definitions into CSS grid template
1470
- * ------------------------------------------------------- */
1471
- const useGridTemplate = (columns) => React.useMemo(() => columns.map((col) => (col.width ? `${col.width}px` : '1fr')).join(' '), [columns]);
1472
- const VirtualizedDataTable = ({ items, columns, sorting = {}, onSort, loading = false, emptyMessage = 'No data available', containerClass = '', wrapperClass = '', rowHeight = 48, maxHeight = 400, }) => {
1505
+ const DENSITY_CELL_CLASS = {
1506
+ compact: 'px-2 py-1.5',
1507
+ default: 'px-3 py-3',
1508
+ spacious: 'px-4 py-4',
1509
+ };
1510
+ const columnWidth = (col) => {
1511
+ if (!col.width)
1512
+ return '1fr';
1513
+ if (typeof col.width === 'number')
1514
+ return `${col.width}px`;
1515
+ return col.width;
1516
+ };
1517
+ 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, }) {
1473
1518
  const scrollRef = React.useRef(null);
1474
1519
  const outerRef = React.useRef(null);
1475
1520
  const headerRef = React.useRef(null);
1476
- const gridTemplate = useGridTemplate(columns);
1477
- /** Sync horizontal scroll of the virtualized body → header */
1521
+ const gridTemplate = React.useMemo(() => columns.map((col) => columnWidth(col)).join(' '), [columns]);
1522
+ const densityCellClass = DENSITY_CELL_CLASS[density];
1523
+ const skeletonRows = Math.max(1, Math.floor(maxHeight / rowHeight));
1478
1524
  React.useEffect(() => {
1479
1525
  const el = outerRef.current;
1480
1526
  if (!el)
@@ -1490,35 +1536,31 @@ const VirtualizedDataTable = ({ items, columns, sorting = {}, onSort, loading =
1490
1536
  if (column.sortable)
1491
1537
  onSort === null || onSort === void 0 ? void 0 : onSort(column);
1492
1538
  }, [onSort]);
1539
+ const itemData = React.useMemo(() => ({ items, columns, gridTemplate, onRowClick, rowClass, density, striped }), [items, columns, gridTemplate, onRowClick, rowClass, density, striped]);
1493
1540
  /* -------------------------------------------------------
1494
- * RENDER: Header row (real DOM for accessibility)
1541
+ * Header
1495
1542
  * ------------------------------------------------------- */
1496
- 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: {
1497
- gridTemplateColumns: gridTemplate,
1498
- minWidth: 'fit-content',
1499
- }, 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' &&
1543
+ 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' &&
1500
1544
  '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' &&
1501
- '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))) }) }));
1545
+ '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))) }) }));
1546
+ const wrapperCls = buildClassName('rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden', wrapperClass);
1502
1547
  /* -------------------------------------------------------
1503
- * LOADING STATE (non-virtualized placeholder)
1548
+ * Loading state
1504
1549
  * ------------------------------------------------------- */
1505
1550
  if (loading) {
1506
- 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: {
1507
- gridTemplateColumns: gridTemplate,
1508
- height: rowHeight,
1509
- }, 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))) })] }) }));
1551
+ 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))) })] }) }));
1510
1552
  }
1511
1553
  /* -------------------------------------------------------
1512
- * EMPTY STATE (non-virtualized → single row)
1554
+ * Empty state
1513
1555
  * ------------------------------------------------------- */
1514
- if (!loading && items.length === 0) {
1515
- 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 })] }) }));
1556
+ if (items.length === 0) {
1557
+ 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 })] }) }));
1516
1558
  }
1517
1559
  /* -------------------------------------------------------
1518
- * MAIN (virtualized body)
1560
+ * Virtualized body
1519
1561
  * ------------------------------------------------------- */
1520
- 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 })] }) }));
1521
- };
1562
+ 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 })] }) }));
1563
+ }
1522
1564
 
1523
1565
  const useIsMobile = () => {
1524
1566
  const [isMobile, setIsMobile] = React.useState(false);