@datum-cloud/datum-ui 0.2.0-alpha.8 → 0.3.0-alpha.9d90881

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 (155) hide show
  1. package/dist/autocomplete/index.mjs +4 -4
  2. package/dist/{autocomplete-DZtI97HP.mjs → autocomplete-e33EmvBu.mjs} +3 -3
  3. package/dist/avatar-stack/index.mjs +2 -2
  4. package/dist/{avatar-stack-JCfBlPB9.mjs → avatar-stack-Ci0cnjxv.mjs} +1 -1
  5. package/dist/calendar-date-picker-BBAg78Lg.mjs +665 -0
  6. package/dist/checkbox/index.mjs +2 -1
  7. package/dist/checkbox-DB5_3E_l.mjs +22 -0
  8. package/dist/checkbox-DMC1Mhaw.mjs +17 -0
  9. package/dist/collapsible/index.mjs +1 -1
  10. package/dist/command/index.mjs +2 -2
  11. package/dist/{command-s0Yv3abE.mjs → command-DQlO6uTL.mjs} +1 -1
  12. package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts +2 -1
  13. package/dist/components/features/calendar-date-picker/calendar-date-picker.d.ts.map +1 -1
  14. package/dist/components/features/data-table/adapters/nuqs-adapter.d.ts +45 -0
  15. package/dist/components/features/data-table/adapters/nuqs-adapter.d.ts.map +1 -0
  16. package/dist/components/features/data-table/columns/selection-column.d.ts +6 -0
  17. package/dist/components/features/data-table/columns/selection-column.d.ts.map +1 -0
  18. package/dist/components/features/data-table/components/bulk-actions.d.ts +3 -0
  19. package/dist/components/features/data-table/components/bulk-actions.d.ts.map +1 -0
  20. package/dist/components/features/data-table/components/column-header.d.ts +3 -0
  21. package/dist/components/features/data-table/components/column-header.d.ts.map +1 -0
  22. package/dist/components/features/data-table/components/content.d.ts +3 -0
  23. package/dist/components/features/data-table/components/content.d.ts.map +1 -0
  24. package/dist/components/features/data-table/components/inline-content.d.ts +3 -0
  25. package/dist/components/features/data-table/components/inline-content.d.ts.map +1 -0
  26. package/dist/components/features/data-table/components/loading.d.ts +3 -0
  27. package/dist/components/features/data-table/components/loading.d.ts.map +1 -0
  28. package/dist/components/features/data-table/components/pagination.d.ts +3 -0
  29. package/dist/components/features/data-table/components/pagination.d.ts.map +1 -0
  30. package/dist/components/features/data-table/components/row-actions.d.ts +3 -0
  31. package/dist/components/features/data-table/components/row-actions.d.ts.map +1 -0
  32. package/dist/components/features/data-table/components/search.d.ts +3 -0
  33. package/dist/components/features/data-table/components/search.d.ts.map +1 -0
  34. package/dist/components/features/data-table/constants.d.ts +5 -0
  35. package/dist/components/features/data-table/constants.d.ts.map +1 -0
  36. package/dist/components/features/data-table/core/client-provider.d.ts +3 -0
  37. package/dist/components/features/data-table/core/client-provider.d.ts.map +1 -0
  38. package/dist/components/features/data-table/core/data-table-context.d.ts +5 -0
  39. package/dist/components/features/data-table/core/data-table-context.d.ts.map +1 -0
  40. package/dist/components/features/data-table/core/server-provider.d.ts +3 -0
  41. package/dist/components/features/data-table/core/server-provider.d.ts.map +1 -0
  42. package/dist/components/features/data-table/data-table.d.ts +29 -0
  43. package/dist/components/features/data-table/data-table.d.ts.map +1 -0
  44. package/dist/components/features/data-table/filters/checkbox-filter.d.ts +3 -0
  45. package/dist/components/features/data-table/filters/checkbox-filter.d.ts.map +1 -0
  46. package/dist/components/features/data-table/filters/date-picker-filter.d.ts +3 -0
  47. package/dist/components/features/data-table/filters/date-picker-filter.d.ts.map +1 -0
  48. package/dist/components/features/data-table/filters/select-filter.d.ts +3 -0
  49. package/dist/components/features/data-table/filters/select-filter.d.ts.map +1 -0
  50. package/dist/components/features/data-table/hooks/index.d.ts +4 -0
  51. package/dist/components/features/data-table/hooks/index.d.ts.map +1 -0
  52. package/dist/components/features/data-table/hooks/use-data-table-client.d.ts +23 -0
  53. package/dist/components/features/data-table/hooks/use-data-table-client.d.ts.map +1 -0
  54. package/dist/components/features/data-table/hooks/use-data-table-context.d.ts +2 -0
  55. package/dist/components/features/data-table/hooks/use-data-table-context.d.ts.map +1 -0
  56. package/dist/components/features/data-table/hooks/use-data-table-server.d.ts +22 -0
  57. package/dist/components/features/data-table/hooks/use-data-table-server.d.ts.map +1 -0
  58. package/dist/components/features/data-table/hooks/use-inline-contents.d.ts +7 -0
  59. package/dist/components/features/data-table/hooks/use-inline-contents.d.ts.map +1 -0
  60. package/dist/components/features/data-table/index.d.ts +10 -0
  61. package/dist/components/features/data-table/index.d.ts.map +1 -0
  62. package/dist/components/features/data-table/types.d.ts +258 -0
  63. package/dist/components/features/data-table/types.d.ts.map +1 -0
  64. package/dist/data-table/index.mjs +1162 -0
  65. package/dist/date-picker/index.mjs +5 -4
  66. package/dist/dialog/index.mjs +2 -2
  67. package/dist/{dialog-bnMMf9GD.mjs → dialog-B2EZJW-q.mjs} +2 -2
  68. package/dist/dropdown/index.mjs +1 -1
  69. package/dist/dropdown-menu-Xahj42Gr.mjs +87 -0
  70. package/dist/dropzone/index.mjs +1 -1
  71. package/dist/empty-content/index.mjs +1 -1
  72. package/dist/form/index.mjs +12 -12
  73. package/dist/grid/index.mjs +1 -1
  74. package/dist/hooks/index.mjs +2 -2
  75. package/dist/hover-card/index.mjs +1 -1
  76. package/dist/icons/index.mjs +1 -1
  77. package/dist/index.mjs +52 -48
  78. package/dist/input/index.mjs +2 -2
  79. package/dist/{input-fzXBheCN.mjs → input-D241oNEm.mjs} +1 -1
  80. package/dist/input-group/index.mjs +3 -3
  81. package/dist/{input-group-CPaFSTEV.mjs → input-group-uobp64zr.mjs} +2 -2
  82. package/dist/input-number/index.mjs +2 -2
  83. package/dist/{input-number-9o62JHRl.mjs → input-number-CEMgBk8-.mjs} +1 -1
  84. package/dist/input-with-addons/index.mjs +1 -1
  85. package/dist/label/index.mjs +2 -1
  86. package/dist/label-ClzLBWRT.mjs +16 -0
  87. package/dist/{label-_ste_Re3.mjs → label-byipFGok.mjs} +1 -12
  88. package/dist/loader-overlay/index.mjs +1 -1
  89. package/dist/map/index.mjs +8 -7
  90. package/dist/{map-WL6jhkSM.mjs → map-DupFPkJT.mjs} +6 -86
  91. package/dist/more-actions/index.mjs +2 -2
  92. package/dist/{more-actions-Ch1f6Mh3.mjs → more-actions-D6OyqZQS.mjs} +2 -2
  93. package/dist/page-title/index.mjs +1 -1
  94. package/dist/popover/index.mjs +1 -1
  95. package/dist/radio-group/index.mjs +1 -1
  96. package/dist/select/index.mjs +1 -1
  97. package/dist/{select-DVlEzD2W.mjs → select-BznmyqBr.mjs} +1 -1
  98. package/dist/sheet/index.mjs +2 -2
  99. package/dist/{sheet-BKiCwtNO.mjs → sheet-Bmayi68h.mjs} +2 -2
  100. package/dist/sidebar/index.mjs +6 -6
  101. package/dist/{sidebar-DfqezV8t.mjs → sidebar-D2zE7rPy.mjs} +5 -5
  102. package/dist/skeleton/index.mjs +1 -1
  103. package/dist/spinner/index.mjs +1 -1
  104. package/dist/stepper/index.mjs +1 -1
  105. package/dist/styles/fonts/AllianceNo1-Medium.ttf +0 -0
  106. package/dist/styles/fonts/AllianceNo1-Regular.ttf +0 -0
  107. package/dist/styles/fonts/AllianceNo1-SemiBold.ttf +0 -0
  108. package/dist/switch/index.mjs +1 -1
  109. package/dist/table/index.mjs +1 -1
  110. package/dist/tabs/index.mjs +1 -1
  111. package/dist/tag-input/index.mjs +2 -2
  112. package/dist/{tag-input-Di7SDNbK.mjs → tag-input-BI8IRBDH.mjs} +1 -1
  113. package/dist/task-queue/index.mjs +4 -4
  114. package/dist/{task-queue-dropdown-DW72ikDH.mjs → task-queue-dropdown-D6k067_W.mjs} +4 -4
  115. package/dist/textarea/index.mjs +2 -2
  116. package/dist/{textarea-CxE3YbC7.mjs → textarea-BZ85VFsJ.mjs} +1 -1
  117. package/dist/theme/index.mjs +1 -1
  118. package/dist/{to-api-format-C2xjQUcI.mjs → to-api-format-CXQ7knV4.mjs} +7 -662
  119. package/dist/toast/index.mjs +1 -1
  120. package/dist/tooltip/index.mjs +1 -1
  121. package/dist/typography/index.mjs +1 -1
  122. package/dist/{use-copy-to-clipboard-ki-WoTml.mjs → use-copy-to-clipboard-CC2hhyYI.mjs} +1 -1
  123. package/dist/{use-stepper-BaToCYMs.mjs → use-stepper-CU75TdjZ.mjs} +14 -14
  124. package/dist/{use-toast-mdn_CqRY.mjs → use-toast-BLBGnOC3.mjs} +1 -1
  125. package/dist/visually-hidden/index.mjs +1 -1
  126. package/package.json +29 -17
  127. package/dist/checkbox-LG1OKTpG.mjs +0 -34
  128. /package/dist/{close.icon-CMNMoXM_.mjs → close.icon-D2r5q3bj.mjs} +0 -0
  129. /package/dist/{col-lrLMZaTJ.mjs → col-Cg_2sTDA.mjs} +0 -0
  130. /package/dist/{collapsible-Bt9UYfv3.mjs → collapsible-Dw71o2um.mjs} +0 -0
  131. /package/dist/{dialog-DXBaT9gA.mjs → dialog-Bm4trnic.mjs} +0 -0
  132. /package/dist/{dropdown-DtSa_lqc.mjs → dropdown-DLZXinlT.mjs} +0 -0
  133. /package/dist/{dropzone-BkOnwrS4.mjs → dropzone-CGyjGnER.mjs} +0 -0
  134. /package/dist/{empty-content-BM9rzI13.mjs → empty-content-ByvwjHUs.mjs} +0 -0
  135. /package/dist/{hover-card-CUPfFUqE.mjs → hover-card-BNrHtWy6.mjs} +0 -0
  136. /package/dist/{input-DuyjEKEW.mjs → input-C-ZmsHkk.mjs} +0 -0
  137. /package/dist/{input-with-addons-BQn7KCTU.mjs → input-with-addons-DzuyGa6G.mjs} +0 -0
  138. /package/dist/{loader-overlay-DUaQSZQP.mjs → loader-overlay-CbxcjyHV.mjs} +0 -0
  139. /package/dist/{map-leaflet-imports-C4JYls8q.mjs → map-leaflet-imports-CgEyVRnp.mjs} +0 -0
  140. /package/dist/{page-title-BJuo81rT.mjs → page-title-CrYQ091u.mjs} +0 -0
  141. /package/dist/{popover-SQlKSz6L.mjs → popover-CYzXdp9q.mjs} +0 -0
  142. /package/dist/{radio-group-Oshv0b-U.mjs → radio-group-WZCIDQCH.mjs} +0 -0
  143. /package/dist/{sheet-CtnP6gTD.mjs → sheet-b9V9soz8.mjs} +0 -0
  144. /package/dist/{skeleton-vzbxA-DQ.mjs → skeleton-D3qW_KvG.mjs} +0 -0
  145. /package/dist/{spinner-BE7k2bAD.mjs → spinner-CKTGKv5n.mjs} +0 -0
  146. /package/dist/{stepper-SWB-u_nM.mjs → stepper-B07hPGG7.mjs} +0 -0
  147. /package/dist/{switch-Calk7Gyw.mjs → switch-CujyyOi6.mjs} +0 -0
  148. /package/dist/{table-CsXBcQLI.mjs → table-fZEvpdD-.mjs} +0 -0
  149. /package/dist/{tabs-D8n-dqnw.mjs → tabs-B7cW59gB.mjs} +0 -0
  150. /package/dist/{textarea-QYRcDEpK.mjs → textarea-BSkDKiej.mjs} +0 -0
  151. /package/dist/{theme.provider-CzCxEFFh.mjs → theme.provider-BG3cS9xe.mjs} +0 -0
  152. /package/dist/{tooltip-Dd3ActSS.mjs → tooltip-CbCWKEzu.mjs} +0 -0
  153. /package/dist/{typography-UA7ZZvgJ.mjs → typography-DdrxIJMd.mjs} +0 -0
  154. /package/dist/{use-debounce-B6wPrZV8.mjs → use-debounce-Dc95PFRX.mjs} +0 -0
  155. /package/dist/{visuallyhidden-aaTUk4Yo.mjs → visuallyhidden-CfBnXfvh.mjs} +0 -0
