@adapttable/mantine 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,1951 +1,1900 @@
1
- import { usePrefersReducedMotion, pageSizeOptions, useTableChrome, ACTIONS_COLUMN_KEY, useChromeBodyData, useFilterTriggerToggle, useChromeScrollReset, filterLabel, useTableData, resolveLabels, isDeclarativeFilters, resolveDisabledReason, rowClickProps, tableRenderModel, headerGroupRow, useHorizontalOverflow, PIN_Z, edgePinStyle, tableMinWidth, useSavedViews, useBulkActionRunner, RANGE_SUFFIXES, readRangeWidget, RANGE_OP_LABEL_KEYS, RANGE_OPS, useFilterOptions, runRowAction, pinnedCellStyle, columnResizeHandleProps, pinnedColumnWidth, useColumnDragState, columnMenuRows, GripIcon, columnReorderKeyProps, nextPinSide, pinActionLabel, EyeIcon, PinIcon, writeRangeWidget } from '@adapttable/core';
2
- export { createHistoryAdapter, createMemoryAdapter, defaultConfirm, defaultLabels, deriveSortByOptions, getHistoryAdapter, useBackendData, useDataTable, useFrontendData, useSavedViews, useTableUrlState } from '@adapttable/core';
3
- import { Group, Pill, Anchor, Stack, Text, Alert, Button, Select, Pagination, Menu, Box, ActionIcon, TextInput, Table, Skeleton, VisuallyHidden, Paper, Progress, Card, Checkbox, Tooltip, Badge, Drawer, Input, Loader, NativeSelect, Popover, NumberInput } from '@mantine/core';
4
- import { useElementSize } from '@mantine/hooks';
5
- import { useEffect, useState, useRef, useMemo, useCallback, memo } from 'react';
6
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
-
8
- // src/DataTable.tsx
1
+ import { ACTIONS_COLUMN_KEY, EyeIcon, GripIcon, PIN_Z, PinIcon, RANGE_OPS, RANGE_OP_LABEL_KEYS, RANGE_SUFFIXES, columnMenuRows, columnReorderKeyProps, columnResizeHandleProps, createHistoryAdapter, createMemoryAdapter, defaultConfirm, defaultLabels, deriveSortByOptions, edgePinStyle, filterLabel, getHistoryAdapter, headerGroupRow, isDeclarativeFilters, nextPinSide, pageSizeOptions, pinActionLabel, pinnedCellStyle, pinnedColumnWidth, readRangeWidget, resolveDisabledReason, resolveLabels, rowClickProps, runRowAction, tableMinWidth, tableRenderModel, useBackendData, useBulkActionRunner, useChromeBodyData, useChromeScrollReset, useColumnDragState, useDataTable, useFilterOptions, useFilterTriggerToggle, useFrontendData, useHorizontalOverflow, usePrefersReducedMotion, useSavedViews, useSavedViews as useSavedViews$1, useTableChrome, useTableData, useTableUrlState, writeRangeWidget } from "@adapttable/core";
2
+ import { ActionIcon, Alert, Anchor, Badge, Box, Button, Card, Checkbox, Drawer, Group, Input, Loader, Menu, NativeSelect, NumberInput, Pagination, Paper, Pill, Popover, Progress, Select, Skeleton, Stack, Table, Text, TextInput, Tooltip, VisuallyHidden } from "@mantine/core";
3
+ import { useElementSize } from "@mantine/hooks";
4
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/animation/useMountStagger.ts
7
+ /**
8
+ * Dependency-free entrance stagger using the Web Animations API. Animates
9
+ * descendants marked with `data-stagger` once on mount (and whenever
10
+ * `deps` change), honoring `prefers-reduced-motion`. Works without GSAP;
11
+ * GSAP fans can swap in their own hook of the same shape.
12
+ *
13
+ * @param ref - Ref to the container whose `[data-stagger]` items animate.
14
+ * @param deps - Re-run the stagger when these change (e.g. the row set).
15
+ * @param options - See {@link MountStaggerOptions}.
16
+ */
9
17
  function useMountStagger(ref, deps, options) {
10
- const reduced = usePrefersReducedMotion();
11
- const { enabled, step = 40, duration = 320 } = options;
12
- const depsKey = deps.map(String).join("|");
13
- useEffect(() => {
14
- if (!enabled || reduced) return;
15
- const root = ref.current;
16
- if (!root) return;
17
- const items = root.querySelectorAll("[data-stagger]");
18
- items.forEach((el, index) => {
19
- if (typeof el.animate !== "function") return;
20
- el.animate(
21
- [
22
- { opacity: 0, transform: "translateY(8px)" },
23
- { opacity: 1, transform: "translateY(0)" }
24
- ],
25
- {
26
- duration,
27
- delay: index * step,
28
- easing: "cubic-bezier(0.16, 1, 0.3, 1)",
29
- fill: "both"
30
- }
31
- );
32
- });
33
- }, [enabled, reduced, step, duration, ref, depsKey]);
18
+ const reduced = usePrefersReducedMotion();
19
+ const { enabled, step = 40, duration = 320 } = options;
20
+ useEffect(() => {
21
+ if (!enabled || reduced) return;
22
+ const root = ref.current;
23
+ if (!root) return;
24
+ root.querySelectorAll("[data-stagger]").forEach((el, index) => {
25
+ if (typeof el.animate !== "function") return;
26
+ el.animate([{
27
+ opacity: 0,
28
+ transform: "translateY(8px)"
29
+ }, {
30
+ opacity: 1,
31
+ transform: "translateY(0)"
32
+ }], {
33
+ duration,
34
+ delay: index * step,
35
+ easing: "cubic-bezier(0.16, 1, 0.3, 1)",
36
+ fill: "both"
37
+ });
38
+ });
39
+ }, [
40
+ enabled,
41
+ reduced,
42
+ step,
43
+ duration,
44
+ ref,
45
+ deps.map(String).join("|")
46
+ ]);
34
47
  }
35
- function ActiveFilterChips({
36
- chips,
37
- onClearAll,
38
- label,
39
- clearAllLabel
40
- }) {
41
- if (chips.length === 0) return null;
42
- return /* @__PURE__ */ jsxs(
43
- Group,
44
- {
45
- gap: 6,
46
- "aria-label": label,
47
- component: "ul",
48
- style: { listStyle: "none", padding: 0, margin: 0 },
49
- children: [
50
- chips.map((chip) => /* @__PURE__ */ jsx(
51
- Pill,
52
- {
53
- component: "li",
54
- withRemoveButton: true,
55
- onRemove: chip.onRemove,
56
- removeButtonProps: {
57
- "aria-label": `${clearAllLabel}: ${chip.label}`
58
- },
59
- children: chip.label
60
- },
61
- chip.key
62
- )),
63
- onClearAll && /* @__PURE__ */ jsx(
64
- Anchor,
65
- {
66
- component: "button",
67
- type: "button",
68
- fz: "xs",
69
- fw: 600,
70
- onClick: onClearAll,
71
- children: clearAllLabel
72
- }
73
- )
74
- ]
75
- }
76
- );
48
+ //#endregion
49
+ //#region src/components/ActiveFilterChips.tsx
50
+ /** A wrapping strip of removable filter chips. Renders nothing when empty. */
51
+ function ActiveFilterChips({ chips, onClearAll, label, clearAllLabel }) {
52
+ if (chips.length === 0) return null;
53
+ return /* @__PURE__ */ jsxs(Group, {
54
+ gap: 6,
55
+ "aria-label": label,
56
+ component: "ul",
57
+ style: {
58
+ listStyle: "none",
59
+ padding: 0,
60
+ margin: 0
61
+ },
62
+ children: [chips.map((chip) => /* @__PURE__ */ jsx(Pill, {
63
+ component: "li",
64
+ withRemoveButton: true,
65
+ onRemove: chip.onRemove,
66
+ removeButtonProps: { "aria-label": `${clearAllLabel}: ${chip.label}` },
67
+ children: chip.label
68
+ }, chip.key)), onClearAll && /* @__PURE__ */ jsx(Anchor, {
69
+ component: "button",
70
+ type: "button",
71
+ fz: "xs",
72
+ fw: 600,
73
+ onClick: onClearAll,
74
+ children: clearAllLabel
75
+ })]
76
+ });
77
77
  }
78
- var asText = (value) => value == null ? "" : String(value);
79
- var asList = (value) => {
80
- if (value == null || value === "") return [];
81
- return Array.isArray(value) ? value : [String(value)];
78
+ //#endregion
79
+ //#region src/components/AutoFilterForm.tsx
80
+ /** A scalar filter value as input text (`""` when unset). */
81
+ const asText = (value) => value == null ? "" : String(value);
82
+ /** A multi-select value as an array, tolerating a scalar from the URL. */
83
+ const asList = (value) => {
84
+ if (value == null || value === "") return [];
85
+ return Array.isArray(value) ? value : [String(value)];
86
+ };
87
+ /** The select's raw option value parsed back to a known operator. */
88
+ const asOp = (value) => RANGE_OPS.find((op) => op === value);
89
+ /** Which operator label set each range type reads. */
90
+ const RANGE_FLAVOUR = {
91
+ numberRange: "number",
92
+ dateRange: "date"
82
93
  };
83
- var asOp = (value) => RANGE_OPS.find((op) => op === value);
84
- var RANGE_FLAVOUR = { numberRange: "number", dateRange: "date" };
85
- function RangeField({
86
- def,
87
- source,
88
- kind,
89
- labels
90
- }) {
91
- const label = filterLabel(def);
92
- const lowKey = def.key + RANGE_SUFFIXES[kind].start;
93
- const highKey = def.key + RANGE_SUFFIXES[kind].end;
94
- const derived = readRangeWidget(source.extra, lowKey, highKey);
95
- const [chosen, setChosen] = useState(null);
96
- const op = chosen ?? derived.op ?? null;
97
- const low = asText(source.extra[lowKey]);
98
- const high = asText(source.extra[highKey]);
99
- const single = op === "lte" ? high : low;
100
- const write = (nextOp, a, b) => source.setExtras(writeRangeWidget(nextOp, a, b, lowKey, highKey));
101
- const handleOp = (value) => {
102
- const next = asOp(value);
103
- setChosen(next ?? null);
104
- write(next, single, "");
105
- };
106
- const flavour = RANGE_FLAVOUR[kind];
107
- const opLabelKeys = RANGE_OP_LABEL_KEYS[flavour];
108
- const data = RANGE_OPS.map((value) => ({
109
- value,
110
- label: labels[opLabelKeys[value]]
111
- }));
112
- const valueInput = (text, value, commit) => flavour === "number" ? /* @__PURE__ */ jsx(
113
- NumberInput,
114
- {
115
- size: "sm",
116
- hideControls: true,
117
- style: { flex: "1 1 6rem", minWidth: "6rem" },
118
- "aria-label": `${label} ${text}`,
119
- placeholder: text,
120
- value,
121
- onChange: (next) => commit(String(next))
122
- }
123
- ) : /* @__PURE__ */ jsx(
124
- TextInput,
125
- {
126
- type: "date",
127
- size: "sm",
128
- style: { flex: "1 1 8.5rem", minWidth: "8.5rem" },
129
- "aria-label": `${label} ${text}`,
130
- placeholder: text,
131
- value,
132
- onChange: (e) => commit(e.currentTarget.value)
133
- }
134
- );
135
- let values = null;
136
- if (op === "between") {
137
- values = /* @__PURE__ */ jsxs(Fragment, { children: [
138
- valueInput(labels.from, low, (next) => write("between", next, high)),
139
- valueInput(labels.to, high, (next) => write("between", low, next))
140
- ] });
141
- } else if (op) {
142
- values = valueInput(labels.value, single, (next) => write(op, next, ""));
143
- }
144
- return /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
145
- /* @__PURE__ */ jsx(Input.Label, { size: "sm", children: label }),
146
- /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "flex-start", children: [
147
- /* @__PURE__ */ jsx(
148
- Select,
149
- {
150
- size: "sm",
151
- clearable: true,
152
- style: { flex: "0 0 8.5rem", width: "8.5rem" },
153
- "aria-label": `${label} ${labels.operator}`,
154
- placeholder: labels.operator,
155
- data,
156
- value: op,
157
- onChange: handleOp,
158
- comboboxProps: { withinPortal: false }
159
- }
160
- ),
161
- values
162
- ] })
163
- ] });
94
+ /**
95
+ * The operator-first control shared by the `numberRange` / `dateRange`
96
+ * types: pick a comparison (Equal / At least / …), then fill ONE value —
97
+ * or From/To when "Between". The persisted state stays the inclusive
98
+ * `Min`/`Max` (`From`/`To`) pair via {@link readRangeWidget} /
99
+ * {@link writeRangeWidget}, so URLs, chips and predicates are unchanged.
100
+ */
101
+ function RangeField({ def, source, kind, labels }) {
102
+ const label = filterLabel(def);
103
+ const lowKey = def.key + RANGE_SUFFIXES[kind].start;
104
+ const highKey = def.key + RANGE_SUFFIXES[kind].end;
105
+ const derived = readRangeWidget(source.extra, lowKey, highKey);
106
+ const [chosen, setChosen] = useState(null);
107
+ const op = chosen ?? derived.op ?? null;
108
+ const low = asText(source.extra[lowKey]);
109
+ const high = asText(source.extra[highKey]);
110
+ /** The one visible value outside "Between" (`lte` stores the upper bound). */
111
+ const single = op === "lte" ? high : low;
112
+ const write = (nextOp, a, b) => source.setExtras(writeRangeWidget(nextOp, a, b, lowKey, highKey));
113
+ const handleOp = (value) => {
114
+ const next = asOp(value);
115
+ setChosen(next ?? null);
116
+ write(next, single, "");
117
+ };
118
+ const flavour = RANGE_FLAVOUR[kind];
119
+ const opLabelKeys = RANGE_OP_LABEL_KEYS[flavour];
120
+ const data = RANGE_OPS.map((value) => ({
121
+ value,
122
+ label: labels[opLabelKeys[value]]
123
+ }));
124
+ const valueInput = (text, value, commit) => flavour === "number" ? /* @__PURE__ */ jsx(NumberInput, {
125
+ size: "sm",
126
+ hideControls: true,
127
+ style: {
128
+ flex: "1 1 6rem",
129
+ minWidth: "6rem"
130
+ },
131
+ "aria-label": `${label} ${text}`,
132
+ placeholder: text,
133
+ value,
134
+ onChange: (next) => commit(String(next))
135
+ }) : /* @__PURE__ */ jsx(TextInput, {
136
+ type: "date",
137
+ size: "sm",
138
+ style: {
139
+ flex: "1 1 8.5rem",
140
+ minWidth: "8.5rem"
141
+ },
142
+ "aria-label": `${label} ${text}`,
143
+ placeholder: text,
144
+ value,
145
+ onChange: (e) => commit(e.currentTarget.value)
146
+ });
147
+ let values = null;
148
+ if (op === "between") values = /* @__PURE__ */ jsxs(Fragment, { children: [valueInput(labels.from, low, (next) => write("between", next, high)), valueInput(labels.to, high, (next) => write("between", low, next))] });
149
+ else if (op) values = valueInput(labels.value, single, (next) => write(op, next, ""));
150
+ return /* @__PURE__ */ jsxs(Stack, {
151
+ gap: 4,
152
+ children: [/* @__PURE__ */ jsx(Input.Label, {
153
+ size: "sm",
154
+ children: label
155
+ }), /* @__PURE__ */ jsxs(Group, {
156
+ gap: "xs",
157
+ align: "flex-start",
158
+ children: [/* @__PURE__ */ jsx(Select, {
159
+ size: "sm",
160
+ clearable: true,
161
+ style: {
162
+ flex: "0 0 8.5rem",
163
+ width: "8.5rem"
164
+ },
165
+ "aria-label": `${label} ${labels.operator}`,
166
+ placeholder: labels.operator,
167
+ data,
168
+ value: op,
169
+ onChange: handleOp,
170
+ comboboxProps: { withinPortal: false }
171
+ }), values]
172
+ })]
173
+ });
164
174
  }
