@dimaan/ui 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -344,7 +344,7 @@ function DashboardMain({ className, children, ...props }) {
344
344
  // stretching the whole layout past 100%.
345
345
  "flex min-h-screen min-w-0 flex-1 flex-col transition-[margin] duration-200 ease-out",
346
346
  // On desktop, push the main column past the fixed sidebar using logical margin.
347
- collapsed ? "lg:ms-[var(--sidebar-width-collapsed)]" : "lg:ms-[var(--sidebar-width)]",
347
+ collapsed ? "lg:ms-0" : "lg:ms-(--sidebar-width)",
348
348
  className
349
349
  ),
350
350
  ...props,
@@ -513,14 +513,16 @@ function Sidebar({ className, children, ...props }) {
513
513
  "fixed inset-y-0 start-0 z-40 flex flex-col",
514
514
  // Surface
515
515
  "bg-sidebar text-sidebar-foreground border-e border-sidebar-border",
516
- // Sizing — width animates between full and collapsed
517
- collapsed ? "w-[var(--sidebar-width-collapsed)]" : "w-[var(--sidebar-width)]",
516
+ // Sizing — always full width; collapse hides it off-canvas (below).
517
+ "w-[var(--sidebar-width)]",
518
518
  // Motion
519
- "transition-[transform,width] duration-200 ease-out",
520
- // Mobile slide: hidden by default, visible when mobileOpen.
521
- // Logical translate via rtl variant so it slides off the inline-start edge
522
- // in both LTR and RTL.
523
- mobileOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full lg:translate-x-0 lg:rtl:translate-x-0",
519
+ "transition-transform duration-200 ease-out",
520
+ // Mobile: slide in/out via mobileOpen. Logical translate (rtl variant)
521
+ // so it slides off the inline-start edge in both LTR and RTL.
522
+ mobileOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full",
523
+ // Desktop: collapse fully hides the sidebar off-canvas; the header
524
+ // trigger slides it back in and the main content reclaims the width.
525
+ collapsed ? "lg:-translate-x-full lg:rtl:translate-x-full" : "lg:translate-x-0 lg:rtl:translate-x-0",
524
526
  className
525
527
  ),
526
528
  ...props,
@@ -543,18 +545,8 @@ function SidebarFooter({ className, children, ...props }) {
543
545
  );
544
546
  }
545
547
  function SidebarGroup({ label, className, children, ...props }) {
546
- const { collapsed } = useDashboardLayout();
547
548
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1 py-2", className), ...props, children: [
548
- label ? /* @__PURE__ */ jsx(
549
- "div",
550
- {
551
- className: cn(
552
- "px-3 pb-1 text-xs font-medium uppercase tracking-wider text-muted-foreground transition-opacity",
553
- collapsed && "pointer-events-none h-0 overflow-hidden opacity-0"
554
- ),
555
- children: label
556
- }
557
- ) : null,
549
+ label ? /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: label }) : null,
558
550
  /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children })
559
551
  ] });
560
552
  }
