@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.cjs CHANGED
@@ -372,7 +372,7 @@ function DashboardMain({ className, children, ...props }) {
372
372
  // stretching the whole layout past 100%.
373
373
  "flex min-h-screen min-w-0 flex-1 flex-col transition-[margin] duration-200 ease-out",
374
374
  // On desktop, push the main column past the fixed sidebar using logical margin.
375
- collapsed ? "lg:ms-[var(--sidebar-width-collapsed)]" : "lg:ms-[var(--sidebar-width)]",
375
+ collapsed ? "lg:ms-0" : "lg:ms-(--sidebar-width)",
376
376
  className
377
377
  ),
378
378
  ...props,
@@ -541,14 +541,16 @@ function Sidebar({ className, children, ...props }) {
541
541
  "fixed inset-y-0 start-0 z-40 flex flex-col",
542
542
  // Surface
543
543
  "bg-sidebar text-sidebar-foreground border-e border-sidebar-border",
544
- // Sizing — width animates between full and collapsed
545
- collapsed ? "w-[var(--sidebar-width-collapsed)]" : "w-[var(--sidebar-width)]",
544
+ // Sizing — always full width; collapse hides it off-canvas (below).
545
+ "w-[var(--sidebar-width)]",
546
546
  // Motion
547
- "transition-[transform,width] duration-200 ease-out",
548
- // Mobile slide: hidden by default, visible when mobileOpen.
549
- // Logical translate via rtl variant so it slides off the inline-start edge
550
- // in both LTR and RTL.
551
- mobileOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full lg:translate-x-0 lg:rtl:translate-x-0",
547
+ "transition-transform duration-200 ease-out",
548
+ // Mobile: slide in/out via mobileOpen. Logical translate (rtl variant)
549
+ // so it slides off the inline-start edge in both LTR and RTL.
550
+ mobileOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full",
551
+ // Desktop: collapse fully hides the sidebar off-canvas; the header
552
+ // trigger slides it back in and the main content reclaims the width.
553
+ collapsed ? "lg:-translate-x-full lg:rtl:translate-x-full" : "lg:translate-x-0 lg:rtl:translate-x-0",
552
554
  className
553
555
  ),
554
556
  ...props,
@@ -571,18 +573,8 @@ function SidebarFooter({ className, children, ...props }) {
571
573
  );
572
574
  }
573
575
  function SidebarGroup({ label, className, children, ...props }) {
574
- const { collapsed } = useDashboardLayout();
575
576
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1 py-2", className), ...props, children: [
576
- label ? /* @__PURE__ */ jsxRuntime.jsx(
577
- "div",
578
- {
579
- className: cn(
580
- "px-3 pb-1 text-xs font-medium uppercase tracking-wider text-muted-foreground transition-opacity",
581
- collapsed && "pointer-events-none h-0 overflow-hidden opacity-0"
582
- ),
583
- children: label
584
- }
585
- ) : null,
577
+ label ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-1 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: label }) : null,
586
578
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children })
587
579
  ] });
588
580
  }