165
- function SelectControl({
166
- def,
167
- source
168
- }) {
169
- const label = filterLabel(def);
170
- const { options, loading } = useFilterOptions(def);
171
- const data = loading ? [{ value: "", label: "\u2026", disabled: true }] : [{ value: "", label: "All" }, ...options];
172
- return /* @__PURE__ */ jsx(
173
- NativeSelect,
174
- {
175
- size: "sm",
176
- label,
177
- data,
178
- value: asText(source.extra[def.key]),
179
- onChange: (e) => source.setExtra(def.key, e.currentTarget.value)
180
- }
181
- );
175
+ /**
176
+ * Single-choice control. Options resolve through {@link useFilterOptions}
177
+ * (static array, async loader, or none); while a loader is in flight the
178
+ * select shows one disabled placeholder option.
179
+ */
180
+ function SelectControl({ def, source }) {
181
+ const label = filterLabel(def);
182
+ const { options, loading } = useFilterOptions(def);
183
+ return /* @__PURE__ */ jsx(NativeSelect, {
184
+ size: "sm",
185
+ label,
186
+ data: loading ? [{
187
+ value: "",
188
+ label: "…",
189
+ disabled: true
190
+ }] : [{
191
+ value: "",
192
+ label: "All"
193
+ }, ...options],
194
+ value: asText(source.extra[def.key]),
195
+ onChange: (e) => source.setExtra(def.key, e.currentTarget.value)
196
+ });
182
197
  }
183
- function MultiSelectControl({
184
- def,
185
- source
186
- }) {
187
- const label = filterLabel(def);
188
- const { options, loading } = useFilterOptions(def);
189
- return /* @__PURE__ */ jsx(
190
- Checkbox.Group,
191
- {
192
- label,
193
- value: asList(source.extra[def.key]),
194
- onChange: (values) => source.setExtra(def.key, values),
195
- children: /* @__PURE__ */ jsx(Group, { gap: "sm", mt: 4, children: loading ? /* @__PURE__ */ jsx(Loader, { size: "xs" }) : options.map((option) => /* @__PURE__ */ jsx(
196
- Checkbox,
197
- {
198
- size: "sm",
199
- value: option.value,
200
- label: option.label
201
- },
202
- option.value
203
- )) })
204
- }
205
- );
198
+ /**
199
+ * Multi-choice control. Options resolve through {@link useFilterOptions};
200
+ * while a loader is in flight the group shows a small spinner instead of
201
+ * checkboxes.
202
+ */
203
+ function MultiSelectControl({ def, source }) {
204
+ const label = filterLabel(def);
205
+ const { options, loading } = useFilterOptions(def);
206
+ return /* @__PURE__ */ jsx(Checkbox.Group, {
207
+ label,
208
+ value: asList(source.extra[def.key]),
209
+ onChange: (values) => source.setExtra(def.key, values),
210
+ children: /* @__PURE__ */ jsx(Group, {
211
+ gap: "sm",
212
+ mt: 4,
213
+ children: loading ? /* @__PURE__ */ jsx(Loader, { size: "xs" }) : options.map((option) => /* @__PURE__ */ jsx(Checkbox, {
214
+ size: "sm",
215
+ value: option.value,
216
+ label: option.label
217
+ }, option.value))
218
+ })
219
+ });
206
220
  }
207
- function FilterControl({
208
- def,
209
- source,
210
- labels
211
- }) {
212
- switch (def.type) {
213
- case "text":
214
- return /* @__PURE__ */ jsx(
215
- TextInput,
216
- {
217
- size: "sm",
218
- label: filterLabel(def),
219
- placeholder: def.placeholder,
220
- value: asText(source.extra[def.key]),
221
- onChange: (e) => source.setExtra(def.key, e.currentTarget.value)
222
- }
223
- );
224
- case "select":
225
- return /* @__PURE__ */ jsx(SelectControl, { def, source });
226
- case "multiSelect":
227
- return /* @__PURE__ */ jsx(MultiSelectControl, { def, source });
228
- case "dateRange":
229
- case "numberRange":
230
- return /* @__PURE__ */ jsx(RangeField, { def, source, kind: def.type, labels });
231
- }
221
+ /** One labeled, kit-native control for a single filter definition. */
222
+ function FilterControl({ def, source, labels }) {
223
+ switch (def.type) {
224
+ case "text": return /* @__PURE__ */ jsx(TextInput, {
225
+ size: "sm",
226
+ label: filterLabel(def),
227
+ placeholder: def.placeholder,
228
+ value: asText(source.extra[def.key]),
229
+ onChange: (e) => source.setExtra(def.key, e.currentTarget.value)
230
+ });
231
+ case "select": return /* @__PURE__ */ jsx(SelectControl, {
232
+ def,
233
+ source
234
+ });
235
+ case "multiSelect": return /* @__PURE__ */ jsx(MultiSelectControl, {
236
+ def,
237
+ source
238
+ });
239
+ case "dateRange":
240
+ case "numberRange": return /* @__PURE__ */ jsx(RangeField, {
241
+ def,
242
+ source,
243
+ kind: def.type,
244
+ labels
245
+ });
246
+ }
232
247
  }
233
- function AutoFilterForm({
234
- defs,
235
- source,
236
- labels
237
- }) {
238
- return /* @__PURE__ */ jsx(Stack, { gap: "sm", children: defs.map((def) => /* @__PURE__ */ jsx(
239
- FilterControl,
240
- {
241
- def,
242
- source,
243
- labels
244
- },
245
- def.key
246
- )) });
248
+ /**
249
+ * The auto-built filter form: one labeled, Mantine-native control per
250
+ * declarative {@link FilterDef}. Values live in the source's `extra` bag
251
+ * (so the URL, chips and — on frontend data — the predicate all follow);
252
+ * clearing a control writes the empty value, which drops the URL param.
253
+ * Range types render operator-first: an operator select plus one value
254
+ * input (two for "Between"), persisted as the inclusive pair.
255
+ *
256
+ * @typeParam TRow - The row type.
257
+ */
258
+ function AutoFilterForm({ defs, source, labels }) {
259
+ return /* @__PURE__ */ jsx(Stack, {
260
+ gap: "sm",
261
+ children: defs.map((def) => /* @__PURE__ */ jsx(FilterControl, {
262
+ def,
263
+ source,
264
+ labels
265
+ }, def.key))
266
+ });
247
267
  }
248
- function BulkActionBar({
249
- selection,
250
- total,
251
- bulkActions,
252
- confirm,
253
- labels
254
- }) {
255
- const { selectedIds, selectedCount, clear, allMatching } = selection;
256
- const { pending, run } = useBulkActionRunner({
257
- confirm,
258
- cancelLabel: labels.cancel,
259
- onComplete: clear
260
- });
261
- if (selectedCount === 0) return null;
262
- const ids = [...selectedIds];
263
- return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
264
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "wrap", gap: "sm", children: [
265
- /* @__PURE__ */ jsx(Text, { fz: "sm", children: labels.selectedCount(selectedCount) }),
266
- /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "wrap", children: [
267
- /* @__PURE__ */ jsx(
268
- Button,
269
- {
270
- size: "xs",
271
- variant: "subtle",
272
- onClick: clear,
273
- disabled: pending !== null,
274
- children: labels.clearAll
275
- }
276
- ),
277
- bulkActions.map((action) => /* @__PURE__ */ jsx(
278
- BulkButton,
279
- {
280
- action,
281
- ids,
282
- pending,
283
- onRun: (a) => {
284
- if (allMatching) run(a, ids, { allMatching: true, total });
285
- else run(a, ids);
286
- }
287
- },
288
- action.key
289
- ))
290
- ] })
291
- ] }),
292
- /* @__PURE__ */ jsx(ScopeBanner, { selection, total, labels })
293
- ] });
268
+ //#endregion
269
+ //#region src/components/BulkActionBar.tsx
270
+ /** Selection toolbar: count, clear, and the configured bulk-action buttons. */
271
+ function BulkActionBar({ selection, total, bulkActions, confirm, labels }) {
272
+ const { selectedIds, selectedCount, clear, allMatching } = selection;
273
+ const { pending, run } = useBulkActionRunner({
274
+ confirm,
275
+ cancelLabel: labels.cancel,
276
+ onComplete: clear
277
+ });
278
+ if (selectedCount === 0) return null;
279
+ const ids = [...selectedIds];
280
+ return /* @__PURE__ */ jsxs(Stack, {
281
+ gap: "xs",
282
+ children: [/* @__PURE__ */ jsxs(Group, {
283
+ justify: "space-between",
284
+ wrap: "wrap",
285
+ gap: "sm",
286
+ children: [/* @__PURE__ */ jsx(Text, {
287
+ fz: "sm",
288
+ children: labels.selectedCount(selectedCount)
289
+ }), /* @__PURE__ */ jsxs(Group, {
290
+ gap: "xs",
291
+ wrap: "wrap",
292
+ children: [/* @__PURE__ */ jsx(Button, {
293
+ size: "xs",
294
+ variant: "subtle",
295
+ onClick: clear,
296
+ disabled: pending !== null,
297
+ children: labels.clearAll
298
+ }), bulkActions.map((action) => /* @__PURE__ */ jsx(BulkButton, {
299
+ action,
300
+ ids,
301
+ pending,
302
+ onRun: (a) => {
303
+ if (allMatching) run(a, ids, {
304
+ allMatching: true,
305
+ total
306
+ });
307
+ else run(a, ids);
308
+ }
309
+ }, action.key))]
310
+ })]
311
+ }), /* @__PURE__ */ jsx(ScopeBanner, {
312
+ selection,
313
+ total,
314
+ labels
315
+ })]
316
+ });
294
317
  }
295
- function ScopeBanner({
296
- selection,
297
- total,
298
- labels
299
- }) {
300
- if (selection.headerState !== "all" || total <= selection.visibleIds.length) {
301
- return null;
302
- }
303
- return /* @__PURE__ */ jsx(Group, { role: "status", gap: "xs", wrap: "wrap", children: selection.allMatching ? /* @__PURE__ */ jsxs(Fragment, { children: [
304
- /* @__PURE__ */ jsx(Text, { fz: "sm", children: labels.allMatchingSelected(total) }),
305
- /* @__PURE__ */ jsx(Button, { size: "xs", variant: "subtle", onClick: selection.clear, children: labels.clearAll })
306
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
307
- /* @__PURE__ */ jsx(Text, { fz: "sm", children: labels.pageSelected(selection.selectedCount) }),
308
- /* @__PURE__ */ jsx(
309
- Button,
310
- {
311
- size: "xs",
312
- variant: "light",
313
- onClick: selection.selectAllMatching,
314
- children: labels.selectAllMatching(total)
315
- }
316
- )
317
- ] }) });
318
+ /**
319
+ * Gmail-style scope banner. When every row on the page is selected but more
320
+ * rows match elsewhere, offer to widen the selection to all matching rows;
321
+ * once widened, announce the scope and offer to clear it.
322
+ */
323
+ function ScopeBanner({ selection, total, labels }) {
324
+ if (selection.headerState !== "all" || total <= selection.visibleIds.length) return null;
325
+ return /* @__PURE__ */ jsx(Group, {
326
+ role: "status",
327
+ gap: "xs",
328
+ wrap: "wrap",
329
+ children: selection.allMatching ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
330
+ fz: "sm",
331
+ children: labels.allMatchingSelected(total)
332
+ }), /* @__PURE__ */ jsx(Button, {
333
+ size: "xs",
334
+ variant: "subtle",
335
+ onClick: selection.clear,
336
+ children: labels.clearAll
337
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
338
+ fz: "sm",
339
+ children: labels.pageSelected(selection.selectedCount)
340
+ }), /* @__PURE__ */ jsx(Button, {
341
+ size: "xs",
342
+ variant: "light",
343
+ onClick: selection.selectAllMatching,
344
+ children: labels.selectAllMatching(total)
345
+ })] })
346
+ });
318
347
  }
319
- function BulkButton({
320
- action,
321
- ids,
322
- pending,
323
- onRun
324
- }) {
325
- const reason = resolveDisabledReason(action.disabledReason?.(ids));
326
- const ineligible = reason !== void 0;
327
- const button = /* @__PURE__ */ jsx(
328
- Button,
329
- {
330
- size: "xs",
331
- color: action.color,
332
- leftSection: action.icon,
333
- onClick: () => onRun(action),
334
- loading: pending === action.key,
335
- disabled: ineligible || pending !== null && pending !== action.key,
336
- children: action.label
337
- }
338
- );
339
- if (reason !== void 0) {
340
- return /* @__PURE__ */ jsx(Tooltip, { label: reason, withArrow: true, openDelay: 150, children: /* @__PURE__ */ jsx("div", { children: button }) });
341
- }
342
- return button;
348
+ function BulkButton({ action, ids, pending, onRun }) {
349
+ const reason = resolveDisabledReason(action.disabledReason?.(ids));
350
+ const ineligible = reason !== void 0;
351
+ const button = /* @__PURE__ */ jsx(Button, {
352
+ size: "xs",
353
+ color: action.color,
354
+ leftSection: action.icon,
355
+ onClick: () => onRun(action),
356
+ loading: pending === action.key,
357
+ disabled: ineligible || pending !== null && pending !== action.key,
358
+ children: action.label
359
+ });
360
+ if (reason !== void 0) return /* @__PURE__ */ jsx(Tooltip, {
361
+ label: reason,
362
+ withArrow: true,
363
+ openDelay: 150,
364
+ children: /* @__PURE__ */ jsx("div", { children: button })
365
+ });
366
+ return button;
343
367
  }
344
- function RowVisibility({
345
- hidden,
346
- name,
347
- labels,
348
- onToggle
349
- }) {
350
- return /* @__PURE__ */ jsxs(Fragment, { children: [
351
- /* @__PURE__ */ jsx(
352
- ActionIcon,
353
- {
354
- variant: hidden ? "subtle" : "light",
355
- color: hidden ? "gray" : "blue",
356
- size: "sm",
357
- "aria-label": `${hidden ? labels.showColumn : labels.hideColumn}: ${name}`,
358
- "aria-pressed": !hidden,
359
- onClick: onToggle,
360
- children: /* @__PURE__ */ jsx(EyeIcon, { off: hidden })
361
- }
362
- ),
363
- /* @__PURE__ */ jsx(
364
- Text,
365
- {
366
- size: "sm",
367
- style: { flex: 1 },
368
- c: hidden ? "dimmed" : void 0,
369
- td: hidden ? "line-through" : void 0,
370
- children: name
371
- }
372
- )
373
- ] });
368
+ //#endregion
369
+ //#region src/components/ColumnMenu.tsx
370
+ /** The eye toggle + struck-through name shared by data and actions rows. */
371
+ function RowVisibility({ hidden, name, labels, onToggle }) {
372
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ActionIcon, {
373
+ variant: hidden ? "subtle" : "light",
374
+ color: hidden ? "gray" : "blue",
375
+ size: "sm",
376
+ "aria-label": `${hidden ? labels.showColumn : labels.hideColumn}: ${name}`,
377
+ "aria-pressed": !hidden,
378
+ onClick: onToggle,
379
+ children: /* @__PURE__ */ jsx(EyeIcon, { off: hidden })
380
+ }), /* @__PURE__ */ jsx(Text, {
381
+ size: "sm",
382
+ style: { flex: 1 },
383
+ c: hidden ? "dimmed" : void 0,
384
+ td: hidden ? "line-through" : void 0,
385
+ children: name
386
+ })] });
374
387
  }
375
- function PinToggle({
376
- pinned,
377
- label,
378
- onClick
379
- }) {
380
- return /* @__PURE__ */ jsx(
381
- ActionIcon,
382
- {
383
- variant: pinned ? "filled" : "subtle",
384
- color: pinned ? "blue" : "gray",
385
- size: "sm",
386
- "aria-label": label,
387
- onClick,
388
- children: /* @__PURE__ */ jsx(PinIcon, {})
389
- }
390
- );
388
+ /** The pin control shared by data and actions rows. */
389
+ function PinToggle({ pinned, label, onClick }) {
390
+ return /* @__PURE__ */ jsx(ActionIcon, {
391
+ variant: pinned ? "filled" : "subtle",
392
+ color: pinned ? "blue" : "gray",
393
+ size: "sm",
394
+ "aria-label": label,
395
+ onClick,
396
+ children: /* @__PURE__ */ jsx(PinIcon, {})
397
+ });
391
398
  }
392
- function ActionsRow({
393
- layout,
394
- labels
395
- }) {
396
- const hidden = layout.isHidden(ACTIONS_COLUMN_KEY);
397
- const pinned = layout.state.pinned[ACTIONS_COLUMN_KEY] === "right";
398
- return /* @__PURE__ */ jsxs(Group, { justify: "flex-start", wrap: "nowrap", gap: 6, px: 4, py: 2, children: [
399
- /* @__PURE__ */ jsx(Box, { w: 22 }),
400
- /* @__PURE__ */ jsx(
401
- RowVisibility,
402
- {
403
- hidden,
404
- name: labels.actions,
405
- labels,
406
- onToggle: () => layout.toggleVisible(ACTIONS_COLUMN_KEY)
407
- }
408
- ),
409
- /* @__PURE__ */ jsx(
410
- PinToggle,
411
- {
412
- pinned,
413
- label: `${pinned ? labels.unpin : labels.pinRight}: ${labels.actions}`,
414
- onClick: () => layout.setPinned(ACTIONS_COLUMN_KEY, pinned ? void 0 : "right")
415
- }
416
- )
417
- ] });
399
+ /**
400
+ * The injected actions column's menu row: the same eye toggle as data
401
+ * columns plus a pin toggle that flips right ↔ unpinned in one click. No
402
+ * drag grip (the column always trails) and no left pin.
403
+ */
404
+ function ActionsRow({ layout, labels }) {
405
+ const hidden = layout.isHidden(ACTIONS_COLUMN_KEY);
406
+ const pinned = layout.state.pinned[ACTIONS_COLUMN_KEY] === "right";
407
+ return /* @__PURE__ */ jsxs(Group, {
408
+ justify: "flex-start",
409
+ wrap: "nowrap",
410
+ gap: 6,
411
+ px: 4,
412
+ py: 2,
413
+ children: [
414
+ /* @__PURE__ */ jsx(Box, { w: 22 }),
415
+ /* @__PURE__ */ jsx(RowVisibility, {
416
+ hidden,
417
+ name: labels.actions,
418
+ labels,
419
+ onToggle: () => layout.toggleVisible(ACTIONS_COLUMN_KEY)
420
+ }),
421
+ /* @__PURE__ */ jsx(PinToggle, {
422
+ pinned,
423
+ label: `${pinned ? labels.unpin : labels.pinRight}: ${labels.actions}`,
424
+ onClick: () => layout.setPinned(ACTIONS_COLUMN_KEY, pinned ? void 0 : "right")
425
+ })
426
+ ]
427
+ });
418
428
  }
419
- function ColumnMenu({
420
- allColumns,
421
- layout,
422
- labels,
423
- hasRowActions = false
424
- }) {
425
- const drag = useColumnDragState();
426
- return /* @__PURE__ */ jsxs(Menu, { closeOnItemClick: false, position: "bottom-end", withinPortal: true, children: [
427
- /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", children: labels.columns }) }),
428
- /* @__PURE__ */ jsx(Menu.Dropdown, { children: /* @__PURE__ */ jsxs(Box, { p: 4, miw: 250, children: [
429
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 600, tt: "uppercase", px: 4, pb: 6, children: labels.columns }),
430
- columnMenuRows(allColumns, layout).map((r) => {
431
- const indicator = drag.rowAttrs(r.key, r.index);
432
- const edge = indicator["data-drop"];
433
- const edgeOffset = edge === "before" ? "2px" : "-2px";
434
- return /* @__PURE__ */ jsxs(
435
- Group,
436
- {
437
- justify: "flex-start",
438
- wrap: "nowrap",
439
- gap: 6,
440
- px: 4,
441
- py: 2,
442
- style: {
443
- cursor: "grab",
444
- opacity: "data-dragging" in indicator ? 0.4 : void 0,
445
- boxShadow: edge ? `inset 0 ${edgeOffset} 0 0 var(--mantine-primary-color-filled)` : void 0
446
- },
447
- ...drag.rowDragProps(r.key, r.index),
448
- ...drag.dropProps(r.index, layout.move),
449
- ...indicator,
450
- children: [
451
- /* @__PURE__ */ jsx(
452
- ActionIcon,
453
- {
454
- variant: "subtle",
455
- color: "gray",
456
- size: "sm",
457
- style: { cursor: "grab" },
458
- ...columnReorderKeyProps(
459
- r.key,
460
- r.index,
461
- layout.move,
462
- `${labels.moveLeft} / ${labels.moveRight}: ${r.name}`
463
- ),
464
- children: /* @__PURE__ */ jsx(GripIcon, {})
465
- }
466
- ),
467
- /* @__PURE__ */ jsx(
468
- RowVisibility,
469
- {
470
- hidden: r.hidden,
471
- name: r.name,
472
- labels,
473
- onToggle: () => layout.toggleVisible(r.key)
474
- }
475
- ),
476
- /* @__PURE__ */ jsx(
477
- PinToggle,
478
- {
479
- pinned: r.pinned !== void 0,
480
- label: `${pinActionLabel(r.pinned, labels)}: ${r.name}`,
481
- onClick: () => layout.setPinned(r.key, nextPinSide(r.pinned))
482
- }
483
- )
484
- ]
485
- },
486
- r.key
487
- );
488
- }),
489
- hasRowActions && /* @__PURE__ */ jsxs(Fragment, { children: [
490
- /* @__PURE__ */ jsx(Menu.Divider, {}),
491
- /* @__PURE__ */ jsx(ActionsRow, { layout, labels })
492
- ] }),
493
- /* @__PURE__ */ jsx(Menu.Divider, {}),
494
- /* @__PURE__ */ jsx(
495
- Button,
496
- {
497
- variant: "subtle",
498
- size: "xs",
499
- fullWidth: true,
500
- justify: "flex-start",
501
- onClick: () => layout.reset(),
502
- children: labels.resetColumns
503
- }
504
- )
505
- ] }) })
506
- ] });
429
+ /**
430
+ * Column-management popover: per-column drag grip (reorder), eye (show/hide),
431
+ * and pin toggle. Keyboard users focus a grip and use arrow keys.
432
+ */
433
+ function ColumnMenu({ allColumns, layout, labels, hasRowActions = false }) {
434
+ const drag = useColumnDragState();
435
+ return /* @__PURE__ */ jsxs(Menu, {
436
+ closeOnItemClick: false,
437
+ position: "bottom-end",
438
+ withinPortal: true,
439
+ children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Button, {
440
+ variant: "default",
441
+ size: "sm",
442
+ children: labels.columns
443
+ }) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: /* @__PURE__ */ jsxs(Box, {
444
+ p: 4,
445
+ miw: 250,
446
+ children: [
447
+ /* @__PURE__ */ jsx(Text, {
448
+ size: "xs",
449
+ c: "dimmed",
450
+ fw: 600,
451
+ tt: "uppercase",
452
+ px: 4,
453
+ pb: 6,
454
+ children: labels.columns
455
+ }),
456
+ columnMenuRows(allColumns, layout).map((r) => {
457
+ const indicator = drag.rowAttrs(r.key, r.index);
458
+ const edge = indicator["data-drop"];
459
+ const edgeOffset = edge === "before" ? "2px" : "-2px";
460
+ return /* @__PURE__ */ jsxs(Group, {
461
+ justify: "flex-start",
462
+ wrap: "nowrap",
463
+ gap: 6,
464
+ px: 4,
465
+ py: 2,
466
+ style: {
467
+ cursor: "grab",
468
+ opacity: "data-dragging" in indicator ? .4 : void 0,
469
+ boxShadow: edge ? `inset 0 ${edgeOffset} 0 0 var(--mantine-primary-color-filled)` : void 0
470
+ },
471
+ ...drag.rowDragProps(r.key, r.index),
472
+ ...drag.dropProps(r.index, layout.move),
473
+ ...indicator,
474
+ children: [
475
+ /* @__PURE__ */ jsx(ActionIcon, {
476
+ variant: "subtle",
477
+ color: "gray",
478
+ size: "sm",
479
+ style: { cursor: "grab" },
480
+ ...columnReorderKeyProps(r.key, r.index, layout.move, `${labels.moveLeft} / ${labels.moveRight}: ${r.name}`),
481
+ children: /* @__PURE__ */ jsx(GripIcon, {})
482
+ }),
483
+ /* @__PURE__ */ jsx(RowVisibility, {
484
+ hidden: r.hidden,
485
+ name: r.name,
486
+ labels,
487
+ onToggle: () => layout.toggleVisible(r.key)
488
+ }),
489
+ /* @__PURE__ */ jsx(PinToggle, {
490
+ pinned: r.pinned !== void 0,
491
+ label: `${pinActionLabel(r.pinned, labels)}: ${r.name}`,
492
+ onClick: () => layout.setPinned(r.key, nextPinSide(r.pinned))
493
+ })
494
+ ]
495
+ }, r.key);
496
+ }),
497
+ hasRowActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Menu.Divider, {}), /* @__PURE__ */ jsx(ActionsRow, {
498
+ layout,
499
+ labels
500
+ })] }),
501
+ /* @__PURE__ */ jsx(Menu.Divider, {}),
502
+ /* @__PURE__ */ jsx(Button, {
503
+ variant: "subtle",
504
+ size: "xs",
505
+ fullWidth: true,
506
+ justify: "flex-start",
507
+ onClick: () => layout.reset(),
508
+ children: labels.resetColumns
509
+ })
510
+ ]
511
+ }) })]
512
+ });
507
513
  }