@@ -0,0 +1,1162 @@
1
+ import { t as cn } from "../utils-Bfgoe-Gm.mjs";
2
+ import { t as Button } from "../button-AzpnV-WB.mjs";
3
+ import { t as Checkbox } from "../checkbox-DB5_3E_l.mjs";
4
+ import "../dialog-Bm4trnic.mjs";
5
+ import { a as CommandInput, i as CommandGroup, o as CommandItem, r as CommandEmpty, s as CommandList, t as Command } from "../command-DQlO6uTL.mjs";
6
+ import { t as Input } from "../input-C-ZmsHkk.mjs";
7
+ import { t as Label } from "../label-ClzLBWRT.mjs";
8
+ import { i as DropdownMenuItem, l as DropdownMenuTrigger, r as DropdownMenuContent, t as DropdownMenu } from "../dropdown-menu-Xahj42Gr.mjs";
9
+ import { i as PopoverTrigger, r as PopoverContent, t as Popover } from "../popover-CYzXdp9q.mjs";
10
+ import { d as Select, f as SelectContent, h as SelectValue, m as SelectTrigger, p as SelectItem } from "../select-BznmyqBr.mjs";
11
+ import { t as Skeleton } from "../skeleton-D3qW_KvG.mjs";
12
+ import { c as TableRow, i as TableCell, n as TableBody, o as TableHead, s as TableHeader, t as Table } from "../table-fZEvpdD-.mjs";
13
+ import { t as CalendarDatePicker } from "../calendar-date-picker-BBAg78Lg.mjs";
14
+ import { cva } from "class-variance-authority";
15
+ import { ArrowDown, ArrowUp, ArrowUpDown, Check, ChevronDown, ChevronLeft, ChevronRight, MoreHorizontal, X } from "lucide-react";
16
+ import { createContext, use, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ import { parseAsInteger, parseAsJson, parseAsString, useQueryStates } from "nuqs";
19
+ import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
20
+
21
+ //#region src/components/features/data-table/constants.ts
22
+ const DEFAULT_PAGE_SIZE = 20;
23
+ const DEFAULT_PAGE_SIZES = [
24
+ 10,
25
+ 20,
26
+ 30,
27
+ 50
28
+ ];
29
+ const DEFAULT_DEBOUNCE_MS = 300;
30
+ const DEFAULT_LOADING_ROWS = 5;
31
+
32
+ //#endregion
33
+ //#region src/components/features/data-table/adapters/nuqs-adapter.ts
34
+ /**
35
+ * Default parsers for core DataTable state.
36
+ * URL params: ?sort=[...]&q=hello&page=0&size=20
37
+ */
38
+ function sortingValidator(value) {
39
+ if (!Array.isArray(value)) return null;
40
+ return value.every((item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.desc === "boolean") ? value : null;
41
+ }
42
+ const coreSearchParams = {
43
+ sort: parseAsJson(sortingValidator).withDefault([]),
44
+ q: parseAsString.withDefault(""),
45
+ page: parseAsInteger.withDefault(0),
46
+ size: parseAsInteger.withDefault(DEFAULT_PAGE_SIZE)
47
+ };
48
+ /**
49
+ * Hook that creates a StateAdapter backed by nuqs URL query state.
50
+ *
51
+ * Manages sorting, search, pageIndex, and pageSize automatically.
52
+ * Consumer only needs to declare custom filter parsers.
53
+ *
54
+ * Requires `nuqs` to be installed in the consumer app.
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * // Zero config — all defaults synced to URL
59
+ * const stateAdapter = useNuqsAdapter()
60
+ * const tableState = useDataTableClient({ data, columns, stateAdapter })
61
+ *
62
+ * // With custom filters synced to URL
63
+ * import { parseAsString } from 'nuqs'
64
+ *
65
+ * const stateAdapter = useNuqsAdapter({
66
+ * filters: { status: parseAsString.withDefault('') },
67
+ * })
68
+ * const tableState = useDataTableClient({ data, columns, stateAdapter })
69
+ * ```
70
+ */
71
+ function useNuqsAdapter(options = {}) {
72
+ const { filters: filterParsers } = options;
73
+ const [coreState, setCoreState] = useQueryStates(coreSearchParams);
74
+ const hasFilters = filterParsers != null && Object.keys(filterParsers).length > 0;
75
+ const [filterState, setFilterState] = useQueryStates(hasFilters ? filterParsers : { _dt: parseAsString.withDefault("") });
76
+ return useMemo(() => ({
77
+ read: () => ({
78
+ sorting: coreState.sort,
79
+ search: coreState.q,
80
+ pageIndex: coreState.page,
81
+ pageSize: coreState.size,
82
+ ...hasFilters ? { filters: filterState } : {}
83
+ }),
84
+ write: (state) => {
85
+ setCoreState({
86
+ sort: state.sorting,
87
+ q: state.search,
88
+ page: state.pageIndex ?? 0,
89
+ size: state.pageSize ?? DEFAULT_PAGE_SIZE
90
+ });
91
+ if (hasFilters) setFilterState(state.filters);
92
+ }
93
+ }), [
94
+ coreState,
95
+ filterState,
96
+ hasFilters,
97
+ setCoreState,
98
+ setFilterState
99
+ ]);
100
+ }
101
+
102
+ //#endregion
103
+ //#region src/components/features/data-table/columns/selection-column.tsx
104
+ const SELECTION_COLUMN_ID = "select";
105
+ function createSelectionColumn(options = {}) {
106
+ const { className, headerClassName, renderHeader, renderCell } = options;
107
+ return {
108
+ id: SELECTION_COLUMN_ID,
109
+ size: 40,
110
+ enableSorting: false,
111
+ enableHiding: false,
112
+ header: renderHeader ?? (({ table }) => /* @__PURE__ */ jsx(Checkbox, {
113
+ checked: table.getIsAllPageRowsSelected() || table.getIsSomePageRowsSelected() && "indeterminate",
114
+ onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value),
115
+ "aria-label": "Select all",
116
+ className: headerClassName
117
+ })),
118
+ cell: renderCell ?? (({ row }) => /* @__PURE__ */ jsx(Checkbox, {
119
+ checked: row.getIsSelected(),
120
+ onCheckedChange: (value) => row.toggleSelected(!!value),
121
+ "aria-label": "Select row",
122
+ className
123
+ }))
124
+ };
125
+ }
126
+ function hasSelectionColumn(columns) {
127
+ return columns.some((col) => "id" in col && col.id === SELECTION_COLUMN_ID);
128
+ }
129
+ function withSelectionColumn(columns, options = {}) {
130
+ if (hasSelectionColumn(columns)) return columns;
131
+ return [createSelectionColumn(options), ...columns];
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/components/features/data-table/core/data-table-context.tsx
136
+ const DataTableContext = createContext(null);
137
+ function useDataTableContext() {
138
+ const context = use(DataTableContext);
139
+ if (!context) throw new Error("useDataTableContext must be used within a DataTable provider");
140
+ return context;
141
+ }
142
+
143
+ //#endregion
144
+ //#region src/components/features/data-table/components/bulk-actions.tsx
145
+ function DataTableBulkActions({ children, className }) {
146
+ const { table, rowSelection } = useDataTableContext();
147
+ const selectedRows = useMemo(() => {
148
+ return table.getFilteredSelectedRowModel().rows.map((row) => row.original);
149
+ }, [table, rowSelection]);
150
+ if (selectedRows.length === 0) return null;
151
+ return /* @__PURE__ */ jsx("div", {
152
+ "data-slot": "dt-bulk-actions",
153
+ className,
154
+ children: children(selectedRows)
155
+ });
156
+ }
157
+
158
+ //#endregion
159
+ //#region src/components/features/data-table/components/column-header.tsx
160
+ function DataTableColumnHeader({ column, title, className }) {
161
+ if (!column.getCanSort()) return /* @__PURE__ */ jsx("div", {
162
+ className: cn(className),
163
+ "data-slot": "dt-column-header",
164
+ children: title
165
+ });
166
+ const sorted = column.getIsSorted();
167
+ return /* @__PURE__ */ jsx("div", {
168
+ className: cn("flex items-center gap-2", className),
169
+ "data-slot": "dt-column-header",
170
+ children: /* @__PURE__ */ jsxs("button", {
171
+ type: "button",
172
+ className: "flex items-center gap-1 hover:text-foreground -ml-3 h-8 px-3 cursor-pointer",
173
+ onClick: () => column.toggleSorting(sorted === "asc"),
174
+ children: [/* @__PURE__ */ jsx("span", { children: title }), sorted === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "size-4" }) : sorted === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "size-4" })]
175
+ })
176
+ });
177
+ }
178
+
179
+ //#endregion
180
+ //#region src/components/features/data-table/components/content.tsx
181
+ function resolveClassName(value, item) {
182
+ if (typeof value === "function") return value(item);
183
+ return value;
184
+ }
185
+ function renderInlineContentRow(entry, colSpan, rows) {
186
+ return /* @__PURE__ */ jsx(TableRow, {
187
+ "data-slot": "dt-inline-content",
188
+ "data-position": entry.position,
189
+ className: cn("transition-all duration-200", entry.className),
190
+ children: /* @__PURE__ */ jsx(TableCell, {
191
+ colSpan,
192
+ children: entry.render({
193
+ onClose: entry.onClose,
194
+ rowData: entry.position === "row" ? rows.find((r) => r.id === entry.rowId)?.original ?? null : null
195
+ })
196
+ })
197
+ }, entry.id);
198
+ }
199
+ function DataTableContent({ emptyMessage, className, tableClassName, headerClassName, headerRowClassName, headerCellClassName, bodyClassName, rowClassName, cellClassName }) {
200
+ const { table, inlineContents } = useDataTableContext();
201
+ const rows = table.getRowModel().rows;
202
+ const openInlineContents = useMemo(() => inlineContents.filter((e) => e.open), [inlineContents]);
203
+ const colSpan = table.getAllColumns().length;
204
+ return /* @__PURE__ */ jsx("div", {
205
+ className: cn("datum-ui-data-table", className),
206
+ "data-slot": "dt",
207
+ style: { overflowX: "auto" },
208
+ children: /* @__PURE__ */ jsxs(Table, {
209
+ className: cn(tableClassName),
210
+ "data-slot": "dt-table",
211
+ children: [/* @__PURE__ */ jsx(TableHeader, {
212
+ className: cn(headerClassName),
213
+ "data-slot": "dt-header",
214
+ children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
215
+ className: cn(headerRowClassName),
216
+ "data-slot": "dt-header-row",
217
+ children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, {
218
+ className: cn(headerCellClassName),
219
+ "data-slot": "dt-header-cell",
220
+ children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
221
+ }, header.id))
222
+ }, headerGroup.id))
223
+ }), /* @__PURE__ */ jsxs(TableBody, {
224
+ className: cn(bodyClassName),
225
+ "data-slot": "dt-body",
226
+ children: [openInlineContents.filter((e) => e.position === "top").map((entry) => renderInlineContentRow(entry, colSpan, rows)), rows.length > 0 ? rows.map((row) => {
227
+ const rowEntry = openInlineContents.find((e) => e.position === "row" && e.rowId === row.id);
228
+ if (rowEntry) return renderInlineContentRow(rowEntry, colSpan, rows);
229
+ return /* @__PURE__ */ jsx(TableRow, {
230
+ className: cn(resolveClassName(rowClassName, row)),
231
+ "data-slot": "dt-row",
232
+ "data-state": row.getIsSelected() ? "selected" : void 0,
233
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
234
+ className: cn(resolveClassName(cellClassName, cell)),
235
+ "data-slot": "dt-cell",
236
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
237
+ }, cell.id))
238
+ }, row.id);
239
+ }) : /* @__PURE__ */ jsx(TableRow, {
240
+ "data-slot": "dt-row",
241
+ children: /* @__PURE__ */ jsx(TableCell, {
242
+ colSpan,
243
+ className: "h-24 text-center",
244
+ "data-slot": "dt-empty",
245
+ children: emptyMessage ?? "No results."
246
+ })
247
+ })]
248
+ })]
249
+ })
250
+ });
251
+ }
252
+
253
+ //#endregion
254
+ //#region src/components/features/data-table/components/inline-content.tsx
255
+ function DataTableInlineContent({ position, rowId, open, onClose, className, children }) {
256
+ const id = useId();
257
+ const { registerInlineContent, unregisterInlineContent } = useDataTableContext();
258
+ const initialRender = useRef(true);
259
+ useEffect(() => {
260
+ registerInlineContent({
261
+ id,
262
+ position,
263
+ rowId,
264
+ open,
265
+ onClose,
266
+ className,
267
+ render: children
268
+ });
269
+ return () => {
270
+ unregisterInlineContent(id);
271
+ };
272
+ }, [
273
+ id,
274
+ registerInlineContent,
275
+ unregisterInlineContent
276
+ ]);
277
+ useEffect(() => {
278
+ if (initialRender.current) {
279
+ initialRender.current = false;
280
+ return;
281
+ }
282
+ registerInlineContent({
283
+ id,
284
+ position,
285
+ rowId,
286
+ open,
287
+ onClose,
288
+ className,
289
+ render: children
290
+ });
291
+ }, [
292
+ id,
293
+ position,
294
+ rowId,
295
+ open,
296
+ onClose,
297
+ className,
298
+ children,
299
+ registerInlineContent
300
+ ]);
301
+ return null;
302
+ }
303
+
304
+ //#endregion
305
+ //#region src/components/features/data-table/components/loading.tsx
306
+ function DataTableLoading({ rows = DEFAULT_LOADING_ROWS, columns = 4, className }) {
307
+ return /* @__PURE__ */ jsx("div", {
308
+ className,
309
+ "data-slot": "dt-loading",
310
+ children: /* @__PURE__ */ jsxs("div", {
311
+ className: "rounded-md border",
312
+ children: [/* @__PURE__ */ jsx("div", {
313
+ className: "border-b",
314
+ children: /* @__PURE__ */ jsx("div", {
315
+ className: "flex gap-4 p-4",
316
+ children: Array.from({ length: columns }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-4 flex-1" }, i))
317
+ })
318
+ }), Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ jsx("div", {
319
+ className: "flex gap-4 border-b p-4 last:border-b-0",
320
+ children: Array.from({ length: columns }, (_, colIndex) => /* @__PURE__ */ jsx(Skeleton, { className: "h-4 flex-1" }, colIndex))
321
+ }, rowIndex))]
322
+ })
323
+ });
324
+ }
325
+
326
+ //#endregion
327
+ //#region src/components/features/data-table/components/pagination.tsx
328
+ function DataTablePagination({ pageSizes = DEFAULT_PAGE_SIZES, className }) {
329
+ const { pagination, mode } = useDataTableContext();
330
+ return /* @__PURE__ */ jsxs("div", {
331
+ className: cn("flex items-center justify-between px-2 py-4", className),
332
+ "data-slot": "dt-pagination",
333
+ children: [/* @__PURE__ */ jsxs("div", {
334
+ className: "flex items-center gap-2",
335
+ children: [/* @__PURE__ */ jsx("span", {
336
+ className: "text-sm text-muted-foreground",
337
+ children: "Rows per page"
338
+ }), /* @__PURE__ */ jsxs(Select, {
339
+ value: String(pagination.pageSize),
340
+ onValueChange: (value) => pagination.setPageSize(Number(value)),
341
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
342
+ className: "h-8 w-[70px]",
343
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: String(pagination.pageSize) })
344
+ }), /* @__PURE__ */ jsx(SelectContent, {
345
+ side: "top",
346
+ children: pageSizes.map((size) => /* @__PURE__ */ jsx(SelectItem, {
347
+ value: String(size),
348
+ children: size
349
+ }, size))
350
+ })]
351
+ })]
352
+ }), /* @__PURE__ */ jsxs("div", {
353
+ className: "flex items-center gap-2",
354
+ children: [
355
+ mode === "client" && pagination.pageIndex !== void 0 && pagination.pageCount !== void 0 && /* @__PURE__ */ jsxs("span", {
356
+ className: "text-sm text-muted-foreground",
357
+ children: [
358
+ "Page",
359
+ " ",
360
+ pagination.pageIndex + 1,
361
+ " ",
362
+ "of",
363
+ " ",
364
+ pagination.pageCount
365
+ ]
366
+ }),
367
+ /* @__PURE__ */ jsx(Button, {
368
+ variant: "outline",
369
+ size: "sm",
370
+ onClick: pagination.prevPage,
371
+ disabled: !pagination.canPrevPage,
372
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
373
+ }),
374
+ /* @__PURE__ */ jsx(Button, {
375
+ variant: "outline",
376
+ size: "sm",
377
+ onClick: pagination.nextPage,
378
+ disabled: !pagination.canNextPage,
379
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
380
+ })
381
+ ]
382
+ })]
383
+ });
384
+ }
385
+
386
+ //#endregion
387
+ //#region src/components/features/data-table/components/row-actions.tsx
388
+ function DataTableRowActions({ row, actions, isLoading = false, className }) {
389
+ const data = row.original;
390
+ const visibleActions = actions.filter((action) => {
391
+ if (action.hidden === void 0) return true;
392
+ return typeof action.hidden === "function" ? !action.hidden(data) : !action.hidden;
393
+ });
394
+ if (visibleActions.length === 0) return null;
395
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
396
+ asChild: true,
397
+ children: /* @__PURE__ */ jsxs(Button, {
398
+ variant: "ghost",
399
+ size: "sm",
400
+ className,
401
+ disabled: isLoading,
402
+ "data-slot": "dt-row-actions",
403
+ children: [/* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" }), /* @__PURE__ */ jsx("span", {
404
+ className: "sr-only",
405
+ children: "Open menu"
406
+ })]
407
+ })
408
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
409
+ align: "end",
410
+ children: visibleActions.map((action) => {
411
+ return /* @__PURE__ */ jsxs(DropdownMenuItem, {
412
+ disabled: typeof action.disabled === "function" ? action.disabled(data) : action.disabled ?? false,
413
+ onClick: () => action.onClick(data),
414
+ className: action.variant === "destructive" ? "text-destructive" : void 0,
415
+ children: [action.icon && /* @__PURE__ */ jsx(action.icon, { className: "mr-2 size-4" }), action.label]
416
+ }, action.label);
417
+ })
418
+ })] });
419
+ }
420
+
421
+ //#endregion
422
+ //#region src/components/features/data-table/components/search.tsx
423
+ function DataTableSearch({ placeholder = "Search...", debounceMs = DEFAULT_DEBOUNCE_MS, className }) {
424
+ const { search, setSearch } = useDataTableContext();
425
+ const [inputValue, setInputValue] = useState(search);
426
+ useEffect(() => {
427
+ setInputValue(search);
428
+ }, [search]);
429
+ useEffect(() => {
430
+ const timer = setTimeout(() => {
431
+ if (inputValue !== search) setSearch(inputValue);
432
+ }, debounceMs);
433
+ return () => clearTimeout(timer);
434
+ }, [
435
+ inputValue,
436
+ debounceMs,
437
+ search,
438
+ setSearch
439
+ ]);
440
+ return /* @__PURE__ */ jsx(Input, {
441
+ placeholder,
442
+ value: inputValue,
443
+ onChange: (e) => setInputValue(e.target.value),
444
+ className,
445
+ "data-slot": "dt-search"
446
+ });
447
+ }
448
+
449
+ //#endregion
450
+ //#region src/components/features/data-table/hooks/use-inline-contents.ts
451
+ function useInlineContents() {
452
+ const [inlineContents, setInlineContents] = useState([]);
453
+ return {
454
+ inlineContents,
455
+ registerInlineContent: useCallback((entry) => {
456
+ setInlineContents((prev) => {
457
+ const index = prev.findIndex((e) => e.id === entry.id);
458
+ if (index >= 0) {
459
+ const next = [...prev];
460
+ next[index] = entry;
461
+ return next;
462
+ }
463
+ return [...prev, entry];
464
+ });
465
+ }, []),
466
+ unregisterInlineContent: useCallback((id) => {
467
+ setInlineContents((prev) => prev.filter((e) => e.id !== id));
468
+ }, [])
469
+ };
470
+ }
471
+
472
+ //#endregion
473
+ //#region src/components/features/data-table/core/client-provider.tsx
474
+ function ClientProvider({ data, columns, sorting, setSorting, filters, setFilter, clearFilter, clearAllFilters, search, setSearch, clearSearch, searchableColumns, searchFn, rowSelection, setRowSelection, pagination, getRowId, enableRowSelection = false, className, children }) {
475
+ const columnFilters = useMemo(() => Object.entries(filters).map(([id, value]) => ({
476
+ id,
477
+ value
478
+ })), [filters]);
479
+ const arrayIncludesFilter = useMemo(() => (row, columnId, filterValue) => {
480
+ if (filterValue == null) return true;
481
+ if (Array.isArray(filterValue) && filterValue.length === 0) return true;
482
+ const cellValue = row.getValue(columnId);
483
+ if (Array.isArray(filterValue)) {
484
+ if (Array.isArray(cellValue)) return cellValue.some((v) => filterValue.includes(v));
485
+ return filterValue.includes(cellValue);
486
+ }
487
+ if (typeof filterValue === "string" && typeof cellValue === "string") return cellValue.toLowerCase().includes(filterValue.toLowerCase());
488
+ return cellValue === filterValue;
489
+ }, []);
490
+ const globalFilterFn = useMemo(() => {
491
+ if (searchFn) return (row, _columnId, filterValue) => searchFn(row.original, String(filterValue));
492
+ if (searchableColumns && searchableColumns.length > 0) return (row, _columnId, filterValue) => {
493
+ const query = String(filterValue).toLowerCase();
494
+ return searchableColumns.some((col) => {
495
+ const cellValue = row.getValue(col);
496
+ return cellValue != null && String(cellValue).toLowerCase().includes(query);
497
+ });
498
+ };
499
+ }, [searchFn, searchableColumns]);
500
+ const table = useReactTable({
501
+ data,
502
+ columns,
503
+ state: {
504
+ sorting,
505
+ rowSelection,
506
+ globalFilter: search,
507
+ columnFilters,
508
+ pagination: {
509
+ pageIndex: pagination.pageIndex,
510
+ pageSize: pagination.pageSize
511
+ }
512
+ },
513
+ filterFns: { arrayIncludes: arrayIncludesFilter },
514
+ defaultColumn: { filterFn: arrayIncludesFilter },
515
+ ...globalFilterFn ? { globalFilterFn } : {},
516
+ onSortingChange: (updater) => {
517
+ setSorting(typeof updater === "function" ? updater(sorting) : updater);
518
+ },
519
+ onRowSelectionChange: (updater) => {
520
+ setRowSelection(typeof updater === "function" ? updater(rowSelection) : updater);
521
+ },
522
+ onPaginationChange: (updater) => {
523
+ const next = typeof updater === "function" ? updater({
524
+ pageIndex: pagination.pageIndex,
525
+ pageSize: pagination.pageSize
526
+ }) : updater;
527
+ pagination.setPageIndex(next.pageIndex);
528
+ pagination.setPageSize(next.pageSize);
529
+ },
530
+ getCoreRowModel: getCoreRowModel(),
531
+ getSortedRowModel: getSortedRowModel(),
532
+ getFilteredRowModel: getFilteredRowModel(),
533
+ getPaginationRowModel: getPaginationRowModel(),
534
+ getRowId,
535
+ enableRowSelection: !!enableRowSelection
536
+ });
537
+ const { inlineContents, registerInlineContent, unregisterInlineContent } = useInlineContents();
538
+ const paginationState = useMemo(() => ({
539
+ canNextPage: table.getCanNextPage(),
540
+ canPrevPage: table.getCanPreviousPage(),
541
+ nextPage: () => table.nextPage(),
542
+ prevPage: () => table.previousPage(),
543
+ pageIndex: pagination.pageIndex,
544
+ pageCount: table.getPageCount(),
545
+ setPageIndex: pagination.setPageIndex,
546
+ pageSize: pagination.pageSize,
547
+ setPageSize: pagination.setPageSize
548
+ }), [table, pagination]);
549
+ return /* @__PURE__ */ jsx(DataTableContext, {
550
+ value: useMemo(() => ({
551
+ table,
552
+ mode: "client",
553
+ sorting,
554
+ filters,
555
+ search,
556
+ rowSelection,
557
+ isLoading: false,
558
+ setSorting,
559
+ setFilter,
560
+ clearFilter,
561
+ clearAllFilters,
562
+ setSearch,
563
+ clearSearch,
564
+ setRowSelection,
565
+ pagination: paginationState,
566
+ inlineContents,
567
+ registerInlineContent,
568
+ unregisterInlineContent
569
+ }), [
570
+ table,
571
+ sorting,
572
+ filters,
573
+ search,
574
+ rowSelection,
575
+ setSorting,
576
+ setFilter,
577
+ clearFilter,
578
+ clearAllFilters,
579
+ setSearch,
580
+ clearSearch,
581
+ setRowSelection,
582
+ paginationState,
583
+ inlineContents,
584
+ registerInlineContent,
585
+ unregisterInlineContent
586
+ ]),
587
+ children: /* @__PURE__ */ jsx("div", {
588
+ className,
589
+ children
590
+ })
591
+ });
592
+ }
593
+
594
+ //#endregion
595
+ //#region src/components/features/data-table/core/server-provider.tsx
596
+ function ServerProvider({ data, columns, isLoading, sorting, setSorting, filters, setFilter, clearFilter, clearAllFilters, search, setSearch, clearSearch, rowSelection, setRowSelection, pagination, getRowId, enableRowSelection = false, className, children }) {
597
+ const table = useReactTable({
598
+ data,
599
+ columns,
600
+ state: {
601
+ sorting,
602
+ rowSelection
603
+ },
604
+ onSortingChange: (updater) => {
605
+ setSorting(typeof updater === "function" ? updater(sorting) : updater);
606
+ },
607
+ onRowSelectionChange: (updater) => {
608
+ setRowSelection(typeof updater === "function" ? updater(rowSelection) : updater);
609
+ },
610
+ getCoreRowModel: getCoreRowModel(),
611
+ getRowId,
612
+ enableRowSelection: !!enableRowSelection,
613
+ manualPagination: true,
614
+ manualSorting: true,
615
+ manualFiltering: true
616
+ });
617
+ const { inlineContents, registerInlineContent, unregisterInlineContent } = useInlineContents();
618
+ const paginationState = useMemo(() => ({
619
+ canNextPage: pagination.hasNextPage,
620
+ canPrevPage: pagination.hasPrevPage,
621
+ nextPage: pagination.nextPage,
622
+ prevPage: pagination.prevPage,
623
+ pageSize: pagination.limit,
624
+ setPageSize: pagination.setLimit
625
+ }), [pagination]);
626
+ return /* @__PURE__ */ jsx(DataTableContext, {
627
+ value: useMemo(() => ({
628
+ table,
629
+ mode: "server",
630
+ sorting,
631
+ filters,
632
+ search,
633
+ rowSelection,
634
+ isLoading,
635
+ setSorting,
636
+ setFilter,
637
+ clearFilter,
638
+ clearAllFilters,
639
+ setSearch,
640
+ clearSearch,
641
+ setRowSelection,
642
+ pagination: paginationState,
643
+ inlineContents,
644
+ registerInlineContent,
645
+ unregisterInlineContent
646
+ }), [
647
+ table,
648
+ sorting,
649
+ filters,
650
+ search,
651
+ rowSelection,
652
+ isLoading,
653
+ setSorting,
654
+ setFilter,
655
+ clearFilter,
656
+ clearAllFilters,
657
+ setSearch,
658
+ clearSearch,
659
+ setRowSelection,
660
+ paginationState,
661
+ inlineContents,
662
+ registerInlineContent,
663
+ unregisterInlineContent
664
+ ]),
665
+ children: /* @__PURE__ */ jsx("div", {
666
+ className,
667
+ children
668
+ })
669
+ });
670
+ }
671
+
672
+ //#endregion
673
+ //#region ../shadcn/ui/badge.tsx
674
+ /**
675
+ * Vanilla shadcn/ui Badge Component
676
+ * Pure shadcn badge without Datum customizations
677
+ * For Datum-specific variants (sunglow, butter), import from @/modules/datum-ui
678
+ */
679
+ const badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2", {
680
+ variants: { variant: {
681
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
682
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
683
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
684
+ outline: "text-foreground"
685
+ } },
686
+ defaultVariants: { variant: "default" }
687
+ });
688
+ function Badge({ className, variant, ...props }) {
689
+ return /* @__PURE__ */ jsx("div", {
690
+ className: cn(badgeVariants({ variant }), className),
691
+ ...props
692
+ });
693
+ }
694
+
695
+ //#endregion
696
+ //#region src/components/features/data-table/filters/checkbox-filter.tsx
697
+ const MAX_VISIBLE_BADGES = 2;
698
+ function CheckboxFilter({ column, label, options, className, checkboxPopoverClassName }) {
699
+ const { filters, setFilter, clearFilter } = useDataTableContext();
700
+ const [open, setOpen] = useState(false);
701
+ const selectedValues = filters[column] ?? [];
702
+ const updateValues = (newValues) => {
703
+ if (newValues.length > 0) setFilter(column, newValues);
704
+ else clearFilter(column);
705
+ };
706
+ const handleToggle = (optionValue, checked) => {
707
+ updateValues(checked ? [...selectedValues, optionValue] : selectedValues.filter((v) => v !== optionValue));
708
+ };
709
+ const removeValue = (optionValue) => {
710
+ updateValues(selectedValues.filter((v) => v !== optionValue));
711
+ };
712
+ const visibleBadges = selectedValues.slice(0, MAX_VISIBLE_BADGES);
713
+ const remainingCount = selectedValues.length - MAX_VISIBLE_BADGES;
714
+ return /* @__PURE__ */ jsxs(Popover, {
715
+ open,
716
+ onOpenChange: setOpen,
717
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
718
+ asChild: true,
719
+ children: /* @__PURE__ */ jsxs(Button, {
720
+ variant: "outline",
721
+ className: cn("justify-between gap-1", className),
722
+ "data-slot": "dt-filter",
723
+ "data-testid": "dt-filter-trigger",
724
+ children: [selectedValues.length > 0 ? /* @__PURE__ */ jsxs("div", {
725
+ className: "flex items-center gap-1 flex-wrap",
726
+ children: [visibleBadges.map((val) => {
727
+ const opt = options.find((o) => o.value === val);
728
+ return /* @__PURE__ */ jsxs(Badge, {
729
+ variant: "secondary",
730
+ className: "text-xs px-1.5 py-0",
731
+ children: [opt?.label ?? val, /* @__PURE__ */ jsx("span", {
732
+ role: "button",
733
+ tabIndex: 0,
734
+ "aria-label": `Remove ${opt?.label ?? val}`,
735
+ className: "ml-1 rounded-sm hover:bg-muted",
736
+ onClick: (e) => {
737
+ e.stopPropagation();
738
+ removeValue(val);
739
+ },
740
+ onKeyDown: (e) => {
741
+ if (e.key === "Enter" || e.key === " ") {
742
+ e.preventDefault();
743
+ removeValue(val);
744
+ }
745
+ },
746
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
747
+ })]
748
+ }, val);
749
+ }), remainingCount > 0 && /* @__PURE__ */ jsxs("span", {
750
+ className: "text-xs text-muted-foreground",
751
+ children: [
752
+ "+",
753
+ remainingCount,
754
+ " ",
755
+ "more"
756
+ ]
757
+ })]
758
+ }) : /* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 opacity-50 ml-auto shrink-0" })]
759
+ })
760
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
761
+ className: cn("popover-content-width-full p-0", checkboxPopoverClassName),
762
+ align: "start",
763
+ children: [/* @__PURE__ */ jsxs("div", {
764
+ className: "flex items-center justify-between p-2 border-b",
765
+ children: [/* @__PURE__ */ jsx("span", {
766
+ className: "text-sm font-medium",
767
+ children: label
768
+ }), selectedValues.length > 0 && /* @__PURE__ */ jsx(Button, {
769
+ variant: "ghost",
770
+ size: "sm",
771
+ className: "h-auto p-1 text-xs",
772
+ onClick: () => clearFilter(column),
773
+ children: "Clear"
774
+ })]
775
+ }), /* @__PURE__ */ jsx("div", {
776
+ className: "max-h-48 overflow-y-auto p-2",
777
+ children: /* @__PURE__ */ jsx("div", {
778
+ className: "flex flex-col gap-2",
779
+ children: options.map((option) => /* @__PURE__ */ jsxs("div", {
780
+ className: "flex items-center gap-2",
781
+ children: [/* @__PURE__ */ jsx(Checkbox, {
782
+ id: `${column}-${option.value}`,
783
+ checked: selectedValues.includes(option.value),
784
+ onCheckedChange: (checked) => handleToggle(option.value, checked === true)
785
+ }), /* @__PURE__ */ jsx(Label, {
786
+ htmlFor: `${column}-${option.value}`,
787
+ className: "text-sm font-normal cursor-pointer",
788
+ children: option.label
789
+ })]
790
+ }, option.value))
791
+ })
792
+ })]
793
+ })]
794
+ });
795
+ }
796
+
797
+ //#endregion
798
+ //#region src/components/features/data-table/filters/date-picker-filter.tsx
799
+ function DatePickerFilter({ column, label, className, datePickerPopoverClassName, disableFuture, disablePast, minDate, maxDate }) {
800
+ const { filters, setFilter, clearFilter } = useDataTableContext();
801
+ const rawValue = filters[column];
802
+ const dateRange = useMemo(() => {
803
+ const date = rawValue ? new Date(rawValue) : void 0;
804
+ return {
805
+ from: date,
806
+ to: date
807
+ };
808
+ }, [rawValue]);
809
+ return /* @__PURE__ */ jsx("div", {
810
+ "data-slot": "dt-filter",
811
+ children: /* @__PURE__ */ jsx(CalendarDatePicker, {
812
+ date: dateRange,
813
+ numberOfMonths: 1,
814
+ closeOnSelect: true,
815
+ placeholder: label,
816
+ triggerClassName: className,
817
+ variant: "outline",
818
+ disableFuture,
819
+ disablePast,
820
+ minDate,
821
+ maxDate,
822
+ popoverClassName: datePickerPopoverClassName,
823
+ onDateSelect: (range) => {
824
+ if (range?.from) setFilter(column, range.from.toISOString());
825
+ else clearFilter(column);
826
+ }
827
+ })
828
+ });
829
+ }
830
+
831
+ //#endregion
832
+ //#region src/components/features/data-table/filters/select-filter.tsx
833
+ function SelectFilter({ column, label, options, placeholder, searchable = true, className, selectPopoverClassName }) {
834
+ const { filters, setFilter, clearFilter } = useDataTableContext();
835
+ const [open, setOpen] = useState(false);
836
+ const value = filters[column];
837
+ const selectedOption = options.find((o) => o.value === value);
838
+ return /* @__PURE__ */ jsxs(Popover, {
839
+ open,
840
+ onOpenChange: setOpen,
841
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
842
+ asChild: true,
843
+ children: /* @__PURE__ */ jsxs(Button, {
844
+ variant: "outline",
845
+ role: "combobox",
846
+ "aria-expanded": open,
847
+ className: cn("justify-between", className),
848
+ "data-slot": "dt-filter",
849
+ "data-testid": "dt-filter-trigger",
850
+ children: [/* @__PURE__ */ jsx("span", {
851
+ className: "truncate",
852
+ children: selectedOption ? selectedOption.label : placeholder ?? label
853
+ }), /* @__PURE__ */ jsxs("div", {
854
+ className: "ml-2 flex items-center gap-1",
855
+ children: [value && /* @__PURE__ */ jsx("span", {
856
+ role: "button",
857
+ "aria-label": `Clear ${label} filter`,
858
+ className: "rounded-sm opacity-70 hover:opacity-100",
859
+ onClick: (e) => {
860
+ e.stopPropagation();
861
+ clearFilter(column);
862
+ },
863
+ onKeyDown: (e) => {
864
+ if (e.key === "Enter" || e.key === " ") {
865
+ e.preventDefault();
866
+ e.stopPropagation();
867
+ clearFilter(column);
868
+ }
869
+ },
870
+ tabIndex: 0,
871
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
872
+ }), /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 opacity-50" })]
873
+ })]
874
+ })
875
+ }), /* @__PURE__ */ jsx(PopoverContent, {
876
+ className: cn("popover-content-width-full p-0", selectPopoverClassName),
877
+ align: "start",
878
+ children: /* @__PURE__ */ jsxs(Command, { children: [searchable && /* @__PURE__ */ jsx(CommandInput, { placeholder: `Search ${label.toLowerCase()}...` }), /* @__PURE__ */ jsxs(CommandList, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: "No results found." }), /* @__PURE__ */ jsx(CommandGroup, { children: options.map((option) => /* @__PURE__ */ jsxs(CommandItem, {
879
+ value: option.value,
880
+ onSelect: () => {
881
+ setFilter(column, option.value);
882
+ setOpen(false);
883
+ },
884
+ children: [/* @__PURE__ */ jsx(Check, { className: cn("mr-2 size-4", value === option.value ? "opacity-100" : "opacity-0") }), option.label]
885
+ }, option.value)) })] })] })
886
+ })]
887
+ });
888
+ }
889
+
890
+ //#endregion
891
+ //#region src/components/features/data-table/data-table.tsx
892
+ const DataTable = {
893
+ Client: ClientProvider,
894
+ Server: ServerProvider,
895
+ Content: DataTableContent,
896
+ InlineContent: DataTableInlineContent,
897
+ ColumnHeader: DataTableColumnHeader,
898
+ Pagination: DataTablePagination,
899
+ Search: DataTableSearch,
900
+ RowActions: DataTableRowActions,
901
+ BulkActions: DataTableBulkActions,
902
+ Loading: DataTableLoading,
903
+ SelectFilter,
904
+ CheckboxFilter,
905
+ DatePickerFilter
906
+ };
907
+
908
+ //#endregion
909
+ //#region src/components/features/data-table/hooks/use-data-table-client.ts
910
+ function useDataTableClient(options) {
911
+ const { data, columns, pageSize: initialPageSize = DEFAULT_PAGE_SIZE, getRowId, enableRowSelection = false, defaultSort = [], defaultFilters = {}, searchableColumns, searchFn, stateAdapter } = options;
912
+ const adapterState = useMemo(() => stateAdapter?.read() ?? {}, []);
913
+ const [sorting, setSorting] = useState(adapterState.sorting ?? defaultSort);
914
+ const [filters, setFilters] = useState(adapterState.filters ?? defaultFilters);
915
+ const [search, setSearch] = useState(adapterState.search ?? "");
916
+ const [rowSelection, setRowSelection] = useState({});
917
+ const [pageIndex, setPageIndex] = useState(adapterState.pageIndex ?? 0);
918
+ const [pageSize, setPageSize] = useState(adapterState.pageSize ?? initialPageSize);
919
+ const setFilter = useCallback((key, value) => {
920
+ setFilters((prev) => ({
921
+ ...prev,
922
+ [key]: value
923
+ }));
924
+ }, []);
925
+ const clearFilter = useCallback((key) => {
926
+ setFilters((prev) => {
927
+ const { [key]: _, ...rest } = prev;
928
+ return rest;
929
+ });
930
+ }, []);
931
+ const clearAllFilters = useCallback(() => {
932
+ setFilters({});
933
+ }, []);
934
+ const clearSearch = useCallback(() => {
935
+ setSearch("");
936
+ }, []);
937
+ useEffect(() => {
938
+ setRowSelection({});
939
+ }, [
940
+ sorting,
941
+ filters,
942
+ search,
943
+ pageIndex,
944
+ pageSize
945
+ ]);
946
+ useEffect(() => {
947
+ stateAdapter?.write({
948
+ sorting,
949
+ filters,
950
+ search,
951
+ pageIndex,
952
+ pageSize
953
+ });
954
+ }, [
955
+ stateAdapter,
956
+ sorting,
957
+ filters,
958
+ search,
959
+ pageIndex,
960
+ pageSize
961
+ ]);
962
+ const pagination = useMemo(() => ({
963
+ pageIndex,
964
+ pageSize,
965
+ setPageIndex,
966
+ setPageSize
967
+ }), [pageIndex, pageSize]);
968
+ const selectionEnabled = !!enableRowSelection;
969
+ const selectionOptions = typeof enableRowSelection === "object" ? enableRowSelection : void 0;
970
+ return {
971
+ data,
972
+ columns: useMemo(() => selectionEnabled ? withSelectionColumn(columns, selectionOptions) : columns, [
973
+ selectionEnabled,
974
+ columns,
975
+ selectionOptions
976
+ ]),
977
+ sorting,
978
+ setSorting,
979
+ filters,
980
+ setFilter,
981
+ clearFilter,
982
+ clearAllFilters,
983
+ search,
984
+ setSearch,
985
+ clearSearch,
986
+ rowSelection,
987
+ setRowSelection,
988
+ pagination,
989
+ getRowId,
990
+ enableRowSelection: selectionEnabled,
991
+ searchableColumns,
992
+ searchFn
993
+ };
994
+ }
995
+
996
+ //#endregion
997
+ //#region src/components/features/data-table/hooks/use-data-table-server.ts
998
+ function useDataTableServer(options) {
999
+ const { columns, fetchFn, transform, limit: initialLimit = DEFAULT_PAGE_SIZE, getRowId, enableRowSelection = false, defaultSort = [], defaultFilters = {}, stateAdapter } = options;
1000
+ const adapterState = useMemo(() => stateAdapter?.read() ?? {}, []);
1001
+ const [data, setData] = useState([]);
1002
+ const [isLoading, setIsLoading] = useState(true);
1003
+ const [sorting, setSorting] = useState(adapterState.sorting ?? defaultSort);
1004
+ const [filters, setFilters] = useState(adapterState.filters ?? defaultFilters);
1005
+ const [search, setSearch] = useState(adapterState.search ?? "");
1006
+ const [rowSelection, setRowSelection] = useState({});
1007
+ const [limit, setLimit] = useState(adapterState.limit ?? initialLimit);
1008
+ const [cursor, setCursor] = useState(void 0);
1009
+ const [nextCursor, setNextCursor] = useState(void 0);
1010
+ const [hasNextPage, setHasNextPage] = useState(false);
1011
+ const [cursorHistory, setCursorHistory] = useState([]);
1012
+ const fetchFnRef = useRef(fetchFn);
1013
+ fetchFnRef.current = fetchFn;
1014
+ const transformRef = useRef(transform);
1015
+ transformRef.current = transform;
1016
+ const setFilter = useCallback((key, value) => {
1017
+ setFilters((prev) => ({
1018
+ ...prev,
1019
+ [key]: value
1020
+ }));
1021
+ }, []);
1022
+ const clearFilter = useCallback((key) => {
1023
+ setFilters((prev) => {
1024
+ const { [key]: _, ...rest } = prev;
1025
+ return rest;
1026
+ });
1027
+ }, []);
1028
+ const clearAllFilters = useCallback(() => {
1029
+ setFilters({});
1030
+ }, []);
1031
+ const clearSearch = useCallback(() => {
1032
+ setSearch("");
1033
+ }, []);
1034
+ const resetPagination = useCallback(() => {
1035
+ setCursor(void 0);
1036
+ setNextCursor(void 0);
1037
+ setCursorHistory([]);
1038
+ setHasNextPage(false);
1039
+ setRowSelection({});
1040
+ }, []);
1041
+ useEffect(() => {
1042
+ resetPagination();
1043
+ }, [
1044
+ sorting,
1045
+ filters,
1046
+ search,
1047
+ limit,
1048
+ resetPagination
1049
+ ]);
1050
+ useEffect(() => {
1051
+ let cancelled = false;
1052
+ setIsLoading(true);
1053
+ fetchFnRef.current({
1054
+ cursor,
1055
+ limit,
1056
+ sorting,
1057
+ filters,
1058
+ search
1059
+ }).then((response) => {
1060
+ if (cancelled) return;
1061
+ const result = transformRef.current(response);
1062
+ setData(result.data);
1063
+ setNextCursor(result.cursor);
1064
+ setHasNextPage(result.hasNextPage);
1065
+ }).catch(() => {
1066
+ if (cancelled) return;
1067
+ setData([]);
1068
+ setNextCursor(void 0);
1069
+ setHasNextPage(false);
1070
+ }).finally(() => {
1071
+ if (!cancelled) setIsLoading(false);
1072
+ });
1073
+ return () => {
1074
+ cancelled = true;
1075
+ };
1076
+ }, [
1077
+ cursor,
1078
+ limit,
1079
+ sorting,
1080
+ filters,
1081
+ search
1082
+ ]);
1083
+ useEffect(() => {
1084
+ stateAdapter?.write({
1085
+ sorting,
1086
+ filters,
1087
+ search,
1088
+ limit
1089
+ });
1090
+ }, [
1091
+ stateAdapter,
1092
+ sorting,
1093
+ filters,
1094
+ search,
1095
+ limit
1096
+ ]);
1097
+ const hasPrevPage = cursorHistory.length > 0;
1098
+ const nextPage = useCallback(() => {
1099
+ if (nextCursor && hasNextPage) {
1100
+ setCursorHistory((prev) => [...prev, cursor ?? ""]);
1101
+ setCursor(nextCursor);
1102
+ setRowSelection({});
1103
+ }
1104
+ }, [
1105
+ nextCursor,
1106
+ hasNextPage,
1107
+ cursor
1108
+ ]);
1109
+ const prevPage = useCallback(() => {
1110
+ setCursorHistory((prev) => {
1111
+ const newHistory = [...prev];
1112
+ const previousCursor = newHistory.pop();
1113
+ setCursor(previousCursor === "" ? void 0 : previousCursor);
1114
+ return newHistory;
1115
+ });
1116
+ setRowSelection({});
1117
+ }, []);
1118
+ const pagination = useMemo(() => ({
1119
+ cursor,
1120
+ limit,
1121
+ hasNextPage,
1122
+ hasPrevPage,
1123
+ nextPage,
1124
+ prevPage,
1125
+ setLimit
1126
+ }), [
1127
+ cursor,
1128
+ limit,
1129
+ hasNextPage,
1130
+ hasPrevPage,
1131
+ nextPage,
1132
+ prevPage
1133
+ ]);
1134
+ const selectionEnabled = !!enableRowSelection;
1135
+ const selectionOptions = typeof enableRowSelection === "object" ? enableRowSelection : void 0;
1136
+ return {
1137
+ data,
1138
+ columns: useMemo(() => selectionEnabled ? withSelectionColumn(columns, selectionOptions) : columns, [
1139
+ selectionEnabled,
1140
+ columns,
1141
+ selectionOptions
1142
+ ]),
1143
+ isLoading,
1144
+ sorting,
1145
+ setSorting,
1146
+ filters,
1147
+ setFilter,
1148
+ clearFilter,
1149
+ clearAllFilters,
1150
+ search,
1151
+ setSearch,
1152
+ clearSearch,
1153
+ rowSelection,
1154
+ setRowSelection,
1155
+ pagination,
1156
+ getRowId,
1157
+ enableRowSelection: selectionEnabled
1158
+ };
1159
+ }
1160
+
1161
+ //#endregion
1162
+ export { DEFAULT_DEBOUNCE_MS, DEFAULT_LOADING_ROWS, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES, DataTable, createSelectionColumn, useDataTableClient, useDataTableContext, useDataTableServer, useNuqsAdapter };