@@ -588,7 +580,6 @@ function SidebarNavGroup({
588
580
  onClick,
589
581
  ...props
590
582
  }) {
591
- const { collapsed } = useDashboardLayout();
592
583
  const submenuId = useId();
593
584
  const [internalOpen, setInternalOpen] = useState(defaultOpen);
594
585
  const isControlled = openProp !== void 0;
@@ -615,27 +606,23 @@ function SidebarNavGroup({
615
606
  const isActive = active || hasActiveChild;
616
607
  const prevHasActiveChild = useRef(false);
617
608
  useEffect(() => {
618
- if (hasActiveChild && !prevHasActiveChild.current && !collapsed) {
609
+ if (hasActiveChild && !prevHasActiveChild.current) {
619
610
  setOpen(true);
620
611
  }
621
612
  prevHasActiveChild.current = hasActiveChild;
622
- }, [hasActiveChild, collapsed, setOpen]);
623
- useEffect(() => {
624
- if (collapsed && open) setOpen(false);
625
- }, [collapsed, open, setOpen]);
626
- const titleAttr = collapsed && typeof label === "string" ? label : props.title ?? void 0;
627
- const showChildren = !collapsed;
613
+ }, [hasActiveChild, setOpen]);
614
+ const titleAttr = props.title ?? void 0;
628
615
  return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col", children: [
629
616
  /* @__PURE__ */ jsxs(
630
617
  "button",
631
618
  {
632
619
  type: "button",
633
- "aria-expanded": showChildren ? open : void 0,
634
- "aria-controls": showChildren ? submenuId : void 0,
620
+ "aria-expanded": open,
621
+ "aria-controls": submenuId,
635
622
  "data-active": isActive ? "true" : void 0,
636
623
  title: titleAttr,
637
624
  onClick: (e) => {
638
- if (showChildren) setOpen(!open);
625
+ setOpen(!open);
639
626
  onClick?.(e);
640
627
  },
641
628
  className: cn(
@@ -643,7 +630,6 @@ function SidebarNavGroup({
643
630
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
644
631
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
645
632
  isActive && "bg-sidebar-accent text-sidebar-accent-foreground",
646
- collapsed && "justify-center px-0",
647
633
  className
648
634
  ),
649
635
  ...props,
@@ -656,18 +642,9 @@ function SidebarNavGroup({
656
642
  }
657
643
  ) : null,
658
644
  icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex size-5 shrink-0 items-center justify-center", children: icon }) : null,
659
- /* @__PURE__ */ jsx(
660
- "span",
661
- {
662
- className: cn(
663
- "flex-1 truncate text-start transition-[opacity,width]",
664
- collapsed && "pointer-events-none w-0 opacity-0"
665
- ),
666
- children: label
667
- }
668
- ),
669
- endSlot && !collapsed ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: endSlot }) : null,
670
- showChildren ? /* @__PURE__ */ jsx(ChevronCaret, { open }) : null
645
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-start", children: label }),
646
+ endSlot ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: endSlot }) : null,
647
+ /* @__PURE__ */ jsx(ChevronCaret, { open })
671
648
  ]
672
649
  }
673
650
  ),
@@ -675,10 +652,10 @@ function SidebarNavGroup({
675
652
  "div",
676
653
  {
677
654
  id: submenuId,
678
- hidden: !showChildren || !open,
655
+ hidden: !open,
679
656
  className: cn(
680
657
  "grid transition-[grid-template-rows] duration-200 ease-out",
681
- showChildren && open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
658
+ open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
682
659
  ),
683
660
  children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children: /* @__PURE__ */ jsx(SidebarNavGroupContext.Provider, { value: contextValue, children }) }) })
684
661
  }
@@ -709,7 +686,6 @@ function SidebarNavItem({
709
686
  end,
710
687
  ...props
711
688
  }) {
712
- const { collapsed } = useDashboardLayout();
713
689
  const resolved = useResolvedPath(to);
714
690
  const group = useContext(SidebarNavGroupContext);
715
691
  const itemId = useId();
@@ -721,13 +697,12 @@ function SidebarNavItem({
721
697
  return () => group.reportActive(itemId, false);
722
698
  }, [group, itemId, isActive]);
723
699
  const labelContent = label ?? children;
724
- const titleAttr = collapsed && typeof labelContent === "string" ? labelContent : props.title;
700
+ const titleAttr = props.title;
725
701
  const getClassName = (active) => cn(
726
702
  "group relative flex h-9 shrink-0 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
727
703
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
728
704
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
729
705
  active && "bg-sidebar-accent text-sidebar-accent-foreground",
730
- collapsed && "justify-center px-0",
731
706
  className
732
707
  );
733
708
  const innerContent = /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -739,17 +714,8 @@ function SidebarNavItem({
739
714
  }
740
715
  ) : null,
741
716
  icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex size-5 shrink-0 items-center justify-center", children: icon }) : null,
742
- /* @__PURE__ */ jsx(
743
- "span",
744
- {
745
- className: cn(
746
- "flex-1 truncate text-start transition-[opacity,width]",
747
- collapsed && "pointer-events-none w-0 opacity-0"
748
- ),
749
- children: labelContent
750
- }
751
- ),
752
- endSlot && !collapsed ? /* @__PURE__ */ jsx("span", { className: "ms-auto flex shrink-0 items-center", children: endSlot }) : null
717
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-start", children: labelContent }),
718
+ endSlot ? /* @__PURE__ */ jsx("span", { className: "ms-auto flex shrink-0 items-center", children: endSlot }) : null
753
719
  ] });