@@ -616,7 +608,6 @@ function SidebarNavGroup({
616
608
  onClick,
617
609
  ...props
618
610
  }) {
619
- const { collapsed } = useDashboardLayout();
620
611
  const submenuId = react.useId();
621
612
  const [internalOpen, setInternalOpen] = react.useState(defaultOpen);
622
613
  const isControlled = openProp !== void 0;
@@ -643,27 +634,23 @@ function SidebarNavGroup({
643
634
  const isActive = active || hasActiveChild;
644
635
  const prevHasActiveChild = react.useRef(false);
645
636
  react.useEffect(() => {
646
- if (hasActiveChild && !prevHasActiveChild.current && !collapsed) {
637
+ if (hasActiveChild && !prevHasActiveChild.current) {
647
638
  setOpen(true);
648
639
  }
649
640
  prevHasActiveChild.current = hasActiveChild;
650
- }, [hasActiveChild, collapsed, setOpen]);
651
- react.useEffect(() => {
652
- if (collapsed && open) setOpen(false);
653
- }, [collapsed, open, setOpen]);
654
- const titleAttr = collapsed && typeof label === "string" ? label : props.title ?? void 0;
655
- const showChildren = !collapsed;
641
+ }, [hasActiveChild, setOpen]);
642
+ const titleAttr = props.title ?? void 0;
656
643
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col", children: [
657
644
  /* @__PURE__ */ jsxRuntime.jsxs(
658
645
  "button",
659
646
  {
660
647
  type: "button",
661
- "aria-expanded": showChildren ? open : void 0,
662
- "aria-controls": showChildren ? submenuId : void 0,
648
+ "aria-expanded": open,
649
+ "aria-controls": submenuId,
663
650
  "data-active": isActive ? "true" : void 0,
664
651
  title: titleAttr,
665
652
  onClick: (e) => {
666
- if (showChildren) setOpen(!open);
653
+ setOpen(!open);
667
654
  onClick?.(e);
668
655
  },
669
656
  className: cn(
@@ -671,7 +658,6 @@ function SidebarNavGroup({
671
658
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
672
659
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
673
660
  isActive && "bg-sidebar-accent text-sidebar-accent-foreground",
674
- collapsed && "justify-center px-0",
675
661
  className
676
662
  ),
677
663
  ...props,
@@ -684,18 +670,9 @@ function SidebarNavGroup({
684
670
  }
685
671
  ) : null,
686
672
  icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex size-5 shrink-0 items-center justify-center", children: icon }) : null,
687
- /* @__PURE__ */ jsxRuntime.jsx(
688
- "span",
689
- {
690
- className: cn(
691
- "flex-1 truncate text-start transition-[opacity,width]",
692
- collapsed && "pointer-events-none w-0 opacity-0"
693
- ),
694
- children: label
695
- }
696
- ),
697
- endSlot && !collapsed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center", children: endSlot }) : null,
698
- showChildren ? /* @__PURE__ */ jsxRuntime.jsx(ChevronCaret, { open }) : null
673
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate text-start", children: label }),
674
+ endSlot ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center", children: endSlot }) : null,
675
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronCaret, { open })
699
676
  ]
700
677
  }
701
678
  ),
@@ -703,10 +680,10 @@ function SidebarNavGroup({
703
680
  "div",
704
681
  {
705
682
  id: submenuId,
706
- hidden: !showChildren || !open,
683
+ hidden: !open,
707
684
  className: cn(
708
685
  "grid transition-[grid-template-rows] duration-200 ease-out",
709
- showChildren && open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
686
+ open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
710
687
  ),
711
688
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNavGroupContext.Provider, { value: contextValue, children }) }) })
712
689
  }
@@ -737,7 +714,6 @@ function SidebarNavItem({
737
714
  end,
738
715
  ...props
739
716
  }) {
740
- const { collapsed } = useDashboardLayout();
741
717
  const resolved = reactRouterDom.useResolvedPath(to);
742
718
  const group = react.useContext(SidebarNavGroupContext);
743
719
  const itemId = react.useId();
@@ -749,13 +725,12 @@ function SidebarNavItem({
749
725
  return () => group.reportActive(itemId, false);
750
726
  }, [group, itemId, isActive]);
751
727
  const labelContent = label ?? children;
752
- const titleAttr = collapsed && typeof labelContent === "string" ? labelContent : props.title;
728
+ const titleAttr = props.title;
753
729
  const getClassName = (active) => cn(
754
730
  "group relative flex h-9 shrink-0 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
755
731
  "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
756
732
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
757
733
  active && "bg-sidebar-accent text-sidebar-accent-foreground",
758
- collapsed && "justify-center px-0",
759
734
  className
760
735
  );
761
736
  const innerContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -767,17 +742,8 @@ function SidebarNavItem({
767
742
  }
768
743
  ) : null,
769
744
  icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex size-5 shrink-0 items-center justify-center", children: icon }) : null,
770
- /* @__PURE__ */ jsxRuntime.jsx(
771
- "span",
772
- {
773
- className: cn(
774
- "flex-1 truncate text-start transition-[opacity,width]",
775
- collapsed && "pointer-events-none w-0 opacity-0"
776
- ),
777
- children: labelContent
778
- }
779
- ),
780
- endSlot && !collapsed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-auto flex shrink-0 items-center", children: endSlot }) : null
745
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate text-start", children: labelContent }),
746
+ endSlot ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-auto flex shrink-0 items-center", children: endSlot }) : null
781
747
  ] });