508
-
509
- // src/density.ts
510
- var DENSITY_SPACING = {
511
- comfortable: { verticalSpacing: "sm", horizontalSpacing: "md" },
512
- compact: { verticalSpacing: 4, horizontalSpacing: "sm" }
514
+ //#endregion
515
+ //#region src/density.ts
516
+ /**
517
+ * Maps each {@link Density} to the Mantine `<Table>` spacing props.
518
+ * `comfortable` keeps the original `sm`/`md` rhythm; `compact` tightens
519
+ * rows with a 4px vertical gap and `sm` horizontal padding.
520
+ */
521
+ const DENSITY_SPACING = {
522
+ comfortable: {
523
+ verticalSpacing: "sm",
524
+ horizontalSpacing: "md"
525
+ },
526
+ compact: {
527
+ verticalSpacing: 4,
528
+ horizontalSpacing: "sm"
529
+ }
513
530
  };
514
- function Svg({
515
- size = 16,
516
- className,
517
- style,
518
- children
519
- }) {
520
- return /* @__PURE__ */ jsx(
521
- "svg",
522
- {
523
- width: size,
524
- height: size,
525
- viewBox: "0 0 24 24",
526
- fill: "none",
527
- stroke: "currentColor",
528
- strokeWidth: 2,
529
- strokeLinecap: "round",
530
- strokeLinejoin: "round",
531
- className,
532
- style,
533
- "aria-hidden": "true",
534
- focusable: "false",
535
- children
536
- }
537
- );
531
+ //#endregion
532
+ //#region src/icons.tsx
533
+ function Svg({ size = 16, className, style, children }) {
534
+ return /* @__PURE__ */ jsx("svg", {
535
+ width: size,
536
+ height: size,
537
+ viewBox: "0 0 24 24",
538
+ fill: "none",
539
+ stroke: "currentColor",
540
+ strokeWidth: 2,
541
+ strokeLinecap: "round",
542
+ strokeLinejoin: "round",
543
+ className,
544
+ style,
545
+ "aria-hidden": "true",
546
+ focusable: "false",
547
+ children
548
+ });
538
549
  }
539
- var SearchIcon = (p) => /* @__PURE__ */ jsxs(Svg, { ...p, children: [
540
- /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
541
- /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
542
- ] });
543
- var ChevronUpIcon = (p) => /* @__PURE__ */ jsx(Svg, { ...p, children: /* @__PURE__ */ jsx("path", { d: "m6 15 6-6 6 6" }) });
544
- var ChevronDownIcon = (p) => /* @__PURE__ */ jsx(Svg, { ...p, children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
545
- var ChevronRightIcon = (p) => /* @__PURE__ */ jsx(Svg, { ...p, children: /* @__PURE__ */ jsx("path", { d: "m9 6 6 6-6 6" }) });
546
- var SelectorIcon = (p) => /* @__PURE__ */ jsx(Svg, { ...p, children: /* @__PURE__ */ jsx("path", { d: "m8 9 4-4 4 4M8 15l4 4 4-4" }) });
547
- var CloseIcon = (p) => /* @__PURE__ */ jsx(Svg, { ...p, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
548
- var FiltersIcon = (p) => /* @__PURE__ */ jsx(Svg, { ...p, children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M7 12h10M10 18h4" }) });
549
- var AlertIcon = (p) => /* @__PURE__ */ jsxs(Svg, { ...p, children: [
550
- /* @__PURE__ */ jsx("path", { d: "M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }),
551
- /* @__PURE__ */ jsx("path", { d: "M12 9v4M12 17h.01" })
552
- ] });
553
- var RefreshIcon = (p) => /* @__PURE__ */ jsxs(Svg, { ...p, children: [
554
- /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-3-6.7L21 8" }),
555
- /* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" })
556
- ] });
557
- var InboxIcon = (p) => /* @__PURE__ */ jsxs(Svg, { ...p, children: [
558
- /* @__PURE__ */ jsx("path", { d: "M22 12h-6l-2 3h-4l-2-3H2" }),
559
- /* @__PURE__ */ jsx("path", { d: "M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1Z" })
560
- ] });
561
- function ExpandToggle({
562
- expanded,
563
- expandLabel,
564
- collapseLabel,
565
- onToggle
566
- }) {
567
- return /* @__PURE__ */ jsx(
568
- ActionIcon,
569
- {
570
- variant: "subtle",
571
- color: "gray",
572
- size: "sm",
573
- "aria-expanded": expanded,
574
- "aria-label": expanded ? collapseLabel : expandLabel,
575
- onClick: onToggle,
576
- children: /* @__PURE__ */ jsx(
577
- ChevronRightIcon,
578
- {
579
- size: 14,
580
- style: {
581
- transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
582
- transition: "transform 150ms ease"
583
- }
584
- }
585
- )
586
- }
587
- );
550
+ /** Magnifying-glass search icon. */
551
+ const SearchIcon = (p) => /* @__PURE__ */ jsxs(Svg, {
552
+ ...p,
553
+ children: [/* @__PURE__ */ jsx("circle", {
554
+ cx: "11",
555
+ cy: "11",
556
+ r: "7"
557
+ }), /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })]
558
+ });
559
+ /** Up chevron (active ascending sort). */
560
+ const ChevronUpIcon = (p) => /* @__PURE__ */ jsx(Svg, {
561
+ ...p,
562
+ children: /* @__PURE__ */ jsx("path", { d: "m6 15 6-6 6 6" })
563
+ });
564
+ /** Down chevron (active descending sort). */
565
+ const ChevronDownIcon = (p) => /* @__PURE__ */ jsx(Svg, {
566
+ ...p,
567
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
568
+ });
569
+ /** Right chevron (collapsed row-detail toggle; rotates 90° when expanded). */
570
+ const ChevronRightIcon = (p) => /* @__PURE__ */ jsx(Svg, {
571
+ ...p,
572
+ children: /* @__PURE__ */ jsx("path", { d: "m9 6 6 6-6 6" })
573
+ });
574
+ /** Up/down selector (inactive sortable column). */
575
+ const SelectorIcon = (p) => /* @__PURE__ */ jsx(Svg, {
576
+ ...p,
577
+ children: /* @__PURE__ */ jsx("path", { d: "m8 9 4-4 4 4M8 15l4 4 4-4" })
578
+ });
579
+ /** Small ✕ used on chips. */
580
+ const CloseIcon = (p) => /* @__PURE__ */ jsx(Svg, {
581
+ ...p,
582
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
583
+ });
584
+ /** Sliders icon for the Filters button. */
585
+ const FiltersIcon = (p) => /* @__PURE__ */ jsx(Svg, {
586
+ ...p,
587
+ children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M7 12h10M10 18h4" })
588
+ });
589
+ /** Triangle alert icon for the error state. */
590
+ const AlertIcon = (p) => /* @__PURE__ */ jsxs(Svg, {
591
+ ...p,
592
+ children: [/* @__PURE__ */ jsx("path", { d: "M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }), /* @__PURE__ */ jsx("path", { d: "M12 9v4M12 17h.01" })]
593
+ });
594
+ /** Refresh icon for retry. */
595
+ const RefreshIcon = (p) => /* @__PURE__ */ jsxs(Svg, {
596
+ ...p,
597
+ children: [/* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-3-6.7L21 8" }), /* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" })]
598
+ });
599
+ /** Inbox icon for the empty state. */
600
+ const InboxIcon = (p) => /* @__PURE__ */ jsxs(Svg, {
601
+ ...p,
602
+ children: [/* @__PURE__ */ jsx("path", { d: "M22 12h-6l-2 3h-4l-2-3H2" }), /* @__PURE__ */ jsx("path", { d: "M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1Z" })]
603
+ });
604
+ //#endregion
605
+ //#region src/components/ExpandToggle.tsx
606
+ /**
607
+ * The chevron that toggles a row's detail panel — shared by the desktop
608
+ * table's leading cell and the mobile card. It is a real button, so the
609
+ * row-click interactive-child guard already keeps it from activating
610
+ * `onRowClick`.
611
+ */
612
+ function ExpandToggle({ expanded, expandLabel, collapseLabel, onToggle }) {
613
+ return /* @__PURE__ */ jsx(ActionIcon, {
614
+ variant: "subtle",
615
+ color: "gray",
616
+ size: "sm",
617
+ "aria-expanded": expanded,
618
+ "aria-label": expanded ? collapseLabel : expandLabel,
619
+ onClick: onToggle,
620
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {
621
+ size: 14,
622
+ style: {
623
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
624
+ transition: "transform 150ms ease"
625
+ }
626
+ })
627
+ });
588
628
  }
589
- var RESIZE_HANDLE_STYLE = {
590
- position: "absolute",
591
- insetInlineEnd: 0,
592
- top: 0,
593
- height: "100%",
594
- width: 8,
595
- cursor: "col-resize",
596
- touchAction: "none",
597
- userSelect: "none"
629
+ //#endregion
630
+ //#region src/components/DesktopTable.tsx
631
+ /** Inline style for an absolutely-positioned column-resize handle. */
632
+ const RESIZE_HANDLE_STYLE = {
633
+ position: "absolute",
634
+ insetInlineEnd: 0,
635
+ top: 0,
636
+ height: "100%",
637
+ width: 8,
638
+ cursor: "col-resize",
639
+ touchAction: "none",
640
+ userSelect: "none"
598
641
  };
599
- function SortIcon({
600
- active,
601
- dir
602
- }) {
603
- if (!active) return /* @__PURE__ */ jsx(SelectorIcon, { size: 12 });
604
- return dir === "asc" ? /* @__PURE__ */ jsx(ChevronUpIcon, { size: 12 }) : /* @__PURE__ */ jsx(ChevronDownIcon, { size: 12 });
642
+ function SortIcon({ active, dir }) {
643
+ if (!active) return /* @__PURE__ */ jsx(SelectorIcon, { size: 12 });
644
+ return dir === "asc" ? /* @__PURE__ */ jsx(ChevronUpIcon, { size: 12 }) : /* @__PURE__ */ jsx(ChevronDownIcon, { size: 12 });
605
645
  }
606
- function HeaderCell({
607
- table,
608
- column,
609
- stickyStyle,
610
- resizeHandle
611
- }) {
612
- const cellProps = table.getHeaderCellProps(column);
613
- const headerStyle = {
614
- ...cellProps.style,
615
- ...stickyStyle
616
- };
617
- if (!column.sortable) {
618
- return /* @__PURE__ */ jsxs(Table.Th, { ...cellProps, style: headerStyle, children: [
619
- column.header,
620
- resizeHandle
621
- ] });
622
- }
623
- const buttonProps = table.getSortButtonProps(column);
624
- const sortIndex = buttonProps["data-sort-index"];
625
- const level = table.source.sortLevels.find((l) => l.key === column.key);
626
- const active = level !== void 0 || table.sortBy === column.key;
627
- return /* @__PURE__ */ jsxs(Table.Th, { ...cellProps, style: headerStyle, children: [
628
- /* @__PURE__ */ jsxs(
629
- Group,
630
- {
631
- component: "button",
632
- gap: 6,
633
- wrap: "nowrap",
634
- display: "inline-flex",
635
- style: {
636
- background: "none",
637
- border: 0,
638
- cursor: "pointer",
639
- font: "inherit",
640
- padding: 0,
641
- color: active ? "var(--mantine-primary-color-filled)" : "inherit"
642
- },
643
- ...buttonProps,
644
- children: [
645
- /* @__PURE__ */ jsx("span", { children: column.header }),
646
- /* @__PURE__ */ jsx(SortIcon, { active, dir: level?.dir ?? table.sortDir }),
647
- typeof sortIndex === "number" && /* @__PURE__ */ jsx(Badge, { component: "span", size: "xs", variant: "light", children: sortIndex })
648
- ]
649
- }
650
- ),
651
- resizeHandle
652
- ] });
646
+ function HeaderCell({ table, column, stickyStyle, resizeHandle }) {
647
+ const cellProps = table.getHeaderCellProps(column);
648
+ const headerStyle = {
649
+ ...cellProps.style,
650
+ ...stickyStyle
651
+ };
652
+ if (!column.sortable) return /* @__PURE__ */ jsxs(Table.Th, {
653
+ ...cellProps,
654
+ style: headerStyle,
655
+ children: [column.header, resizeHandle]
656
+ });
657
+ const buttonProps = table.getSortButtonProps(column);
658
+ const sortIndex = buttonProps["data-sort-index"];
659
+ const level = table.source.sortLevels.find((l) => l.key === column.key);
660
+ const active = level !== void 0 || table.sortBy === column.key;
661
+ return /* @__PURE__ */ jsxs(Table.Th, {
662
+ ...cellProps,
663
+ style: headerStyle,
664
+ children: [/* @__PURE__ */ jsxs(Group, {
665
+ component: "button",
666
+ gap: 6,
667
+ wrap: "nowrap",
668
+ display: "inline-flex",
669
+ style: {
670
+ background: "none",
671
+ border: 0,
672
+ cursor: "pointer",
673
+ font: "inherit",
674
+ padding: 0,
675
+ color: active ? "var(--mantine-primary-color-filled)" : "inherit"
676
+ },
677
+ ...buttonProps,
678
+ children: [
679
+ /* @__PURE__ */ jsx("span", { children: column.header }),
680
+ /* @__PURE__ */ jsx(SortIcon, {
681
+ active,
682
+ dir: level?.dir ?? table.sortDir
683
+ }),
684
+ typeof sortIndex === "number" && /* @__PURE__ */ jsx(Badge, {
685
+ component: "span",
686
+ size: "xs",
687
+ variant: "light",
688
+ children: sortIndex
689
+ })
690
+ ]
691
+ }), resizeHandle]
692
+ });
653
693
  }
654
- function RowActions({
655
- row,
656
- actions,
657
- confirm,
658
- cancelLabel
659
- }) {
660
- return /* @__PURE__ */ jsx(Group, { gap: 4, justify: "flex-end", wrap: "nowrap", children: actions.map((action) => {
661
- if (action.isHidden?.(row)) return null;
662
- const reason = resolveDisabledReason(action.disabledReason?.(row));
663
- const disabled = reason !== void 0 || (action.isDisabled?.(row) ?? false);
664
- const handleClick = disabled ? void 0 : (e) => {
665
- e.stopPropagation();
666
- runRowAction(action, row, confirm, cancelLabel);
667
- };
668
- return action.icon ? /* @__PURE__ */ jsx(
669
- Tooltip,
670
- {
671
- label: reason ?? action.label,
672
- withArrow: true,
673
- openDelay: 200,
674
- children: /* @__PURE__ */ jsx(
675
- ActionIcon,
676
- {
677
- variant: "subtle",
678
- color: action.color,
679
- size: "sm",
680
- disabled,
681
- "aria-label": action.label,
682
- onClick: handleClick,
683
- children: action.icon
684
- }
685
- )
686
- },
687
- action.key
688
- ) : /* @__PURE__ */ jsx(
689
- Button,
690
- {
691
- variant: "subtle",
692
- color: action.color,
693
- size: "compact-sm",
694
- disabled,
695
- onClick: handleClick,
696
- children: action.label
697
- },
698
- action.key
699
- );
700
- }) });
694
+ function RowActions({ row, actions, confirm, cancelLabel }) {
695
+ return /* @__PURE__ */ jsx(Group, {
696
+ gap: 4,
697
+ justify: "flex-end",
698
+ wrap: "nowrap",
699
+ children: actions.map((action) => {
700
+ if (action.isHidden?.(row)) return null;
701
+ const reason = resolveDisabledReason(action.disabledReason?.(row));
702
+ const disabled = reason !== void 0 || (action.isDisabled?.(row) ?? false);
703
+ const handleClick = disabled ? void 0 : (e) => {
704
+ e.stopPropagation();
705
+ runRowAction(action, row, confirm, cancelLabel);
706
+ };
707
+ return action.icon ? /* @__PURE__ */ jsx(Tooltip, {
708
+ label: reason ?? action.label,
709
+ withArrow: true,
710
+ openDelay: 200,
711
+ children: /* @__PURE__ */ jsx(ActionIcon, {
712
+ variant: "subtle",
713
+ color: action.color,
714
+ size: "sm",
715
+ disabled,
716
+ "aria-label": action.label,
717
+ onClick: handleClick,
718
+ children: action.icon
719
+ })
720
+ }, action.key) : /* @__PURE__ */ jsx(Button, {
721
+ variant: "subtle",
722
+ color: action.color,
723
+ size: "compact-sm",
724
+ disabled,
725
+ onClick: handleClick,
726
+ children: action.label
727
+ }, action.key);
728
+ })
729
+ });
701
730
  }
702
- var COMPARED_ROW_PROPS = [
703
- "row",
704
- "index",
705
- "id",
706
- "columns",
707
- "getCellProps",
708
- "selected",
709
- "selectLabel",
710
- "onToggleSelect",
711
- "expanded",
712
- "expandLabel",
713
- "collapseLabel",
714
- "onToggleExpand",
715
- "renderRowDetail",
716
- "columnSpan",
717
- "rowActions",
718
- "confirm",
719
- "cancelLabel",
720
- "onRowClick",
721
- "prefetch",
722
- "className",
723
- "measureElement",
724
- "pinSignature"
731
+ /** Every row prop the memo comparator checks with `Object.is`. */
732
+ const COMPARED_ROW_PROPS = [
733
+ "row",
734
+ "index",
735
+ "id",
736
+ "columns",
737
+ "getCellProps",
738
+ "selected",
739
+ "selectLabel",
740
+ "onToggleSelect",
741
+ "expanded",
742
+ "expandLabel",
743
+ "collapseLabel",
744
+ "onToggleExpand",
745
+ "renderRowDetail",
746
+ "columnSpan",
747
+ "rowActions",
748
+ "confirm",
749
+ "cancelLabel",
750
+ "onRowClick",
751
+ "prefetch",
752
+ "className",
753
+ "measureElement",
754
+ "pinSignature"
725
755
  ];
756
+ /**
757
+ * Row memo comparator: `Object.is` over every prop except the per-render
758
+ * style derivations excluded above. All event handlers passed to the row
759
+ * are identity-stable (or compared here, so a changed handler re-renders
760
+ * the row and is captured fresh) — a held row can never fire a stale
761
+ * closure.
762
+ */
726
763
  function desktopRowPropsEqual(prev, next) {
727
- return COMPARED_ROW_PROPS.every((key) => Object.is(prev[key], next[key]));
764
+ return COMPARED_ROW_PROPS.every((key) => Object.is(prev[key], next[key]));
728
765
  }
766
+ /**
767
+ * Sticky style for a leading chrome cell (chevron / checkbox) pinned
768
+ * `inset` px past the inline-start edge, active only while a data column is
769
+ * pinned on that side. Body cells pass a `background` so scrolled data
770
+ * never shows through.
771
+ */
729
772
  function leadingPinStyle(active, inset, zIndex, background) {
730
- if (!active) return void 0;
731
- const style = pinnedCellStyle({ side: "left", inset }, zIndex);
732
- return background ? { ...style, background } : style;
773
+ if (!active) return void 0;
774
+ const style = pinnedCellStyle({
775
+ side: "left",
776
+ inset
777
+ }, zIndex);
778
+ return background ? {
779
+ ...style,
780
+ background
781
+ } : style;
733
782
  }
783
+ /**
784
+ * Visual fingerprint of the pin layout (sides, insets, edge-pinned chrome
785
+ * columns). Memoized rows compare this one string instead of the per-render
786
+ * style objects derived from it.
787
+ */
734
788
  function pinLayoutSignature(columns, pinOffset, hasLeftPin, actionsEdgePinned) {
735
- const perColumn = columns.map((column) => {
736
- const pin = pinOffset?.(column.key);
737
- return pin ? `${column.key}:${pin.side}${pin.inset}` : column.key;
738
- });
739
- return `${perColumn.join("|")}|${String(hasLeftPin)}|${String(actionsEdgePinned)}`;
789
+ return `${columns.map((column) => {
790
+ const pin = pinOffset?.(column.key);
791
+ return pin ? `${column.key}:${pin.side}${pin.inset}` : column.key;
792
+ }).join("|")}|${String(hasLeftPin)}|${String(actionsEdgePinned)}`;
740
793
  }
741
- function DesktopRowBase({
742
- row,
743
- index,
744
- id,
745
- columns,
746
- getCellProps,
747
- selected,
748
- selectLabel,
749
- onToggleSelect,
750
- expanded,
751
- expandLabel,
752
- collapseLabel,
753
- onToggleExpand,
754
- renderRowDetail,
755
- columnSpan,
756
- rowActions,
757
- confirm,
758
- cancelLabel,
759
- onRowClick,
760
- prefetch,
761
- className,
762
- measureElement,
763
- pinStyleFor,
764
- selectionCellStyle,
765
- expansionCellStyle,
766
- actionsCellStyle
767
- }) {
768
- const showActions = (rowActions?.length ?? 0) > 0;
769
- return /* @__PURE__ */ jsxs(Fragment, { children: [
770
- /* @__PURE__ */ jsxs(
771
- Table.Tr,
772
- {
773
- role: "row",
774
- "data-index": index,
775
- "aria-selected": selected,
776
- ...rowClickProps(row, onRowClick),
777
- className,
778
- ref: measureElement,
779
- "data-stagger": "",
780
- onMouseEnter: prefetch ? () => prefetch(row) : void 0,
781
- children: [
782
- expanded !== void 0 && /* @__PURE__ */ jsx(Table.Td, { ta: "center", style: expansionCellStyle, children: /* @__PURE__ */ jsx(
783
- ExpandToggle,
784
- {
785
- expanded,
786
- expandLabel,
787
- collapseLabel,
788
- onToggle: () => onToggleExpand(id)
789
- }
790
- ) }),
791
- selected !== void 0 && /* @__PURE__ */ jsx(Table.Td, { ta: "center", style: selectionCellStyle, children: /* @__PURE__ */ jsx(
792
- Checkbox,
793
- {
794
- "aria-label": selectLabel,
795
- checked: selected,
796
- onChange: () => onToggleSelect(id)
797
- }
798
- ) }),
799
- columns.map((column) => /* @__PURE__ */ jsx(
800
- Table.Td,
801
- {
802
- ...getCellProps(column),
803
- style: pinStyleFor(column.key),
804
- children: column.Cell ? /* @__PURE__ */ jsx(column.Cell, { row, rowIndex: index }) : column.accessor?.(row)
805
- },
806
- column.key
807
- )),
808
- showActions && /* @__PURE__ */ jsx(Table.Td, { ta: "end", style: actionsCellStyle, children: /* @__PURE__ */ jsx(
809
- RowActions,
810
- {
811
- row,
812
- actions: rowActions,
813
- confirm,
814
- cancelLabel
815
- }
816
- ) })
817
- ]
818
- }
819
- ),
820
- expanded === true && /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnSpan, children: renderRowDetail(row) }) })
821
- ] });
794
+ /**
795
+ * One desktop row (plus its detail row when expanded), extracted so it can
796
+ * be memoized: typing in the search box or toggling another row's checkbox
797
+ * re-renders the table chrome but leaves untouched rows alone.
798
+ */
799
+ function DesktopRowBase({ row, index, id, columns, getCellProps, selected, selectLabel, onToggleSelect, expanded, expandLabel, collapseLabel, onToggleExpand, renderRowDetail, columnSpan, rowActions, confirm, cancelLabel, onRowClick, prefetch, className, measureElement, pinStyleFor, selectionCellStyle, expansionCellStyle, actionsCellStyle }) {
800
+ const showActions = (rowActions?.length ?? 0) > 0;
801
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Table.Tr, {
802
+ role: "row",
803
+ "data-index": index,
804
+ "aria-selected": selected,
805
+ ...rowClickProps(row, onRowClick),
806
+ className,
807
+ ref: measureElement,
808
+ "data-stagger": "",
809
+ onMouseEnter: prefetch ? () => prefetch(row) : void 0,
810
+ children: [
811
+ expanded !== void 0 && /* @__PURE__ */ jsx(Table.Td, {
812
+ ta: "center",
813
+ style: expansionCellStyle,
814
+ children: /* @__PURE__ */ jsx(ExpandToggle, {
815
+ expanded,
816
+ expandLabel,
817
+ collapseLabel,
818
+ onToggle: () => onToggleExpand(id)
819
+ })
820
+ }),
821
+ selected !== void 0 && /* @__PURE__ */ jsx(Table.Td, {
822
+ ta: "center",
823
+ style: selectionCellStyle,
824
+ children: /* @__PURE__ */ jsx(Checkbox, {
825
+ "aria-label": selectLabel,
826
+ checked: selected,
827
+ onChange: () => onToggleSelect(id)
828
+ })
829
+ }),
830
+ columns.map((column) => /* @__PURE__ */ jsx(Table.Td, {
831
+ ...getCellProps(column),
832
+ style: pinStyleFor(column.key),
833
+ children: column.Cell ? /* @__PURE__ */ jsx(column.Cell, {
834
+ row,
835
+ rowIndex: index
836
+ }) : column.accessor?.(row)
837
+ }, column.key)),
838
+ showActions && /* @__PURE__ */ jsx(Table.Td, {
839
+ ta: "end",
840
+ style: actionsCellStyle,
841
+ children: /* @__PURE__ */ jsx(RowActions, {
842
+ row,
843
+ actions: rowActions,
844
+ confirm,
845
+ cancelLabel
846
+ })
847
+ })
848
+ ]
849
+ }), expanded === true && /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, {
850
+ colSpan: columnSpan,
851
+ children: renderRowDetail(row)
852
+ }) })] });
822
853
  }
823
- function DesktopTable({
824
- table,
825
- rows,
826
- rowActions,
827
- confirm,
828
- prefetch,
829
- onRowClick,
830
- rowClassName,
831
- renderRowDetail,
832
- summaryRow,
833
- expansion,
834
- getRowId,
835
- bodyRef,
836
- className,
837
- rowEntries,
838
- paddingTop = 0,
839
- paddingBottom = 0,
840
- measureElement,
841
- stickyHeaderOffset = 0,
842
- stickyHeader = false,
843
- pinOffset,
844
- maxHeight,
845
- virtualScrollRef,
846
- setWidth,
847
- columnWidths,
848
- resizeLabel = "Resize column",
849
- actionsPinned = false,
850
- density = "comfortable"
851
- }) {
852
- const { columns, selection, labels, showActions, entries, columnSpan } = tableRenderModel({
853
- table,
854
- rows,
855
- rowActions,
856
- getRowId,
857
- rowEntries,
858
- renderRowDetail,
859
- expansion
860
- });
861
- const expandable = expansion !== void 0;
862
- const groupCells = headerGroupRow(columns);
863
- const summaryCells = summaryRow?.(rows);
864
- const hasRightPin = table.columns.some(
865
- (c) => pinOffset?.(c.key)?.side === "right"
866
- );
867
- const actionsEdgePinned = showActions && (hasRightPin || actionsPinned);
868
- const hasPinned = table.columns.some((c) => pinOffset?.(c.key) != null) || actionsEdgePinned;
869
- const { ref: wrapperRef, overflowing } = useHorizontalOverflow();
870
- const inScrollBox = maxHeight != null || hasPinned || overflowing;
871
- const headerCellStyle = stickyHeader ? {
872
- position: "sticky",
873
- top: inScrollBox ? 0 : stickyHeaderOffset,
874
- zIndex: PIN_Z.header,
875
- background: "var(--mantine-color-body)",
876
- boxShadow: "0 1px 0 var(--mantine-color-default-border)"
877
- } : { background: "var(--mantine-color-body)" };
878
- const expansionWidth = 36;
879
- const selectionWidth = 40;
880
- const actionsWidth = 120;
881
- const expansionLead = expandable ? expansionWidth : 0;
882
- const leads = {
883
- left: expansionLead + (selection ? selectionWidth : 0),
884
- right: showActions ? actionsWidth : 0
885
- };
886
- const hasLeftPin = table.columns.some(
887
- (c) => pinOffset?.(c.key)?.side === "left"
888
- );
889
- const pinBg = "var(--mantine-color-body)";
890
- const headerStyleFor = (column) => {
891
- const key = column.key;
892
- const pin = pinnedCellStyle(pinOffset?.(key), PIN_Z.headerPinned, leads);
893
- const merged = {
894
- ...headerCellStyle,
895
- ...pin,
896
- // A pinned column renders at the same width its sticky inset assumed,
897
- // so stacked pins stay flush even with no declared width.
898
- width: pin ? pinnedColumnWidth(column, columnWidths) : columnWidths?.[key]
899
- };
900
- if (setWidth && !merged.position) merged.position = "relative";
901
- return merged;
902
- };
903
- const expansionHeaderStyle = {
904
- ...headerCellStyle,
905
- ...leadingPinStyle(hasLeftPin, 0, PIN_Z.headerPinned)
906
- };
907
- const selectionHeaderStyle = {
908
- ...headerCellStyle,
909
- ...leadingPinStyle(hasLeftPin, expansionLead, PIN_Z.headerPinned)
910
- };
911
- const actionsHeaderStyle = {
912
- ...headerCellStyle,
913
- ...edgePinStyle("right", actionsEdgePinned, PIN_Z.headerPinned)
914
- };
915
- const edgeBodyStyle = (side, active) => {
916
- const pin = edgePinStyle(side, active, PIN_Z.body);
917
- return pin ? { ...pin, background: pinBg } : void 0;
918
- };
919
- const expansionCellStyle = leadingPinStyle(hasLeftPin, 0, PIN_Z.body, pinBg);
920
- const selectionCellStyle = leadingPinStyle(
921
- hasLeftPin,
922
- expansionLead,
923
- PIN_Z.body,
924
- pinBg
925
- );
926
- const actionsCellStyle = edgeBodyStyle("right", actionsEdgePinned);
927
- const columnName = (column) => typeof column.header === "string" ? column.header : column.key;
928
- const resizeHandleFor = (column) => setWidth ? /* @__PURE__ */ jsx(
929
- "span",
930
- {
931
- ...columnResizeHandleProps(
932
- column.key,
933
- setWidth,
934
- `${resizeLabel}: ${columnName(column)}`
935
- ),
936
- style: RESIZE_HANDLE_STYLE
937
- }
938
- ) : void 0;
939
- const bodyPinStyle = (key) => {
940
- const pin = pinnedCellStyle(pinOffset?.(key), PIN_Z.body, leads);
941
- return pin ? { ...pin, background: pinBg } : void 0;
942
- };
943
- const { verticalSpacing, horizontalSpacing } = DENSITY_SPACING[density];
944
- const minWidth = tableMinWidth(columns, {
945
- widths: columnWidths,
946
- extra: expansionLead + (selection ? 40 : 0) + (showActions ? 120 : 0)
947
- });
948
- const selectionRef = useRef(selection);
949
- selectionRef.current = selection;
950
- const toggleSelect = useCallback(
951
- (id) => selectionRef.current.toggle(id),
952
- []
953
- );
954
- const Row = useMemo(
955
- () => memo(DesktopRowBase, desktopRowPropsEqual),
956
- []
957
- );
958
- const pinSignature = pinLayoutSignature(
959
- columns,
960
- pinOffset,
961
- hasLeftPin,
962
- actionsEdgePinned
963
- );
964
- const wrapperStyle = maxHeight == null ? {
965
- width: "100%",
966
- ...hasPinned || overflowing ? { overflowX: "auto" } : {}
967
- } : { width: "100%", maxHeight, overflow: "auto" };
968
- return /* @__PURE__ */ jsx(
969
- "div",
970
- {
971
- ref: (node) => {
972
- wrapperRef(node);
973
- virtualScrollRef?.(node);
974
- },
975
- style: wrapperStyle,
976
- children: /* @__PURE__ */ jsxs(
977
- Table,
978
- {
979
- ...table.getTableProps(),
980
- className,
981
- highlightOnHover: true,
982
- verticalSpacing,
983
- horizontalSpacing,
984
- miw: Math.max(480, minWidth),
985
- children: [
986
- /* @__PURE__ */ jsxs(Table.Thead, { style: { background: "var(--mantine-color-body)" }, children: [
987
- groupCells && /* @__PURE__ */ jsxs(Table.Tr, { children: [
988
- expandable && /* @__PURE__ */ jsx(Table.Th, {}),
989
- selection && /* @__PURE__ */ jsx(Table.Th, {}),
990
- groupCells.map((cell) => /* @__PURE__ */ jsx(
991
- Table.Th,
992
- {
993
- colSpan: cell.span,
994
- ta: "center",
995
- fw: 600,
996
- style: {
997
- borderBottom: "1px solid var(--mantine-color-default-border)"
998
- },
999
- children: cell.label
1000
- },
1001
- cell.key
1002
- )),
1003
- showActions && /* @__PURE__ */ jsx(Table.Th, {})
1004
- ] }),
1005
- /* @__PURE__ */ jsxs(Table.Tr, { ...table.getHeaderRowProps(), children: [
1006
- expandable && /* @__PURE__ */ jsx(
1007
- Table.Th,
1008
- {
1009
- w: expansionWidth,
1010
- ta: "center",
1011
- style: expansionHeaderStyle,
1012
- children: /* @__PURE__ */ jsx(VisuallyHidden, { children: labels.expandRow })
1013
- }
1014
- ),
1015
- selection && /* @__PURE__ */ jsx(
1016
- Table.Th,
1017
- {
1018
- w: selectionWidth,
1019
- ta: "center",
1020
- style: selectionHeaderStyle,
1021
- children: /* @__PURE__ */ jsx(
1022
- Checkbox,
1023
- {
1024
- "aria-label": labels.selectAll,
1025
- checked: selection.headerState === "all",
1026
- indeterminate: selection.headerState === "some",
1027
- onChange: selection.toggleAll
1028
- }
1029
- )
1030
- }
1031
- ),
1032
- columns.map((column) => /* @__PURE__ */ jsx(
1033
- HeaderCell,
1034
- {
1035
- table,
1036
- column,
1037
- stickyStyle: headerStyleFor(column),
1038
- resizeHandle: resizeHandleFor(column)
1039
- },
1040
- column.key
1041
- )),
1042
- showActions && /* @__PURE__ */ jsx(Table.Th, { ta: "end", w: actionsWidth, style: actionsHeaderStyle, children: labels.actions })
1043
- ] })
1044
- ] }),
1045
- /* @__PURE__ */ jsxs(Table.Tbody, { ref: bodyRef, children: [
1046
- paddingTop > 0 && /* @__PURE__ */ jsx(Table.Tr, { "aria-hidden": true, children: /* @__PURE__ */ jsx(
1047
- Table.Td,
1048
- {
1049
- colSpan: columnSpan,
1050
- style: { height: paddingTop, padding: 0 }
1051
- }
1052
- ) }),
1053
- entries.map(({ row, index, key }) => {
1054
- const id = getRowId(row);
1055
- return /* @__PURE__ */ jsx(
1056
- Row,
1057
- {
1058
- row,
1059
- index,
1060
- id,
1061
- columns,
1062
- getCellProps: table.getCellProps,
1063
- selected: selection?.isSelected(id),
1064
- selectLabel: labels.selectRow,
1065
- onToggleSelect: toggleSelect,
1066
- expanded: expansion?.isExpanded(id),
1067
- expandLabel: labels.expandRow,
1068
- collapseLabel: labels.collapseRow,
1069
- onToggleExpand: expansion?.toggle,
1070
- renderRowDetail,
1071
- columnSpan,
1072
- rowActions,
1073
- confirm,
1074
- cancelLabel: labels.cancel,
1075
- onRowClick,
1076
- prefetch,
1077
- className: rowClassName?.(row, index),
1078
- measureElement,
1079
- pinStyleFor: bodyPinStyle,
1080
- selectionCellStyle,
1081
- expansionCellStyle,
1082
- actionsCellStyle,
1083
- pinSignature
1084
- },
1085
- key
1086
- );
1087
- }),
1088
- paddingBottom > 0 && /* @__PURE__ */ jsx(Table.Tr, { "aria-hidden": true, children: /* @__PURE__ */ jsx(
1089
- Table.Td,
1090
- {
1091
- colSpan: columnSpan,
1092
- style: { height: paddingBottom, padding: 0 }
1093
- }
1094
- ) })
1095
- ] }),
1096
- summaryCells && /* @__PURE__ */ jsx(Table.Tfoot, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1097
- expandable && /* @__PURE__ */ jsx(Table.Td, {}),
1098
- selection && /* @__PURE__ */ jsx(Table.Td, {}),
1099
- columns.map((column) => /* @__PURE__ */ jsx(
1100
- Table.Td,
1101
- {
1102
- ...table.getCellProps(column),
1103
- fw: 600,
1104
- c: "dimmed",
1105
- children: summaryCells[column.key]
1106
- },
1107
- column.key
1108
- )),
1109
- showActions && /* @__PURE__ */ jsx(Table.Td, {})
1110
- ] }) })
1111
- ]
1112
- }
1113
- )
1114
- }
1115
- );
854
+ /** Desktop table rendering driven by core prop-getters. */
855
+ function DesktopTable({ table, rows, rowActions, confirm, prefetch, onRowClick, rowClassName, renderRowDetail, summaryRow, expansion, getRowId, bodyRef, className, rowEntries, paddingTop = 0, paddingBottom = 0, measureElement, stickyHeaderOffset = 0, stickyHeader = false, pinOffset, maxHeight, virtualScrollRef, setWidth, columnWidths, resizeLabel = "Resize column", actionsPinned = false, density = "comfortable" }) {
856
+ const { columns, selection, labels, showActions, entries, columnSpan } = tableRenderModel({
857
+ table,
858
+ rows,
859
+ rowActions,
860
+ getRowId,
861
+ rowEntries,
862
+ renderRowDetail,
863
+ expansion
864
+ });
865
+ const expandable = expansion !== void 0;
866
+ const groupCells = headerGroupRow(columns);
867
+ const summaryCells = summaryRow?.(rows);
868
+ const hasRightPin = table.columns.some((c) => pinOffset?.(c.key)?.side === "right");
869
+ const actionsEdgePinned = showActions && (hasRightPin || actionsPinned);
870
+ const hasPinned = table.columns.some((c) => pinOffset?.(c.key) != null) || actionsEdgePinned;
871
+ const { ref: wrapperRef, overflowing } = useHorizontalOverflow();
872
+ const headerCellStyle = stickyHeader ? {
873
+ position: "sticky",
874
+ top: maxHeight != null || hasPinned || overflowing ? 0 : stickyHeaderOffset,
875
+ zIndex: PIN_Z.header,
876
+ background: "var(--mantine-color-body)",
877
+ boxShadow: "0 1px 0 var(--mantine-color-default-border)"
878
+ } : { background: "var(--mantine-color-body)" };
879
+ const expansionWidth = 36;
880
+ const selectionWidth = 40;
881
+ const actionsWidth = 120;
882
+ const expansionLead = expandable ? expansionWidth : 0;
883
+ const leads = {
884
+ left: expansionLead + (selection ? selectionWidth : 0),
885
+ right: showActions ? actionsWidth : 0
886
+ };
887
+ const hasLeftPin = table.columns.some((c) => pinOffset?.(c.key)?.side === "left");
888
+ const pinBg = "var(--mantine-color-body)";
889
+ const headerStyleFor = (column) => {
890
+ const key = column.key;
891
+ const pin = pinnedCellStyle(pinOffset?.(key), PIN_Z.headerPinned, leads);
892
+ const merged = {
893
+ ...headerCellStyle,
894
+ ...pin,
895
+ width: pin ? pinnedColumnWidth(column, columnWidths) : columnWidths?.[key]
896
+ };
897
+ if (setWidth && !merged.position) merged.position = "relative";
898
+ return merged;
899
+ };
900
+ const expansionHeaderStyle = {
901
+ ...headerCellStyle,
902
+ ...leadingPinStyle(hasLeftPin, 0, PIN_Z.headerPinned)
903
+ };
904
+ const selectionHeaderStyle = {
905
+ ...headerCellStyle,
906
+ ...leadingPinStyle(hasLeftPin, expansionLead, PIN_Z.headerPinned)
907
+ };
908
+ const actionsHeaderStyle = {
909
+ ...headerCellStyle,
910
+ ...edgePinStyle("right", actionsEdgePinned, PIN_Z.headerPinned)
911
+ };
912
+ const edgeBodyStyle = (side, active) => {
913
+ const pin = edgePinStyle(side, active, PIN_Z.body);
914
+ return pin ? {
915
+ ...pin,
916
+ background: pinBg
917
+ } : void 0;
918
+ };
919
+ const expansionCellStyle = leadingPinStyle(hasLeftPin, 0, PIN_Z.body, pinBg);
920
+ const selectionCellStyle = leadingPinStyle(hasLeftPin, expansionLead, PIN_Z.body, pinBg);
921
+ const actionsCellStyle = edgeBodyStyle("right", actionsEdgePinned);
922
+ const columnName = (column) => typeof column.header === "string" ? column.header : column.key;
923
+ const resizeHandleFor = (column) => setWidth ? /* @__PURE__ */ jsx("span", {
924
+ ...columnResizeHandleProps(column.key, setWidth, `${resizeLabel}: ${columnName(column)}`),
925
+ style: RESIZE_HANDLE_STYLE
926
+ }) : void 0;
927
+ const bodyPinStyle = (key) => {
928
+ const pin = pinnedCellStyle(pinOffset?.(key), PIN_Z.body, leads);
929
+ return pin ? {
930
+ ...pin,
931
+ background: pinBg
932
+ } : void 0;
933
+ };
934
+ const { verticalSpacing, horizontalSpacing } = DENSITY_SPACING[density];
935
+ const minWidth = tableMinWidth(columns, {
936
+ widths: columnWidths,
937
+ extra: expansionLead + (selection ? 40 : 0) + (showActions ? 120 : 0)
938
+ });
939
+ const selectionRef = useRef(selection);
940
+ selectionRef.current = selection;
941
+ const toggleSelect = useCallback((id) => selectionRef.current.toggle(id), []);
942
+ const Row = useMemo(() => memo(DesktopRowBase, desktopRowPropsEqual), []);
943
+ const pinSignature = pinLayoutSignature(columns, pinOffset, hasLeftPin, actionsEdgePinned);
944
+ return /* @__PURE__ */ jsx("div", {
945
+ ref: (node) => {
946
+ wrapperRef(node);
947
+ virtualScrollRef?.(node);
948
+ },
949
+ style: maxHeight == null ? {
950
+ width: "100%",
951
+ ...hasPinned || overflowing ? { overflowX: "auto" } : {}
952
+ } : {
953
+ width: "100%",
954
+ maxHeight,
955
+ overflow: "auto"
956
+ },
957
+ children: /* @__PURE__ */ jsxs(Table, {
958
+ ...table.getTableProps(),
959
+ className,
960
+ highlightOnHover: true,
961
+ verticalSpacing,
962
+ horizontalSpacing,
963
+ miw: Math.max(480, minWidth),
964
+ style: stickyHeader ? {
965
+ borderCollapse: "separate",
966
+ borderSpacing: 0
967
+ } : void 0,
968
+ children: [
969
+ /* @__PURE__ */ jsxs(Table.Thead, {
970
+ style: { background: "var(--mantine-color-body)" },
971
+ children: [groupCells && /* @__PURE__ */ jsxs(Table.Tr, { children: [
972
+ expandable && /* @__PURE__ */ jsx(Table.Th, {}),
973
+ selection && /* @__PURE__ */ jsx(Table.Th, {}),
974
+ groupCells.map((cell) => /* @__PURE__ */ jsx(Table.Th, {
975
+ colSpan: cell.span,
976
+ ta: "center",
977
+ fw: 600,
978
+ style: { borderBottom: "1px solid var(--mantine-color-default-border)" },
979
+ children: cell.label
980
+ }, cell.key)),
981
+ showActions && /* @__PURE__ */ jsx(Table.Th, {})
982
+ ] }), /* @__PURE__ */ jsxs(Table.Tr, {
983
+ ...table.getHeaderRowProps(),
984
+ children: [
985
+ expandable && /* @__PURE__ */ jsx(Table.Th, {
986
+ w: expansionWidth,
987
+ ta: "center",
988
+ style: expansionHeaderStyle,
989
+ children: /* @__PURE__ */ jsx(VisuallyHidden, { children: labels.expandRow })
990
+ }),
991
+ selection && /* @__PURE__ */ jsx(Table.Th, {
992
+ w: selectionWidth,
993
+ ta: "center",
994
+ style: selectionHeaderStyle,
995
+ children: /* @__PURE__ */ jsx(Checkbox, {
996
+ "aria-label": labels.selectAll,
997
+ checked: selection.headerState === "all",
998
+ indeterminate: selection.headerState === "some",
999
+ onChange: selection.toggleAll
1000
+ })
1001
+ }),
1002
+ columns.map((column) => /* @__PURE__ */ jsx(HeaderCell, {
1003
+ table,
1004
+ column,
1005
+ stickyStyle: headerStyleFor(column),
1006
+ resizeHandle: resizeHandleFor(column)
1007
+ }, column.key)),
1008
+ showActions && /* @__PURE__ */ jsx(Table.Th, {
1009
+ ta: "end",
1010
+ w: actionsWidth,
1011
+ style: actionsHeaderStyle,
1012
+ children: labels.actions
1013
+ })
1014
+ ]
1015
+ })]
1016
+ }),
1017
+ /* @__PURE__ */ jsxs(Table.Tbody, {
1018
+ ref: bodyRef,
1019
+ children: [
1020
+ paddingTop > 0 && /* @__PURE__ */ jsx(Table.Tr, {
1021
+ "aria-hidden": true,
1022
+ children: /* @__PURE__ */ jsx(Table.Td, {
1023
+ colSpan: columnSpan,
1024
+ style: {
1025
+ height: paddingTop,
1026
+ padding: 0
1027
+ }
1028
+ })
1029
+ }),
1030
+ entries.map(({ row, index, key }) => {
1031
+ const id = getRowId(row);
1032
+ return /* @__PURE__ */ jsx(Row, {
1033
+ row,
1034
+ index,
1035
+ id,
1036
+ columns,
1037
+ getCellProps: table.getCellProps,
1038
+ selected: selection?.isSelected(id),
1039
+ selectLabel: labels.selectRow,
1040
+ onToggleSelect: toggleSelect,
1041
+ expanded: expansion?.isExpanded(id),
1042
+ expandLabel: labels.expandRow,
1043
+ collapseLabel: labels.collapseRow,
1044
+ onToggleExpand: expansion?.toggle,
1045
+ renderRowDetail,
1046
+ columnSpan,
1047
+ rowActions,
1048
+ confirm,
1049
+ cancelLabel: labels.cancel,
1050
+ onRowClick,
1051
+ prefetch,
1052
+ className: rowClassName?.(row, index),
1053
+ measureElement,
1054
+ pinStyleFor: bodyPinStyle,
1055
+ selectionCellStyle,
1056
+ expansionCellStyle,
1057
+ actionsCellStyle,
1058
+ pinSignature
1059
+ }, key);
1060
+ }),
1061
+ paddingBottom > 0 && /* @__PURE__ */ jsx(Table.Tr, {
1062
+ "aria-hidden": true,
1063
+ children: /* @__PURE__ */ jsx(Table.Td, {
1064
+ colSpan: columnSpan,
1065
+ style: {
1066
+ height: paddingBottom,
1067
+ padding: 0
1068
+ }
1069
+ })
1070
+ })
1071
+ ]
1072
+ }),
1073
+ summaryCells && /* @__PURE__ */ jsx(Table.Tfoot, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1074
+ expandable && /* @__PURE__ */ jsx(Table.Td, {}),
1075
+ selection && /* @__PURE__ */ jsx(Table.Td, {}),
1076
+ columns.map((column) => /* @__PURE__ */ jsx(Table.Td, {
1077
+ ...table.getCellProps(column),
1078
+ fw: 600,
1079
+ c: "dimmed",
1080
+ children: summaryCells[column.key]
1081
+ }, column.key)),
1082
+ showActions && /* @__PURE__ */ jsx(Table.Td, {})
1083
+ ] }) })
1084
+ ]
1085
+ })
1086
+ });
1116
1087
  }
1117
- function EmptyState({
1118
- title,
1119
- description,
1120
- icon,
1121
- action
1122
- }) {
1123
- return /* @__PURE__ */ jsxs(
1124
- Stack,
1125
- {
1126
- role: "status",
1127
- align: "center",
1128
- justify: "center",
1129
- gap: 6,
1130
- py: 48,
1131
- px: 16,
1132
- children: [
1133
- /* @__PURE__ */ jsx(Text, { c: "dimmed", "aria-hidden": true, children: icon ?? /* @__PURE__ */ jsx(InboxIcon, { size: 40 }) }),
1134
- /* @__PURE__ */ jsx(Text, { fw: 600, fz: "md", children: title }),
1135
- description && /* @__PURE__ */ jsx(Text, { c: "dimmed", fz: "sm", ta: "center", maw: 360, children: description }),
1136
- action
1137
- ]
1138
- }
1139
- );
1088
+ //#endregion
1089
+ //#region src/components/EmptyState.tsx
1090
+ /** Centred "nothing to show" placeholder. */
1091
+ function EmptyState({ title, description, icon, action }) {
1092
+ return /* @__PURE__ */ jsxs(Stack, {
1093
+ role: "status",
1094
+ align: "center",
1095
+ justify: "center",
1096
+ gap: 6,
1097
+ py: 48,
1098
+ px: 16,
1099
+ children: [
1100
+ /* @__PURE__ */ jsx(Text, {
1101
+ c: "dimmed",
1102
+ "aria-hidden": true,
1103
+ children: icon ?? /* @__PURE__ */ jsx(InboxIcon, { size: 40 })
1104
+ }),
1105
+ /* @__PURE__ */ jsx(Text, {
1106
+ fw: 600,
1107
+ fz: "md",
1108
+ children: title
1109
+ }),
1110
+ description && /* @__PURE__ */ jsx(Text, {
1111
+ c: "dimmed",
1112
+ fz: "sm",
1113
+ ta: "center",
1114
+ maw: 360,
1115
+ children: description
1116
+ }),
1117
+ action
1118
+ ]
1119
+ });
1140
1120
  }
1141
- function ErrorState({
1142
- error,
1143
- title,
1144
- message,
1145
- retryLabel,
1146
- onRetry,
1147
- isRetrying
1148
- }) {
1149
- return /* @__PURE__ */ jsx(
1150
- Alert,
1151
- {
1152
- icon: /* @__PURE__ */ jsx(AlertIcon, { size: 16 }),
1153
- color: "red",
1154
- variant: "light",
1155
- title,
1156
- children: /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", wrap: "nowrap", gap: "md", children: [
1157
- /* @__PURE__ */ jsxs("div", { children: [
1158
- /* @__PURE__ */ jsx(Text, { fz: "sm", children: message }),
1159
- /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", mt: 2, children: error.message })
1160
- ] }),
1161
- onRetry && /* @__PURE__ */ jsx(
1162
- Button,
1163
- {
1164
- size: "xs",
1165
- variant: "light",
1166
- color: "red",
1167
- leftSection: /* @__PURE__ */ jsx(RefreshIcon, { size: 14 }),
1168
- onClick: onRetry,
1169
- loading: isRetrying,
1170
- children: retryLabel
1171
- }
1172
- )
1173
- ] })
1174
- }
1175
- );
1121
+ //#endregion
1122
+ //#region src/components/ErrorState.tsx
1123
+ /** Inline error alert with an optional retry button. */
1124
+ function ErrorState({ error, title, message, retryLabel, onRetry, isRetrying }) {
1125
+ return /* @__PURE__ */ jsx(Alert, {
1126
+ icon: /* @__PURE__ */ jsx(AlertIcon, { size: 16 }),
1127
+ color: "red",
1128
+ variant: "light",
1129
+ title,
1130
+ children: /* @__PURE__ */ jsxs(Group, {
1131
+ justify: "space-between",
1132
+ align: "center",
1133
+ wrap: "nowrap",
1134
+ gap: "md",
1135
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Text, {
1136
+ fz: "sm",
1137
+ children: message
1138
+ }), /* @__PURE__ */ jsx(Text, {
1139
+ fz: "xs",
1140
+ c: "dimmed",
1141
+ mt: 2,
1142
+ children: error.message
1143
+ })] }), onRetry && /* @__PURE__ */ jsx(Button, {
1144
+ size: "xs",
1145
+ variant: "light",
1146
+ color: "red",
1147
+ leftSection: /* @__PURE__ */ jsx(RefreshIcon, { size: 14 }),
1148
+ onClick: onRetry,
1149
+ loading: isRetrying,
1150
+ children: retryLabel
1151
+ })]
1152
+ })
1153
+ });
1176
1154
  }
