@eifi1/ui-kit 0.4.0

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/README.md +219 -0
  2. package/dist/components/account-settings.d.ts +71 -0
  3. package/dist/components/account-settings.js +159 -0
  4. package/dist/components/account-settings.js.map +1 -0
  5. package/dist/components/alert-banner.d.ts +20 -0
  6. package/dist/components/alert-banner.js +39 -0
  7. package/dist/components/alert-banner.js.map +1 -0
  8. package/dist/components/amount-input.d.ts +117 -0
  9. package/dist/components/amount-input.js +243 -0
  10. package/dist/components/amount-input.js.map +1 -0
  11. package/dist/components/calculator.d.ts +42 -0
  12. package/dist/components/calculator.js +161 -0
  13. package/dist/components/calculator.js.map +1 -0
  14. package/dist/components/chart.d.ts +58 -0
  15. package/dist/components/chart.js +158 -0
  16. package/dist/components/chart.js.map +1 -0
  17. package/dist/components/combobox-core.d.ts +77 -0
  18. package/dist/components/combobox-core.js +263 -0
  19. package/dist/components/combobox-core.js.map +1 -0
  20. package/dist/components/combobox.d.ts +141 -0
  21. package/dist/components/combobox.js +550 -0
  22. package/dist/components/combobox.js.map +1 -0
  23. package/dist/components/currency-select.d.ts +37 -0
  24. package/dist/components/currency-select.js +131 -0
  25. package/dist/components/currency-select.js.map +1 -0
  26. package/dist/components/data-table-filter-popover.d.ts +28 -0
  27. package/dist/components/data-table-filter-popover.js +201 -0
  28. package/dist/components/data-table-filter-popover.js.map +1 -0
  29. package/dist/components/data-table-filters.d.ts +5 -0
  30. package/dist/components/data-table-filters.js +118 -0
  31. package/dist/components/data-table-filters.js.map +1 -0
  32. package/dist/components/data-table-labels.d.ts +33 -0
  33. package/dist/components/data-table-labels.js +53 -0
  34. package/dist/components/data-table-labels.js.map +1 -0
  35. package/dist/components/data-table-pagination.d.ts +18 -0
  36. package/dist/components/data-table-pagination.js +92 -0
  37. package/dist/components/data-table-pagination.js.map +1 -0
  38. package/dist/components/data-table-sort.d.ts +23 -0
  39. package/dist/components/data-table-sort.js +55 -0
  40. package/dist/components/data-table-sort.js.map +1 -0
  41. package/dist/components/data-table.d.ts +5 -0
  42. package/dist/components/data-table.js +926 -0
  43. package/dist/components/data-table.js.map +1 -0
  44. package/dist/components/date-picker.d.ts +85 -0
  45. package/dist/components/date-picker.js +253 -0
  46. package/dist/components/date-picker.js.map +1 -0
  47. package/dist/components/dropdown.d.ts +98 -0
  48. package/dist/components/dropdown.js +122 -0
  49. package/dist/components/dropdown.js.map +1 -0
  50. package/dist/components/entity-combobox.d.ts +49 -0
  51. package/dist/components/entity-combobox.js +115 -0
  52. package/dist/components/entity-combobox.js.map +1 -0
  53. package/dist/components/file-dropzone.d.ts +18 -0
  54. package/dist/components/file-dropzone.js +101 -0
  55. package/dist/components/file-dropzone.js.map +1 -0
  56. package/dist/components/full-bleed-dialog.d.ts +57 -0
  57. package/dist/components/full-bleed-dialog.js +68 -0
  58. package/dist/components/full-bleed-dialog.js.map +1 -0
  59. package/dist/components/grouped-picker.d.ts +37 -0
  60. package/dist/components/grouped-picker.js +87 -0
  61. package/dist/components/grouped-picker.js.map +1 -0
  62. package/dist/components/hover-menu.d.ts +16 -0
  63. package/dist/components/hover-menu.js +113 -0
  64. package/dist/components/hover-menu.js.map +1 -0
  65. package/dist/components/mini-calendar.d.ts +33 -0
  66. package/dist/components/mini-calendar.js +133 -0
  67. package/dist/components/mini-calendar.js.map +1 -0
  68. package/dist/components/modal.d.ts +76 -0
  69. package/dist/components/modal.js +156 -0
  70. package/dist/components/modal.js.map +1 -0
  71. package/dist/components/multi-entity-combobox.d.ts +47 -0
  72. package/dist/components/multi-entity-combobox.js +116 -0
  73. package/dist/components/multi-entity-combobox.js.map +1 -0
  74. package/dist/components/multi-select.d.ts +39 -0
  75. package/dist/components/multi-select.js +132 -0
  76. package/dist/components/multi-select.js.map +1 -0
  77. package/dist/components/number-input.d.ts +74 -0
  78. package/dist/components/number-input.js +114 -0
  79. package/dist/components/number-input.js.map +1 -0
  80. package/dist/components/numpad-sheet.d.ts +45 -0
  81. package/dist/components/numpad-sheet.js +102 -0
  82. package/dist/components/numpad-sheet.js.map +1 -0
  83. package/dist/components/picker-sheet.d.ts +44 -0
  84. package/dist/components/picker-sheet.js +78 -0
  85. package/dist/components/picker-sheet.js.map +1 -0
  86. package/dist/components/popover.d.ts +20 -0
  87. package/dist/components/popover.js +46 -0
  88. package/dist/components/popover.js.map +1 -0
  89. package/dist/components/search-field.d.ts +52 -0
  90. package/dist/components/search-field.js +64 -0
  91. package/dist/components/search-field.js.map +1 -0
  92. package/dist/components/settings-fields.d.ts +39 -0
  93. package/dist/components/settings-fields.js +17 -0
  94. package/dist/components/settings-fields.js.map +1 -0
  95. package/dist/components/swipeable-row.d.ts +60 -0
  96. package/dist/components/swipeable-row.js +115 -0
  97. package/dist/components/swipeable-row.js.map +1 -0
  98. package/dist/components/toggle-group.d.ts +34 -0
  99. package/dist/components/toggle-group.js +90 -0
  100. package/dist/components/toggle-group.js.map +1 -0
  101. package/dist/components/tooltip.d.ts +86 -0
  102. package/dist/components/tooltip.js +171 -0
  103. package/dist/components/tooltip.js.map +1 -0
  104. package/dist/components/ui.d.ts +239 -0
  105. package/dist/components/ui.js +512 -0
  106. package/dist/components/ui.js.map +1 -0
  107. package/dist/components/use-mobile-reveal.d.ts +31 -0
  108. package/dist/components/use-mobile-reveal.js +33 -0
  109. package/dist/components/use-mobile-reveal.js.map +1 -0
  110. package/dist/components/use-table-state.d.ts +54 -0
  111. package/dist/components/use-table-state.js +153 -0
  112. package/dist/components/use-table-state.js.map +1 -0
  113. package/dist/components/user-avatar.d.ts +23 -0
  114. package/dist/components/user-avatar.js +33 -0
  115. package/dist/components/user-avatar.js.map +1 -0
  116. package/dist/components/wizard-stepper.d.ts +14 -0
  117. package/dist/components/wizard-stepper.js +27 -0
  118. package/dist/components/wizard-stepper.js.map +1 -0
  119. package/dist/data-table-filters-aXyf0Xub.d.ts +261 -0
  120. package/dist/feedback/feedback-attachment.d.ts +63 -0
  121. package/dist/feedback/feedback-attachment.js +160 -0
  122. package/dist/feedback/feedback-attachment.js.map +1 -0
  123. package/dist/feedback/feedback-dialog.d.ts +72 -0
  124. package/dist/feedback/feedback-dialog.js +109 -0
  125. package/dist/feedback/feedback-dialog.js.map +1 -0
  126. package/dist/feedback/feedback-inbox.d.ts +226 -0
  127. package/dist/feedback/feedback-inbox.js +315 -0
  128. package/dist/feedback/feedback-inbox.js.map +1 -0
  129. package/dist/hooks/use-anchored-panel.d.ts +80 -0
  130. package/dist/hooks/use-anchored-panel.js +71 -0
  131. package/dist/hooks/use-anchored-panel.js.map +1 -0
  132. package/dist/hooks/use-anchored-rect.d.ts +24 -0
  133. package/dist/hooks/use-anchored-rect.js +40 -0
  134. package/dist/hooks/use-anchored-rect.js.map +1 -0
  135. package/dist/hooks/use-body-scroll-lock.d.ts +9 -0
  136. package/dist/hooks/use-body-scroll-lock.js +41 -0
  137. package/dist/hooks/use-body-scroll-lock.js.map +1 -0
  138. package/dist/hooks/use-close-transition.d.ts +41 -0
  139. package/dist/hooks/use-close-transition.js +36 -0
  140. package/dist/hooks/use-close-transition.js.map +1 -0
  141. package/dist/hooks/use-dismiss.d.ts +34 -0
  142. package/dist/hooks/use-dismiss.js +41 -0
  143. package/dist/hooks/use-dismiss.js.map +1 -0
  144. package/dist/hooks/use-media-query.d.ts +8 -0
  145. package/dist/hooks/use-media-query.js +18 -0
  146. package/dist/hooks/use-media-query.js.map +1 -0
  147. package/dist/hooks/use-overlay-history.d.ts +10 -0
  148. package/dist/hooks/use-overlay-history.js +99 -0
  149. package/dist/hooks/use-overlay-history.js.map +1 -0
  150. package/dist/hooks/use-row-swipe.d.ts +30 -0
  151. package/dist/hooks/use-row-swipe.js +128 -0
  152. package/dist/hooks/use-row-swipe.js.map +1 -0
  153. package/dist/index.d.ts +75 -0
  154. package/dist/index.js +90 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/lib/calc.d.ts +89 -0
  157. package/dist/lib/calc.js +180 -0
  158. package/dist/lib/calc.js.map +1 -0
  159. package/dist/lib/cn.d.ts +6 -0
  160. package/dist/lib/cn.js +7 -0
  161. package/dist/lib/cn.js.map +1 -0
  162. package/dist/lib/dates.d.ts +84 -0
  163. package/dist/lib/dates.js +108 -0
  164. package/dist/lib/dates.js.map +1 -0
  165. package/dist/lib/logger.d.ts +17 -0
  166. package/dist/lib/logger.js +30 -0
  167. package/dist/lib/logger.js.map +1 -0
  168. package/dist/lib/safe-storage.d.ts +23 -0
  169. package/dist/lib/safe-storage.js +20 -0
  170. package/dist/lib/safe-storage.js.map +1 -0
  171. package/dist/search/command-palette.d.ts +71 -0
  172. package/dist/search/command-palette.js +198 -0
  173. package/dist/search/command-palette.js.map +1 -0
  174. package/dist/shell/app-shell.d.ts +64 -0
  175. package/dist/shell/app-shell.js +237 -0
  176. package/dist/shell/app-shell.js.map +1 -0
  177. package/dist/shell/option-switcher-menu.d.ts +24 -0
  178. package/dist/shell/option-switcher-menu.js +44 -0
  179. package/dist/shell/option-switcher-menu.js.map +1 -0
  180. package/dist/shell/role-switcher.d.ts +19 -0
  181. package/dist/shell/role-switcher.js +28 -0
  182. package/dist/shell/role-switcher.js.map +1 -0
  183. package/dist/shell/top-bar.d.ts +15 -0
  184. package/dist/shell/top-bar.js +29 -0
  185. package/dist/shell/top-bar.js.map +1 -0
  186. package/dist/shell/topbar-action-menu.d.ts +44 -0
  187. package/dist/shell/topbar-action-menu.js +49 -0
  188. package/dist/shell/topbar-action-menu.js.map +1 -0
  189. package/dist/shell/topbar-controls.d.ts +54 -0
  190. package/dist/shell/topbar-controls.js +152 -0
  191. package/dist/shell/topbar-controls.js.map +1 -0
  192. package/dist/theme/chart-palette.d.ts +60 -0
  193. package/dist/theme/chart-palette.js +79 -0
  194. package/dist/theme/chart-palette.js.map +1 -0
  195. package/dist/theme/palette-presets.d.ts +39 -0
  196. package/dist/theme/palette-presets.js +440 -0
  197. package/dist/theme/palette-presets.js.map +1 -0
  198. package/dist/theme/palette-store.d.ts +45 -0
  199. package/dist/theme/palette-store.js +52 -0
  200. package/dist/theme/palette-store.js.map +1 -0
  201. package/dist/theme/theme-store.d.ts +57 -0
  202. package/dist/theme/theme-store.js +96 -0
  203. package/dist/theme/theme-store.js.map +1 -0
  204. package/dist/tour/tour.d.ts +104 -0
  205. package/dist/tour/tour.js +351 -0
  206. package/dist/tour/tour.js.map +1 -0
  207. package/dist/wizard/stepper-nav.d.ts +30 -0
  208. package/dist/wizard/stepper-nav.js +146 -0
  209. package/dist/wizard/stepper-nav.js.map +1 -0
  210. package/dist/wizard/types.d.ts +124 -0
  211. package/dist/wizard/types.js +24 -0
  212. package/dist/wizard/types.js.map +1 -0
  213. package/dist/wizard/use-wizard.d.ts +6 -0
  214. package/dist/wizard/use-wizard.js +222 -0
  215. package/dist/wizard/use-wizard.js.map +1 -0
  216. package/dist/wizard/validation.d.ts +23 -0
  217. package/dist/wizard/validation.js +13 -0
  218. package/dist/wizard/validation.js.map +1 -0
  219. package/dist/wizard/wizard-context.d.ts +19 -0
  220. package/dist/wizard/wizard-context.js +15 -0
  221. package/dist/wizard/wizard-context.js.map +1 -0
  222. package/dist/wizard/wizard-step.d.ts +11 -0
  223. package/dist/wizard/wizard-step.js +12 -0
  224. package/dist/wizard/wizard-step.js.map +1 -0
  225. package/dist/wizard/wizard-summary.d.ts +18 -0
  226. package/dist/wizard/wizard-summary.js +41 -0
  227. package/dist/wizard/wizard-summary.js.map +1 -0
  228. package/package.json +87 -0
  229. package/tokens.css +254 -0