782
748
  if (render) {
783
749
  return render({
@@ -939,7 +905,7 @@ var badgeDotSizeClass = {
939
905
  md: "size-2"
940
906
  };
941
907
  var badgeBaseClass = "inline-flex shrink-0 items-center rounded-full border font-medium leading-none whitespace-nowrap select-none transition-colors";
942
- var Badge = react.forwardRef(function Badge2({ variant = "default", size = "md", tone = "solid", dot = false, className, children, ...props }, ref) {
908
+ var Badge = react.forwardRef(function Badge2({ variant = "default", size = "md", tone = "soft", dot = false, className, children, ...props }, ref) {
943
909
  const variantClass = tone === "soft" ? badgeSoftVariantClass[variant] : badgeVariantClass[variant];
944
910
  return /* @__PURE__ */ jsxRuntime.jsxs(
945
911
  "span",
@@ -2355,9 +2321,9 @@ function Pagination({
2355
2321
  children: isRtl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { "aria-hidden": "true", className: "size-3.5" })
2356
2322
  }
2357
2323
  ),
2358
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-1 text-foreground", children: [
2359
- pageIndex + 1,
2360
- " / ",
2324
+ /* @__PURE__ */ jsxRuntime.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 }),
2325
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-0.5 text-muted-foreground", children: [
2326
+ "/ ",
2361
2327
  pageCount
2362
2328
  ] }),
2363
2329
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2382,7 +2348,7 @@ function Toolbar({ count, onClear, renderLabel, clearLabel, children }) {
2382
2348
  {
2383
2349
  role: "toolbar",
2384
2350
  "aria-label": "Bulk actions",
2385
- className: "flex flex-wrap items-center gap-3 rounded-md border border-border bg-muted/40 px-3 py-2 text-sm",
2351
+ 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)]",
2386
2352
  children: [
2387
2353
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: renderLabel ? renderLabel(count) : `${count} selected` }),
2388
2354
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ms-auto flex flex-wrap items-center gap-2", children: [
@@ -2398,18 +2364,18 @@ function Toolbar({ count, onClear, renderLabel, clearLabel, children }) {
2398
2364
  var tableSizeClass = {
2399
2365
  sm: {
2400
2366
  row: "",
2401
- cell: "px-3 py-1.5 text-xs tabular-nums",
2402
- head: "whitespace-nowrap px-3 py-2 text-xs font-medium"
2367
+ cell: "px-3 py-2 text-xs tabular-nums",
2368
+ head: "whitespace-nowrap px-3 py-3 text-[11px] font-semibold uppercase tracking-wider"
2403
2369
  },
2404
2370
  md: {
2405
2371
  row: "",
2406
- cell: "px-4 py-2.5 text-sm tabular-nums",
2407
- head: "whitespace-nowrap px-4 py-2.5 text-xs font-medium uppercase tracking-wide"
2372
+ cell: "px-4 py-3.5 text-sm tabular-nums",
2373
+ head: "whitespace-nowrap px-4 py-4 text-xs font-semibold uppercase tracking-wider"
2408
2374
  },
2409
2375
  lg: {
2410
2376
  row: "",
2411
- cell: "px-5 py-3.5 text-sm tabular-nums",
2412
- head: "whitespace-nowrap px-5 py-3 text-sm font-medium"
2377
+ cell: "px-6 py-4 text-sm tabular-nums",
2378
+ head: "whitespace-nowrap px-6 py-5 text-[13px] font-semibold uppercase tracking-wider"
2413
2379
  }
2414
2380
  };
2415
2381
  var tableBaseClass = "w-full caption-bottom border-collapse";
@@ -2466,6 +2432,7 @@ function Table(props) {
2466
2432
  maxHeight,
2467
2433
  striped = false,
2468
2434
  onRowClick,
2435
+ getRowAccent,
2469
2436
  tableRef,
2470
2437
  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
2471
2438
  showPagination,
@@ -2537,166 +2504,198 @@ function Table(props) {
2537
2504
  children: bulkActions(selectedRowsInData)
2538
2505
  }
2539
2506
  ),
2540
- /* @__PURE__ */ jsxRuntime.jsx(
2541
- "div",
2542
- {
2543
- className: cn(
2544
- "overflow-x-auto rounded-xl border border-border bg-card shadow-[var(--shadow-card)]",
2545
- maxHeight !== void 0 && "overflow-y-auto"
2546
- ),
2547
- style: maxHeight !== void 0 ? { maxHeight } : void 0,
2548
- children: /* @__PURE__ */ jsxRuntime.jsxs(
2549
- "table",
2550
- {
2551
- ref: tableRef,
2552
- "aria-label": ariaLabel,
2553
- "aria-labelledby": ariaLabelledBy,
2554
- "aria-rowcount": totalRowCount,
2555
- className: cn(tableBaseClass, "text-sm text-foreground", tableClassName),
2556
- children: [
2557
- caption ? /* @__PURE__ */ jsxRuntime.jsx("caption", { className: "sr-only", children: caption }) : null,
2558
- /* @__PURE__ */ jsxRuntime.jsx(
2559
- "thead",
2560
- {
2561
- className: cn(
2562
- // Clean opaque header (so a sticky header fully hides the rows
2563
- // scrolling underneath it) with a hairline bottom rule drawn via an
2564
- // inset shadow — it stays attached to the sticky header instead of
2565
- // collapsing into the first row's border.
2566
- "bg-card text-muted-foreground shadow-[inset_0_-1px_0_var(--color-border)]",
2567
- maxHeight !== void 0 && "sticky top-0 z-10"
2568
- ),
2569
- children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2570
- enableRowSelection ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: cn("w-10", sizeClasses.head), children: /* @__PURE__ */ jsxRuntime.jsx(
2571
- Checkbox,
2572
- {
2573
- "aria-label": "Select all rows on this page",
2574
- checked: allOnPageSelected,
2575
- indeterminate: someOnPageSelected,
2576
- disabled: selectableRowIds.length === 0,
2577
- onCheckedChange: toggleHeader
2578
- }
2579
- ) }) : null,
2580
- columns.map((column) => {
2581
- const isSorted = effectiveSort.columnId === column.id;
2582
- const ariaSort = isSorted ? effectiveSort.direction === "asc" ? "ascending" : "descending" : "none";
2583
- return /* @__PURE__ */ jsxRuntime.jsx(
2584
- "th",
2585
- {
2586
- scope: "col",
2587
- "aria-sort": column.sortable ? ariaSort : void 0,
2588
- className: cn(
2589
- sizeClasses.head,
2590
- alignClass[column.align ?? "start"],
2591
- column.className
2592
- ),
2593
- children: column.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
2594
- "button",
2595
- {
2596
- type: "button",
2597
- onClick: () => handleSortClick(column.id),
2598
- 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",
2599
- "aria-label": sortAriaLabel(column, effectiveSort),
2600
- children: [
2601
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: renderHeader(column.header) }),
2602
- /* @__PURE__ */ jsxRuntime.jsx(
2603
- SortIndicator,
2604
- {
2605
- active: isSorted,
2606
- direction: isSorted ? effectiveSort.direction : null
2607
- }
2608
- )
2609
- ]
2610
- }
2611
- ) : renderHeader(column.header)
2612
- },
2613
- column.id
2614
- );
2615
- })
2616
- ] })
2617
- }
2618
- ),
2619
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(
2620
- SkeletonRows,
2621
- {
2622
- rowCount: skeletonCount,
2623
- columnCount: totalColumnCount,
2624
- cellClassName: sizeClasses.cell
2625
- }
2626
- ) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
2627
- "td",
2628
- {
2629
- colSpan: totalColumnCount,
2630
- className: cn(sizeClasses.cell, "py-10 text-center text-muted-foreground"),
2631
- children: emptyState ?? "No data"
2632
- }
2633
- ) }) : data.map((row, rowIndex) => {
2634
- const id = getRowId(row, rowIndex);
2635
- const isSelected = selected.has(id);
2636
- const rowSelectable = isRowSelectable ? isRowSelectable(row) : true;
2637
- return /* @__PURE__ */ jsxRuntime.jsxs(
2638
- "tr",
2507
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative overflow-hidden rounded-xl border border-border bg-card shadow-[var(--shadow-card)]", children: [
2508
+ /* @__PURE__ */ jsxRuntime.jsx(
2509
+ "span",
2510
+ {
2511
+ "aria-hidden": "true",
2512
+ "data-testid": "table-accent",
2513
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 h-[3px] bg-gradient-table"
2514
+ }
2515
+ ),
2516
+ /* @__PURE__ */ jsxRuntime.jsx(
2517
+ "div",
2518
+ {
2519
+ className: cn("overflow-x-auto", maxHeight !== void 0 && "overflow-y-auto"),
2520
+ style: maxHeight !== void 0 ? { maxHeight } : void 0,
2521
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2522
+ "table",
2523
+ {
2524
+ ref: tableRef,
2525
+ "aria-label": ariaLabel,
2526
+ "aria-labelledby": ariaLabelledBy,
2527
+ "aria-rowcount": totalRowCount,
2528
+ className: cn(tableBaseClass, "text-sm text-foreground", tableClassName),
2529
+ children: [
2530
+ caption ? /* @__PURE__ */ jsxRuntime.jsx("caption", { className: "sr-only", children: caption }) : null,
2531
+ /* @__PURE__ */ jsxRuntime.jsx(
2532
+ "thead",
2639
2533
  {
2640
- "data-selected": isSelected ? "true" : void 0,
2641
- "aria-selected": enableRowSelection ? isSelected : void 0,
2642
2534
  className: cn(
2643
- "border-t border-border/60 first:border-t-0 transition-colors",
2644
- "hover:bg-primary/5",
2645
- striped && rowIndex % 2 === 1 && "bg-muted/20",
2646
- isSelected && selectedRowClass,
2647
- onRowClick && "cursor-pointer"
2535
+ // Clean opaque header (so a sticky header fully hides the rows
2536
+ // scrolling underneath it) with a hairline bottom rule drawn via an
2537
+ // inset shadow it stays attached to the sticky header instead of
2538
+ // collapsing into the first row's border.
2539
+ // Opaque header background so a sticky header fully hides the rows
2540
+ // scrolling underneath it (a translucent tint would let them bleed through).
2541
+ "bg-muted text-muted-foreground shadow-[inset_0_-1px_0_var(--color-border)]",
2542
+ maxHeight !== void 0 && "sticky top-0 z-10"
2648
2543
  ),
2649
- onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
2650
- children: [
2651
- enableRowSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: cn(sizeClasses.cell, "w-10"), children: /* @__PURE__ */ jsxRuntime.jsx(
2544
+ children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2545
+ enableRowSelection ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: cn("w-10", sizeClasses.head), children: /* @__PURE__ */ jsxRuntime.jsx(
2652
2546
  Checkbox,
2653
2547
  {
2654
- "aria-label": `Select row ${rowIndex + 1}`,
2655
- checked: isSelected,
2656
- disabled: !rowSelectable,
2657
- onCheckedChange: (next) => toggleRow(id, next),
2658
- onClick: stopRowClickPropagation
2548
+ "aria-label": "Select all rows on this page",
2549
+ checked: allOnPageSelected,
2550
+ indeterminate: someOnPageSelected,
2551
+ disabled: selectableRowIds.length === 0,
2552
+ onCheckedChange: toggleHeader
2659
2553
  }
2660
2554
  ) }) : null,
2661
- columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(
2662
- "td",
2663
- {
2664
- className: cn(
2665
- sizeClasses.cell,
2666
- alignClass[column.align ?? "start"],
2667
- column.className
2668
- ),
2669
- children: renderCell(column, row, rowIndex)
2670
- },
2671
- column.id
2672
- ))
2673
- ]
2674
- },
2675
- id
2676
- );
2677
- }) })
2678
- ]
2679
- }
2680
- )
2681
- }
2682
- ),
2683
- paginationVisible ? /* @__PURE__ */ jsxRuntime.jsx(
2684
- Pagination,
2685
- {
2686
- pageIndex,
2687
- pageSize,
2688
- pageCount,
2689
- totalRowCount,
2690
- pageSizeOptions,
2691
- onChange: handlePaginationChange,
2692
- labels
2693
- }
2694
- ) : null
2555
+ columns.map((column) => {
2556
+ const isSorted = effectiveSort.columnId === column.id;
2557
+ const ariaSort = isSorted ? effectiveSort.direction === "asc" ? "ascending" : "descending" : "none";
2558
+ return /* @__PURE__ */ jsxRuntime.jsx(
2559
+ "th",
2560
+ {
2561
+ scope: "col",
2562
+ "aria-sort": column.sortable ? ariaSort : void 0,
2563
+ className: cn(
2564
+ sizeClasses.head,
2565
+ alignClass[column.align ?? "start"],
2566
+ column.className,
2567
+ isSorted && "text-primary"
2568
+ ),
2569
+ children: column.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
2570
+ "button",
2571
+ {
2572
+ type: "button",
2573
+ onClick: () => handleSortClick(column.id),
2574
+ 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",
2575
+ "aria-label": sortAriaLabel(column, effectiveSort),
2576
+ children: [
2577
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: renderHeader(column.header) }),
2578
+ /* @__PURE__ */ jsxRuntime.jsx(
2579
+ SortIndicator,
2580
+ {
2581
+ active: isSorted,
2582
+ direction: isSorted ? effectiveSort.direction : null
2583
+ }
2584
+ )
2585
+ ]
2586
+ }
2587
+ ) : renderHeader(column.header)
2588
+ },
2589
+ column.id
2590
+ );
2591
+ })
2592
+ ] })
2593
+ }
2594
+ ),
2595
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(
2596
+ SkeletonRows,
2597
+ {
2598
+ rowCount: skeletonCount,
2599
+ columnCount: totalColumnCount,
2600
+ cellClassName: sizeClasses.cell
2601
+ }
2602
+ ) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
2603
+ "td",
2604
+ {
2605
+ colSpan: totalColumnCount,
2606
+ className: cn(sizeClasses.cell, "py-10 text-center text-muted-foreground"),
2607
+ children: emptyState ?? "No data"
2608
+ }
2609
+ ) }) : data.map((row, rowIndex) => {
2610
+ const id = getRowId(row, rowIndex);
2611
+ const isSelected = selected.has(id);
2612
+ const rowSelectable = isRowSelectable ? isRowSelectable(row) : true;
2613
+ const accent = getRowAccent?.(row, rowIndex);
2614
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2615
+ "tr",
2616
+ {
2617
+ "data-selected": isSelected ? "true" : void 0,
2618
+ "aria-selected": enableRowSelection ? isSelected : void 0,
2619
+ className: cn(
2620
+ "border-b border-border/60 last:border-b-0 transition-colors",
2621
+ "hover:bg-muted/60",
2622
+ striped && rowIndex % 2 === 1 && "bg-muted/20",
2623
+ isSelected && selectedRowClass,
2624
+ onRowClick && "cursor-pointer"
2625
+ ),
2626
+ onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
2627
+ children: [
2628
+ enableRowSelection ? /* @__PURE__ */ jsxRuntime.jsxs("td", { className: cn(sizeClasses.cell, "relative w-10"), children: [
2629
+ accent ? /* @__PURE__ */ jsxRuntime.jsx(RowAccent, { color: accent }) : null,
2630
+ /* @__PURE__ */ jsxRuntime.jsx(
2631
+ Checkbox,
2632
+ {
2633
+ "aria-label": `Select row ${rowIndex + 1}`,
2634
+ checked: isSelected,
2635
+ disabled: !rowSelectable,
2636
+ onCheckedChange: (next) => toggleRow(id, next),
2637
+ onClick: stopRowClickPropagation
2638
+ }
2639
+ )
2640
+ ] }) : null,
2641
+ columns.map((column, colIndex) => {
2642
+ const isFirst = colIndex === 0 && !enableRowSelection;
2643
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2644
+ "td",
2645
+ {
2646
+ className: cn(
2647
+ sizeClasses.cell,
2648
+ alignClass[column.align ?? "start"],
2649
+ column.className,
2650
+ isFirst && "relative"
2651
+ ),
2652
+ children: [
2653
+ isFirst && accent ? /* @__PURE__ */ jsxRuntime.jsx(RowAccent, { color: accent }) : null,
2654
+ renderCell(column, row, rowIndex)
2655
+ ]
2656
+ },
2657
+ column.id
2658
+ );
2659
+ })
2660
+ ]
2661
+ },
2662
+ id
2663
+ );
2664
+ }) })
2665
+ ]
2666
+ }
2667
+ )
2668
+ }
2669
+ ),
2670
+ paginationVisible ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(
2671
+ Pagination,
2672
+ {
2673
+ pageIndex,
2674
+ pageSize,
2675
+ pageCount,
2676
+ totalRowCount,
2677
+ pageSizeOptions,
2678
+ onChange: handlePaginationChange,
2679
+ labels
2680
+ }
2681
+ ) }) : null
2682
+ ] })
2695
2683
  ] });