1177
- function FilterDrawer({
1178
- opened,
1179
- onClose,
1180
- filters,
1181
- activeFilterCount,
1182
- onClearFilters,
1183
- labels,
1184
- dir = "ltr"
1185
- }) {
1186
- return /* @__PURE__ */ jsx(
1187
- Drawer,
1188
- {
1189
- opened,
1190
- onClose,
1191
- position: dir === "rtl" ? "left" : "right",
1192
- size: 380,
1193
- title: labels.filters,
1194
- overlayProps: { opacity: 0.4, blur: 2 },
1195
- closeButtonProps: { "aria-label": labels.cancel },
1196
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", mih: "60vh", justify: "space-between", children: [
1197
- /* @__PURE__ */ jsx(Stack, { gap: "md", children: filters }),
1198
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", pt: "md", children: [
1199
- /* @__PURE__ */ jsx(
1200
- Button,
1201
- {
1202
- variant: "subtle",
1203
- onClick: onClearFilters,
1204
- disabled: activeFilterCount === 0,
1205
- children: labels.clearAll
1206
- }
1207
- ),
1208
- /* @__PURE__ */ jsx(Button, { onClick: onClose, children: labels.applyFilters })
1209
- ] })
1210
- ] })
1211
- }
1212
- );
1155
+ //#endregion
1156
+ //#region src/components/FilterDrawer.tsx
1157
+ /** Right-side drawer holding the caller's filter widgets + apply/clear. */
1158
+ function FilterDrawer({ opened, onClose, filters, activeFilterCount, onClearFilters, labels, dir = "ltr" }) {
1159
+ return /* @__PURE__ */ jsx(Drawer, {
1160
+ opened,
1161
+ onClose,
1162
+ position: dir === "rtl" ? "left" : "right",
1163
+ size: 380,
1164
+ title: labels.filters,
1165
+ overlayProps: {
1166
+ opacity: .4,
1167
+ blur: 2
1168
+ },
1169
+ closeButtonProps: { "aria-label": labels.cancel },
1170
+ children: /* @__PURE__ */ jsxs(Stack, {
1171
+ gap: "md",
1172
+ mih: "60vh",
1173
+ justify: "space-between",
1174
+ children: [/* @__PURE__ */ jsx(Stack, {
1175
+ gap: "md",
1176
+ children: filters
1177
+ }), /* @__PURE__ */ jsxs(Group, {
1178
+ justify: "space-between",
1179
+ pt: "md",
1180
+ children: [/* @__PURE__ */ jsx(Button, {
1181
+ variant: "subtle",
1182
+ onClick: onClearFilters,
1183
+ disabled: activeFilterCount === 0,
1184
+ children: labels.clearAll
1185
+ }), /* @__PURE__ */ jsx(Button, {
1186
+ onClick: onClose,
1187
+ children: labels.applyFilters
1188
+ })]
1189
+ })]
1190
+ })
1191
+ });
1213
1192
  }