@@ -0,0 +1,926 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment as Fragment2, useMemo, useRef, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import {
5
+ ArrowDown,
6
+ ArrowUp,
7
+ ArrowUpDown,
8
+ ChevronDown,
9
+ ChevronRight,
10
+ Filter,
11
+ X
12
+ } from "lucide-react";
13
+ import { useSearchParams } from "react-router";
14
+ import { Card } from "./ui.js";
15
+ import { cn } from "../lib/cn.js";
16
+ import {
17
+ defaultFilterState,
18
+ isFilterActive,
19
+ resolveFilter,
20
+ rowMatches
21
+ } from "./data-table-filters.js";
22
+ import { nextSorts } from "./data-table-sort.js";
23
+ import { FilterPopover } from "./data-table-filter-popover.js";
24
+ import { Pagination } from "./data-table-pagination.js";
25
+ import { SwipeableRow } from "./swipeable-row.js";
26
+ import { useBackdropClose } from "./modal.js";
27
+ import { useBodyScrollLock } from "../hooks/use-body-scroll-lock.js";
28
+ import { useOverlayHistory } from "../hooks/use-overlay-history.js";
29
+ import { FullBleedDialog } from "./full-bleed-dialog.js";
30
+ import { Popover } from "./popover.js";
31
+ import { useMediaQuery } from "../hooks/use-media-query.js";
32
+ import { resolveDataTableLabels } from "./data-table-labels.js";
33
+ import { Tooltip } from "./tooltip.js";
34
+ function cleanAttrs(attrs) {
35
+ if (!attrs) return void 0;
36
+ const out = {};
37
+ for (const [k, v] of Object.entries(attrs)) if (v !== void 0) out[k] = v;
38
+ return out;
39
+ }
40
+ const isPlainLeftClick = (e) => e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
41
+ function RowLink({
42
+ href,
43
+ onActivate,
44
+ className,
45
+ children,
46
+ ...rest
47
+ }) {
48
+ return /* @__PURE__ */ jsx(
49
+ "a",
50
+ {
51
+ ...rest,
52
+ href,
53
+ draggable: false,
54
+ className,
55
+ onClickCapture: (e) => {
56
+ if (isPlainLeftClick(e)) e.preventDefault();
57
+ },
58
+ onClick: (e) => {
59
+ if (!isPlainLeftClick(e)) {
60
+ e.stopPropagation();
61
+ return;
62
+ }
63
+ onActivate?.();
64
+ },
65
+ children
66
+ }
67
+ );
68
+ }
69
+ import { DEFAULT_PERSIST_PREFIX, useTableState } from "./use-table-state.js";
70
+ import { useMobileReveal } from "./use-mobile-reveal.js";
71
+ function DataTable({
72
+ rows,
73
+ columns,
74
+ rowKey,
75
+ defaultPageSize = 25,
76
+ onRowClick,
77
+ rowHref,
78
+ expandedRow,
79
+ isExpanded,
80
+ rowClassName,
81
+ rowAttributes,
82
+ paginated = true,
83
+ empty,
84
+ storageKey,
85
+ urlSync = false,
86
+ fillHeight = false,
87
+ maxBodyHeight = "calc(100dvh - 12rem)",
88
+ serverPagination,
89
+ filters: filtersProp,
90
+ onFiltersChange,
91
+ sorts: sortsProp,
92
+ onSortsChange,
93
+ selection,
94
+ labels: labelsProp,
95
+ locale,
96
+ storageKeyPrefix = DEFAULT_PERSIST_PREFIX,
97
+ mobileExpandAsDialog = false,
98
+ mobileGroupBy,
99
+ mobileGroupLabel,
100
+ mobileCard,
101
+ mobileSwipeActions,
102
+ leadingRow
103
+ }) {
104
+ const isServer = !!serverPagination;
105
+ const labels = resolveDataTableLabels(labelsProp);
106
+ const [, setSearchParams] = useSearchParams();
107
+ const {
108
+ sorts,
109
+ filters,
110
+ setInternalSorts,
111
+ setInternalFilters,
112
+ page,
113
+ setPage,
114
+ pageSize,
115
+ setPageSize,
116
+ widths,
117
+ setWidths,
118
+ hiddenCols,
119
+ setHiddenCols,
120
+ showSettings,
121
+ setShowSettings,
122
+ headRefs
123
+ } = useTableState({
124
+ columns,
125
+ storageKey,
126
+ storageKeyPrefix,
127
+ urlSync,
128
+ setSearchParams,
129
+ defaultPageSize,
130
+ sortsProp,
131
+ filtersProp
132
+ });
133
+ const selectOptionsByKey = useMemo(() => {
134
+ const map = {};
135
+ for (const col of columns) {
136
+ const f = resolveFilter(col);
137
+ if (!f || f.type !== "select") continue;
138
+ if (f.options && f.options.length > 0) {
139
+ map[col.key] = f.options.map((o) => ({ value: o.value, label: o.label ?? o.value }));
140
+ continue;
141
+ }
142
+ const seen = /* @__PURE__ */ new Set();
143
+ for (const row of rows) {
144
+ const v = f.getValue(row);
145
+ if (v && !seen.has(v)) seen.add(v);
146
+ }
147
+ map[col.key] = Array.from(seen).sort().map((v) => ({ value: v, label: v }));
148
+ }
149
+ return map;
150
+ }, [columns, rows]);
151
+ const filtered = useMemo(() => {
152
+ if (isServer) return rows;
153
+ let result = rows;
154
+ for (const col of columns) {
155
+ const state = filters[col.key];
156
+ if (!state || !isFilterActive(state)) continue;
157
+ result = result.filter((row) => rowMatches(col, row, state));
158
+ }
159
+ return result;
160
+ }, [rows, columns, filters, isServer]);
161
+ const sorted = useMemo(() => {
162
+ if (isServer) return filtered;
163
+ const chain = sorts.map((s) => {
164
+ const col = columns.find((c) => c.key === s.key);
165
+ return col?.sortBy ? { sortBy: col.sortBy, dir: s.dir } : null;
166
+ }).filter((c) => c !== null);
167
+ if (!chain.length) return filtered;
168
+ const copy = [...filtered];
169
+ copy.sort((a, b) => {
170
+ for (const { sortBy, dir } of chain) {
171
+ const av = sortBy(a);
172
+ const bv = sortBy(b);
173
+ if (av == null && bv == null) continue;
174
+ if (av == null) return 1;
175
+ if (bv == null) return -1;
176
+ if (av < bv) return dir === "asc" ? -1 : 1;
177
+ if (av > bv) return dir === "asc" ? 1 : -1;
178
+ }
179
+ return 0;
180
+ });
181
+ return copy;
182
+ }, [filtered, sorts, columns, isServer]);
183
+ const unpaged = !isServer && !paginated;
184
+ const effectivePageSize = isServer ? serverPagination.pageSize : unpaged || pageSize === Infinity ? sorted.length || 1 : pageSize;
185
+ const paginationTotal = isServer ? serverPagination.total : sorted.length;
186
+ const paginationPageSize = isServer ? serverPagination.pageSize : pageSize;
187
+ const totalPages = isServer ? Math.max(1, Math.ceil(serverPagination.total / Math.max(1, serverPagination.pageSize))) : Math.max(1, Math.ceil(sorted.length / effectivePageSize));
188
+ const safePage = isServer ? Math.min(Math.max(0, serverPagination.page), totalPages - 1) : Math.min(page, totalPages - 1);
189
+ const slice = isServer || unpaged || pageSize === Infinity ? sorted : sorted.slice(safePage * effectivePageSize, safePage * effectivePageSize + effectivePageSize);
190
+ const toggleSort = (key, additive) => {
191
+ const next = nextSorts(sorts, key, additive);
192
+ if (onSortsChange) onSortsChange(next);
193
+ else setInternalSorts(next);
194
+ };
195
+ const setFilterValue = (key, value) => {
196
+ if (onFiltersChange) {
197
+ onFiltersChange({ ...filters, [key]: value });
198
+ return;
199
+ }
200
+ setInternalFilters((s) => ({ ...s, [key]: value }));
201
+ setPage(0);
202
+ };
203
+ const clearFilter = (key) => {
204
+ if (onFiltersChange) {
205
+ const rest = { ...filters };
206
+ delete rest[key];
207
+ onFiltersChange(rest);
208
+ return;
209
+ }
210
+ setInternalFilters((s) => {
211
+ const rest = { ...s };
212
+ delete rest[key];
213
+ return rest;
214
+ });
215
+ setPage(0);
216
+ };
217
+ const clearAllFilters = () => {
218
+ const keys = columns.filter((col) => !isServer || onFiltersChange ? resolveFilter(col) : null).map((col) => col.key);
219
+ if (!keys.length) return;
220
+ if (onFiltersChange) {
221
+ const rest = { ...filters };
222
+ for (const k of keys) delete rest[k];
223
+ onFiltersChange(rest);
224
+ return;
225
+ }
226
+ setInternalFilters((s) => {
227
+ const rest = { ...s };
228
+ for (const k of keys) delete rest[k];
229
+ return rest;
230
+ });
231
+ setPage(0);
232
+ };
233
+ const visibleColumns = useMemo(
234
+ () => columns.filter((c) => !hiddenCols.has(c.key)),
235
+ [columns, hiddenCols]
236
+ );
237
+ const linkColumn = rowHref ? visibleColumns.find((c) => c.mobilePrimary && !c.noRowLink) ?? visibleColumns.find((c) => !c.noRowLink) : void 0;
238
+ const startResize = (e, key) => {
239
+ e.preventDefault();
240
+ e.stopPropagation();
241
+ const th = headRefs.get(key);
242
+ const startWidth = th?.getBoundingClientRect().width ?? 100;
243
+ const startX = e.clientX;
244
+ const onMove = (ev) => {
245
+ const next = Math.max(40, Math.round(startWidth + (ev.clientX - startX)));
246
+ setWidths((w) => ({ ...w, [key]: next }));
247
+ };
248
+ const onUp = () => {
249
+ window.removeEventListener("mousemove", onMove);
250
+ window.removeEventListener("mouseup", onUp);
251
+ document.body.style.userSelect = "";
252
+ document.body.style.cursor = "";
253
+ };
254
+ document.body.style.userSelect = "none";
255
+ document.body.style.cursor = "col-resize";
256
+ window.addEventListener("mousemove", onMove);
257
+ window.addEventListener("mouseup", onUp);
258
+ };
259
+ const autoSizeAll = () => {
260
+ setWidths({});
261
+ requestAnimationFrame(() => {
262
+ const next = {};
263
+ const cols = columns.filter((c) => !hiddenCols.has(c.key));
264
+ cols.forEach((col, idx) => {
265
+ if (idx === cols.length - 1) return;
266
+ const th = headRefs.get(col.key);
267
+ if (th) next[col.key] = Math.ceil(th.getBoundingClientRect().width);
268
+ });
269
+ setWidths(next);
270
+ });
271
+ };
272
+ const visibleCount = visibleColumns.length;
273
+ const totalCount = columns.length;
274
+ const totalColSpan = visibleCount + (selection ? 1 : 0);
275
+ const columnsCountLabel = `${labels.columns} (${visibleCount}/${totalCount})`;
276
+ const isMdUp = useMediaQuery("(min-width: 768px)", true);
277
+ const mobileColumns = visibleColumns.filter((c) => !c.mobileHidden);
278
+ const mobilePrimaryCol = mobileColumns.find((c) => c.mobilePrimary) ?? mobileColumns[0] ?? null;
279
+ const mobileSecondaryColumns = mobileColumns.filter((c) => c !== mobilePrimaryCol);
280
+ const mobileCardLinkable = mobileColumns.every((c) => !c.noRowLink);
281
+ const { mobileSlice, canRevealMoreMobile, loadMoreRef } = useMobileReveal({
282
+ rows: sorted,
283
+ slice,
284
+ isServer,
285
+ isMdUp,
286
+ defaultPageSize
287
+ });
288
+ const selectionAnchor = useRef(null);
289
+ const canSelect = (row) => !!selection && (selection.isSelectable?.(row) ?? true);
290
+ const selectRange = (toIndex) => {
291
+ if (!selection || selectionAnchor.current === null) return false;
292
+ const [lo, hi] = [selectionAnchor.current, toIndex].sort((a, b) => a - b);
293
+ for (let i = lo; i <= hi; i++) {
294
+ const r = slice[i];
295
+ if (r && canSelect(r)) selection.onToggle(r, true);
296
+ }
297
+ return true;
298
+ };
299
+ const mobileDialogRow = mobileExpandAsDialog && !isMdUp ? mobileSlice.find((r) => isExpanded?.(r)) : void 0;
300
+ const mobileDialogContent = mobileDialogRow ? expandedRow?.(mobileDialogRow) : null;
301
+ const dialogOpen = !!(mobileDialogRow && mobileDialogContent);
302
+ const closeDialog = () => {
303
+ if (mobileDialogRow) onRowClick?.(mobileDialogRow);
304
+ };
305
+ const renderMobileRow = (row) => {
306
+ const expanded = isExpanded?.(row) ?? false;
307
+ const expansion = expanded ? expandedRow?.(row) : null;
308
+ const tint = rowClassName?.(row);
309
+ const interactive = !!onRowClick;
310
+ const opensDetail = interactive && !!expandedRow;
311
+ const swipe = mobileSwipeActions?.(row);
312
+ const swipeEnabled = !!swipe && !expanded;
313
+ const href = mobileCardLinkable ? rowHref?.(row) : void 0;
314
+ const cardClass = cn(
315
+ "w-full px-4 py-3 text-left flex items-center gap-3",
316
+ // Live #320's other half: the card acknowledges the touch before the sheet
317
+ // arrives. On a cold route the data can take a beat, and an unacknowledged tap
318
+ // reads as "did that register?" — which is most of what "abrupt" means here.
319
+ // `origin-center` + a 0.5% squeeze is deliberately almost subliminal: this fires
320
+ // on every row of a long list, so anything larger becomes the list's personality
321
+ // rather than feedback. `transition-transform` alone, so the existing background
322
+ // flip stays instant.
323
+ interactive && "cursor-pointer transition-transform duration-100 active:scale-[0.995] active:bg-slate-50 dark:active:bg-slate-800/40 motion-reduce:transition-none motion-reduce:active:scale-100"
324
+ );
325
+ const cardInner = /* @__PURE__ */ jsxs(Fragment, { children: [
326
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: mobileCard ? mobileCard(row) : /* @__PURE__ */ jsxs(Fragment, { children: [
327
+ mobilePrimaryCol && /* @__PURE__ */ jsx("div", { className: "font-medium", children: mobilePrimaryCol.cell(row) }),
328
+ mobileSecondaryColumns.length > 0 && /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-sm", children: mobileSecondaryColumns.map((col) => /* @__PURE__ */ jsxs(Fragment2, { children: [
329
+ /* @__PURE__ */ jsx("dt", { className: "text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400 self-center", children: col.header }),
330
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0 text-slate-700 dark:text-slate-200 self-center", children: col.cell(row) })
331
+ ] }, col.key)) })
332
+ ] }) }),
333
+ opensDetail && // `aria-hidden`: the row already announces itself as a button with
334
+ // `aria-expanded`, so the icon would only add a second, wordless stop.
335
+ (mobileExpandAsDialog ? /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": true, className: "size-4 shrink-0 text-slate-400" }) : /* @__PURE__ */ jsx(
336
+ ChevronDown,
337
+ {
338
+ "aria-hidden": true,
339
+ className: cn(
340
+ "size-4 shrink-0 text-slate-400 transition-transform",
341
+ expanded && "rotate-180"
342
+ )
343
+ }
344
+ ))
345
+ ] });
346
+ const body = interactive && href ? (
347
+ // A card with a URL of its own is a LINK, not a div wearing role="button"
348
+ // (feedback #451): the phone's long-press "open in new tab" and a screen
349
+ // reader's link semantics both come free, and the fake role goes away.
350
+ /* @__PURE__ */ jsx(
351
+ RowLink,
352
+ {
353
+ href,
354
+ onActivate: () => onRowClick(row),
355
+ "aria-expanded": expandedRow ? expanded : void 0,
356
+ className: cardClass,
357
+ onKeyDown: (e) => {
358
+ if (e.key !== " ") return;
359
+ e.preventDefault();
360
+ onRowClick(row);
361
+ },
362
+ children: cardInner
363
+ }
364
+ )
365
+ ) : /* @__PURE__ */ jsx(
366
+ "div",
367
+ {
368
+ role: interactive ? "button" : void 0,
369
+ tabIndex: interactive ? 0 : void 0,
370
+ onClick: interactive ? () => onRowClick(row) : void 0,
371
+ onKeyDown: interactive ? (e) => {
372
+ if (e.key === "Enter" || e.key === " ") {
373
+ e.preventDefault();
374
+ onRowClick(row);
375
+ }
376
+ } : void 0,
377
+ "aria-expanded": expandedRow ? expanded : void 0,
378
+ className: cardClass,
379
+ children: cardInner
380
+ }
381
+ );
382
+ return /* @__PURE__ */ jsxs("li", { className: cn(tint), ...cleanAttrs(rowAttributes?.(row)), children: [
383
+ swipeEnabled ? /* @__PURE__ */ jsx(SwipeableRow, { enabled: true, left: swipe.left, right: swipe.right, children: body }) : body,
384
+ expansion && !mobileExpandAsDialog && /* @__PURE__ */ jsx("div", { className: "px-4 py-3 bg-slate-50/60 dark:bg-slate-800/20 border-t border-slate-100 dark:border-slate-800", children: expansion })
385
+ ] }, rowKey(row));
386
+ };
387
+ const mobileGroups = mobileGroupBy ? mobileSlice.reduce((acc, row) => {
388
+ const key = mobileGroupBy(row);
389
+ const last = acc[acc.length - 1];
390
+ if (last && last.key === key) last.rows.push(row);
391
+ else acc.push({ key, rows: [row] });
392
+ return acc;
393
+ }, []) : null;
394
+ return /* @__PURE__ */ jsxs(Card, { flush: true, className: cn("overflow-clip", fillHeight && "md:flex md:flex-1 md:flex-col md:min-h-0"), children: [
395
+ !isMdUp && /* @__PURE__ */ jsxs("div", { children: [
396
+ (!isServer || onFiltersChange) && /* @__PURE__ */ jsx(
397
+ MobileFilters,
398
+ {
399
+ columns,
400
+ filters,
401
+ selectOptionsByKey,
402
+ isServer,
403
+ hasFilterCallback: !!onFiltersChange,
404
+ onSetFilter: setFilterValue,
405
+ onClearFilter: clearFilter,
406
+ onClearAll: clearAllFilters,
407
+ labels,
408
+ locale
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsxs("ul", { className: "divide-y divide-slate-100 dark:divide-slate-800", children: [
412
+ mobileGroups ? mobileGroups.map((g) => /* @__PURE__ */ jsxs(Fragment2, { children: [
413
+ /* @__PURE__ */ jsx("li", { className: "sticky top-0 z-10 bg-slate-50/95 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-slate-500 backdrop-blur dark:bg-slate-800/80 dark:text-slate-400", children: mobileGroupLabel ? mobileGroupLabel(g.key) : g.key }),
414
+ g.rows.map(renderMobileRow)
415
+ ] }, `hb-group:${g.key}`)) : mobileSlice.map(renderMobileRow),
416
+ mobileSlice.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-4 py-6 text-center text-sm text-slate-500 dark:text-slate-400", children: empty ?? "\u2014" }),
417
+ canRevealMoreMobile && /* @__PURE__ */ jsx(
418
+ "li",
419
+ {
420
+ ref: loadMoreRef,
421
+ className: "px-4 py-4 text-center text-xs text-slate-400 dark:text-slate-500",
422
+ children: labels.loading
423
+ }
424
+ )
425
+ ] }),
426
+ isServer && /* @__PURE__ */ jsx(
427
+ Pagination,
428
+ {
429
+ page: safePage,
430
+ totalPages,
431
+ pageSize: paginationPageSize,
432
+ total: paginationTotal,
433
+ onPage: serverPagination.onPageChange,
434
+ onPageSize: (n) => serverPagination.onPageSizeChange?.(n),
435
+ labels
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx(
439
+ FullBleedDialog,
440
+ {
441
+ open: dialogOpen,
442
+ onClose: closeDialog,
443
+ closeLabel: labels.close,
444
+ header: mobileDialogRow ? mobilePrimaryCol?.cell(mobileDialogRow) : null,
445
+ children: mobileDialogContent
446
+ }
447
+ )
448
+ ] }),
449
+ isMdUp && /* @__PURE__ */ jsxs("div", { className: cn("flex", fillHeight && "min-h-0 flex-1"), children: [
450
+ /* @__PURE__ */ jsxs("div", { className: cn("relative min-w-0 flex-1", fillHeight && "flex flex-col min-h-0"), children: [
451
+ /* @__PURE__ */ jsx(
452
+ "div",
453
+ {
454
+ className: cn("overflow-auto [scrollbar-gutter:stable]", fillHeight && "flex-1 min-h-0"),
455
+ style: fillHeight ? void 0 : { maxHeight: maxBodyHeight },
456
+ children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
457
+ /* @__PURE__ */ jsx("thead", { className: "bg-slate-50 dark:bg-slate-800/40 text-slate-600 dark:text-slate-400", children: /* @__PURE__ */ jsxs("tr", { className: "text-left", children: [
458
+ selection && /* @__PURE__ */ jsx("th", { className: "sticky top-0 z-10 w-10 bg-slate-50 px-3 py-2 align-middle dark:bg-slate-800/95 backdrop-blur-sm", children: /* @__PURE__ */ jsx(
459
+ "input",
460
+ {
461
+ type: "checkbox",
462
+ className: "size-4 align-middle accent-indigo-600",
463
+ checked: selection.allSelected,
464
+ ref: (el) => {
465
+ if (el) el.indeterminate = selection.someSelected && !selection.allSelected;
466
+ },
467
+ onChange: (e) => selection.onToggleAll(e.target.checked),
468
+ "aria-label": labels.selectAllRows
469
+ }
470
+ ) }),
471
+ visibleColumns.map((col) => {
472
+ const sortable = (!isServer || !!onSortsChange) && !!col.sortBy;
473
+ const filter = !isServer || onFiltersChange ? resolveFilter(col) : null;
474
+ const filterState = filters[col.key] ?? (filter ? defaultFilterState(filter) : void 0);
475
+ const sortIdx = sorts.findIndex((s) => s.key === col.key);
476
+ const active = sortIdx !== -1;
477
+ const Icon = active ? sorts[sortIdx].dir === "asc" ? ArrowUp : ArrowDown : ArrowUpDown;
478
+ const filterActive = filterState && isFilterActive(filterState);
479
+ const isRightAligned = col.headClassName?.includes("text-right");
480
+ const width = widths[col.key];
481
+ return /* @__PURE__ */ jsxs(
482
+ "th",
483
+ {
484
+ ref: (el) => {
485
+ if (el) headRefs.set(col.key, el);
486
+ else headRefs.delete(col.key);
487
+ },
488
+ "aria-sort": sortIdx === 0 ? sorts[0].dir === "asc" ? "ascending" : "descending" : void 0,
489
+ style: width ? { width, minWidth: width, maxWidth: width } : void 0,
490
+ className: cn(
491
+ "relative px-3 py-2 font-medium align-middle whitespace-nowrap",
492
+ // Keep the header visible while the user scrolls the page.
493
+ // Each <th> carries its own bg so the row doesn't render
494
+ // transparent over the data rows underneath.
495
+ "sticky top-0 z-10 bg-slate-50 dark:bg-slate-800/95 backdrop-blur-sm",
496
+ col.headClassName
497
+ ),
498
+ children: [
499
+ /* @__PURE__ */ jsxs(
500
+ "div",
501
+ {
502
+ className: cn(
503
+ "flex items-center gap-1",
504
+ isRightAligned && "flex-row-reverse"
505
+ ),
506
+ children: [
507
+ /* @__PURE__ */ jsx(Tooltip, { label: sortable ? labels.sortHint : void 0, portal: true, children: /* @__PURE__ */ jsxs(
508
+ "button",
509
+ {
510
+ type: "button",
511
+ onClick: (e) => sortable && toggleSort(col.key, e.shiftKey),
512
+ disabled: !sortable,
513
+ className: cn(
514
+ "flex items-center gap-1 text-left",
515
+ sortable && "hover:text-slate-900 dark:hover:text-slate-200"
516
+ ),
517
+ children: [
518
+ /* @__PURE__ */ jsx("span", { children: col.header }),
519
+ sortable && /* @__PURE__ */ jsx(Icon, { className: cn("size-3", active ? "opacity-100" : "opacity-40") }),
520
+ active && sorts.length > 1 && /* @__PURE__ */ jsx("span", { className: "text-[9px] font-semibold leading-none text-brand", children: sortIdx + 1 })
521
+ ]
522
+ }
523
+ ) }),
524
+ filter && /* @__PURE__ */ jsx(
525
+ Popover,
526
+ {
527
+ width: filter.type === "date" ? 420 : void 0,
528
+ trigger: ({ toggle, ref }) => /* @__PURE__ */ jsx(
529
+ "button",
530
+ {
531
+ type: "button",
532
+ ref,
533
+ onClick: toggle,
534
+ "aria-label": labels.filter,
535
+ className: cn(
536
+ "rounded p-1 transition-colors",
537
+ filterActive ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-500/20 dark:text-indigo-300" : "text-slate-400 hover:text-slate-700 hover:bg-slate-100 dark:text-slate-500 dark:hover:text-slate-200 dark:hover:bg-slate-800"
538
+ ),
539
+ children: /* @__PURE__ */ jsx(Filter, { className: "size-3" })
540
+ }
541
+ ),
542
+ children: () => /* @__PURE__ */ jsx(
543
+ FilterPopover,
544
+ {
545
+ column: col,
546
+ state: filterState ?? defaultFilterState(filter),
547
+ onChange: (next) => setFilterValue(col.key, next),
548
+ onClear: () => clearFilter(col.key),
549
+ selectOptions: selectOptionsByKey[col.key] ?? [],
550
+ labels,
551
+ locale
552
+ }
553
+ )
554
+ }
555
+ )
556
+ ]
557
+ }
558
+ ),
559
+ /* @__PURE__ */ jsx(
560
+ "span",
561
+ {
562
+ role: "separator",
563
+ "aria-orientation": "vertical",
564
+ onMouseDown: (e) => startResize(e, col.key),
565
+ onDoubleClick: (e) => {
566
+ e.preventDefault();
567
+ setWidths((w) => {
568
+ const next = { ...w };
569
+ delete next[col.key];
570
+ return next;
571
+ });
572
+ },
573
+ className: "absolute right-0 top-0 z-10 h-full w-1.5 -translate-x-1/2 cursor-col-resize select-none hover:bg-indigo-300/60 dark:hover:bg-indigo-500/40"
574
+ }
575
+ )
576
+ ]
577
+ },
578
+ col.key
579
+ );
580
+ })
581
+ ] }) }),
582
+ /* @__PURE__ */ jsxs("tbody", { children: [
583
+ leadingRow && /* @__PURE__ */ jsx("tr", { className: "border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: leadingRow }) }),
584
+ slice.map((row, rowIndex) => {
585
+ const expanded = isExpanded?.(row) ?? false;
586
+ const expansion = expanded ? expandedRow?.(row) : null;
587
+ const rowInteractive = !!onRowClick || !!selection;
588
+ const href = rowHref?.(row);
589
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
590
+ /* @__PURE__ */ jsxs(
591
+ "tr",
592
+ {
593
+ ...cleanAttrs(rowAttributes?.(row)),
594
+ className: cn(
595
+ "border-t border-slate-100 dark:border-slate-800",
596
+ rowInteractive && "cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800/40",
597
+ rowClassName?.(row)
598
+ ),
599
+ onClick: rowInteractive ? (e) => {
600
+ if (selection && canSelect(row)) {
601
+ if (e.shiftKey) {
602
+ window.getSelection?.()?.removeAllRanges();
603
+ if (selectRange(rowIndex)) return;
604
+ selectionAnchor.current = rowIndex;
605
+ selection.onToggle(row, !selection.isSelected(row));
606
+ return;
607
+ }
608
+ if (e.metaKey || e.ctrlKey) {
609
+ selectionAnchor.current = rowIndex;
610
+ selection.onToggle(row, !selection.isSelected(row));
611
+ return;
612
+ }
613
+ }
614
+ onRowClick?.(row);
615
+ } : void 0,
616
+ children: [
617
+ selection && /* @__PURE__ */ jsx(
618
+ "td",
619
+ {
620
+ className: "w-10 px-3 py-2 align-top",
621
+ onClick: (e) => e.stopPropagation(),
622
+ children: canSelect(row) && /* @__PURE__ */ jsx(
623
+ "input",
624
+ {
625
+ type: "checkbox",
626
+ className: "size-4 accent-indigo-600",
627
+ checked: selection.isSelected(row),
628
+ onClick: (e) => {
629
+ if (e.shiftKey && selectRange(rowIndex)) e.preventDefault();
630
+ },
631
+ onChange: (e) => {
632
+ selectionAnchor.current = rowIndex;
633
+ selection.onToggle(row, e.target.checked);
634
+ },
635
+ "aria-label": labels.selectRow
636
+ }
637
+ )
638
+ }
639
+ ),
640
+ visibleColumns.map((col) => {
641
+ const width = widths[col.key];
642
+ return /* @__PURE__ */ jsx(
643
+ "td",
644
+ {
645
+ style: width ? { width, minWidth: width, maxWidth: width } : void 0,
646
+ className: cn("px-3 py-2 align-top", width && "overflow-hidden text-ellipsis", col.className),
647
+ children: col === linkColumn && href ? (
648
+ // No `onActivate`: the click is left to bubble to the
649
+ // <tr> handler above, which already owns expand-vs-select.
650
+ // Calling it here as well would toggle the row twice.
651
+ /* @__PURE__ */ jsx(RowLink, { href, className: "block", children: col.cell(row) })
652
+ ) : col.cell(row)
653
+ },
654
+ col.key
655
+ );
656
+ })
657
+ ]
658
+ }
659
+ ),
660
+ expansion && /* @__PURE__ */ jsx("tr", { className: "bg-slate-50/60 dark:bg-slate-800/20 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: /* @__PURE__ */ jsx("div", { className: "w-0 min-w-full px-3 py-3", children: expansion }) }) })
661
+ ] }, rowKey(row));
662
+ }),
663
+ slice.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
664
+ "td",
665
+ {
666
+ colSpan: totalColSpan,
667
+ className: "px-3 py-4 text-center text-slate-500 dark:text-slate-400",
668
+ children: empty ?? "\u2014"
669
+ }
670
+ ) })
671
+ ] })
672
+ ] })
673
+ }
674
+ ),
675
+ !unpaged && /* @__PURE__ */ jsx(
676
+ Pagination,
677
+ {
678
+ page: safePage,
679
+ totalPages,
680
+ pageSize: paginationPageSize,
681
+ total: paginationTotal,
682
+ onPage: isServer ? serverPagination.onPageChange : setPage,
683
+ onPageSize: isServer ? (n) => serverPagination.onPageSizeChange?.(n) : (n) => {
684
+ setPageSize(n);
685
+ setPage(0);
686
+ },
687
+ labels
688
+ }
689
+ )
690
+ ] }),
691
+ /* @__PURE__ */ jsx(Tooltip, { label: columnsCountLabel, portal: true, children: /* @__PURE__ */ jsx(
692
+ "button",
693
+ {
694
+ type: "button",
695
+ onClick: () => setShowSettings(true),
696
+ "aria-label": columnsCountLabel,
697
+ "aria-hidden": showSettings,
698
+ tabIndex: showSettings ? -1 : 0,
699
+ className: cn(
700
+ "group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out",
701
+ "border-l border-slate-100 dark:border-slate-800",
702
+ "hover:bg-slate-50 dark:hover:bg-slate-800/40",
703
+ showSettings ? "w-0 border-l-0" : "w-8 cursor-pointer"
704
+ ),
705
+ children: /* @__PURE__ */ jsx(
706
+ "span",
707
+ {
708
+ className: "whitespace-nowrap text-xs font-medium tracking-wide text-slate-500 group-hover:text-slate-700 dark:text-slate-400 dark:group-hover:text-slate-200",
709
+ style: { writingMode: "vertical-rl", transform: "rotate(180deg)" },
710
+ children: columnsCountLabel
711
+ }
712
+ )
713
+ }
714
+ ) }),
715
+ /* @__PURE__ */ jsx(
716
+ "div",
717
+ {
718
+ className: cn(
719
+ "shrink-0 overflow-hidden transition-[width] duration-200 ease-out",
720
+ showSettings ? "w-56" : "w-0"
721
+ ),
722
+ children: /* @__PURE__ */ jsxs("div", { className: "flex w-56 flex-col border-l border-slate-100 p-2 dark:border-slate-800", children: [
723
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-1", children: [
724
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400", children: columnsCountLabel }),
725
+ /* @__PURE__ */ jsx(Tooltip, { label: labels.close, portal: true, children: /* @__PURE__ */ jsx(
726
+ "button",
727
+ {
728
+ type: "button",
729
+ onClick: () => setShowSettings(false),
730
+ "aria-label": labels.close,
731
+ className: "rounded p-0.5 text-slate-400 hover:bg-slate-100 hover:text-slate-700 dark:text-slate-500 dark:hover:bg-slate-800 dark:hover:text-slate-200",
732
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
733
+ }
734
+ ) })
735
+ ] }),
736
+ /* @__PURE__ */ jsx(
737
+ "button",
738
+ {
739
+ type: "button",
740
+ onClick: autoSizeAll,
741
+ className: "mb-2 rounded border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-50 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800",
742
+ children: labels.autoSize
743
+ }
744
+ ),
745
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: columns.map((col) => {
746
+ const checked = !hiddenCols.has(col.key);
747
+ const headerLabel = typeof col.header === "string" ? col.header : col.key;
748
+ return /* @__PURE__ */ jsxs(
749
+ "label",
750
+ {
751
+ className: "inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-slate-100 dark:hover:bg-slate-800",
752
+ children: [
753
+ /* @__PURE__ */ jsx(
754
+ "input",
755
+ {
756
+ type: "checkbox",
757
+ className: "size-3.5",
758
+ checked,
759
+ onChange: (e) => {
760
+ setHiddenCols((s) => {
761
+ const next = new Set(s);
762
+ if (e.target.checked) next.delete(col.key);
763
+ else next.add(col.key);
764
+ return next;
765
+ });
766
+ }
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: headerLabel })
770
+ ]
771
+ },
772
+ col.key
773
+ );
774
+ }) })
775
+ ] })
776
+ }
777
+ )
778
+ ] })
779
+ ] });
780
+ }
781
+ function MobileFilters({
782
+ columns,
783
+ filters,
784
+ selectOptionsByKey,
785
+ isServer,
786
+ hasFilterCallback,
787
+ onSetFilter,
788
+ onClearFilter,
789
+ onClearAll,
790
+ labels,
791
+ locale
792
+ }) {
793
+ const [open, setOpen] = useState(false);
794
+ const [expanded, setExpanded] = useState(null);
795
+ const backdropClose = useBackdropClose(() => setOpen(false));
796
+ useBodyScrollLock(open);
797
+ useOverlayHistory(open, () => setOpen(false));
798
+ const filterable = useMemo(
799
+ () => columns.map((col) => ({ col, filter: !isServer || hasFilterCallback ? resolveFilter(col) : null })).filter((x) => !!x.filter),
800
+ [columns, isServer, hasFilterCallback]
801
+ );
802
+ if (filterable.length === 0) return null;
803
+ const activeCount = filterable.filter(({ col }) => {
804
+ const s = filters[col.key];
805
+ return s && isFilterActive(s);
806
+ }).length;
807
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-slate-100 px-4 py-2 dark:border-slate-800", children: [
808
+ /* @__PURE__ */ jsxs(
809
+ "button",
810
+ {
811
+ type: "button",
812
+ onClick: () => setOpen(true),
813
+ className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800",
814
+ children: [
815
+ /* @__PURE__ */ jsx(Filter, { className: "size-4" }),
816
+ /* @__PURE__ */ jsx("span", { children: labels.filters }),
817
+ activeCount > 0 && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-4 items-center justify-center rounded-full bg-sky-100 px-1 text-[11px] font-semibold text-sky-700 dark:bg-sky-500/20 dark:text-sky-300", children: activeCount })
818
+ ]
819
+ }
820
+ ),
821
+ activeCount > 0 && /* @__PURE__ */ jsx(
822
+ "button",
823
+ {
824
+ type: "button",
825
+ onClick: onClearAll,
826
+ className: "text-xs text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200",
827
+ children: labels.clearAll
828
+ }
829
+ ),
830
+ open && createPortal(
831
+ /* @__PURE__ */ jsx(
832
+ "div",
833
+ {
834
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-black/40",
835
+ role: "dialog",
836
+ "aria-modal": "true",
837
+ ...backdropClose,
838
+ children: /* @__PURE__ */ jsxs("div", { className: "flex max-h-[85vh] w-full flex-col overflow-hidden rounded-t-2xl bg-white shadow-xl dark:bg-slate-900", children: [
839
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-slate-100 px-4 py-3 dark:border-slate-800", children: [
840
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: labels.filters }),
841
+ /* @__PURE__ */ jsx(
842
+ "button",
843
+ {
844
+ type: "button",
845
+ onClick: () => setOpen(false),
846
+ "aria-label": labels.close,
847
+ className: "-mr-1 rounded p-1.5 text-slate-500 hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-slate-200",
848
+ children: /* @__PURE__ */ jsx(X, { className: "size-5" })
849
+ }
850
+ )
851
+ ] }),
852
+ /* @__PURE__ */ jsx("div", { className: "flex-1 divide-y divide-slate-100 overflow-y-auto overscroll-contain dark:divide-slate-800", children: filterable.map(({ col, filter }) => {
853
+ const state = filters[col.key];
854
+ const active = !!state && isFilterActive(state);
855
+ const isItemOpen = expanded === col.key;
856
+ return /* @__PURE__ */ jsxs("div", { children: [
857
+ /* @__PURE__ */ jsxs(
858
+ "button",
859
+ {
860
+ type: "button",
861
+ onClick: () => setExpanded(isItemOpen ? null : col.key),
862
+ "aria-expanded": isItemOpen,
863
+ className: "flex w-full items-center justify-between gap-2 px-4 py-3 text-left",
864
+ children: [
865
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-slate-700 dark:text-slate-200", children: [
866
+ col.header,
867
+ active && /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-brand" })
868
+ ] }),
869
+ /* @__PURE__ */ jsx(
870
+ ChevronDown,
871
+ {
872
+ className: cn(
873
+ "size-4 shrink-0 text-slate-400 transition-transform",
874
+ isItemOpen && "rotate-180"
875
+ )
876
+ }
877
+ )
878
+ ]
879
+ }
880
+ ),
881
+ isItemOpen && /* @__PURE__ */ jsx("div", { className: "px-4 pb-3", children: /* @__PURE__ */ jsx(
882
+ FilterPopover,
883
+ {
884
+ column: col,
885
+ state: state ?? defaultFilterState(filter),
886
+ onChange: (next) => onSetFilter(col.key, next),
887
+ onClear: () => onClearFilter(col.key),
888
+ selectOptions: selectOptionsByKey[col.key] ?? [],
889
+ labels,
890
+ locale
891
+ }
892
+ ) })
893
+ ] }, col.key);
894
+ }) }),
895
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-slate-100 px-4 py-3 dark:border-slate-800", children: [
896
+ /* @__PURE__ */ jsx(
897
+ "button",
898
+ {
899
+ type: "button",
900
+ onClick: onClearAll,
901
+ disabled: activeCount === 0,
902
+ className: "text-sm text-slate-500 hover:text-slate-700 disabled:opacity-40 dark:text-slate-400 dark:hover:text-slate-200",
903
+ children: labels.clearAll
904
+ }
905
+ ),
906
+ /* @__PURE__ */ jsx(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ onClick: () => setOpen(false),
911
+ className: "rounded-md bg-slate-900 px-4 py-2 text-sm font-medium text-white dark:bg-slate-100 dark:text-slate-900",
912
+ children: labels.done
913
+ }
914
+ )
915
+ ] })
916
+ ] })
917
+ }
918
+ ),
919
+ document.body
920
+ )
921
+ ] });
922
+ }
923
+ export {
924
+ DataTable
925
+ };
926
+ //# sourceMappingURL=data-table.js.map