2696
2684
  }
2697
2685
  function renderHeader(header) {
2698
2686
  return typeof header === "function" ? header() : header;
2699
2687
  }
2688
+ function RowAccent({ color }) {
2689
+ return /* @__PURE__ */ jsxRuntime.jsx(
2690
+ "span",
2691
+ {
2692
+ "aria-hidden": "true",
2693
+ "data-testid": "row-accent",
2694
+ className: "pointer-events-none absolute inset-y-1 start-0 w-[3px] rounded-full",
2695
+ style: { background: color }
2696
+ }
2697
+ );
2698
+ }
2700
2699
  function renderCell(column, row, rowIndex) {
2701
2700
  if (column.render) return column.render(row, rowIndex);
2702
2701
  if (column.accessor !== void 0) {
@@ -2721,10 +2720,18 @@ function stopRowClickPropagation(event) {
2721
2720
  function SkeletonRows({ rowCount, columnCount, cellClassName }) {
2722
2721
  const rowKeys = Array.from({ length: Math.max(0, rowCount) }, (_, i) => `skeleton-row-${i}`);
2723
2722
  const colKeys = Array.from({ length: Math.max(1, columnCount) }, (_, i) => `skeleton-col-${i}`);
2724
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: rowKeys.map((rowKey) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-t border-border", "data-testid": "table-skeleton-row", children: colKeys.map((colKey) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: cellClassName, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, `${rowKey}-${colKey}`)) }, rowKey)) });
2723
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: rowKeys.map((rowKey) => /* @__PURE__ */ jsxRuntime.jsx(
2724
+ "tr",
2725
+ {
2726
+ className: "border-b border-border/60 last:border-b-0",
2727
+ "data-testid": "table-skeleton-row",
2728
+ children: colKeys.map((colKey) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: cellClassName, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, `${rowKey}-${colKey}`))
2729
+ },
2730
+ rowKey
2731
+ )) });
2725
2732
  }
2726
2733
  function SortIndicator({ active, direction }) {
2727
- const className = cn("size-3.5 shrink-0", active ? "text-foreground" : "text-muted-foreground");
2734
+ const className = cn("size-3.5 shrink-0", active ? "text-primary" : "text-muted-foreground");
2728
2735
  if (!active) return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { "aria-hidden": "true", className });
2729
2736
  return direction === "asc" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { "aria-hidden": "true", className }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": "true", className });
2730
2737
  }