1193
+ //#endregion
1194
+ //#region src/components/MobileCards.tsx
1214
1195
  function mobileLabel(column) {
1215
- return column.mobileLabel ?? (typeof column.header === "string" ? column.header : column.key);
1196
+ return column.mobileLabel ?? (typeof column.header === "string" ? column.header : column.key);
1216
1197
  }
1217
- function MobileCards({
1218
- table,
1219
- rows,
1220
- rowActions,
1221
- confirm,
1222
- getRowId,
1223
- bodyRef,
1224
- className,
1225
- rowEntries,
1226
- paddingTop = 0,
1227
- paddingBottom = 0,
1228
- measureElement,
1229
- density = "comfortable",
1230
- onRowClick,
1231
- rowClassName,
1232
- renderRowDetail,
1233
- summaryRow,
1234
- expansion
1235
- }) {
1236
- const { columns, selection, labels } = table;
1237
- const compact = density === "compact";
1238
- const cardPadding = compact ? "sm" : "md";
1239
- const cardGap = compact ? 4 : "xs";
1240
- const entries = rowEntries ?? rows.map((row, index) => ({
1241
- row,
1242
- index,
1243
- key: getRowId(row)
1244
- }));
1245
- const summaryCells = summaryRow?.(rows);
1246
- return /* @__PURE__ */ jsxs(
1247
- Stack,
1248
- {
1249
- gap: compact ? "xs" : "sm",
1250
- ref: bodyRef,
1251
- className,
1252
- ...table.getTableProps({ role: "list" }),
1253
- children: [
1254
- paddingTop > 0 && /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: { height: paddingTop } }),
1255
- entries.map(({ row, index, key }) => {
1256
- const id = getRowId(row);
1257
- return /* @__PURE__ */ jsx(
1258
- Card,
1259
- {
1260
- ...rowClickProps(row, onRowClick),
1261
- className: rowClassName?.(row, index),
1262
- ref: measureElement,
1263
- "data-index": index,
1264
- withBorder: true,
1265
- radius: "md",
1266
- padding: cardPadding,
1267
- role: "listitem",
1268
- "data-stagger": "",
1269
- children: /* @__PURE__ */ jsxs(Stack, { gap: cardGap, children: [
1270
- selection && /* @__PURE__ */ jsx(
1271
- Checkbox,
1272
- {
1273
- "aria-label": labels.selectRow,
1274
- checked: selection.isSelected(id),
1275
- onChange: () => selection.toggle(id)
1276
- }
1277
- ),
1278
- expansion && /* @__PURE__ */ jsx(Group, { justify: "flex-end", children: /* @__PURE__ */ jsx(
1279
- ExpandToggle,
1280
- {
1281
- expanded: expansion.isExpanded(id),
1282
- expandLabel: labels.expandRow,
1283
- collapseLabel: labels.collapseRow,
1284
- onToggle: () => expansion.toggle(id)
1285
- }
1286
- ) }),
1287
- columns.map((column) => /* @__PURE__ */ jsxs("div", { children: [
1288
- /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", tt: "uppercase", fw: 500, children: mobileLabel(column) }),
1289
- /* @__PURE__ */ jsx(Text, { component: "div", fz: "sm", children: column.Cell ? /* @__PURE__ */ jsx(column.Cell, { row, rowIndex: index }) : column.accessor?.(row) })
1290
- ] }, column.key)),
1291
- expansion?.isExpanded(id) === true && /* @__PURE__ */ jsx("div", { children: renderRowDetail(row) }),
1292
- rowActions && rowActions.length > 0 && /* @__PURE__ */ jsx(Group, { gap: 4, justify: "flex-end", pt: 4, children: rowActions.map((action) => {
1293
- if (action.isHidden?.(row)) return null;
1294
- const reason = resolveDisabledReason(
1295
- action.disabledReason?.(row)
1296
- );
1297
- const disabled = reason !== void 0 || (action.isDisabled?.(row) ?? false);
1298
- const run = disabled ? void 0 : () => runRowAction(action, row, confirm, labels.cancel);
1299
- return action.icon ? /* @__PURE__ */ jsx(
1300
- Tooltip,
1301
- {
1302
- label: reason ?? action.label,
1303
- withArrow: true,
1304
- openDelay: 200,
1305
- children: /* @__PURE__ */ jsx(
1306
- ActionIcon,
1307
- {
1308
- variant: "subtle",
1309
- color: action.color,
1310
- size: "sm",
1311
- disabled,
1312
- "aria-label": action.label,
1313
- onClick: run,
1314
- children: action.icon
1315
- }
1316
- )
1317
- },
1318
- action.key
1319
- ) : /* @__PURE__ */ jsx(
1320
- Button,
1321
- {
1322
- variant: "subtle",
1323
- color: action.color,
1324
- size: "compact-sm",
1325
- disabled,
1326
- onClick: run,
1327
- children: action.label
1328
- },
1329
- action.key
1330
- );
1331
- }) })
1332
- ] })
1333
- },
1334
- key
1335
- );
1336
- }),
1337
- paddingBottom > 0 && /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: { height: paddingBottom } }),
1338
- summaryCells && /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", padding: cardPadding, role: "listitem", children: /* @__PURE__ */ jsx(Stack, { gap: cardGap, children: columns.filter((column) => summaryCells[column.key] !== void 0).map((column) => /* @__PURE__ */ jsxs("div", { children: [
1339
- /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", tt: "uppercase", fw: 500, children: mobileLabel(column) }),
1340
- /* @__PURE__ */ jsx(Text, { component: "div", fz: "sm", fw: 600, children: summaryCells[column.key] })
1341
- ] }, column.key)) }) })
1342
- ]
1343
- }
1344
- );
1198
+ /** Mobile rendering: one Mantine Card per row with labelled key/value rows. */
1199
+ function MobileCards({ table, rows, rowActions, confirm, getRowId, bodyRef, className, rowEntries, paddingTop = 0, paddingBottom = 0, measureElement, density = "comfortable", onRowClick, rowClassName, renderRowDetail, summaryRow, expansion }) {
1200
+ const { columns, selection, labels } = table;
1201
+ const compact = density === "compact";
1202
+ const cardPadding = compact ? "sm" : "md";
1203
+ const cardGap = compact ? 4 : "xs";
1204
+ const entries = rowEntries ?? rows.map((row, index) => ({
1205
+ row,
1206
+ index,
1207
+ key: getRowId(row)
1208
+ }));
1209
+ const summaryCells = summaryRow?.(rows);
1210
+ return /* @__PURE__ */ jsxs(Stack, {
1211
+ gap: compact ? "xs" : "sm",
1212
+ ref: bodyRef,
1213
+ className,
1214
+ ...table.getTableProps({ role: "list" }),
1215
+ children: [
1216
+ paddingTop > 0 && /* @__PURE__ */ jsx("div", {
1217
+ "aria-hidden": true,
1218
+ style: { height: paddingTop }
1219
+ }),
1220
+ entries.map(({ row, index, key }) => {
1221
+ const id = getRowId(row);
1222
+ return /* @__PURE__ */ jsx(Card, {
1223
+ ...rowClickProps(row, onRowClick),
1224
+ className: rowClassName?.(row, index),
1225
+ ref: measureElement,
1226
+ "data-index": index,
1227
+ withBorder: true,
1228
+ radius: "md",
1229
+ padding: cardPadding,
1230
+ role: "listitem",
1231
+ "data-stagger": "",
1232
+ children: /* @__PURE__ */ jsxs(Stack, {
1233
+ gap: cardGap,
1234
+ children: [
1235
+ selection && /* @__PURE__ */ jsx(Checkbox, {
1236
+ "aria-label": labels.selectRow,
1237
+ checked: selection.isSelected(id),
1238
+ onChange: () => selection.toggle(id)
1239
+ }),
1240
+ expansion && /* @__PURE__ */ jsx(Group, {
1241
+ justify: "flex-end",
1242
+ children: /* @__PURE__ */ jsx(ExpandToggle, {
1243
+ expanded: expansion.isExpanded(id),
1244
+ expandLabel: labels.expandRow,
1245
+ collapseLabel: labels.collapseRow,
1246
+ onToggle: () => expansion.toggle(id)
1247
+ })
1248
+ }),
1249
+ columns.map((column) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Text, {
1250
+ fz: "xs",
1251
+ c: "dimmed",
1252
+ tt: "uppercase",
1253
+ fw: 500,
1254
+ children: mobileLabel(column)
1255
+ }), /* @__PURE__ */ jsx(Text, {
1256
+ component: "div",
1257
+ fz: "sm",
1258
+ children: column.Cell ? /* @__PURE__ */ jsx(column.Cell, {
1259
+ row,
1260
+ rowIndex: index
1261
+ }) : column.accessor?.(row)
1262
+ })] }, column.key)),
1263
+ expansion?.isExpanded(id) === true && /* @__PURE__ */ jsx("div", { children: renderRowDetail(row) }),
1264
+ rowActions && rowActions.length > 0 && /* @__PURE__ */ jsx(Group, {
1265
+ gap: 4,
1266
+ justify: "flex-end",
1267
+ pt: 4,
1268
+ children: rowActions.map((action) => {
1269
+ if (action.isHidden?.(row)) return null;
1270
+ const reason = resolveDisabledReason(action.disabledReason?.(row));
1271
+ const disabled = reason !== void 0 || (action.isDisabled?.(row) ?? false);
1272
+ const run = disabled ? void 0 : () => runRowAction(action, row, confirm, labels.cancel);
1273
+ return action.icon ? /* @__PURE__ */ jsx(Tooltip, {
1274
+ label: reason ?? action.label,
1275
+ withArrow: true,
1276
+ openDelay: 200,
1277
+ children: /* @__PURE__ */ jsx(ActionIcon, {
1278
+ variant: "subtle",
1279
+ color: action.color,
1280
+ size: "sm",
1281
+ disabled,
1282
+ "aria-label": action.label,
1283
+ onClick: run,
1284
+ children: action.icon
1285
+ })
1286
+ }, action.key) : /* @__PURE__ */ jsx(Button, {
1287
+ variant: "subtle",
1288
+ color: action.color,
1289
+ size: "compact-sm",
1290
+ disabled,
1291
+ onClick: run,
1292
+ children: action.label
1293
+ }, action.key);
1294
+ })
1295
+ })
1296
+ ]
1297
+ })
1298
+ }, key);
1299
+ }),
1300
+ paddingBottom > 0 && /* @__PURE__ */ jsx("div", {
1301
+ "aria-hidden": true,
1302
+ style: { height: paddingBottom }
1303
+ }),
1304
+ summaryCells && /* @__PURE__ */ jsx(Card, {
1305
+ withBorder: true,
1306
+ radius: "md",
1307
+ padding: cardPadding,
1308
+ role: "listitem",
1309
+ children: /* @__PURE__ */ jsx(Stack, {
1310
+ gap: cardGap,
1311
+ children: columns.filter((column) => summaryCells[column.key] !== void 0).map((column) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Text, {
1312
+ fz: "xs",
1313
+ c: "dimmed",
1314
+ tt: "uppercase",
1315
+ fw: 500,
1316
+ children: mobileLabel(column)
1317
+ }), /* @__PURE__ */ jsx(Text, {
1318
+ component: "div",
1319
+ fz: "sm",
1320
+ fw: 600,
1321
+ children: summaryCells[column.key]
1322
+ })] }, column.key))
1323
+ })
1324
+ })
1325
+ ]
1326
+ });
1345
1327
  }
