@stackframe/dashboard-ui-components 2.8.84 → 2.8.86

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.
Files changed (229) hide show
  1. package/dist/components/analytics-chart/analytics-chart-pie.d.ts +67 -0
  2. package/dist/components/analytics-chart/analytics-chart-pie.d.ts.map +1 -0
  3. package/dist/components/analytics-chart/analytics-chart-pie.js +253 -0
  4. package/dist/components/analytics-chart/analytics-chart-pie.js.map +1 -0
  5. package/dist/components/analytics-chart/analytics-chart.d.ts +554 -0
  6. package/dist/components/analytics-chart/analytics-chart.d.ts.map +1 -0
  7. package/dist/components/analytics-chart/analytics-chart.js +1021 -0
  8. package/dist/components/analytics-chart/analytics-chart.js.map +1 -0
  9. package/dist/components/analytics-chart/default-analytics-chart-tooltip.d.ts +66 -0
  10. package/dist/components/analytics-chart/default-analytics-chart-tooltip.d.ts.map +1 -0
  11. package/dist/components/analytics-chart/default-analytics-chart-tooltip.js +179 -0
  12. package/dist/components/analytics-chart/default-analytics-chart-tooltip.js.map +1 -0
  13. package/dist/components/analytics-chart/format.d.ts +13 -0
  14. package/dist/components/analytics-chart/format.d.ts.map +1 -0
  15. package/dist/components/analytics-chart/format.js +138 -0
  16. package/dist/components/analytics-chart/format.js.map +1 -0
  17. package/dist/components/analytics-chart/index.d.ts +8 -0
  18. package/dist/components/analytics-chart/index.js +184 -0
  19. package/dist/components/analytics-chart/palette.d.ts +15 -0
  20. package/dist/components/analytics-chart/palette.d.ts.map +1 -0
  21. package/dist/components/analytics-chart/palette.js +60 -0
  22. package/dist/components/analytics-chart/palette.js.map +1 -0
  23. package/dist/components/analytics-chart/render-data-series.d.ts +28 -0
  24. package/dist/components/analytics-chart/render-data-series.d.ts.map +1 -0
  25. package/dist/components/analytics-chart/render-data-series.js +109 -0
  26. package/dist/components/analytics-chart/render-data-series.js.map +1 -0
  27. package/dist/components/analytics-chart/state.d.ts +54 -0
  28. package/dist/components/analytics-chart/state.d.ts.map +1 -0
  29. package/dist/components/analytics-chart/state.js +142 -0
  30. package/dist/components/analytics-chart/state.js.map +1 -0
  31. package/dist/components/analytics-chart/strings.d.ts +33 -0
  32. package/dist/components/analytics-chart/strings.d.ts.map +1 -0
  33. package/dist/components/analytics-chart/strings.js +37 -0
  34. package/dist/components/analytics-chart/strings.js.map +1 -0
  35. package/dist/components/analytics-chart/types.d.ts +157 -0
  36. package/dist/components/analytics-chart/types.d.ts.map +1 -0
  37. package/dist/components/analytics-chart/types.js +21 -0
  38. package/dist/components/analytics-chart/types.js.map +1 -0
  39. package/dist/components/badge.d.ts +16 -0
  40. package/dist/components/badge.d.ts.map +1 -1
  41. package/dist/components/badge.js +16 -0
  42. package/dist/components/badge.js.map +1 -1
  43. package/dist/components/button.d.ts +15 -1
  44. package/dist/components/button.d.ts.map +1 -1
  45. package/dist/components/button.js +14 -0
  46. package/dist/components/button.js.map +1 -1
  47. package/dist/components/card.d.ts +28 -0
  48. package/dist/components/card.d.ts.map +1 -1
  49. package/dist/components/card.js +28 -0
  50. package/dist/components/card.js.map +1 -1
  51. package/dist/components/chart-card.d.ts +29 -0
  52. package/dist/components/chart-card.d.ts.map +1 -1
  53. package/dist/components/chart-card.js +29 -0
  54. package/dist/components/chart-card.js.map +1 -1
  55. package/dist/components/chart-legend.d.ts +1 -2
  56. package/dist/components/chart-legend.d.ts.map +1 -1
  57. package/dist/components/chart-legend.js +0 -4
  58. package/dist/components/chart-legend.js.map +1 -1
  59. package/dist/components/data-grid/data-grid-sizing.d.ts +11 -0
  60. package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -0
  61. package/dist/components/data-grid/data-grid-sizing.js +34 -0
  62. package/dist/components/data-grid/data-grid-sizing.js.map +1 -0
  63. package/dist/components/data-grid/data-grid-toolbar.d.ts +31 -0
  64. package/dist/components/data-grid/data-grid-toolbar.d.ts.map +1 -0
  65. package/dist/components/data-grid/data-grid-toolbar.js +226 -0
  66. package/dist/components/data-grid/data-grid-toolbar.js.map +1 -0
  67. package/dist/components/data-grid/data-grid.d.ts +233 -0
  68. package/dist/components/data-grid/data-grid.d.ts.map +1 -0
  69. package/dist/components/data-grid/data-grid.js +871 -0
  70. package/dist/components/data-grid/data-grid.js.map +1 -0
  71. package/dist/components/data-grid/index.d.ts +7 -0
  72. package/dist/components/data-grid/index.js +176 -0
  73. package/dist/components/data-grid/state.d.ts +91 -0
  74. package/dist/components/data-grid/state.d.ts.map +1 -0
  75. package/dist/components/data-grid/state.js +329 -0
  76. package/dist/components/data-grid/state.js.map +1 -0
  77. package/dist/components/data-grid/strings.d.ts +8 -0
  78. package/dist/components/data-grid/strings.d.ts.map +1 -0
  79. package/dist/components/data-grid/strings.js +42 -0
  80. package/dist/components/data-grid/strings.js.map +1 -0
  81. package/dist/components/data-grid/types.d.ts +242 -0
  82. package/dist/components/data-grid/types.d.ts.map +1 -0
  83. package/dist/components/data-grid/types.js +0 -0
  84. package/dist/components/data-grid/use-data-source.d.ts +79 -0
  85. package/dist/components/data-grid/use-data-source.d.ts.map +1 -0
  86. package/dist/components/data-grid/use-data-source.js +236 -0
  87. package/dist/components/data-grid/use-data-source.js.map +1 -0
  88. package/dist/components/empty-state.d.ts +16 -0
  89. package/dist/components/empty-state.d.ts.map +1 -1
  90. package/dist/components/empty-state.js +16 -0
  91. package/dist/components/empty-state.js.map +1 -1
  92. package/dist/components/metric-card.d.ts +24 -0
  93. package/dist/components/metric-card.d.ts.map +1 -1
  94. package/dist/components/metric-card.js +24 -0
  95. package/dist/components/metric-card.js.map +1 -1
  96. package/dist/components/progress-bar.d.ts +10 -0
  97. package/dist/components/progress-bar.d.ts.map +1 -1
  98. package/dist/components/progress-bar.js +10 -0
  99. package/dist/components/progress-bar.js.map +1 -1
  100. package/dist/components/separator.d.ts +9 -0
  101. package/dist/components/separator.d.ts.map +1 -1
  102. package/dist/components/separator.js +9 -0
  103. package/dist/components/separator.js.map +1 -1
  104. package/dist/components/skeleton.d.ts +12 -0
  105. package/dist/components/skeleton.d.ts.map +1 -1
  106. package/dist/components/skeleton.js +12 -0
  107. package/dist/components/skeleton.js.map +1 -1
  108. package/dist/components/table.d.ts +25 -0
  109. package/dist/components/table.d.ts.map +1 -1
  110. package/dist/components/table.js +25 -0
  111. package/dist/components/table.js.map +1 -1
  112. package/dist/dashboard-ui-components.global.js +8562 -2857
  113. package/dist/dashboard-ui-components.global.js.map +4 -4
  114. package/dist/esm/components/analytics-chart/analytics-chart-pie.d.ts +67 -0
  115. package/dist/esm/components/analytics-chart/analytics-chart-pie.d.ts.map +1 -0
  116. package/dist/esm/components/analytics-chart/analytics-chart-pie.js +251 -0
  117. package/dist/esm/components/analytics-chart/analytics-chart-pie.js.map +1 -0
  118. package/dist/esm/components/analytics-chart/analytics-chart.d.ts +554 -0
  119. package/dist/esm/components/analytics-chart/analytics-chart.d.ts.map +1 -0
  120. package/dist/esm/components/analytics-chart/analytics-chart.js +1019 -0
  121. package/dist/esm/components/analytics-chart/analytics-chart.js.map +1 -0
  122. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.d.ts +66 -0
  123. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.d.ts.map +1 -0
  124. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.js +176 -0
  125. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.js.map +1 -0
  126. package/dist/esm/components/analytics-chart/format.d.ts +13 -0
  127. package/dist/esm/components/analytics-chart/format.d.ts.map +1 -0
  128. package/dist/esm/components/analytics-chart/format.js +133 -0
  129. package/dist/esm/components/analytics-chart/format.js.map +1 -0
  130. package/dist/esm/components/analytics-chart/index.d.ts +8 -0
  131. package/dist/esm/components/analytics-chart/index.js +9 -0
  132. package/dist/esm/components/analytics-chart/palette.d.ts +15 -0
  133. package/dist/esm/components/analytics-chart/palette.d.ts.map +1 -0
  134. package/dist/esm/components/analytics-chart/palette.js +55 -0
  135. package/dist/esm/components/analytics-chart/palette.js.map +1 -0
  136. package/dist/esm/components/analytics-chart/render-data-series.d.ts +28 -0
  137. package/dist/esm/components/analytics-chart/render-data-series.d.ts.map +1 -0
  138. package/dist/esm/components/analytics-chart/render-data-series.js +107 -0
  139. package/dist/esm/components/analytics-chart/render-data-series.js.map +1 -0
  140. package/dist/esm/components/analytics-chart/state.d.ts +54 -0
  141. package/dist/esm/components/analytics-chart/state.d.ts.map +1 -0
  142. package/dist/esm/components/analytics-chart/state.js +126 -0
  143. package/dist/esm/components/analytics-chart/state.js.map +1 -0
  144. package/dist/esm/components/analytics-chart/strings.d.ts +33 -0
  145. package/dist/esm/components/analytics-chart/strings.d.ts.map +1 -0
  146. package/dist/esm/components/analytics-chart/strings.js +34 -0
  147. package/dist/esm/components/analytics-chart/strings.js.map +1 -0
  148. package/dist/esm/components/analytics-chart/types.d.ts +157 -0
  149. package/dist/esm/components/analytics-chart/types.d.ts.map +1 -0
  150. package/dist/esm/components/analytics-chart/types.js +18 -0
  151. package/dist/esm/components/analytics-chart/types.js.map +1 -0
  152. package/dist/esm/components/badge.d.ts +16 -0
  153. package/dist/esm/components/badge.d.ts.map +1 -1
  154. package/dist/esm/components/badge.js +16 -0
  155. package/dist/esm/components/badge.js.map +1 -1
  156. package/dist/esm/components/button.d.ts +14 -0
  157. package/dist/esm/components/button.d.ts.map +1 -1
  158. package/dist/esm/components/button.js +14 -0
  159. package/dist/esm/components/button.js.map +1 -1
  160. package/dist/esm/components/card.d.ts +28 -0
  161. package/dist/esm/components/card.d.ts.map +1 -1
  162. package/dist/esm/components/card.js +28 -0
  163. package/dist/esm/components/card.js.map +1 -1
  164. package/dist/esm/components/chart-card.d.ts +29 -0
  165. package/dist/esm/components/chart-card.d.ts.map +1 -1
  166. package/dist/esm/components/chart-card.js +29 -0
  167. package/dist/esm/components/chart-card.js.map +1 -1
  168. package/dist/esm/components/chart-legend.d.ts +1 -2
  169. package/dist/esm/components/chart-legend.d.ts.map +1 -1
  170. package/dist/esm/components/chart-legend.js +1 -3
  171. package/dist/esm/components/chart-legend.js.map +1 -1
  172. package/dist/esm/components/data-grid/data-grid-sizing.d.ts +11 -0
  173. package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -0
  174. package/dist/esm/components/data-grid/data-grid-sizing.js +29 -0
  175. package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -0
  176. package/dist/esm/components/data-grid/data-grid-toolbar.d.ts +31 -0
  177. package/dist/esm/components/data-grid/data-grid-toolbar.d.ts.map +1 -0
  178. package/dist/esm/components/data-grid/data-grid-toolbar.js +223 -0
  179. package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -0
  180. package/dist/esm/components/data-grid/data-grid.d.ts +233 -0
  181. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -0
  182. package/dist/esm/components/data-grid/data-grid.js +868 -0
  183. package/dist/esm/components/data-grid/data-grid.js.map +1 -0
  184. package/dist/esm/components/data-grid/index.d.ts +7 -0
  185. package/dist/esm/components/data-grid/index.js +7 -0
  186. package/dist/esm/components/data-grid/state.d.ts +91 -0
  187. package/dist/esm/components/data-grid/state.d.ts.map +1 -0
  188. package/dist/esm/components/data-grid/state.js +305 -0
  189. package/dist/esm/components/data-grid/state.js.map +1 -0
  190. package/dist/esm/components/data-grid/strings.d.ts +8 -0
  191. package/dist/esm/components/data-grid/strings.d.ts.map +1 -0
  192. package/dist/esm/components/data-grid/strings.js +39 -0
  193. package/dist/esm/components/data-grid/strings.js.map +1 -0
  194. package/dist/esm/components/data-grid/types.d.ts +242 -0
  195. package/dist/esm/components/data-grid/types.d.ts.map +1 -0
  196. package/dist/esm/components/data-grid/types.js +1 -0
  197. package/dist/esm/components/data-grid/use-data-source.d.ts +79 -0
  198. package/dist/esm/components/data-grid/use-data-source.d.ts.map +1 -0
  199. package/dist/esm/components/data-grid/use-data-source.js +234 -0
  200. package/dist/esm/components/data-grid/use-data-source.js.map +1 -0
  201. package/dist/esm/components/empty-state.d.ts +16 -0
  202. package/dist/esm/components/empty-state.d.ts.map +1 -1
  203. package/dist/esm/components/empty-state.js +16 -0
  204. package/dist/esm/components/empty-state.js.map +1 -1
  205. package/dist/esm/components/metric-card.d.ts +24 -0
  206. package/dist/esm/components/metric-card.d.ts.map +1 -1
  207. package/dist/esm/components/metric-card.js +24 -0
  208. package/dist/esm/components/metric-card.js.map +1 -1
  209. package/dist/esm/components/progress-bar.d.ts +10 -0
  210. package/dist/esm/components/progress-bar.d.ts.map +1 -1
  211. package/dist/esm/components/progress-bar.js +10 -0
  212. package/dist/esm/components/progress-bar.js.map +1 -1
  213. package/dist/esm/components/separator.d.ts +9 -0
  214. package/dist/esm/components/separator.d.ts.map +1 -1
  215. package/dist/esm/components/separator.js +9 -0
  216. package/dist/esm/components/separator.js.map +1 -1
  217. package/dist/esm/components/skeleton.d.ts +12 -0
  218. package/dist/esm/components/skeleton.d.ts.map +1 -1
  219. package/dist/esm/components/skeleton.js +12 -0
  220. package/dist/esm/components/skeleton.js.map +1 -1
  221. package/dist/esm/components/table.d.ts +25 -0
  222. package/dist/esm/components/table.d.ts.map +1 -1
  223. package/dist/esm/components/table.js +25 -0
  224. package/dist/esm/components/table.js.map +1 -1
  225. package/dist/esm/index.d.ts +4 -2
  226. package/dist/esm/index.js +6 -2
  227. package/dist/index.d.ts +15 -2
  228. package/dist/index.js +16 -7
  229. package/package.json +4 -3