754
720
  if (render) {
755
721
  return render({
@@ -911,7 +877,7 @@ var badgeDotSizeClass = {
911
877
  md: "size-2"
912
878
  };
913
879
  var badgeBaseClass = "inline-flex shrink-0 items-center rounded-full border font-medium leading-none whitespace-nowrap select-none transition-colors";
914
- var Badge = forwardRef(function Badge2({ variant = "default", size = "md", tone = "solid", dot = false, className, children, ...props }, ref) {
880
+ var Badge = forwardRef(function Badge2({ variant = "default", size = "md", tone = "soft", dot = false, className, children, ...props }, ref) {
915
881
  const variantClass = tone === "soft" ? badgeSoftVariantClass[variant] : badgeVariantClass[variant];
916
882
  return /* @__PURE__ */ jsxs(
917
883
  "span",
@@ -2327,9 +2293,9 @@ function Pagination({
2327
2293
  children: isRtl ? /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true", className: "size-3.5" })
2328
2294
  }
2329
2295
  ),
2330
- /* @__PURE__ */ jsxs("span", { className: "px-1 text-foreground", children: [
2331
- pageIndex + 1,
2332
- " / ",
2296
+ /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-7 items-center justify-center rounded-lg bg-gradient-table px-2.5 py-0.5 text-xs font-semibold text-white", children: pageIndex + 1 }),
2297
+ /* @__PURE__ */ jsxs("span", { className: "px-0.5 text-muted-foreground", children: [
2298
+ "/ ",
2333
2299
  pageCount
2334
2300
  ] }),
2335
2301
  /* @__PURE__ */ jsx(
@@ -2354,7 +2320,7 @@ function Toolbar({ count, onClear, renderLabel, clearLabel, children }) {
2354
2320
  {
2355
2321
  role: "toolbar",
2356
2322
  "aria-label": "Bulk actions",
2357
- className: "flex flex-wrap items-center gap-3 rounded-md border border-border bg-muted/40 px-3 py-2 text-sm",
2323
+ className: "flex flex-wrap items-center gap-3 rounded-xl border border-border bg-muted/50 px-3.5 py-2.5 text-sm shadow-[var(--shadow-xs)]",
2358
2324
  children: [
2359
2325
  /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: renderLabel ? renderLabel(count) : `${count} selected` }),
2360
2326
  /* @__PURE__ */ jsxs("div", { className: "ms-auto flex flex-wrap items-center gap-2", children: [
@@ -2370,18 +2336,18 @@ function Toolbar({ count, onClear, renderLabel, clearLabel, children }) {
2370
2336
  var tableSizeClass = {
2371
2337
  sm: {
2372
2338
  row: "",
2373
- cell: "px-3 py-1.5 text-xs tabular-nums",
2374
- head: "whitespace-nowrap px-3 py-2 text-xs font-medium"
2339
+ cell: "px-3 py-2 text-xs tabular-nums",
2340
+ head: "whitespace-nowrap px-3 py-3 text-[11px] font-semibold uppercase tracking-wider"
2375
2341
  },
2376
2342
  md: {
2377
2343
  row: "",
2378
- cell: "px-4 py-2.5 text-sm tabular-nums",
2379
- head: "whitespace-nowrap px-4 py-2.5 text-xs font-medium uppercase tracking-wide"
2344
+ cell: "px-4 py-3.5 text-sm tabular-nums",
2345
+ head: "whitespace-nowrap px-4 py-4 text-xs font-semibold uppercase tracking-wider"
2380
2346
  },
2381
2347
  lg: {
2382
2348
  row: "",
2383
- cell: "px-5 py-3.5 text-sm tabular-nums",
2384
- head: "whitespace-nowrap px-5 py-3 text-sm font-medium"
2349
+ cell: "px-6 py-4 text-sm tabular-nums",
2350
+ head: "whitespace-nowrap px-6 py-5 text-[13px] font-semibold uppercase tracking-wider"
2385
2351
  }
2386
2352
  };
2387
2353
  var tableBaseClass = "w-full caption-bottom border-collapse";
@@ -2438,6 +2404,7 @@ function Table(props) {
2438
2404
  maxHeight,
2439
2405
  striped = false,
2440
2406
  onRowClick,
2407
+ getRowAccent,
2441
2408
  tableRef,
2442
2409
  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
2443
2410
  showPagination,
@@ -2509,166 +2476,198 @@ function Table(props) {
2509
2476
  children: bulkActions(selectedRowsInData)
2510
2477
  }
2511
2478
  ),
2512
- /* @__PURE__ */ jsx(
2513
- "div",
2514
- {
2515
- className: cn(
2516
- "overflow-x-auto rounded-xl border border-border bg-card shadow-[var(--shadow-card)]",
2517
- maxHeight !== void 0 && "overflow-y-auto"
2518
- ),
2519
- style: maxHeight !== void 0 ? { maxHeight } : void 0,
2520
- children: /* @__PURE__ */ jsxs(
2521
- "table",
2522
- {
2523
- ref: tableRef,
2524
- "aria-label": ariaLabel,
2525
- "aria-labelledby": ariaLabelledBy,
2526
- "aria-rowcount": totalRowCount,
2527
- className: cn(tableBaseClass, "text-sm text-foreground", tableClassName),
2528
- children: [
2529
- caption ? /* @__PURE__ */ jsx("caption", { className: "sr-only", children: caption }) : null,
2530
- /* @__PURE__ */ jsx(
2531
- "thead",
2532
- {
2533
- className: cn(
2534
- // Clean opaque header (so a sticky header fully hides the rows
2535
- // scrolling underneath it) with a hairline bottom rule drawn via an
2536
- // inset shadow — it stays attached to the sticky header instead of
2537
- // collapsing into the first row's border.
2538
- "bg-card text-muted-foreground shadow-[inset_0_-1px_0_var(--color-border)]",
2539
- maxHeight !== void 0 && "sticky top-0 z-10"
2540
- ),
2541
- children: /* @__PURE__ */ jsxs("tr", { children: [
2542
- enableRowSelection ? /* @__PURE__ */ jsx("th", { scope: "col", className: cn("w-10", sizeClasses.head), children: /* @__PURE__ */ jsx(
2543
- Checkbox,
2544
- {
2545
- "aria-label": "Select all rows on this page",
2546
- checked: allOnPageSelected,
2547
- indeterminate: someOnPageSelected,
2548
- disabled: selectableRowIds.length === 0,
2549
- onCheckedChange: toggleHeader
2550
- }
2551
- ) }) : null,
2552
- columns.map((column) => {
2553
- const isSorted = effectiveSort.columnId === column.id;
2554
- const ariaSort = isSorted ? effectiveSort.direction === "asc" ? "ascending" : "descending" : "none";
2555
- return /* @__PURE__ */ jsx(
2556
- "th",
2557
- {
2558
- scope: "col",
2559
- "aria-sort": column.sortable ? ariaSort : void 0,
2560
- className: cn(
2561
- sizeClasses.head,
2562
- alignClass[column.align ?? "start"],
2563
- column.className
2564
- ),
2565
- children: column.sortable ? /* @__PURE__ */ jsxs(
2566
- "button",
2567
- {
2568
- type: "button",
2569
- onClick: () => handleSortClick(column.id),
2570
- className: "inline-flex items-center gap-1.5 font-inherit uppercase tracking-inherit text-inherit hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
2571
- "aria-label": sortAriaLabel(column, effectiveSort),
2572
- children: [
2573
- /* @__PURE__ */ jsx("span", { children: renderHeader(column.header) }),
2574
- /* @__PURE__ */ jsx(
2575
- SortIndicator,
2576
- {
2577
- active: isSorted,
2578
- direction: isSorted ? effectiveSort.direction : null
2579
- }
2580
- )
2581
- ]
2582
- }
2583
- ) : renderHeader(column.header)
2584
- },
2585
- column.id
2586
- );
2587
- })
2588
- ] })
2589
- }
2590
- ),
2591
- /* @__PURE__ */ jsx("tbody", { children: loading ? /* @__PURE__ */ jsx(
2592
- SkeletonRows,
2593
- {
2594
- rowCount: skeletonCount,
2595
- columnCount: totalColumnCount,
2596
- cellClassName: sizeClasses.cell
2597
- }
2598
- ) : data.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
2599
- "td",
2600
- {
2601
- colSpan: totalColumnCount,
2602
- className: cn(sizeClasses.cell, "py-10 text-center text-muted-foreground"),
2603
- children: emptyState ?? "No data"
2604
- }
2605
- ) }) : data.map((row, rowIndex) => {
2606
- const id = getRowId(row, rowIndex);
2607
- const isSelected = selected.has(id);
2608
- const rowSelectable = isRowSelectable ? isRowSelectable(row) : true;
2609
- return /* @__PURE__ */ jsxs(
2610
- "tr",
2479
+ /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-xl border border-border bg-card shadow-[var(--shadow-card)]", children: [
2480
+ /* @__PURE__ */ jsx(
2481
+ "span",
2482
+ {
2483
+ "aria-hidden": "true",
2484
+ "data-testid": "table-accent",
2485
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-[3px] bg-gradient-table"
2486
+ }
2487
+ ),
2488
+ /* @__PURE__ */ jsx(
2489
+ "div",
2490
+ {
2491
+ className: cn("overflow-x-auto", maxHeight !== void 0 && "overflow-y-auto"),
2492
+ style: maxHeight !== void 0 ? { maxHeight } : void 0,
2493
+ children: /* @__PURE__ */ jsxs(
2494
+ "table",
2495
+ {
2496
+ ref: tableRef,
2497
+ "aria-label": ariaLabel,
2498
+ "aria-labelledby": ariaLabelledBy,
2499
+ "aria-rowcount": totalRowCount,
2500
+ className: cn(tableBaseClass, "text-sm text-foreground", tableClassName),
2501
+ children: [
2502
+ caption ? /* @__PURE__ */ jsx("caption", { className: "sr-only", children: caption }) : null,
2503
+ /* @__PURE__ */ jsx(
2504
+ "thead",
2611
2505
  {
2612
- "data-selected": isSelected ? "true" : void 0,
2613
- "aria-selected": enableRowSelection ? isSelected : void 0,
2614
2506
  className: cn(
2615
- "border-t border-border/60 first:border-t-0 transition-colors",
2616
- "hover:bg-primary/5",
2617
- striped && rowIndex % 2 === 1 && "bg-muted/20",
2618
- isSelected && selectedRowClass,
2619
- onRowClick && "cursor-pointer"
2507
+ // Clean opaque header (so a sticky header fully hides the rows
2508
+ // scrolling underneath it) with a hairline bottom rule drawn via an
2509
+ // inset shadow it stays attached to the sticky header instead of
2510
+ // collapsing into the first row's border.
2511
+ // Opaque header background so a sticky header fully hides the rows
2512
+ // scrolling underneath it (a translucent tint would let them bleed through).
2513
+ "bg-muted text-muted-foreground shadow-[inset_0_-1px_0_var(--color-border)]",
2514
+ maxHeight !== void 0 && "sticky top-0 z-10"
2620
2515
  ),
2621
- onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
2622
- children: [
2623
- enableRowSelection ? /* @__PURE__ */ jsx("td", { className: cn(sizeClasses.cell, "w-10"), children: /* @__PURE__ */ jsx(
2516
+ children: /* @__PURE__ */ jsxs("tr", { children: [
2517
+ enableRowSelection ? /* @__PURE__ */ jsx("th", { scope: "col", className: cn("w-10", sizeClasses.head), children: /* @__PURE__ */ jsx(
2624
2518
  Checkbox,
2625
2519
  {
2626
- "aria-label": `Select row ${rowIndex + 1}`,
2627
- checked: isSelected,
2628
- disabled: !rowSelectable,
2629
- onCheckedChange: (next) => toggleRow(id, next),
2630
- onClick: stopRowClickPropagation
2520
+ "aria-label": "Select all rows on this page",
2521
+ checked: allOnPageSelected,
2522
+ indeterminate: someOnPageSelected,
2523
+ disabled: selectableRowIds.length === 0,
2524
+ onCheckedChange: toggleHeader
2631
2525
  }
2632
2526
  ) }) : null,
2633
- columns.map((column) => /* @__PURE__ */ jsx(
2634
- "td",
2635
- {
2636
- className: cn(
2637
- sizeClasses.cell,
2638
- alignClass[column.align ?? "start"],
2639
- column.className
2640
- ),
2641
- children: renderCell(column, row, rowIndex)
2642
- },
2643
- column.id
2644
- ))
2645
- ]
2646
- },
2647
- id
2648
- );
2649
- }) })
2650
- ]
2651
- }
2652
- )
2653
- }
2654
- ),
2655
- paginationVisible ? /* @__PURE__ */ jsx(
2656
- Pagination,
2657
- {
2658
- pageIndex,
2659
- pageSize,
2660
- pageCount,
2661
- totalRowCount,
2662
- pageSizeOptions,
2663
- onChange: handlePaginationChange,
2664
- labels
2665
- }
2666
- ) : null
2527
+ columns.map((column) => {
2528
+ const isSorted = effectiveSort.columnId === column.id;
2529
+ const ariaSort = isSorted ? effectiveSort.direction === "asc" ? "ascending" : "descending" : "none";
2530
+ return /* @__PURE__ */ jsx(
2531
+ "th",
2532
+ {
2533
+ scope: "col",
2534
+ "aria-sort": column.sortable ? ariaSort : void 0,
2535
+ className: cn(
2536
+ sizeClasses.head,
2537
+ alignClass[column.align ?? "start"],
2538
+ column.className,
2539
+ isSorted && "text-primary"
2540
+ ),
2541
+ children: column.sortable ? /* @__PURE__ */ jsxs(
2542
+ "button",
2543
+ {
2544
+ type: "button",
2545
+ onClick: () => handleSortClick(column.id),
2546
+ className: "inline-flex items-center gap-1.5 font-inherit uppercase tracking-inherit text-inherit hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
2547
+ "aria-label": sortAriaLabel(column, effectiveSort),
2548
+ children: [
2549
+ /* @__PURE__ */ jsx("span", { children: renderHeader(column.header) }),
2550
+ /* @__PURE__ */ jsx(
2551
+ SortIndicator,
2552
+ {
2553
+ active: isSorted,
2554
+ direction: isSorted ? effectiveSort.direction : null
2555
+ }
2556
+ )
2557
+ ]
2558
+ }
2559
+ ) : renderHeader(column.header)
2560
+ },
2561
+ column.id
2562
+ );
2563
+ })
2564
+ ] })
2565
+ }
2566
+ ),
2567
+ /* @__PURE__ */ jsx("tbody", { children: loading ? /* @__PURE__ */ jsx(
2568
+ SkeletonRows,
2569
+ {
2570
+ rowCount: skeletonCount,
2571
+ columnCount: totalColumnCount,
2572
+ cellClassName: sizeClasses.cell
2573
+ }
2574
+ ) : data.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
2575
+ "td",
2576
+ {
2577
+ colSpan: totalColumnCount,
2578
+ className: cn(sizeClasses.cell, "py-10 text-center text-muted-foreground"),
2579
+ children: emptyState ?? "No data"
2580
+ }
2581
+ ) }) : data.map((row, rowIndex) => {
2582
+ const id = getRowId(row, rowIndex);
2583
+ const isSelected = selected.has(id);
2584
+ const rowSelectable = isRowSelectable ? isRowSelectable(row) : true;
2585
+ const accent = getRowAccent?.(row, rowIndex);
2586
+ return /* @__PURE__ */ jsxs(
2587
+ "tr",
2588
+ {
2589
+ "data-selected": isSelected ? "true" : void 0,
2590
+ "aria-selected": enableRowSelection ? isSelected : void 0,
2591
+ className: cn(
2592
+ "border-b border-border/60 last:border-b-0 transition-colors",
2593
+ "hover:bg-muted/60",
2594
+ striped && rowIndex % 2 === 1 && "bg-muted/20",
2595
+ isSelected && selectedRowClass,
2596
+ onRowClick && "cursor-pointer"
2597
+ ),
2598
+ onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
2599
+ children: [
2600
+ enableRowSelection ? /* @__PURE__ */ jsxs("td", { className: cn(sizeClasses.cell, "relative w-10"), children: [
2601
+ accent ? /* @__PURE__ */ jsx(RowAccent, { color: accent }) : null,
2602
+ /* @__PURE__ */ jsx(
2603
+ Checkbox,
2604
+ {
2605
+ "aria-label": `Select row ${rowIndex + 1}`,
2606
+ checked: isSelected,
2607
+ disabled: !rowSelectable,
2608
+ onCheckedChange: (next) => toggleRow(id, next),
2609
+ onClick: stopRowClickPropagation
2610
+ }
2611
+ )
2612
+ ] }) : null,
2613
+ columns.map((column, colIndex) => {
2614
+ const isFirst = colIndex === 0 && !enableRowSelection;
2615
+ return /* @__PURE__ */ jsxs(
2616
+ "td",
2617
+ {
2618
+ className: cn(
2619
+ sizeClasses.cell,
2620
+ alignClass[column.align ?? "start"],
2621
+ column.className,
2622
+ isFirst && "relative"
2623
+ ),
2624
+ children: [
2625
+ isFirst && accent ? /* @__PURE__ */ jsx(RowAccent, { color: accent }) : null,
2626
+ renderCell(column, row, rowIndex)
2627
+ ]
2628
+ },
2629
+ column.id
2630
+ );
2631
+ })
2632
+ ]
2633
+ },
2634
+ id
2635
+ );
2636
+ }) })
2637
+ ]
2638
+ }
2639
+ )
2640
+ }
2641
+ ),
2642
+ paginationVisible ? /* @__PURE__ */ jsx("div", { className: "border-t border-border px-4 py-3", children: /* @__PURE__ */ jsx(
2643
+ Pagination,
2644
+ {
2645
+ pageIndex,
2646
+ pageSize,
2647
+ pageCount,
2648
+ totalRowCount,
2649
+ pageSizeOptions,
2650
+ onChange: handlePaginationChange,
2651
+ labels
2652
+ }
2653
+ ) }) : null
2654
+ ] })
2667
2655
  ] });
2668
2656
  }
2669
2657
  function renderHeader(header) {
2670
2658
  return typeof header === "function" ? header() : header;
2671
2659
  }
2660
+ function RowAccent({ color }) {
2661
+ return /* @__PURE__ */ jsx(
2662
+ "span",
2663
+ {
2664
+ "aria-hidden": "true",
2665
+ "data-testid": "row-accent",
2666
+ className: "pointer-events-none absolute inset-y-1 start-0 w-[3px] rounded-full",
2667
+ style: { background: color }
2668
+ }
2669
+ );
2670
+ }
2672
2671
  function renderCell(column, row, rowIndex) {
2673
2672
  if (column.render) return column.render(row, rowIndex);
2674
2673
  if (column.accessor !== void 0) {
@@ -2693,10 +2692,18 @@ function stopRowClickPropagation(event) {
2693
2692
  function SkeletonRows({ rowCount, columnCount, cellClassName }) {
2694
2693
  const rowKeys = Array.from({ length: Math.max(0, rowCount) }, (_, i) => `skeleton-row-${i}`);
2695
2694
  const colKeys = Array.from({ length: Math.max(1, columnCount) }, (_, i) => `skeleton-col-${i}`);
2696
- return /* @__PURE__ */ jsx(Fragment, { children: rowKeys.map((rowKey) => /* @__PURE__ */ jsx("tr", { className: "border-t border-border", "data-testid": "table-skeleton-row", children: colKeys.map((colKey) => /* @__PURE__ */ jsx("td", { className: cellClassName, children: /* @__PURE__ */ jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, `${rowKey}-${colKey}`)) }, rowKey)) });
2695
+ return /* @__PURE__ */ jsx(Fragment, { children: rowKeys.map((rowKey) => /* @__PURE__ */ jsx(
2696
+ "tr",
2697
+ {
2698
+ className: "border-b border-border/60 last:border-b-0",
2699
+ "data-testid": "table-skeleton-row",
2700
+ children: colKeys.map((colKey) => /* @__PURE__ */ jsx("td", { className: cellClassName, children: /* @__PURE__ */ jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, `${rowKey}-${colKey}`))
2701
+ },
2702
+ rowKey
2703
+ )) });
2697
2704
  }
2698
2705
  function SortIndicator({ active, direction }) {
2699
- const className = cn("size-3.5 shrink-0", active ? "text-foreground" : "text-muted-foreground");
2706
+ const className = cn("size-3.5 shrink-0", active ? "text-primary" : "text-muted-foreground");
2700
2707
  if (!active) return /* @__PURE__ */ jsx(ChevronsUpDown, { "aria-hidden": "true", className });
2701
2708
  return direction === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true", className }) : /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true", className });
2702
2709
  }