1346
- function PaginationFooter({
1347
- page,
1348
- totalPages,
1349
- limit,
1350
- total,
1351
- fromIndex,
1352
- toIndex,
1353
- onPageChange,
1354
- onLimitChange,
1355
- labels
1356
- }) {
1357
- const safeTotalPages = Math.max(totalPages, 1);
1358
- const safePage = Math.min(Math.max(page, 1), safeTotalPages);
1359
- const options = pageSizeOptions(limit).map((n) => ({
1360
- value: String(n),
1361
- label: String(n)
1362
- }));
1363
- return /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", wrap: "wrap", gap: "md", pt: "xs", children: [
1364
- /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "center", wrap: "nowrap", children: [
1365
- /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", children: labels.rowsPerPage }),
1366
- /* @__PURE__ */ jsx(
1367
- Select,
1368
- {
1369
- "aria-label": labels.rowsPerPage,
1370
- data: options,
1371
- value: String(limit),
1372
- onChange: (v) => onLimitChange(Number(v)),
1373
- size: "xs",
1374
- w: 76,
1375
- allowDeselect: false,
1376
- comboboxProps: { withinPortal: false }
1377
- }
1378
- ),
1379
- total > 0 && /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", children: labels.showing({ from: fromIndex, to: toIndex, total }) })
1380
- ] }),
1381
- /* @__PURE__ */ jsxs(Group, { gap: "sm", align: "center", wrap: "nowrap", children: [
1382
- /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", children: labels.pageOf({ page: safePage, total: safeTotalPages }) }),
1383
- /* @__PURE__ */ jsx(
1384
- Pagination,
1385
- {
1386
- total: safeTotalPages,
1387
- value: safePage,
1388
- onChange: onPageChange,
1389
- size: "sm",
1390
- siblings: 1,
1391
- boundaries: 1,
1392
- getControlProps: (control) => ({
1393
- "aria-label": control === "previous" ? labels.previousPage : labels.nextPage
1394
- })
1395
- }
1396
- )
1397
- ] })
1398
- ] });
1328
+ //#endregion
1329
+ //#region src/components/PaginationFooter.tsx
1330
+ /** Desktop pagination bar: page-size + range on the left, pager on the right. */
1331
+ function PaginationFooter({ page, totalPages, limit, total, fromIndex, toIndex, onPageChange, onLimitChange, labels }) {
1332
+ const safeTotalPages = Math.max(totalPages, 1);
1333
+ const safePage = Math.min(Math.max(page, 1), safeTotalPages);
1334
+ const options = pageSizeOptions(limit).map((n) => ({
1335
+ value: String(n),
1336
+ label: String(n)
1337
+ }));
1338
+ return /* @__PURE__ */ jsxs(Group, {
1339
+ justify: "space-between",
1340
+ align: "center",
1341
+ wrap: "wrap",
1342
+ gap: "md",
1343
+ pt: "xs",
1344
+ children: [/* @__PURE__ */ jsxs(Group, {
1345
+ gap: "xs",
1346
+ align: "center",
1347
+ wrap: "nowrap",
1348
+ children: [
1349
+ /* @__PURE__ */ jsx(Text, {
1350
+ fz: "xs",
1351
+ c: "dimmed",
1352
+ children: labels.rowsPerPage
1353
+ }),
1354
+ /* @__PURE__ */ jsx(Select, {
1355
+ "aria-label": labels.rowsPerPage,
1356
+ data: options,
1357
+ value: String(limit),
1358
+ onChange: (v) => onLimitChange(Number(v)),
1359
+ size: "xs",
1360
+ w: 76,
1361
+ allowDeselect: false,
1362
+ comboboxProps: { withinPortal: false }
1363
+ }),
1364
+ total > 0 && /* @__PURE__ */ jsx(Text, {
1365
+ fz: "xs",
1366
+ c: "dimmed",
1367
+ children: labels.showing({
1368
+ from: fromIndex,
1369
+ to: toIndex,
1370
+ total
1371
+ })
1372
+ })
1373
+ ]
1374
+ }), /* @__PURE__ */ jsxs(Group, {
1375
+ gap: "sm",
1376
+ align: "center",
1377
+ wrap: "nowrap",
1378
+ children: [/* @__PURE__ */ jsx(Text, {
1379
+ fz: "xs",
1380
+ c: "dimmed",
1381
+ children: labels.pageOf({
1382
+ page: safePage,
1383
+ total: safeTotalPages
1384
+ })
1385
+ }), /* @__PURE__ */ jsx(Pagination, {
1386
+ total: safeTotalPages,
1387
+ value: safePage,
1388
+ onChange: onPageChange,
1389
+ size: "sm",
1390
+ siblings: 1,
1391
+ boundaries: 1,
1392
+ getControlProps: (control) => ({ "aria-label": control === "previous" ? labels.previousPage : labels.nextPage })
1393
+ })]
1394
+ })]
1395
+ });
1399
1396
  }
1400
- function SavedViewsMenu({
1401
- views,
1402
- labels
1403
- }) {
1404
- const [name, setName] = useState("");
1405
- const trimmed = name.trim();
1406
- const handleSave = () => {
1407
- views.save(trimmed);
1408
- setName("");
1409
- };
1410
- return /* @__PURE__ */ jsxs(Menu, { closeOnItemClick: false, position: "bottom-end", withinPortal: true, children: [
1411
- /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", children: labels.savedViews }) }),
1412
- /* @__PURE__ */ jsx(Menu.Dropdown, { children: /* @__PURE__ */ jsxs(Box, { p: 4, miw: 220, children: [
1413
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 600, tt: "uppercase", px: 4, pb: 6, children: labels.savedViews }),
1414
- views.views.map((view) => /* @__PURE__ */ jsxs(Group, { gap: 6, px: 4, py: 2, wrap: "nowrap", children: [
1415
- /* @__PURE__ */ jsx(
1416
- Button,
1417
- {
1418
- variant: "subtle",
1419
- size: "compact-sm",
1420
- justify: "flex-start",
1421
- style: { flex: 1 },
1422
- onClick: () => views.apply(view.name),
1423
- children: view.name
1424
- }
1425
- ),
1426
- /* @__PURE__ */ jsx(
1427
- ActionIcon,
1428
- {
1429
- variant: "subtle",
1430
- color: "gray",
1431
- size: "sm",
1432
- "aria-label": `${labels.deleteView}: ${view.name}`,
1433
- onClick: () => views.remove(view.name),
1434
- children: /* @__PURE__ */ jsx(CloseIcon, { size: 12 })
1435
- }
1436
- )
1437
- ] }, view.name)),
1438
- /* @__PURE__ */ jsx(Menu.Divider, {}),
1439
- /* @__PURE__ */ jsxs(Group, { gap: 6, p: 4, wrap: "nowrap", children: [
1440
- /* @__PURE__ */ jsx(
1441
- TextInput,
1442
- {
1443
- size: "xs",
1444
- style: { flex: 1 },
1445
- placeholder: labels.viewName,
1446
- value: name,
1447
- onChange: (e) => setName(e.currentTarget.value)
1448
- }
1449
- ),
1450
- /* @__PURE__ */ jsx(Button, { size: "xs", disabled: trimmed === "", onClick: handleSave, children: labels.saveView })
1451
- ] })
1452
- ] }) })
1453
- ] });
1397
+ //#endregion
1398
+ //#region src/components/SavedViewsMenu.tsx
1399
+ /**
1400
+ * Saved-views menu: lists every captured view (click a name to apply it, the
1401
+ * trailing to delete it) above a save row that captures the table's
1402
+ * CURRENT URL state under the typed name. Pairs with core's `useSavedViews`
1403
+ * and composes into the `toolbar` slot — or let `<DataTable savedViews>`
1404
+ * mount it for you next to the Columns menu.
1405
+ */
1406
+ function SavedViewsMenu({ views, labels }) {
1407
+ const [name, setName] = useState("");
1408
+ const trimmed = name.trim();
1409
+ const handleSave = () => {
1410
+ views.save(trimmed);
1411
+ setName("");
1412
+ };
1413
+ return /* @__PURE__ */ jsxs(Menu, {
1414
+ closeOnItemClick: false,
1415
+ position: "bottom-end",
1416
+ withinPortal: true,
1417
+ children: [/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(Button, {
1418
+ variant: "default",
1419
+ size: "sm",
1420
+ children: labels.savedViews
1421
+ }) }), /* @__PURE__ */ jsx(Menu.Dropdown, { children: /* @__PURE__ */ jsxs(Box, {
1422
+ p: 4,
1423
+ miw: 220,
1424
+ children: [
1425
+ /* @__PURE__ */ jsx(Text, {
1426
+ size: "xs",
1427
+ c: "dimmed",
1428
+ fw: 600,
1429
+ tt: "uppercase",
1430
+ px: 4,
1431
+ pb: 6,
1432
+ children: labels.savedViews
1433
+ }),
1434
+ views.views.map((view) => /* @__PURE__ */ jsxs(Group, {
1435
+ gap: 6,
1436
+ px: 4,
1437
+ py: 2,
1438
+ wrap: "nowrap",
1439
+ children: [/* @__PURE__ */ jsx(Button, {
1440
+ variant: "subtle",
1441
+ size: "compact-sm",
1442
+ justify: "flex-start",
1443
+ style: { flex: 1 },
1444
+ onClick: () => views.apply(view.name),
1445
+ children: view.name
1446
+ }), /* @__PURE__ */ jsx(ActionIcon, {
1447
+ variant: "subtle",
1448
+ color: "gray",
1449
+ size: "sm",
1450
+ "aria-label": `${labels.deleteView}: ${view.name}`,
1451
+ onClick: () => views.remove(view.name),
1452
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 12 })
1453
+ })]
1454
+ }, view.name)),
1455
+ /* @__PURE__ */ jsx(Menu.Divider, {}),
1456
+ /* @__PURE__ */ jsxs(Group, {
1457
+ gap: 6,
1458
+ p: 4,
1459
+ wrap: "nowrap",
1460
+ children: [/* @__PURE__ */ jsx(TextInput, {
1461
+ size: "xs",
1462
+ style: { flex: 1 },
1463
+ placeholder: labels.viewName,
1464
+ value: name,
1465
+ onChange: (e) => setName(e.currentTarget.value)
1466
+ }), /* @__PURE__ */ jsx(Button, {
1467
+ size: "xs",
1468
+ disabled: trimmed === "",
1469
+ onClick: handleSave,
1470
+ children: labels.saveView
1471
+ })]
1472
+ })
1473
+ ]
1474
+ }) })]
1475
+ });
1454
1476
  }