@@ -0,0 +1,868 @@
1
+ "use client";
2
+
3
+ import { ArrowDown, ArrowUp, CaretDown, CaretUp, CheckSquare, MinusSquare, Square } from "@phosphor-icons/react";
4
+ import { cn } from "@stackframe/stack-ui";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";
7
+ import { clearSelection, exportToCsv, formatGridDate, getSortDirection, getSortIndex, isColumnVisible, resolveColumnValue, resolveColumnWidth, selectAll, toggleRowSelection, toggleSort } from "./state.js";
8
+ import { resolveDataGridStrings } from "./strings.js";
9
+ import { useVirtualizer } from "@tanstack/react-virtual";
10
+ import { DesignSkeleton } from "../skeleton.js";
11
+ import { DataGridToolbar } from "./data-grid-toolbar.js";
12
+ import { applyDraggedColumnWidth, clampColumnWidth, createGridSizingStyle, getColumnSizingStyle } from "./data-grid-sizing.js";
13
+
14
+ //#region src/components/data-grid/data-grid.tsx
15
+ function ResizeHandle({ onResize, onResizeEnd }) {
16
+ const startXRef = useRef(0);
17
+ const rafRef = useRef(0);
18
+ const latestDeltaRef = useRef(0);
19
+ const callbacksRef = useRef({
20
+ onResize,
21
+ onResizeEnd
22
+ });
23
+ callbacksRef.current = {
24
+ onResize,
25
+ onResizeEnd
26
+ };
27
+ const onPointerDown = useCallback((e) => {
28
+ e.preventDefault();
29
+ e.stopPropagation();
30
+ startXRef.current = e.clientX;
31
+ latestDeltaRef.current = 0;
32
+ const el = e.currentTarget;
33
+ el.setPointerCapture(e.pointerId);
34
+ let finished = false;
35
+ const onMove = (ev) => {
36
+ latestDeltaRef.current = ev.clientX - startXRef.current;
37
+ if (rafRef.current !== 0) return;
38
+ rafRef.current = requestAnimationFrame(() => {
39
+ rafRef.current = 0;
40
+ callbacksRef.current.onResize(latestDeltaRef.current);
41
+ });
42
+ };
43
+ const finish = () => {
44
+ if (finished) return;
45
+ finished = true;
46
+ if (rafRef.current !== 0) {
47
+ cancelAnimationFrame(rafRef.current);
48
+ rafRef.current = 0;
49
+ callbacksRef.current.onResize(latestDeltaRef.current);
50
+ }
51
+ el.removeEventListener("pointermove", onMove);
52
+ el.removeEventListener("pointerup", finish);
53
+ el.removeEventListener("pointercancel", finish);
54
+ el.removeEventListener("lostpointercapture", finish);
55
+ if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId);
56
+ callbacksRef.current.onResizeEnd();
57
+ };
58
+ el.addEventListener("pointermove", onMove);
59
+ el.addEventListener("pointerup", finish);
60
+ el.addEventListener("pointercancel", finish);
61
+ el.addEventListener("lostpointercapture", finish);
62
+ }, []);
63
+ return /* @__PURE__ */ jsx("div", {
64
+ className: cn("absolute right-0 top-0 bottom-0 z-10 w-[5px] cursor-col-resize touch-none", "group-hover/header:bg-foreground/[0.06] hover:!bg-blue-500/30", "transition-colors duration-100"),
65
+ onClick: (e) => {
66
+ e.preventDefault();
67
+ e.stopPropagation();
68
+ },
69
+ onPointerDown
70
+ });
71
+ }
72
+ function HeaderCell({ col, isSorted, sortIndex, resizable, onSort, onResize, onResizeEnd }) {
73
+ const ctx = {
74
+ columnId: col.id,
75
+ columnDef: col,
76
+ isSorted,
77
+ sortIndex
78
+ };
79
+ const label = typeof col.header === "function" ? col.header(ctx) : col.header;
80
+ const sortable = col.sortable !== false;
81
+ return /* @__PURE__ */ jsxs("div", {
82
+ className: cn("group/header relative flex items-center gap-1.5 px-3 select-none bg-transparent", "border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0", sortable && "cursor-pointer"),
83
+ style: getColumnSizingStyle(col),
84
+ "data-col-id": col.id,
85
+ onClick: (e) => sortable && onSort(col.id, e.metaKey || e.ctrlKey),
86
+ role: "columnheader",
87
+ "aria-sort": isSorted === "asc" ? "ascending" : isSorted === "desc" ? "descending" : "none",
88
+ children: [
89
+ /* @__PURE__ */ jsx("span", {
90
+ className: cn("flex-1 truncate text-xs font-semibold uppercase tracking-wider text-muted-foreground", col.align === "center" && "text-center", col.align === "right" && "text-right"),
91
+ children: label
92
+ }),
93
+ isSorted && /* @__PURE__ */ jsxs("span", {
94
+ className: "flex items-center gap-0.5 text-foreground/60",
95
+ children: [isSorted === "asc" ? /* @__PURE__ */ jsx(ArrowUp, {
96
+ className: "h-3 w-3",
97
+ weight: "bold"
98
+ }) : /* @__PURE__ */ jsx(ArrowDown, {
99
+ className: "h-3 w-3",
100
+ weight: "bold"
101
+ }), sortIndex != null && /* @__PURE__ */ jsx("span", {
102
+ className: "text-[10px] font-medium tabular-nums",
103
+ children: sortIndex
104
+ })]
105
+ }),
106
+ !isSorted && sortable && /* @__PURE__ */ jsxs("span", {
107
+ className: "hidden group-hover/header:flex items-center text-foreground/20",
108
+ children: [/* @__PURE__ */ jsx(CaretUp, {
109
+ className: "h-2.5 w-2.5 -mb-[1px]",
110
+ weight: "bold"
111
+ }), /* @__PURE__ */ jsx(CaretDown, {
112
+ className: "h-2.5 w-2.5 -mt-[1px]",
113
+ weight: "bold"
114
+ })]
115
+ }),
116
+ resizable && col.resizable !== false && /* @__PURE__ */ jsx(ResizeHandle, {
117
+ onResize: (delta) => onResize(col.id, delta),
118
+ onResizeEnd
119
+ })
120
+ ]
121
+ });
122
+ }
123
+ function DataCell({ col, row, rowId, rowIndex, isSelected, dateDisplay }) {
124
+ const value = resolveColumnValue(col, row);
125
+ const ctx = {
126
+ row,
127
+ rowId,
128
+ rowIndex,
129
+ value,
130
+ columnId: col.id,
131
+ isSelected,
132
+ dateDisplay
133
+ };
134
+ const isDateCol = col.type === "date" || col.type === "dateTime";
135
+ let content;
136
+ if (col.renderCell) content = col.renderCell(ctx);
137
+ else if (isDateCol) content = renderDateCell(value, dateDisplay, col);
138
+ else content = formatCellValue(value);
139
+ const hasCellClick = col.onCellClick || col.onCellDoubleClick;
140
+ return /* @__PURE__ */ jsx("div", {
141
+ className: cn("flex items-center px-3 truncate bg-transparent", "border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0", "text-sm text-foreground", col.align === "center" && "justify-center", col.align === "right" && "justify-end", hasCellClick && "cursor-pointer"),
142
+ style: getColumnSizingStyle(col),
143
+ "data-col-id": col.id,
144
+ role: "gridcell",
145
+ onClick: col.onCellClick ? (e) => {
146
+ e.stopPropagation();
147
+ col.onCellClick(ctx, e);
148
+ } : void 0,
149
+ onDoubleClick: col.onCellDoubleClick ? (e) => {
150
+ e.stopPropagation();
151
+ col.onCellDoubleClick(ctx, e);
152
+ } : void 0,
153
+ children: content
154
+ });
155
+ }
156
+ function formatCellValue(value) {
157
+ if (value == null) return /* @__PURE__ */ jsx("span", {
158
+ className: "text-muted-foreground/40",
159
+ children: "-"
160
+ });
161
+ if (typeof value === "boolean") return /* @__PURE__ */ jsx("span", {
162
+ className: cn("inline-flex items-center px-1.5 py-0.5 rounded-md text-xs font-medium", value ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400" : "bg-foreground/[0.04] text-muted-foreground"),
163
+ children: value ? "Yes" : "No"
164
+ });
165
+ if (value instanceof Date) return /* @__PURE__ */ jsx("span", {
166
+ className: "tabular-nums text-muted-foreground",
167
+ children: value.toLocaleDateString()
168
+ });
169
+ return /* @__PURE__ */ jsx("span", {
170
+ className: "truncate",
171
+ children: String(value)
172
+ });
173
+ }
174
+ /** Built-in date cell — mirrors what `formatGridDate` returns but wraps
175
+ * the display in a `<span>` with a `title` tooltip showing the absolute
176
+ * datetime. Only used when the column has `type: "date" | "dateTime"`
177
+ * and no custom `renderCell`. */
178
+ function renderDateCell(value, dateDisplay, col) {
179
+ const { display, tooltip } = formatGridDate(value, dateDisplay, {
180
+ parseValue: col.parseValue,
181
+ dateFormat: col.dateFormat
182
+ });
183
+ if (display == null) return /* @__PURE__ */ jsx("span", {
184
+ className: "text-muted-foreground/40",
185
+ children: "-"
186
+ });
187
+ return /* @__PURE__ */ jsx("span", {
188
+ className: "tabular-nums text-muted-foreground truncate cursor-help",
189
+ title: tooltip ?? void 0,
190
+ children: display
191
+ });
192
+ }
193
+ function SkeletonRow({ columns, height, showCheckbox }) {
194
+ return /* @__PURE__ */ jsxs("div", {
195
+ className: "flex",
196
+ style: { height },
197
+ role: "row",
198
+ children: [showCheckbox && /* @__PURE__ */ jsx("div", {
199
+ className: "flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]",
200
+ style: { width: 44 },
201
+ children: /* @__PURE__ */ jsx(DesignSkeleton, { className: "h-4 w-4 rounded" })
202
+ }), columns.map((col) => /* @__PURE__ */ jsx("div", {
203
+ className: "flex items-center px-3 border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0",
204
+ style: getColumnSizingStyle(col),
205
+ children: /* @__PURE__ */ jsx(DesignSkeleton, {
206
+ className: "h-3.5 rounded-md",
207
+ style: { width: `${40 + Math.random() * 40}%` }
208
+ })
209
+ }, col.id))]
210
+ });
211
+ }
212
+ function SelectionCheckbox({ checked, indeterminate, onChange, ariaLabel }) {
213
+ const Icon = indeterminate ? MinusSquare : checked ? CheckSquare : Square;
214
+ return /* @__PURE__ */ jsx("button", {
215
+ className: cn("flex items-center justify-center w-full h-full", "hover:bg-foreground/[0.04] transition-colors duration-75", checked || indeterminate ? "text-blue-600 dark:text-blue-400" : "text-muted-foreground/40 hover:text-muted-foreground/60"),
216
+ onClick: (e) => {
217
+ e.stopPropagation();
218
+ onChange(e);
219
+ },
220
+ "aria-label": ariaLabel,
221
+ role: "checkbox",
222
+ "aria-checked": indeterminate ? "mixed" : checked,
223
+ children: /* @__PURE__ */ jsx(Icon, {
224
+ className: "h-4 w-4",
225
+ weight: checked || indeterminate ? "fill" : "regular"
226
+ })
227
+ });
228
+ }
229
+ function InfiniteScrollSentinel({ onIntersect, isLoading, strings }) {
230
+ const ref = useRef(null);
231
+ useEffect(() => {
232
+ const el = ref.current;
233
+ if (!el) return;
234
+ const observer = new IntersectionObserver((entries) => {
235
+ if (entries[0]?.isIntersecting) onIntersect();
236
+ }, { rootMargin: "200px" });
237
+ observer.observe(el);
238
+ return () => observer.disconnect();
239
+ }, [onIntersect]);
240
+ return /* @__PURE__ */ jsx("div", {
241
+ ref,
242
+ className: "flex items-center justify-center py-4",
243
+ children: isLoading && /* @__PURE__ */ jsxs("div", {
244
+ className: "flex items-center gap-2 text-xs text-muted-foreground",
245
+ children: [/* @__PURE__ */ jsx("div", { className: "h-3 w-3 rounded-full border-2 border-current border-t-transparent animate-spin" }), strings.loadingMore]
246
+ })
247
+ });
248
+ }
249
+ function DefaultFooter({ ctx, pagination, onChange }) {
250
+ const { state, totalRowCount, visibleRowCount, selectedRowCount, strings } = ctx;
251
+ const totalPages = totalRowCount != null ? Math.max(1, Math.ceil(totalRowCount / state.pagination.pageSize)) : void 0;
252
+ const setPage = (pageIndex) => onChange((s) => ({
253
+ ...s,
254
+ pagination: {
255
+ ...s.pagination,
256
+ pageIndex
257
+ }
258
+ }));
259
+ const setPageSize = (pageSize) => onChange((s) => ({
260
+ ...s,
261
+ pagination: {
262
+ ...s.pagination,
263
+ pageSize,
264
+ pageIndex: 0
265
+ }
266
+ }));
267
+ return /* @__PURE__ */ jsxs("div", {
268
+ className: "flex items-center justify-between px-4 py-2.5 border-t border-foreground/[0.06] text-xs text-muted-foreground",
269
+ children: [/* @__PURE__ */ jsxs("div", {
270
+ className: "flex items-center gap-3",
271
+ children: [selectedRowCount > 0 && /* @__PURE__ */ jsx("span", {
272
+ className: "font-medium text-foreground",
273
+ children: strings.rowsSelected(selectedRowCount)
274
+ }), totalRowCount != null && /* @__PURE__ */ jsxs("span", { children: [
275
+ visibleRowCount,
276
+ " of ",
277
+ totalRowCount,
278
+ " rows"
279
+ ] })]
280
+ }), pagination !== "infinite" && totalPages != null && /* @__PURE__ */ jsxs("div", {
281
+ className: "flex items-center gap-3",
282
+ children: [/* @__PURE__ */ jsxs("div", {
283
+ className: "flex items-center gap-1.5",
284
+ children: [/* @__PURE__ */ jsx("span", { children: strings.rowsPerPage }), /* @__PURE__ */ jsx("select", {
285
+ className: cn("h-7 rounded-lg border border-black/[0.08] dark:border-white/[0.08] bg-background px-1.5", "text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-foreground/[0.1]", "cursor-pointer"),
286
+ value: state.pagination.pageSize,
287
+ onChange: (e) => setPageSize(Number(e.target.value)),
288
+ children: [
289
+ 10,
290
+ 25,
291
+ 50,
292
+ 100
293
+ ].map((size) => /* @__PURE__ */ jsx("option", {
294
+ value: size,
295
+ children: size
296
+ }, size))
297
+ })]
298
+ }), /* @__PURE__ */ jsxs("div", {
299
+ className: "flex items-center gap-1",
300
+ children: [
301
+ /* @__PURE__ */ jsx("button", {
302
+ className: cn("h-7 w-7 flex items-center justify-center rounded-lg", "hover:bg-foreground/[0.04] disabled:opacity-30 disabled:cursor-not-allowed", "transition-colors duration-75"),
303
+ onClick: () => setPage(state.pagination.pageIndex - 1),
304
+ disabled: state.pagination.pageIndex === 0,
305
+ "aria-label": "Previous page",
306
+ children: /* @__PURE__ */ jsx(CaretUp, {
307
+ className: "h-3.5 w-3.5 -rotate-90",
308
+ weight: "bold"
309
+ })
310
+ }),
311
+ /* @__PURE__ */ jsx("span", {
312
+ className: "px-2 tabular-nums font-medium",
313
+ children: strings.pageOf(state.pagination.pageIndex + 1, totalPages)
314
+ }),
315
+ /* @__PURE__ */ jsx("button", {
316
+ className: cn("h-7 w-7 flex items-center justify-center rounded-lg", "hover:bg-foreground/[0.04] disabled:opacity-30 disabled:cursor-not-allowed", "transition-colors duration-75"),
317
+ onClick: () => setPage(state.pagination.pageIndex + 1),
318
+ disabled: state.pagination.pageIndex >= totalPages - 1,
319
+ "aria-label": "Next page",
320
+ children: /* @__PURE__ */ jsx(CaretDown, {
321
+ className: "h-3.5 w-3.5 -rotate-90",
322
+ weight: "bold"
323
+ })
324
+ })
325
+ ]
326
+ })]
327
+ })]
328
+ });
329
+ }
330
+ /**
331
+ * Interactive table with sorting, quick search, pagination, selection,
332
+ * and virtualization. Handles 10k+ rows smoothly. Pair with
333
+ * `useDataSource` for client-side data; use an async `dataSource`
334
+ * generator for server or infinite-scroll modes.
335
+ *
336
+ * ## Mental model (read this first — everything else depends on it)
337
+ *
338
+ * DataGrid is a **display** component. It does NOT sort, search, or
339
+ * paginate your data directly — you own that, but `useDataSource` does
340
+ * it for you. The `rows` prop is always the already-processed slice to
341
+ * show. The grid tracks user intent in `state` (sort model, quick
342
+ * search text, page index). You feed that state into `useDataSource`,
343
+ * and its output goes back in as `rows`.
344
+ *
345
+ * `useDataSource` IS the processor. Given your full dataset and the
346
+ * grid's state, it returns the searched + sorted + paginated rows
347
+ * ready to pass to DataGrid. This is the ONLY correct pattern for
348
+ * client-side data — do NOT pass a raw array to `rows`.
349
+ *
350
+ * ## Search (client vs async)
351
+ *
352
+ * - **Client mode** (`useDataSource` with `data`): a case-insensitive
353
+ * substring match across every column is applied automatically.
354
+ * Override the matcher with `matchRow` for fuzzy / weighted search,
355
+ * or disable by passing `matchRow: () => true`.
356
+ * - **Async mode** (`useDataSource` with `dataSource`): `state.quickSearch`
357
+ * is forwarded to the generator as `params.quickSearch`. Same
358
+ * mechanism as `params.sorting` — a change triggers a refetch, and
359
+ * the generator is the "matching logic" (typically a WHERE / ILIKE
360
+ * clause in the backend query). The grid does NO client-side
361
+ * filtering in async mode.
362
+ *
363
+ * ## The canonical pattern
364
+ *
365
+ * ```tsx
366
+ * // 1. Columns — define OUTSIDE the component or inside a useMemo. Must be stable.
367
+ * const columns = React.useMemo(() => [
368
+ * { id: "name", header: "Name", accessor: "name", width: 180, type: "string" },
369
+ * { id: "email", header: "Email", accessor: "email", width: 240, type: "string" },
370
+ * { id: "role", header: "Role", accessor: "role", width: 120, type: "singleSelect",
371
+ * valueOptions: [{ value: "admin", label: "Admin" }, { value: "member", label: "Member" }] },
372
+ * { id: "signUps", header: "Sign-ups", accessor: "signUps", width: 120, type: "number", align: "right",
373
+ * renderCell: ({ value }) => <span className="tabular-nums">{Number(value).toLocaleString()}</span> },
374
+ * ], []);
375
+ *
376
+ * // 2. Grid state — one hook, initialized from the columns. NEVER build the state object by hand.
377
+ * const [gridState, setGridState] = React.useState(() => createDefaultDataGridState(columns));
378
+ *
379
+ * // 3. Data source — wires your raw array through the grid state. ALWAYS call this
380
+ * // hook unconditionally at the top level (no if/return before it).
381
+ * const gridData = useDataSource({
382
+ * data: users, // your raw array (can be [] while loading)
383
+ * columns,
384
+ * getRowId: (row) => row.id,
385
+ * sorting: gridState.sorting,
386
+ * quickSearch: gridState.quickSearch,
387
+ * pagination: gridState.pagination,
388
+ * paginationMode: "client", // "client" | "server" | "infinite"
389
+ * });
390
+ *
391
+ * // 4. Render — `rows` comes from gridData.rows, NOT from your raw array.
392
+ * <DataGrid
393
+ * columns={columns}
394
+ * rows={gridData.rows}
395
+ * getRowId={(row) => row.id}
396
+ * totalRowCount={gridData.totalRowCount}
397
+ * isLoading={gridData.isLoading}
398
+ * state={gridState}
399
+ * onChange={setGridState}
400
+ * selectionMode="none" // "none" | "single" | "multiple"
401
+ * maxHeight={480}
402
+ * />
403
+ * ```
404
+ *
405
+ * ## Iron rules (violating any of these breaks the grid)
406
+ *
407
+ * 1. The prop is `rows`, NOT `data`. There is no `data` prop on DataGrid.
408
+ * `data` belongs on `useDataSource`.
409
+ * 2. `rows` is ALWAYS `gridData.rows`. Never pass your raw array to
410
+ * `rows` — the grid won't search, sort, or paginate it.
411
+ * 3. Columns must be stable across renders. Define them outside the
412
+ * component or wrap in `React.useMemo`. A fresh columns array every
413
+ * render will reset sorting state.
414
+ * 4. Initialize grid state with `createDefaultDataGridState(columns)`.
415
+ * Do NOT spell out the state object manually — you will miss fields
416
+ * and crash.
417
+ * 5. `onChange` takes a `SetStateAction` (the setter you got from
418
+ * `useState`). Pass `setGridState` directly. Do NOT wrap it unless
419
+ * you know exactly what you're doing.
420
+ * 6. Call `useDataSource` ONCE per grid, at the top level, before any
421
+ * early return. It contains hooks.
422
+ * 7. `renderCell` is a PURE function of its context. NEVER call React
423
+ * hooks inside it (no `useState`, `useMemo`, `useEffect`, nothing).
424
+ * If you need derived data per row, compute it BEFORE the render —
425
+ * e.g. build a `Map<rowId, sparklineData>` in a `useMemo` and look
426
+ * it up in `renderCell`.
427
+ * 8. `toolbar` accepts `false` (hide it) or a render function
428
+ * `(ctx) => ReactNode`. Anything else — `true`, `undefined`, a state
429
+ * variable — will either show the default toolbar or crash. If you
430
+ * just want the default toolbar, omit the prop entirely.
431
+ * 9. The toolbar's search input writes to `state.quickSearch`. That
432
+ * value is consumed by `useDataSource` — client mode filters
433
+ * client-side, async mode forwards to the generator. Do NOT wire
434
+ * a separate "controlled" search prop, everything flows through
435
+ * grid state.
436
+ *
437
+ * ## renderCell — what you can and cannot do inside it
438
+ *
439
+ * ```tsx
440
+ * // OK — pure rendering from ctx:
441
+ * renderCell: ({ value }) => <span className="tabular-nums">{Number(value).toLocaleString()}</span>
442
+ * renderCell: ({ row }) => <Badge variant={row.active ? "default" : "outline"}>{row.status}</Badge>
443
+ *
444
+ * // OK — looking up pre-computed data by row id:
445
+ * // BEFORE the return, in the component body:
446
+ * const sparklinesById = React.useMemo(() => {
447
+ * const m = new Map();
448
+ * for (const u of users) {
449
+ * m.set(u.id, u.recentActivity.map((n, i) => ({ ts: i, values: { primary: n } })));
450
+ * }
451
+ * return m;
452
+ * }, [users]);
453
+ * // Then inside the column def:
454
+ * renderCell: ({ rowId }) => <MiniSparkline data={sparklinesById.get(rowId) ?? []} />
455
+ *
456
+ * // NOT OK — hooks inside renderCell:
457
+ * renderCell: ({ row }) => {
458
+ * const [hovered, setHovered] = React.useState(false); // ← crashes the grid
459
+ * const data = React.useMemo(() => ..., []); // ← crashes the grid
460
+ * return ...;
461
+ * }
462
+ *
463
+ * // NOT OK — embedding AnalyticsChart (or any other controlled, stateful chart) per row:
464
+ * // AnalyticsChart owns its own state, tooltips, zoom, and virtualized data
465
+ * // pipeline. Instantiating one per row is expensive and fights the grid's
466
+ * // virtualizer. Don't do it.
467
+ * ```
468
+ *
469
+ * ## Sparklines and mini-charts in cells — use raw Recharts
470
+ *
471
+ * If you want a tiny chart (sparkline, micro bar chart, trend line) inside
472
+ * a cell, drop down to raw `Recharts.*` components — they are lightweight
473
+ * and stateless, so they render cleanly per row without owning any state.
474
+ * Read pre-computed points off the row (or off a `Map<rowId, points>` you
475
+ * built in a `useMemo` above) and pass them directly to the Recharts
476
+ * primitive. Do NOT wrap them in `DesignChartContainer` or
477
+ * `DesignChartCard` inside a cell — those add chrome meant for full-size
478
+ * charts.
479
+ *
480
+ * ```tsx
481
+ * // OK — raw Recharts sparkline per row:
482
+ * renderCell: ({ rowId }) => {
483
+ * const points = sparklinesById.get(rowId) ?? [];
484
+ * return (
485
+ * <Recharts.ResponsiveContainer width="100%" height={28}>
486
+ * <Recharts.LineChart data={points} margin={{ top: 2, right: 2, bottom: 2, left: 2 }}>
487
+ * <Recharts.Line type="monotone" dataKey="v" stroke="currentColor" strokeWidth={1.5} dot={false} isAnimationActive={false} />
488
+ * </Recharts.LineChart>
489
+ * </Recharts.ResponsiveContainer>
490
+ * );
491
+ * }
492
+ * ```
493
+ *
494
+ * Keep in-cell Recharts configs minimal: no axes, no tooltips, no animation
495
+ * (`isAnimationActive={false}`), tight margins, fixed height. The goal is a
496
+ * visual summary, not an interactive chart.
497
+ *
498
+ * ## State shape (from `createDefaultDataGridState`)
499
+ *
500
+ * ```ts
501
+ * {
502
+ * sorting: [], // { columnId, direction: "asc" | "desc" }[]
503
+ * quickSearch: "", // search input text
504
+ * dateDisplay: "relative", // "relative" | "absolute"
505
+ * columnVisibility: {}, columnWidths: {...},
506
+ * columnPinning: { left: [], right: [] }, columnOrder: [...],
507
+ * pagination: { pageIndex: 0, pageSize: 50 },
508
+ * selection: { selectedIds: new Set(), anchorId: null },
509
+ * }
510
+ * ```
511
+ *
512
+ * Everything is updated through `setGridState` — the toolbar, header,
513
+ * and footer all call it for you. You do not need to wire any of this
514
+ * manually.
515
+ *
516
+ * ## Height and scrolling
517
+ *
518
+ * DataGrid is NOT a card. It has no border, rounded corners, or shadow of
519
+ * its own. Wrap it in whatever chrome you want — a `DesignCard`, a section,
520
+ * or just raw layout. The grid itself fills its parent's height via
521
+ * `h-full`.
522
+ *
523
+ * How the grid gets its height (pick ONE):
524
+ * 1. Bounded parent — put the grid inside a flex/grid container with a
525
+ * definite height (e.g. `flex-1 min-h-0` inside a page-filling flex
526
+ * column). The grid stretches to that height and scrolls its body.
527
+ * 2. `maxHeight` prop — pass a number (pixels) or CSS string
528
+ * (`"480px"`, `"60vh"`, `"100%"`). The grid caps at that size and
529
+ * scrolls its body.
530
+ * 3. Unbounded — omit `maxHeight` and let the parent grow freely. The
531
+ * grid renders at its full content height and the page scrolls. Fine
532
+ * for small lists; bad UX for thousands of rows.
533
+ *
534
+ * The toolbar, header, and footer are always `shrink-0`; only the body
535
+ * scrolls. You do NOT need to subtract toolbar/footer heights from
536
+ * `maxHeight` — the grid's internal flex layout handles that.
537
+ *
538
+ * ## When to use what
539
+ *
540
+ * - Simple static list, < 20 rows, no interaction → use a plain table component instead.
541
+ * - Interactive table, sortable + searchable, any size → `DataGrid` +
542
+ * `useDataSource` with `paginationMode: "client"`.
543
+ * - Infinite scroll over a huge dataset you fetch in pages → `dataSource` async
544
+ * generator + `paginationMode: "infinite"`. Only reach for this if you actually
545
+ * need pagination over a remote source. For anything that fits in memory,
546
+ * `"client"` is simpler and faster.
547
+ *
548
+ * ## Features you get for free
549
+ *
550
+ * Quick search, sortable columns (shift-click for multi-sort), column
551
+ * visibility toggle, column resize, CSV export, virtualized rendering
552
+ * for 10k+ rows, keyboard navigation, and a relative/absolute date
553
+ * toggle for `date` / `dateTime` columns.
554
+ */
555
+ function DataGrid(props) {
556
+ const { columns: allColumns, rows, getRowId, totalRowCount, isLoading = false, isRefetching = false, hasMore = false, isLoadingMore = false, onLoadMore, state, onChange, paginationMode = "paginated", selectionMode = "none", resizable = true, rowHeight = 44, headerHeight = 44, overscan = 5, maxHeight, toolbar, toolbarExtra, emptyState, loadingState, footer, footerExtra, exportFilename = "export", strings: stringsOverride, className, onRowClick, onRowDoubleClick, onSelectionChange, onSortChange } = props;
557
+ const strings = useMemo(() => resolveDataGridStrings(stringsOverride), [stringsOverride]);
558
+ const visibleColumns = useMemo(() => (state.columnOrder.length > 0 ? state.columnOrder.map((id) => allColumns.find((c) => c.id === id)).filter(Boolean) : allColumns).filter((col) => isColumnVisible(col.id, state.columnVisibility)), [
559
+ allColumns,
560
+ state.columnOrder,
561
+ state.columnVisibility
562
+ ]);
563
+ const rowIds = useMemo(() => rows.map(getRowId), [rows, getRowId]);
564
+ const visibleColumnMetrics = useMemo(() => {
565
+ const widths = /* @__PURE__ */ new Map();
566
+ let totalWidth = selectionMode !== "none" ? 44 : 0;
567
+ for (const col of visibleColumns) {
568
+ const width = resolveColumnWidth(col, state.columnWidths[col.id]);
569
+ widths.set(col.id, width);
570
+ totalWidth += width;
571
+ }
572
+ return {
573
+ widths,
574
+ totalWidth
575
+ };
576
+ }, [
577
+ selectionMode,
578
+ state.columnWidths,
579
+ visibleColumns
580
+ ]);
581
+ const gridSizingStyle = useMemo(() => createGridSizingStyle(visibleColumnMetrics.widths, visibleColumnMetrics.totalWidth), [visibleColumnMetrics]);
582
+ const resizeRef = useRef(null);
583
+ const gridRef = useRef(null);
584
+ const handleSort = useCallback((columnId, multi) => {
585
+ onChange((s) => {
586
+ const next = toggleSort(s.sorting, columnId, multi);
587
+ onSortChange?.(next);
588
+ return {
589
+ ...s,
590
+ sorting: next
591
+ };
592
+ });
593
+ }, [onChange, onSortChange]);
594
+ const handleResize = useCallback((columnId, delta) => {
595
+ const col = allColumns.find((c) => c.id === columnId);
596
+ if (!col) return;
597
+ if (!resizeRef.current || resizeRef.current.columnId !== columnId) {
598
+ const baseWidth = visibleColumnMetrics.widths.get(columnId) ?? resolveColumnWidth(col, state.columnWidths[columnId]);
599
+ resizeRef.current = {
600
+ columnId,
601
+ baseWidth,
602
+ baseTotalWidth: visibleColumnMetrics.totalWidth,
603
+ latestWidth: baseWidth
604
+ };
605
+ }
606
+ const newWidth = clampColumnWidth(col, resizeRef.current.baseWidth + delta);
607
+ resizeRef.current.latestWidth = newWidth;
608
+ if (gridRef.current) applyDraggedColumnWidth(gridRef.current, columnId, newWidth, resizeRef.current.baseTotalWidth + (newWidth - resizeRef.current.baseWidth));
609
+ }, [
610
+ allColumns,
611
+ state.columnWidths,
612
+ visibleColumnMetrics
613
+ ]);
614
+ useLayoutEffect(() => {
615
+ const r = resizeRef.current;
616
+ if (r && gridRef.current) applyDraggedColumnWidth(gridRef.current, r.columnId, r.latestWidth, r.baseTotalWidth + (r.latestWidth - r.baseWidth));
617
+ }, [gridSizingStyle]);
618
+ const handleResizeEnd = useCallback(() => {
619
+ const r = resizeRef.current;
620
+ resizeRef.current = null;
621
+ if (!r || r.latestWidth === r.baseWidth) return;
622
+ onChange((s) => ({
623
+ ...s,
624
+ columnWidths: {
625
+ ...s.columnWidths,
626
+ [r.columnId]: r.latestWidth
627
+ }
628
+ }));
629
+ }, [onChange]);
630
+ const handleRowClick = useCallback((row, rowId, event) => {
631
+ if (selectionMode !== "none") onChange((s) => {
632
+ const next = toggleRowSelection(s.selection, rowId, selectionMode, event.shiftKey, event.metaKey || event.ctrlKey, rowIds);
633
+ if (onSelectionChange) {
634
+ const selectedRows = rows.filter((r) => next.selectedIds.has(getRowId(r)));
635
+ setTimeout(() => onSelectionChange(next.selectedIds, selectedRows), 0);
636
+ }
637
+ return {
638
+ ...s,
639
+ selection: next
640
+ };
641
+ });
642
+ onRowClick?.(row, rowId, event);
643
+ }, [
644
+ selectionMode,
645
+ onChange,
646
+ onRowClick,
647
+ onSelectionChange,
648
+ rowIds,
649
+ rows,
650
+ getRowId
651
+ ]);
652
+ const handleRowSelectionCheckboxClick = useCallback((row, rowId, event) => {
653
+ handleRowClick(row, rowId, event);
654
+ }, [handleRowClick]);
655
+ const handleSelectAll = useCallback(() => {
656
+ onChange((s) => {
657
+ const allSelected = rowIds.every((id) => s.selection.selectedIds.has(id));
658
+ const next = allSelected ? clearSelection() : selectAll(rowIds);
659
+ if (onSelectionChange) {
660
+ const selectedRows = allSelected ? [] : rows;
661
+ setTimeout(() => onSelectionChange(next.selectedIds, [...selectedRows]), 0);
662
+ }
663
+ return {
664
+ ...s,
665
+ selection: next
666
+ };
667
+ });
668
+ }, [
669
+ onChange,
670
+ rowIds,
671
+ rows,
672
+ onSelectionChange
673
+ ]);
674
+ const handleExportCsv = useCallback(() => {
675
+ exportToCsv(rows, visibleColumns, exportFilename);
676
+ }, [
677
+ rows,
678
+ visibleColumns,
679
+ exportFilename
680
+ ]);
681
+ const scrollContainerRef = useRef(null);
682
+ const headerScrollRef = useRef(null);
683
+ const rowVirtualizer = useVirtualizer({
684
+ count: rows.length,
685
+ getScrollElement: () => scrollContainerRef.current,
686
+ estimateSize: () => rowHeight,
687
+ overscan
688
+ });
689
+ const handleBodyScroll = useCallback(() => {
690
+ const body = scrollContainerRef.current;
691
+ const header = headerScrollRef.current;
692
+ if (body && header) header.scrollLeft = body.scrollLeft;
693
+ }, []);
694
+ const toolbarCtx = useMemo(() => ({
695
+ state,
696
+ onChange,
697
+ columns: allColumns,
698
+ visibleColumns,
699
+ totalRowCount,
700
+ selectedRowCount: state.selection.selectedIds.size,
701
+ strings,
702
+ exportCsv: handleExportCsv
703
+ }), [
704
+ state,
705
+ onChange,
706
+ allColumns,
707
+ visibleColumns,
708
+ totalRowCount,
709
+ strings,
710
+ handleExportCsv
711
+ ]);
712
+ const footerCtx = useMemo(() => ({
713
+ state,
714
+ totalRowCount,
715
+ visibleRowCount: rows.length,
716
+ selectedRowCount: state.selection.selectedIds.size,
717
+ paginationMode,
718
+ strings
719
+ }), [
720
+ state,
721
+ totalRowCount,
722
+ rows.length,
723
+ paginationMode,
724
+ strings
725
+ ]);
726
+ const allSelected = rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id));
727
+ const someSelected = !allSelected && rowIds.some((id) => state.selection.selectedIds.has(id));
728
+ return /* @__PURE__ */ jsxs("div", {
729
+ ref: gridRef,
730
+ className: cn("flex flex-col h-full min-h-0 bg-transparent", className),
731
+ style: maxHeight != null ? {
732
+ ...gridSizingStyle,
733
+ maxHeight
734
+ } : gridSizingStyle,
735
+ role: "grid",
736
+ "aria-rowcount": totalRowCount ?? rows.length,
737
+ "aria-colcount": visibleColumns.length,
738
+ children: [
739
+ toolbar !== false && /* @__PURE__ */ jsx("div", {
740
+ className: "relative shrink-0 bg-transparent",
741
+ children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DataGridToolbar, {
742
+ ctx: toolbarCtx,
743
+ extra: typeof toolbarExtra === "function" ? toolbarExtra(toolbarCtx) : toolbarExtra
744
+ })
745
+ }),
746
+ /* @__PURE__ */ jsxs("div", {
747
+ className: "relative flex min-h-0 flex-1 flex-col",
748
+ children: [
749
+ isRefetching && /* @__PURE__ */ jsx("div", {
750
+ className: "absolute top-0 left-0 right-0 h-0.5 z-30 bg-foreground/[0.04] overflow-hidden",
751
+ children: /* @__PURE__ */ jsx("div", { className: "h-full w-1/3 bg-blue-500/60 rounded-full animate-pulse" })
752
+ }),
753
+ /* @__PURE__ */ jsx("div", {
754
+ ref: headerScrollRef,
755
+ className: "overflow-hidden shrink-0 border-b border-foreground/[0.06]",
756
+ children: /* @__PURE__ */ jsxs("div", {
757
+ className: "flex",
758
+ style: {
759
+ height: headerHeight,
760
+ minWidth: visibleColumnMetrics.totalWidth
761
+ },
762
+ role: "row",
763
+ children: [selectionMode !== "none" && /* @__PURE__ */ jsx("div", {
764
+ className: "flex items-center justify-center border-r border-foreground/[0.04]",
765
+ style: { width: 44 },
766
+ children: selectionMode === "multiple" && /* @__PURE__ */ jsx(SelectionCheckbox, {
767
+ checked: allSelected,
768
+ indeterminate: someSelected,
769
+ onChange: handleSelectAll,
770
+ ariaLabel: "Select all rows"
771
+ })
772
+ }), visibleColumns.map((col) => /* @__PURE__ */ jsx(HeaderCell, {
773
+ col,
774
+ isSorted: getSortDirection(state.sorting, col.id),
775
+ sortIndex: getSortIndex(state.sorting, col.id),
776
+ resizable,
777
+ onSort: handleSort,
778
+ onResize: handleResize,
779
+ onResizeEnd: handleResizeEnd
780
+ }, col.id))]
781
+ })
782
+ }),
783
+ /* @__PURE__ */ jsxs("div", {
784
+ ref: scrollContainerRef,
785
+ className: cn("min-h-0 overflow-auto flex-1 bg-transparent", "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5", "[&::-webkit-scrollbar-track]:bg-transparent", "[&::-webkit-scrollbar-thumb]:bg-foreground/[0.08] [&::-webkit-scrollbar-thumb]:rounded-full", "[&::-webkit-scrollbar-thumb]:hover:bg-foreground/[0.15]"),
786
+ onScroll: handleBodyScroll,
787
+ children: [
788
+ isLoading && /* @__PURE__ */ jsx("div", {
789
+ style: { minWidth: visibleColumnMetrics.totalWidth },
790
+ children: loadingState ?? Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsx(SkeletonRow, {
791
+ columns: visibleColumns,
792
+ height: rowHeight,
793
+ showCheckbox: selectionMode !== "none"
794
+ }, i))
795
+ }),
796
+ !isLoading && rows.length === 0 && /* @__PURE__ */ jsx("div", {
797
+ className: "flex items-center justify-center py-16 text-sm text-muted-foreground",
798
+ style: { minWidth: visibleColumnMetrics.totalWidth },
799
+ children: emptyState ?? strings.noData
800
+ }),
801
+ !isLoading && rows.length > 0 && /* @__PURE__ */ jsx("div", {
802
+ style: {
803
+ height: rowVirtualizer.getTotalSize(),
804
+ width: "100%",
805
+ minWidth: visibleColumnMetrics.totalWidth,
806
+ position: "relative"
807
+ },
808
+ children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
809
+ const row = rows[virtualRow.index];
810
+ const rowId = getRowId(row);
811
+ const isSelected = state.selection.selectedIds.has(rowId);
812
+ const isOddRow = virtualRow.index % 2 === 1;
813
+ return /* @__PURE__ */ jsxs("div", {
814
+ className: cn("absolute left-0 w-full flex", "border-b border-black/[0.03] dark:border-white/[0.03]", "transition-colors duration-75", isSelected ? "bg-blue-500/[0.06] dark:bg-blue-400/[0.08] hover:bg-blue-500/[0.08] dark:hover:bg-blue-400/[0.1]" : isOddRow ? "bg-foreground/[0.02] dark:bg-foreground/[0.03] hover:bg-foreground/[0.04] dark:hover:bg-foreground/[0.06]" : "hover:bg-foreground/[0.025] dark:hover:bg-foreground/[0.04]", selectionMode !== "none" && "cursor-pointer"),
815
+ style: {
816
+ height: rowHeight,
817
+ transform: `translateY(${virtualRow.start}px)`
818
+ },
819
+ onClick: (e) => handleRowClick(row, rowId, e),
820
+ onDoubleClick: (e) => onRowDoubleClick?.(row, rowId, e),
821
+ role: "row",
822
+ "aria-rowindex": virtualRow.index + 2,
823
+ "aria-selected": isSelected,
824
+ "data-row-id": rowId,
825
+ "data-state": isSelected ? "selected" : void 0,
826
+ children: [selectionMode !== "none" && /* @__PURE__ */ jsx("div", {
827
+ className: "flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]",
828
+ style: { width: 44 },
829
+ children: /* @__PURE__ */ jsx(SelectionCheckbox, {
830
+ checked: isSelected,
831
+ onChange: (event) => handleRowSelectionCheckboxClick(row, rowId, event),
832
+ ariaLabel: `Select row ${rowId}`
833
+ })
834
+ }), visibleColumns.map((col) => /* @__PURE__ */ jsx(DataCell, {
835
+ col,
836
+ row,
837
+ rowId,
838
+ rowIndex: virtualRow.index,
839
+ isSelected,
840
+ dateDisplay: state.dateDisplay
841
+ }, col.id))]
842
+ }, rowId);
843
+ })
844
+ }),
845
+ paginationMode === "infinite" && hasMore && !isLoading && /* @__PURE__ */ jsx(InfiniteScrollSentinel, {
846
+ onIntersect: onLoadMore ?? (() => {}),
847
+ isLoading: isLoadingMore,
848
+ strings
849
+ })
850
+ ]
851
+ })
852
+ ]
853
+ }),
854
+ footer !== false && /* @__PURE__ */ jsxs("div", {
855
+ className: "relative z-10 shrink-0 bg-transparent",
856
+ children: [footer ? footer(footerCtx) : /* @__PURE__ */ jsx(DefaultFooter, {
857
+ ctx: footerCtx,
858
+ pagination: paginationMode,
859
+ onChange
860
+ }), footerExtra && (typeof footerExtra === "function" ? footerExtra(footerCtx) : footerExtra)]
861
+ })
862
+ ]
863
+ });
864
+ }
865
+
866
+ //#endregion
867
+ export { DataGrid };
868
+ //# sourceMappingURL=data-grid.js.map