1455
- function TableSkeleton({
1456
- columns,
1457
- rows = 5,
1458
- loadingLabel
1459
- }) {
1460
- const colKeys = Array.from({ length: Math.max(columns, 1) }, (_, i) => i);
1461
- const rowKeys = Array.from({ length: rows }, (_, i) => i);
1462
- return /* @__PURE__ */ jsxs("div", { role: "status", "aria-busy": "true", "aria-live": "polite", children: [
1463
- /* @__PURE__ */ jsxs(Table, { children: [
1464
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsx(Table.Tr, { children: colKeys.map((c) => /* @__PURE__ */ jsx(Table.Th, { children: /* @__PURE__ */ jsx(
1465
- Skeleton,
1466
- {
1467
- height: 12,
1468
- radius: "sm",
1469
- width: c === 0 ? "55%" : "40%"
1470
- }
1471
- ) }, c)) }) }),
1472
- /* @__PURE__ */ jsx(Table.Tbody, { children: rowKeys.map((r) => /* @__PURE__ */ jsx(Table.Tr, { children: colKeys.map((c) => /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1473
- Skeleton,
1474
- {
1475
- height: 14,
1476
- radius: "sm",
1477
- width: c === 0 ? "70%" : "55%"
1478
- }
1479
- ) }, c)) }, r)) })
1480
- ] }),
1481
- loadingLabel ? /* @__PURE__ */ jsx(VisuallyHidden, { children: loadingLabel }) : null
1482
- ] });
1477
+ //#endregion
1478
+ //#region src/components/TableSkeleton.tsx
1479
+ /** Loading placeholder that mirrors the table shape to avoid layout shift. */
1480
+ function TableSkeleton({ columns, rows = 5, loadingLabel }) {
1481
+ const colKeys = Array.from({ length: Math.max(columns, 1) }, (_, i) => i);
1482
+ const rowKeys = Array.from({ length: rows }, (_, i) => i);
1483
+ return /* @__PURE__ */ jsxs("div", {
1484
+ role: "status",
1485
+ "aria-busy": "true",
1486
+ "aria-live": "polite",
1487
+ children: [/* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsx(Table.Tr, { children: colKeys.map((c) => /* @__PURE__ */ jsx(Table.Th, { children: /* @__PURE__ */ jsx(Skeleton, {
1488
+ height: 12,
1489
+ radius: "sm",
1490
+ width: c === 0 ? "55%" : "40%"
1491
+ }) }, c)) }) }), /* @__PURE__ */ jsx(Table.Tbody, { children: rowKeys.map((r) => /* @__PURE__ */ jsx(Table.Tr, { children: colKeys.map((c) => /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Skeleton, {
1492
+ height: 14,
1493
+ radius: "sm",
1494
+ width: c === 0 ? "70%" : "55%"
1495
+ }) }, c)) }, r)) })] }), loadingLabel ? /* @__PURE__ */ jsx(VisuallyHidden, { children: loadingLabel }) : null]
1496
+ });
1483
1497
  }
1484
- function FilterPopover({
1485
- open,
1486
- onClose,
1487
- filters,
1488
- activeFilterCount,
1489
- onClearFilters,
1490
- labels,
1491
- dir = "ltr",
1492
- children
1493
- }) {
1494
- return /* @__PURE__ */ jsxs(
1495
- Popover,
1496
- {
1497
- opened: open,
1498
- onDismiss: onClose,
1499
- position: dir === "rtl" ? "bottom-start" : "bottom-end",
1500
- withinPortal: true,
1501
- shadow: "md",
1502
- radius: "md",
1503
- width: 340,
1504
- children: [
1505
- /* @__PURE__ */ jsx(Popover.Target, { children }),
1506
- /* @__PURE__ */ jsxs(Popover.Dropdown, { children: [
1507
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", mb: "sm", children: [
1508
- /* @__PURE__ */ jsx(Text, { fw: 600, fz: "sm", children: labels.filters }),
1509
- /* @__PURE__ */ jsx(
1510
- Button,
1511
- {
1512
- variant: "subtle",
1513
- size: "compact-xs",
1514
- onClick: onClearFilters,
1515
- disabled: activeFilterCount === 0,
1516
- children: labels.clearAll
1517
- }
1518
- )
1519
- ] }),
1520
- /* @__PURE__ */ jsx(Stack, { gap: "md", children: filters })
1521
- ] })
1522
- ]
1523
- }
1524
- );
1498
+ //#endregion
1499
+ //#region src/components/FilterPopover.tsx
1500
+ /**
1501
+ * Anchored filter card (the default filter container). Opens under the Filters
1502
+ * button and tracks it on scroll. No backdrop — the background stays visible
1503
+ * and interactive; clicking outside or pressing Escape closes it. Pair with
1504
+ * `filtersMode="drawer"` for the slide-in panel instead.
1505
+ */
1506
+ function FilterPopover({ open, onClose, filters, activeFilterCount, onClearFilters, labels, dir = "ltr", children }) {
1507
+ return /* @__PURE__ */ jsxs(Popover, {
1508
+ opened: open,
1509
+ onDismiss: onClose,
1510
+ position: dir === "rtl" ? "bottom-start" : "bottom-end",
1511
+ withinPortal: true,
1512
+ shadow: "md",
1513
+ radius: "md",
1514
+ width: 340,
1515
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children }), /* @__PURE__ */ jsxs(Popover.Dropdown, { children: [/* @__PURE__ */ jsxs(Group, {
1516
+ justify: "space-between",
1517
+ align: "center",
1518
+ mb: "sm",
1519
+ children: [/* @__PURE__ */ jsx(Text, {
1520
+ fw: 600,
1521
+ fz: "sm",
1522
+ children: labels.filters
1523
+ }), /* @__PURE__ */ jsx(Button, {
1524
+ variant: "subtle",
1525
+ size: "compact-xs",
1526
+ onClick: onClearFilters,
1527
+ disabled: activeFilterCount === 0,
1528
+ children: labels.clearAll
1529
+ })]
1530
+ }), /* @__PURE__ */ jsx(Stack, {
1531
+ gap: "md",
1532
+ children: filters
1533
+ })] })]
1534
+ });
1525
1535
  }
1526
- function Toolbar({
1527
- table,
1528
- hideSearch,
1529
- searchPlaceholder,
1530
- sortByOptions,
1531
- customToolbar,
1532
- hasFilters,
1533
- activeFilterCount,
1534
- onToggleFilters,
1535
- onFiltersTriggerPointerDown,
1536
- onCloseFilters,
1537
- filtersOpen,
1538
- filtersMode,
1539
- filters,
1540
- onClearFilters,
1541
- dir,
1542
- columnMenu,
1543
- showRowsPerPage,
1544
- className
1545
- }) {
1546
- const { labels, source } = table;
1547
- const searchProps = table.getSearchInputProps(
1548
- searchPlaceholder ? { placeholder: searchPlaceholder } : void 0
1549
- );
1550
- const sortOptions = sortByOptions ?? (table.isMobile ? table.sortByOptions : void 0);
1551
- const filtersButton = /* @__PURE__ */ jsx(
1552
- Button,
1553
- {
1554
- variant: "default",
1555
- size: "sm",
1556
- "aria-expanded": filtersMode === "popover" ? filtersOpen : void 0,
1557
- "data-active": filtersOpen || void 0,
1558
- leftSection: /* @__PURE__ */ jsx(FiltersIcon, { size: 16 }),
1559
- rightSection: activeFilterCount > 0 ? /* @__PURE__ */ jsx(Badge, { size: "sm", circle: true, children: activeFilterCount }) : void 0,
1560
- onPointerDown: onFiltersTriggerPointerDown,
1561
- onClick: onToggleFilters,
1562
- children: labels.filters
1563
- }
1564
- );
1565
- return /* @__PURE__ */ jsxs(
1566
- Group,
1567
- {
1568
- gap: "sm",
1569
- justify: "space-between",
1570
- align: "center",
1571
- className,
1572
- children: [
1573
- !hideSearch && /* @__PURE__ */ jsx(
1574
- TextInput,
1575
- {
1576
- ...searchProps,
1577
- leftSection: /* @__PURE__ */ jsx(SearchIcon, { size: 14 }),
1578
- size: "sm",
1579
- style: { flex: 1, minWidth: 160, maxWidth: 360 }
1580
- }
1581
- ),
1582
- /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "center", children: [
1583
- sortOptions && sortOptions.length > 0 && /* @__PURE__ */ jsx(
1584
- Select,
1585
- {
1586
- "aria-label": labels.sortBy,
1587
- placeholder: labels.sortBy,
1588
- data: sortOptions,
1589
- value: source.sortBy ?? null,
1590
- onChange: (v) => source.setSort(v ?? void 0, source.sortDir ?? "asc"),
1591
- clearable: true,
1592
- size: "sm",
1593
- w: 160,
1594
- comboboxProps: { withinPortal: false }
1595
- }
1596
- ),
1597
- customToolbar,
1598
- hasFilters && (filtersMode === "popover" ? /* @__PURE__ */ jsx(
1599
- FilterPopover,
1600
- {
1601
- open: filtersOpen,
1602
- onClose: onCloseFilters,
1603
- filters,
1604
- activeFilterCount,
1605
- onClearFilters,
1606
- labels,
1607
- dir,
1608
- children: filtersButton
1609
- }
1610
- ) : filtersButton),
1611
- columnMenu,
1612
- showRowsPerPage && /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "center", children: [
1613
- /* @__PURE__ */ jsx(Text, { fz: "xs", c: "dimmed", children: labels.rowsPerPage }),
1614
- /* @__PURE__ */ jsx(
1615
- Select,
1616
- {
1617
- "aria-label": labels.rowsPerPage,
1618
- data: pageSizeOptions(source.limit).map((n) => ({
1619
- value: String(n),
1620
- label: String(n)
1621
- })),
1622
- value: String(source.limit),
1623
- onChange: (v) => source.setLimit(Number(v)),
1624
- size: "sm",
1625
- w: 80,
1626
- allowDeselect: false,
1627
- comboboxProps: { withinPortal: false }
1628
- }
1629
- )
1630
- ] })
1631
- ] })
1632
- ]
1633
- }
1634
- );
1536
+ //#endregion
1537
+ //#region src/components/Toolbar.tsx
1538
+ /** Sticky toolbar: search, optional sort select, custom slot, filters, size. */
1539
+ function Toolbar({ table, hideSearch, searchPlaceholder, sortByOptions, customToolbar, hasFilters, activeFilterCount, onToggleFilters, onFiltersTriggerPointerDown, onCloseFilters, filtersOpen, filtersMode, filters, onClearFilters, dir, columnMenu, showRowsPerPage, className }) {
1540
+ const { labels, source } = table;
1541
+ const searchProps = table.getSearchInputProps(searchPlaceholder ? { placeholder: searchPlaceholder } : void 0);
1542
+ const sortOptions = sortByOptions ?? (table.isMobile ? table.sortByOptions : void 0);
1543
+ const filtersButton = /* @__PURE__ */ jsx(Button, {
1544
+ variant: "default",
1545
+ size: "sm",
1546
+ "aria-expanded": filtersMode === "popover" ? filtersOpen : void 0,
1547
+ "data-active": filtersOpen || void 0,
1548
+ leftSection: /* @__PURE__ */ jsx(FiltersIcon, { size: 16 }),
1549
+ rightSection: activeFilterCount > 0 ? /* @__PURE__ */ jsx(Badge, {
1550
+ size: "sm",
1551
+ circle: true,
1552
+ children: activeFilterCount
1553
+ }) : void 0,
1554
+ onPointerDown: onFiltersTriggerPointerDown,
1555
+ onClick: onToggleFilters,
1556
+ children: labels.filters
1557
+ });
1558
+ return /* @__PURE__ */ jsxs(Group, {
1559
+ gap: "sm",
1560
+ justify: "space-between",
1561
+ align: "center",
1562
+ className,
1563
+ children: [!hideSearch && /* @__PURE__ */ jsx(TextInput, {
1564
+ ...searchProps,
1565
+ leftSection: /* @__PURE__ */ jsx(SearchIcon, { size: 14 }),
1566
+ size: "sm",
1567
+ style: {
1568
+ flex: 1,
1569
+ minWidth: 160,
1570
+ maxWidth: 360
1571
+ }
1572
+ }), /* @__PURE__ */ jsxs(Group, {
1573
+ gap: "xs",
1574
+ align: "center",
1575
+ children: [
1576
+ sortOptions && sortOptions.length > 0 && /* @__PURE__ */ jsx(Select, {
1577
+ "aria-label": labels.sortBy,
1578
+ placeholder: labels.sortBy,
1579
+ data: sortOptions,
1580
+ value: source.sortBy ?? null,
1581
+ onChange: (v) => source.setSort(v ?? void 0, source.sortDir ?? "asc"),
1582
+ clearable: true,
1583
+ size: "sm",
1584
+ w: 160,
1585
+ comboboxProps: { withinPortal: false }
1586
+ }),
1587
+ customToolbar,
1588
+ hasFilters && (filtersMode === "popover" ? /* @__PURE__ */ jsx(FilterPopover, {
1589
+ open: filtersOpen,
1590
+ onClose: onCloseFilters,
1591
+ filters,
1592
+ activeFilterCount,
1593
+ onClearFilters,
1594
+ labels,
1595
+ dir,
1596
+ children: filtersButton
1597
+ }) : filtersButton),
1598
+ columnMenu,
1599
+ showRowsPerPage && /* @__PURE__ */ jsxs(Group, {
1600
+ gap: "xs",
1601
+ align: "center",
1602
+ children: [/* @__PURE__ */ jsx(Text, {
1603
+ fz: "xs",
1604
+ c: "dimmed",
1605
+ children: labels.rowsPerPage
1606
+ }), /* @__PURE__ */ jsx(Select, {
1607
+ "aria-label": labels.rowsPerPage,
1608
+ data: pageSizeOptions(source.limit).map((n) => ({
1609
+ value: String(n),
1610
+ label: String(n)
1611
+ })),
1612
+ value: String(source.limit),
1613
+ onChange: (v) => source.setLimit(Number(v)),
1614
+ size: "sm",
1615
+ w: 80,
1616
+ allowDeselect: false,
1617
+ comboboxProps: { withinPortal: false }
1618
+ })]
1619
+ })
1620
+ ]
1621
+ })]
1622
+ });
1635
1623
  }
1636
- var stickyToolbarStyle = (top) => ({
1637
- position: "sticky",
1638
- top,
1639
- zIndex: 3,
1640
- background: "var(--mantine-color-body)",
1641
- paddingBottom: "var(--mantine-spacing-xs)"
1624
+ //#endregion
1625
+ //#region src/DataTable.tsx
1626
+ const stickyToolbarStyle = (top) => ({
1627
+ position: "sticky",
1628
+ top,
1629
+ zIndex: 3,
1630
+ background: "var(--mantine-color-body)",
1631
+ paddingBottom: "var(--mantine-spacing-xs)"
1642
1632
  });
1643
- function ColumnMenuSlot({
1644
- enabled,
1645
- ...props
1646
- }) {
1647
- if (!enabled) return null;
1648
- return /* @__PURE__ */ jsx(ColumnMenu, { ...props });
1633
+ /** The Columns menu, rendered inline in the toolbar — or nothing when off. */
1634
+ function ColumnMenuSlot({ enabled, ...props }) {
1635
+ if (!enabled) return null;
1636
+ return /* @__PURE__ */ jsx(ColumnMenu, { ...props });
1649
1637
  }
1650
- function SavedViewsSlot({
1651
- options,
1652
- labels
1653
- }) {
1654
- const views = useSavedViews(options);
1655
- return /* @__PURE__ */ jsx(SavedViewsMenu, { views, labels });
1638
+ /**
1639
+ * The Saved-views menu in the toolbar. A component (not inline JSX) so
1640
+ * `useSavedViews` only runs when the `savedViews` prop is set.
1641
+ */
1642
+ function SavedViewsSlot({ options, labels }) {
1643
+ return /* @__PURE__ */ jsx(SavedViewsMenu, {
1644
+ views: useSavedViews$1(options),
1645
+ labels
1646
+ });
1656
1647
  }
1648
+ /**
1649
+ * Resolve the data tier (source ▸ server ▸ frontend) and the declarative
1650
+ * filter runtime into the full chrome prop set: the RESOLVED source, the
1651
+ * filters node (auto-built form for the declarative array, JSX as-is) and
1652
+ * the chip-label resolvers (derived under the caller's — the user wins
1653
+ * per key). Everything downstream consumes these.
1654
+ */
1657
1655
  function useResolvedTableProps(props) {
1658
- const { source, runtime } = useTableData({
1659
- locale: props.locale,
1660
- source: props.source,
1661
- data: props.data,
1662
- total: props.total,
1663
- loading: props.loading,
1664
- onQueryChange: props.onQueryChange,
1665
- columns: props.columns,
1666
- filters: props.filters,
1667
- urlKey: props.urlKey,
1668
- adapter: props.urlSync === false ? void 0 : props.urlAdapter,
1669
- enabled: props.urlSync
1670
- });
1671
- const labels = useMemo(() => resolveLabels(props.labels), [props.labels]);
1672
- let filters;
1673
- if (isDeclarativeFilters(props.filters) || props.filters === void 0) {
1674
- filters = runtime.defs.length > 0 ? /* @__PURE__ */ jsx(AutoFilterForm, { defs: runtime.defs, source, labels }) : void 0;
1675
- } else {
1676
- filters = props.filters;
1677
- }
1678
- const filterLabels = useMemo(
1679
- () => ({ ...runtime.filterLabels, ...props.filterLabels }),
1680
- [runtime.filterLabels, props.filterLabels]
1681
- );
1682
- return { ...props, source, filters, filterLabels };
1656
+ const { source, runtime } = useTableData({
1657
+ locale: props.locale,
1658
+ source: props.source,
1659
+ data: props.data,
1660
+ total: props.total,
1661
+ loading: props.loading,
1662
+ onQueryChange: props.onQueryChange,
1663
+ columns: props.columns,
1664
+ filters: props.filters,
1665
+ urlKey: props.urlKey,
1666
+ adapter: props.urlSync === false ? void 0 : props.urlAdapter,
1667
+ enabled: props.urlSync
1668
+ });
1669
+ const labels = useMemo(() => resolveLabels(props.labels), [props.labels]);
1670
+ let filters;
1671
+ if (isDeclarativeFilters(props.filters) || props.filters === void 0) filters = runtime.defs.length > 0 ? /* @__PURE__ */ jsx(AutoFilterForm, {
1672
+ defs: runtime.defs,
1673
+ source,
1674
+ labels
1675
+ }) : void 0;
1676
+ else filters = props.filters;
1677
+ const filterLabels = useMemo(() => ({
1678
+ ...runtime.filterLabels,
1679
+ ...props.filterLabels
1680
+ }), [runtime.filterLabels, props.filterLabels]);
1681
+ return {
1682
+ ...props,
1683
+ source,
1684
+ filters,
1685
+ filterLabels
1686
+ };
1683
1687
  }
1688
+ /**
1689
+ * Batteries-included Mantine data table. Drop in `columns`, a `rowKey`,
1690
+ * and a data tier — raw `data` (frontend), `data` + `onQueryChange`
1691
+ * (server), or a prebuilt `source` — to get a fully styled, sortable,
1692
+ * filterable, paginated table with selection, bulk actions, RTL, dark
1693
+ * mode, and optional entrance animation.
1694
+ *
1695
+ * @typeParam TRow - The row type.
1696
+ */
1684
1697
  function DataTable(props) {
1685
- const chromeProps = useResolvedTableProps(props);
1686
- const {
1687
- source,
1688
- rowActions,
1689
- searchPlaceholder,
1690
- sortByOptions,
1691
- dir,
1692
- prefetch,
1693
- hideSearch,
1694
- filters,
1695
- filtersMode = "popover",
1696
- bulkActions,
1697
- slots,
1698
- classNames,
1699
- toolbar: customToolbar,
1700
- skeletonRows,
1701
- stickyTop = 0,
1702
- animate = false,
1703
- stickyHeader = false,
1704
- enableColumnMenu = false,
1705
- savedViews
1706
- } = chromeProps;
1707
- const density = chromeProps.density ?? "comfortable";
1708
- const chrome = useTableChrome(chromeProps);
1709
- const { table, isMobile, confirm, getRowId } = chrome;
1710
- const hasRowActions = (rowActions?.length ?? 0) > 0;
1711
- const visibleRowActions = chrome.columnLayout.isHidden(ACTIONS_COLUMN_KEY) ? void 0 : rowActions;
1712
- const actionsPinned = chrome.columnLayout.state.pinned[ACTIONS_COLUMN_KEY] === "right";
1713
- const { virtualization, loadMoreRef, canLoadMore, virtualScrollRef } = useChromeBodyData(chrome, chromeProps);
1714
- const [drawerOpened, setDrawerOpened] = useState(false);
1715
- const filtersTrigger = useFilterTriggerToggle(drawerOpened, setDrawerOpened);
1716
- const rootRef = useRef(null);
1717
- const { ref: toolbarRef, height: toolbarHeight } = useElementSize();
1718
- const desktopBodyRef = useRef(null);
1719
- const mobileBodyRef = useRef(null);
1720
- useChromeScrollReset(rootRef, chrome, chromeProps);
1721
- useMountStagger(
1722
- isMobile ? mobileBodyRef : desktopBodyRef,
1723
- [virtualization.rows.length, isMobile],
1724
- { enabled: animate }
1725
- );
1726
- let body;
1727
- if (chrome.body === "skeleton") {
1728
- body = slots?.skeleton ?? /* @__PURE__ */ jsx(
1729
- TableSkeleton,
1730
- {
1731
- columns: table.columns.length || 1,
1732
- rows: skeletonRows ?? source.limit,
1733
- loadingLabel: table.labels.loading
1734
- }
1735
- );
1736
- } else if (chrome.body === "empty") {
1737
- body = slots?.empty ?? (chrome.emptyVariant === "noResults" ? /* @__PURE__ */ jsx(
1738
- EmptyState,
1739
- {
1740
- title: table.labels.noResults,
1741
- action: /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", onClick: chrome.clearFilters, children: table.labels.clearAll })
1742
- }
1743
- ) : /* @__PURE__ */ jsx(EmptyState, { title: table.labels.noData }));
1744
- } else if (chrome.body === "mobile") {
1745
- body = /* @__PURE__ */ jsx(
1746
- MobileCards,
1747
- {
1748
- table,
1749
- rows: source.rows,
1750
- rowActions: visibleRowActions,
1751
- confirm,
1752
- getRowId,
1753
- onRowClick: props.onRowClick,
1754
- rowClassName: props.rowClassName,
1755
- renderRowDetail: props.renderRowDetail,
1756
- summaryRow: props.summaryRow,
1757
- expansion: chrome.detail?.expansion,
1758
- bodyRef: mobileBodyRef,
1759
- className: classNames?.card,
1760
- rowEntries: virtualization.enabled ? virtualization.rows : void 0,
1761
- paddingTop: virtualization.paddingTop,
1762
- paddingBottom: virtualization.paddingBottom,
1763
- measureElement: virtualization.measureElement,
1764
- density
1765
- }
1766
- );
1767
- } else {
1768
- body = /* @__PURE__ */ jsx(
1769
- DesktopTable,
1770
- {
1771
- table,
1772
- rows: source.rows,
1773
- rowActions: visibleRowActions,
1774
- confirm,
1775
- prefetch,
1776
- onRowClick: props.onRowClick,
1777
- rowClassName: props.rowClassName,
1778
- renderRowDetail: props.renderRowDetail,
1779
- summaryRow: props.summaryRow,
1780
- expansion: chrome.detail?.expansion,
1781
- getRowId,
1782
- bodyRef: desktopBodyRef,
1783
- className: classNames?.table,
1784
- rowEntries: virtualization.enabled ? virtualization.rows : void 0,
1785
- paddingTop: virtualization.paddingTop,
1786
- paddingBottom: virtualization.paddingBottom,
1787
- measureElement: virtualization.measureElement,
1788
- stickyHeaderOffset: stickyTop + toolbarHeight,
1789
- stickyHeader,
1790
- pinOffset: chrome.columnLayout.pinOffset,
1791
- actionsPinned,
1792
- maxHeight: props.maxHeight,
1793
- virtualScrollRef,
1794
- setWidth: props.resizableColumns ? chrome.columnLayout.setWidth : void 0,
1795
- columnWidths: chrome.columnLayout.state.widths,
1796
- resizeLabel: table.labels.resizeColumn,
1797
- density
1798
- }
1799
- );
1800
- }
1801
- return /* @__PURE__ */ jsxs(
1802
- Paper,
1803
- {
1804
- ref: rootRef,
1805
- p: "xs",
1806
- radius: "md",
1807
- withBorder: true,
1808
- dir,
1809
- "aria-busy": chrome.isRefreshing || void 0,
1810
- className: classNames?.root,
1811
- children: [
1812
- /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1813
- /* @__PURE__ */ jsx(
1814
- Box,
1815
- {
1816
- ref: toolbarRef,
1817
- style: stickyToolbarStyle(stickyTop),
1818
- className: classNames?.toolbar,
1819
- children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1820
- /* @__PURE__ */ jsx(
1821
- Toolbar,
1822
- {
1823
- table,
1824
- hideSearch,
1825
- searchPlaceholder,
1826
- sortByOptions,
1827
- customToolbar,
1828
- hasFilters: Boolean(filters),
1829
- activeFilterCount: chrome.activeFilterCount,
1830
- filtersMode,
1831
- filters,
1832
- filtersOpen: drawerOpened,
1833
- onToggleFilters: filtersTrigger.onClick,
1834
- onFiltersTriggerPointerDown: filtersTrigger.onPointerDown,
1835
- onCloseFilters: () => setDrawerOpened(false),
1836
- onClearFilters: chrome.clearFilters,
1837
- dir,
1838
- columnMenu: /* @__PURE__ */ jsxs(Fragment, { children: [
1839
- savedViews && /* @__PURE__ */ jsx(
1840
- SavedViewsSlot,
1841
- {
1842
- options: {
1843
- adapter: chromeProps.urlAdapter,
1844
- urlKey: chromeProps.urlKey,
1845
- ...savedViews
1846
- },
1847
- labels: table.labels
1848
- }
1849
- ),
1850
- /* @__PURE__ */ jsx(
1851
- ColumnMenuSlot,
1852
- {
1853
- enabled: enableColumnMenu && !isMobile,
1854
- allColumns: chrome.allColumns,
1855
- layout: chrome.columnLayout,
1856
- labels: table.labels,
1857
- hasRowActions
1858
- }
1859
- )
1860
- ] }),
1861
- showRowsPerPage: canLoadMore
1862
- }
1863
- ),
1864
- /* @__PURE__ */ jsx(
1865
- ActiveFilterChips,
1866
- {
1867
- chips: chrome.mergedChips,
1868
- onClearAll: chrome.clearFilters,
1869
- label: table.labels.filters,
1870
- clearAllLabel: table.labels.clearAll
1871
- }
1872
- ),
1873
- table.selection && bulkActions && /* @__PURE__ */ jsx(
1874
- BulkActionBar,
1875
- {
1876
- selection: table.selection,
1877
- total: source.total,
1878
- bulkActions,
1879
- confirm,
1880
- labels: table.labels
1881
- }
1882
- )
1883
- ] })
1884
- }
1885
- ),
1886
- chrome.isRefreshing && /* @__PURE__ */ jsx(
1887
- Progress,
1888
- {
1889
- size: "xs",
1890
- animated: true,
1891
- value: 100,
1892
- "aria-label": table.labels.loading
1893
- }
1894
- ),
1895
- source.error && /* @__PURE__ */ jsx(
1896
- ErrorState,
1897
- {
1898
- error: source.error,
1899
- title: table.labels.errorTitle,
1900
- message: table.labels.errorMessage,
1901
- retryLabel: table.labels.retry,
1902
- onRetry: source.refetch ? () => void source.refetch?.() : void 0,
1903
- isRetrying: source.isFetching
1904
- }
1905
- ),
1906
- !source.error && body,
1907
- canLoadMore && source.hasNextPage && /* @__PURE__ */ jsx(Group, { ref: loadMoreRef, justify: "center", py: "xs", children: /* @__PURE__ */ jsx(
1908
- Button,
1909
- {
1910
- variant: "default",
1911
- size: "sm",
1912
- loading: source.isFetchingNextPage,
1913
- onClick: () => source.fetchNextPage(),
1914
- children: table.labels.loadMore
1915
- }
1916
- ) }),
1917
- chrome.showFooter && /* @__PURE__ */ jsx(Box, { className: classNames?.footer, children: /* @__PURE__ */ jsx(
1918
- PaginationFooter,
1919
- {
1920
- page: table.pagination.safePage,
1921
- totalPages: table.pagination.totalPages,
1922
- limit: source.limit,
1923
- total: source.total,
1924
- fromIndex: table.pagination.fromIndex,
1925
- toIndex: table.pagination.toIndex,
1926
- onPageChange: source.setPage,
1927
- onLimitChange: source.setLimit,
1928
- labels: table.labels
1929
- }
1930
- ) })
1931
- ] }),
1932
- filters && filtersMode === "drawer" && /* @__PURE__ */ jsx(
1933
- FilterDrawer,
1934
- {
1935
- opened: drawerOpened,
1936
- onClose: () => setDrawerOpened(false),
1937
- filters,
1938
- activeFilterCount: chrome.activeFilterCount,
1939
- onClearFilters: chrome.clearFilters,
1940
- labels: table.labels,
1941
- dir
1942
- }
1943
- )
1944
- ]
1945
- }
1946
- );
1698
+ const chromeProps = useResolvedTableProps(props);
1699
+ const { source, rowActions, searchPlaceholder, sortByOptions, dir, prefetch, hideSearch, filters, filtersMode = "popover", bulkActions, slots, classNames, toolbar: customToolbar, skeletonRows, stickyTop = 0, animate = false, stickyHeader = false, enableColumnMenu = false, savedViews } = chromeProps;
1700
+ const density = chromeProps.density ?? "comfortable";
1701
+ const chrome = useTableChrome(chromeProps);
1702
+ const { table, isMobile, confirm, getRowId } = chrome;
1703
+ const hasRowActions = (rowActions?.length ?? 0) > 0;
1704
+ const visibleRowActions = chrome.columnLayout.isHidden(ACTIONS_COLUMN_KEY) ? void 0 : rowActions;
1705
+ const actionsPinned = chrome.columnLayout.state.pinned[ACTIONS_COLUMN_KEY] === "right";
1706
+ const { virtualization, loadMoreRef, canLoadMore, virtualScrollRef } = useChromeBodyData(chrome, chromeProps);
1707
+ const [drawerOpened, setDrawerOpened] = useState(false);
1708
+ const filtersTrigger = useFilterTriggerToggle(drawerOpened, setDrawerOpened);
1709
+ const rootRef = useRef(null);
1710
+ const { ref: toolbarRef, height: toolbarHeight } = useElementSize();
1711
+ const desktopBodyRef = useRef(null);
1712
+ const mobileBodyRef = useRef(null);
1713
+ useChromeScrollReset(rootRef, chrome, chromeProps);
1714
+ useMountStagger(isMobile ? mobileBodyRef : desktopBodyRef, [virtualization.rows.length, isMobile], { enabled: animate });
1715
+ let body;
1716
+ if (chrome.body === "skeleton") body = slots?.skeleton ?? /* @__PURE__ */ jsx(TableSkeleton, {
1717
+ columns: table.columns.length || 1,
1718
+ rows: skeletonRows ?? source.limit,
1719
+ loadingLabel: table.labels.loading
1720
+ });
1721
+ else if (chrome.body === "empty") body = slots?.empty ?? (chrome.emptyVariant === "noResults" ? /* @__PURE__ */ jsx(EmptyState, {
1722
+ title: table.labels.noResults,
1723
+ action: /* @__PURE__ */ jsx(Button, {
1724
+ variant: "light",
1725
+ size: "sm",
1726
+ onClick: chrome.clearFilters,
1727
+ children: table.labels.clearAll
1728
+ })
1729
+ }) : /* @__PURE__ */ jsx(EmptyState, { title: table.labels.noData }));
1730
+ else if (chrome.body === "mobile") body = /* @__PURE__ */ jsx(MobileCards, {
1731
+ table,
1732
+ rows: source.rows,
1733
+ rowActions: visibleRowActions,
1734
+ confirm,
1735
+ getRowId,
1736
+ onRowClick: props.onRowClick,
1737
+ rowClassName: props.rowClassName,
1738
+ renderRowDetail: props.renderRowDetail,
1739
+ summaryRow: props.summaryRow,
1740
+ expansion: chrome.detail?.expansion,
1741
+ bodyRef: mobileBodyRef,
1742
+ className: classNames?.card,
1743
+ rowEntries: virtualization.enabled ? virtualization.rows : void 0,
1744
+ paddingTop: virtualization.paddingTop,
1745
+ paddingBottom: virtualization.paddingBottom,
1746
+ measureElement: virtualization.measureElement,
1747
+ density
1748
+ });
1749
+ else body = /* @__PURE__ */ jsx(DesktopTable, {
1750
+ table,
1751
+ rows: source.rows,
1752
+ rowActions: visibleRowActions,
1753
+ confirm,
1754
+ prefetch,
1755
+ onRowClick: props.onRowClick,
1756
+ rowClassName: props.rowClassName,
1757
+ renderRowDetail: props.renderRowDetail,
1758
+ summaryRow: props.summaryRow,
1759
+ expansion: chrome.detail?.expansion,
1760
+ getRowId,
1761
+ bodyRef: desktopBodyRef,
1762
+ className: classNames?.table,
1763
+ rowEntries: virtualization.enabled ? virtualization.rows : void 0,
1764
+ paddingTop: virtualization.paddingTop,
1765
+ paddingBottom: virtualization.paddingBottom,
1766
+ measureElement: virtualization.measureElement,
1767
+ stickyHeaderOffset: stickyTop + toolbarHeight,
1768
+ stickyHeader,
1769
+ pinOffset: chrome.columnLayout.pinOffset,
1770
+ actionsPinned,
1771
+ maxHeight: props.maxHeight,
1772
+ virtualScrollRef,
1773
+ setWidth: props.resizableColumns ? chrome.columnLayout.setWidth : void 0,
1774
+ columnWidths: chrome.columnLayout.state.widths,
1775
+ resizeLabel: table.labels.resizeColumn,
1776
+ density
1777
+ });
1778
+ return /* @__PURE__ */ jsxs(Paper, {
1779
+ ref: rootRef,
1780
+ p: "xs",
1781
+ radius: "md",
1782
+ withBorder: true,
1783
+ dir,
1784
+ "aria-busy": chrome.isRefreshing || void 0,
1785
+ className: classNames?.root,
1786
+ children: [/* @__PURE__ */ jsxs(Stack, {
1787
+ gap: "xs",
1788
+ children: [
1789
+ /* @__PURE__ */ jsx(Box, {
1790
+ ref: toolbarRef,
1791
+ style: stickyToolbarStyle(stickyTop),
1792
+ className: classNames?.toolbar,
1793
+ children: /* @__PURE__ */ jsxs(Stack, {
1794
+ gap: "xs",
1795
+ children: [
1796
+ /* @__PURE__ */ jsx(Toolbar, {
1797
+ table,
1798
+ hideSearch,
1799
+ searchPlaceholder,
1800
+ sortByOptions,
1801
+ customToolbar,
1802
+ hasFilters: Boolean(filters),
1803
+ activeFilterCount: chrome.activeFilterCount,
1804
+ filtersMode,
1805
+ filters,
1806
+ filtersOpen: drawerOpened,
1807
+ onToggleFilters: filtersTrigger.onClick,
1808
+ onFiltersTriggerPointerDown: filtersTrigger.onPointerDown,
1809
+ onCloseFilters: () => setDrawerOpened(false),
1810
+ onClearFilters: chrome.clearFilters,
1811
+ dir,
1812
+ columnMenu: /* @__PURE__ */ jsxs(Fragment, { children: [savedViews && /* @__PURE__ */ jsx(SavedViewsSlot, {
1813
+ options: {
1814
+ adapter: chromeProps.urlAdapter,
1815
+ urlKey: chromeProps.urlKey,
1816
+ ...savedViews
1817
+ },
1818
+ labels: table.labels
1819
+ }), /* @__PURE__ */ jsx(ColumnMenuSlot, {
1820
+ enabled: enableColumnMenu && !isMobile,
1821
+ allColumns: chrome.allColumns,
1822
+ layout: chrome.columnLayout,
1823
+ labels: table.labels,
1824
+ hasRowActions
1825
+ })] }),
1826
+ showRowsPerPage: canLoadMore
1827
+ }),
1828
+ /* @__PURE__ */ jsx(ActiveFilterChips, {
1829
+ chips: chrome.mergedChips,
1830
+ onClearAll: chrome.clearFilters,
1831
+ label: table.labels.filters,
1832
+ clearAllLabel: table.labels.clearAll
1833
+ }),
1834
+ table.selection && bulkActions && /* @__PURE__ */ jsx(BulkActionBar, {
1835
+ selection: table.selection,
1836
+ total: source.total,
1837
+ bulkActions,
1838
+ confirm,
1839
+ labels: table.labels
1840
+ })
1841
+ ]
1842
+ })
1843
+ }),
1844
+ chrome.isRefreshing && /* @__PURE__ */ jsx(Progress, {
1845
+ size: "xs",
1846
+ animated: true,
1847
+ value: 100,
1848
+ "aria-label": table.labels.loading
1849
+ }),
1850
+ source.error && /* @__PURE__ */ jsx(ErrorState, {
1851
+ error: source.error,
1852
+ title: table.labels.errorTitle,
1853
+ message: table.labels.errorMessage,
1854
+ retryLabel: table.labels.retry,
1855
+ onRetry: source.refetch ? () => void source.refetch?.() : void 0,
1856
+ isRetrying: source.isFetching
1857
+ }),
1858
+ !source.error && body,
1859
+ canLoadMore && source.hasNextPage && /* @__PURE__ */ jsx(Group, {
1860
+ ref: loadMoreRef,
1861
+ justify: "center",
1862
+ py: "xs",
1863
+ children: /* @__PURE__ */ jsx(Button, {
1864
+ variant: "default",
1865
+ size: "sm",
1866
+ loading: source.isFetchingNextPage,
1867
+ onClick: () => source.fetchNextPage(),
1868
+ children: table.labels.loadMore
1869
+ })
1870
+ }),
1871
+ chrome.showFooter && /* @__PURE__ */ jsx(Box, {
1872
+ className: classNames?.footer,
1873
+ children: /* @__PURE__ */ jsx(PaginationFooter, {
1874
+ page: table.pagination.safePage,
1875
+ totalPages: table.pagination.totalPages,
1876
+ limit: source.limit,
1877
+ total: source.total,
1878
+ fromIndex: table.pagination.fromIndex,
1879
+ toIndex: table.pagination.toIndex,
1880
+ onPageChange: source.setPage,
1881
+ onLimitChange: source.setLimit,
1882
+ labels: table.labels
1883
+ })
1884
+ })
1885
+ ]
1886
+ }), filters && filtersMode === "drawer" && /* @__PURE__ */ jsx(FilterDrawer, {
1887
+ opened: drawerOpened,
1888
+ onClose: () => setDrawerOpened(false),
1889
+ filters,
1890
+ activeFilterCount: chrome.activeFilterCount,
1891
+ onClearFilters: chrome.clearFilters,
1892
+ labels: table.labels,
1893
+ dir
1894
+ })]
1895
+ });
1947
1896
  }
1897
+ //#endregion
1898
+ export { ActiveFilterChips, AutoFilterForm, DataTable, EmptyState, ErrorState, PaginationFooter, SavedViewsMenu, TableSkeleton, createHistoryAdapter, createMemoryAdapter, defaultConfirm, defaultLabels, deriveSortByOptions, getHistoryAdapter, useBackendData, useDataTable, useFrontendData, useMountStagger, useSavedViews, useTableUrlState };
1948
1899
 
1949
- export { ActiveFilterChips, AutoFilterForm, DataTable, EmptyState, ErrorState, PaginationFooter, SavedViewsMenu, TableSkeleton, useMountStagger };
1950
- //# sourceMappingURL=index.js.map
1951
1900
  //# sourceMappingURL=index.js.map