@burtson-labs/ui 0.12.4 → 0.13.1

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.
@@ -2,7 +2,8 @@ import { cn } from "../lib/utils.js";
2
2
  import { Button } from "./button.js";
3
3
  import { Input } from "./input.js";
4
4
  import { Checkbox } from "./checkbox.js";
5
- import { Pagination } from "./pagination.js";
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select.js";
6
+ import { Pagination, pageWindow, paginationSummary, usePagination } from "./pagination.js";
6
7
  import { Skeleton } from "./skeleton.js";
7
8
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
8
9
  import * as React from "react";
@@ -12,6 +13,34 @@ import ChevronsUpDown from "@burtson-labs/icons/react/chevrons-up-down";
12
13
  import ArrowUp from "@burtson-labs/icons/react/arrow-up";
13
14
  import ArrowDown from "@burtson-labs/icons/react/arrow-down";
14
15
  //#region src/components/data-table.tsx
16
+ var BREAKPOINTS = {
17
+ sm: 640,
18
+ md: 768,
19
+ lg: 1024,
20
+ xl: 1280
21
+ };
22
+ var hideBelowClass = {
23
+ sm: "hidden sm:table-cell",
24
+ md: "hidden md:table-cell",
25
+ lg: "hidden lg:table-cell",
26
+ xl: "hidden xl:table-cell"
27
+ };
28
+ var subscribeNone = () => () => void 0;
29
+ /**
30
+ * True while the viewport is narrower than `bp`. Without matchMedia (server
31
+ * render, tests) it is false, so the table is the fallback.
32
+ */
33
+ function useNarrowerThan(bp) {
34
+ const query = bp ? `(max-width: ${BREAKPOINTS[bp] - .02}px)` : null;
35
+ const subscribe = React.useCallback((onChange) => {
36
+ if (!query || typeof matchMedia !== "function") return () => void 0;
37
+ const mq = matchMedia(query);
38
+ mq.addEventListener?.("change", onChange);
39
+ return () => mq.removeEventListener?.("change", onChange);
40
+ }, [query]);
41
+ return React.useSyncExternalStore(query ? subscribe : subscribeNone, () => Boolean(query && typeof matchMedia === "function" && matchMedia(query).matches), () => false);
42
+ }
43
+ var textOf = (node) => typeof node === "string" || typeof node === "number" ? String(node) : void 0;
15
44
  var nextSort = (current, id) => {
16
45
  if (!current || current.columnId !== id) return {
17
46
  columnId: id,
@@ -26,15 +55,27 @@ var INTERACTIVE = "a,button,input,select,textarea,[role=\"checkbox\"],[role=\"me
26
55
  /**
27
56
  * A table recipe for records: sort, search, select, page, and row actions,
28
57
  * with loading, empty and error states. Fully controlled, so the data can
29
- * come from a server; the table never reorders rows itself.
58
+ * come from a server; the table never reorders rows itself. On a phone the
59
+ * rows become cards (`cardsBelow`), with the same columns.
30
60
  */
31
- function DataTable({ "aria-label": ariaLabel, columns, rows, getRowId, getRowLabel, sort, onSortChange, filter, onFilterChange, filterPlaceholder = "Search", selected, onSelectedChange, onRowAction, rowActions, page, pageCount, onPageChange, pageSummary, loading = false, error, onRetry, empty = "No results.", toolbar, density = "default", stickyHeader, className, ...props }) {
61
+ function DataTable({ "aria-label": ariaLabel, columns, rows: rowsProp, getRowId, getRowLabel, sort, onSortChange, filter, onFilterChange, filterPlaceholder = "Search", selected, onSelectedChange, onRowAction, rowActions, page, pageCount, onPageChange, pageSummary, pageSize, pageSizeOptions, onPageSizeChange, totalRows, paginate, rowNoun, loading = false, error, onRetry, empty = "No results.", toolbar, density = "default", stickyHeader, cardsBelow = "md", className, ...props }) {
62
+ const paginateOptions = paginate === true ? {} : paginate || void 0;
63
+ const paging = usePagination(rowsProp, {
64
+ defaultPageSize: paginateOptions?.pageSize,
65
+ pageSizeOptions: paginateOptions?.pageSizeOptions,
66
+ storageKey: paginateOptions?.storageKey,
67
+ resetKey: `${filter ?? ""}\u0000${sort?.columnId ?? ""}\u0000${sort?.direction ?? ""}`
68
+ });
69
+ const rows = paginateOptions ? paging.rows : rowsProp;
70
+ const cards = useNarrowerThan(cardsBelow);
71
+ const uid = React.useId();
32
72
  const selectable = Boolean(onSelectedChange);
33
73
  const selectedSet = React.useMemo(() => new Set(selected ?? []), [selected]);
34
74
  const ids = rows.map(getRowId);
35
75
  const pageSelected = ids.filter((id) => selectedSet.has(id)).length;
36
76
  const allState = ids.length > 0 && pageSelected === ids.length ? true : pageSelected > 0 ? "indeterminate" : false;
37
- const colCount = columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0);
77
+ const tableColumns = columns.filter((c) => !c.cardOnly);
78
+ const colCount = tableColumns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0);
38
79
  const interactive = Boolean(onRowAction) || selectable;
39
80
  const [active, setActive] = React.useState(0);
40
81
  const frameRef = React.useRef(null);
@@ -89,112 +130,119 @@ function DataTable({ "aria-label": ariaLabel, columns, rows, getRowId, getRowLab
89
130
  }
90
131
  }
91
132
  };
92
- const showToolbar = Boolean(onFilterChange || toolbar || selectable && selectedSet.size);
93
- let body;
94
- if (error) body = /* @__PURE__ */ jsx(TableRow, {
95
- className: "hover:bg-transparent",
96
- children: /* @__PURE__ */ jsx(TableCell, {
97
- colSpan: colCount,
98
- className: "h-auto py-8 text-center whitespace-normal",
99
- children: /* @__PURE__ */ jsxs("div", {
100
- role: "alert",
101
- className: "grid justify-items-center gap-3 text-sm",
102
- children: [/* @__PURE__ */ jsx("span", {
103
- className: "text-destructive",
104
- children: error
105
- }), onRetry && /* @__PURE__ */ jsx(Button, {
106
- type: "button",
107
- size: "sm",
108
- variant: "outline",
109
- onClick: onRetry,
110
- children: "Try again"
133
+ const sortable = onSortChange ? columns.filter((c) => c.sortable) : [];
134
+ const columnLabel = (c) => c.cardLabel ?? textOf(c.header) ?? c.id;
135
+ const cardSort = cards && sortable.length > 0 && onSortChange ? /* @__PURE__ */ jsxs(Select, {
136
+ value: sort ? `${sort.columnId}:${sort.direction}` : "none",
137
+ onValueChange: (v) => {
138
+ if (v === "none") return onSortChange(null);
139
+ const [columnId = "", direction] = v.split(":");
140
+ onSortChange({
141
+ columnId,
142
+ direction: direction === "desc" ? "desc" : "asc"
143
+ });
144
+ },
145
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
146
+ "aria-label": "Sort by",
147
+ className: "pointer-coarse:h-11",
148
+ children: /* @__PURE__ */ jsx(SelectValue, {})
149
+ }), /* @__PURE__ */ jsxs(SelectContent, {
150
+ align: "end",
151
+ children: [/* @__PURE__ */ jsx(SelectItem, {
152
+ value: "none",
153
+ children: "Default order"
154
+ }), sortable.flatMap((c) => [/* @__PURE__ */ jsxs(SelectItem, {
155
+ value: `${c.id}:asc`,
156
+ children: [columnLabel(c), " ascending"]
157
+ }, `${c.id}:asc`), /* @__PURE__ */ jsxs(SelectItem, {
158
+ value: `${c.id}:desc`,
159
+ children: [columnLabel(c), " descending"]
160
+ }, `${c.id}:desc`)])]
161
+ })]
162
+ }) : null;
163
+ const cardSelectAll = cards && selectable ? /* @__PURE__ */ jsxs("label", {
164
+ htmlFor: `${uid}-all`,
165
+ className: "flex min-h-11 cursor-pointer items-center gap-2.5 pr-1 text-sm sm:min-h-9",
166
+ children: [/* @__PURE__ */ jsx(Checkbox, {
167
+ id: `${uid}-all`,
168
+ checked: allState,
169
+ onCheckedChange: toggleAll,
170
+ disabled: rows.length === 0,
171
+ "aria-label": "Select all rows on this page"
172
+ }), "Select all"]
173
+ }) : null;
174
+ const showToolbar = Boolean(onFilterChange || toolbar || selectable && selectedSet.size || cardSort || cardSelectAll);
175
+ const withNoun = (summary, total) => rowNoun && total ? `${summary} ${rowNoun}` : summary;
176
+ let pager = null;
177
+ if (paginateOptions) {
178
+ if (paging.hasPages) pager = /* @__PURE__ */ jsx(Pagination, {
179
+ ...paging.paginationProps,
180
+ summary: withNoun(paging.summary, paging.total)
181
+ });
182
+ } else if (onPageChange && page !== void 0 && pageCount !== void 0 && pageCount > 0) pager = /* @__PURE__ */ jsx(Pagination, {
183
+ page,
184
+ pageCount,
185
+ onPageChange,
186
+ summary: pageSummary ?? (totalRows !== void 0 && pageSize ? withNoun(paginationSummary(pageWindow({
187
+ page,
188
+ pageSize,
189
+ total: totalRows
190
+ })), totalRows) : void 0),
191
+ pageSize,
192
+ pageSizeOptions,
193
+ onPageSizeChange
194
+ });
195
+ const toolbarNode = showToolbar && /* @__PURE__ */ jsxs("div", {
196
+ className: "flex flex-wrap items-center gap-2",
197
+ children: [
198
+ onFilterChange && /* @__PURE__ */ jsxs("div", {
199
+ className: "relative min-w-0 basis-full sm:max-w-xs sm:flex-1 sm:basis-0",
200
+ children: [/* @__PURE__ */ jsx(Search, {
201
+ className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground",
202
+ "aria-hidden": true
203
+ }), /* @__PURE__ */ jsx(Input, {
204
+ type: "search",
205
+ value: filter ?? "",
206
+ onChange: (e) => onFilterChange(e.target.value),
207
+ placeholder: filterPlaceholder,
208
+ "aria-label": `Search ${ariaLabel.toLowerCase()}`,
209
+ className: "pl-8"
111
210
  })]
211
+ }),
212
+ cardSelectAll,
213
+ selectable && /* @__PURE__ */ jsx("span", {
214
+ "aria-live": "polite",
215
+ className: "text-xs text-muted-foreground tabular-nums",
216
+ children: selectedSet.size ? `${selectedSet.size} selected` : ""
217
+ }),
218
+ (toolbar || cardSort) && /* @__PURE__ */ jsxs("div", {
219
+ className: "ml-auto flex flex-wrap items-center gap-2",
220
+ children: [cardSort, toolbar]
112
221
  })
113
- })
114
- });
115
- else if (loading && rows.length === 0) body = Array.from({ length: 3 }, (_, r) => /* @__PURE__ */ jsx(TableRow, {
116
- className: "hover:bg-transparent",
117
- "aria-hidden": true,
118
- children: Array.from({ length: colCount }, (_, c) => /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full max-w-40" }) }, c))
119
- }, `loading-${r}`));
120
- else if (rows.length === 0) body = /* @__PURE__ */ jsx(TableRow, {
121
- className: "hover:bg-transparent",
122
- children: /* @__PURE__ */ jsx(TableCell, {
123
- colSpan: colCount,
124
- className: "h-auto py-10 text-center whitespace-normal text-muted-foreground",
125
- children: empty
126
- })
127
- });
128
- else body = rows.map((row, i) => {
129
- const id = getRowId(row);
130
- const isSelected = selectedSet.has(id);
131
- const label = getRowLabel?.(row) ?? id;
132
- return /* @__PURE__ */ jsxs(TableRow, {
133
- selected: isSelected,
134
- tabIndex: interactive ? i === activeRow ? 0 : -1 : void 0,
135
- onFocus: () => setActive(i),
136
- onKeyDown: interactive ? (e) => onRowKeyDown(e, i, row) : void 0,
137
- onClick: onRowAction ? (e) => {
138
- if (e.target.closest(INTERACTIVE)) return;
139
- onRowAction(row);
140
- } : void 0,
141
- className: cn(interactive && "outline-none focus-visible:bg-muted/60 focus-visible:shadow-[inset_2px_0_0_var(--ring)]", onRowAction && "cursor-pointer"),
142
- children: [
143
- selectable && /* @__PURE__ */ jsx(TableCell, {
144
- className: "w-10",
145
- children: /* @__PURE__ */ jsx(Checkbox, {
146
- checked: isSelected,
147
- onCheckedChange: () => toggle(id),
148
- "aria-label": `Select ${label}`,
149
- tabIndex: -1
150
- })
151
- }),
152
- columns.map((col) => /* @__PURE__ */ jsx(TableCell, {
153
- numeric: col.numeric,
154
- className: col.className,
155
- children: col.cell(row)
156
- }, col.id)),
157
- rowActions && /* @__PURE__ */ jsx(TableCell, {
158
- className: "w-12 text-right",
159
- children: rowActions(row)
160
- })
161
- ]
162
- }, id);
222
+ ]
163
223
  });
164
224
  return /* @__PURE__ */ jsxs("div", {
165
225
  "data-slot": "data-table",
166
- className: cn("grid min-w-0 gap-3", className),
226
+ "data-layout": cards ? "cards" : "table",
227
+ className: cn("grid min-w-0 grid-cols-[minmax(0,1fr)] gap-3", className),
167
228
  ...props,
168
229
  children: [
169
- showToolbar && /* @__PURE__ */ jsxs("div", {
170
- className: "flex flex-wrap items-center gap-2",
171
- children: [
172
- onFilterChange && /* @__PURE__ */ jsxs("div", {
173
- className: "relative w-full max-w-xs min-w-0 flex-1",
174
- children: [/* @__PURE__ */ jsx(Search, {
175
- className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground",
176
- "aria-hidden": true
177
- }), /* @__PURE__ */ jsx(Input, {
178
- type: "search",
179
- value: filter ?? "",
180
- onChange: (e) => onFilterChange(e.target.value),
181
- placeholder: filterPlaceholder,
182
- "aria-label": `Search ${ariaLabel.toLowerCase()}`,
183
- className: "pl-8"
184
- })]
185
- }),
186
- selectable && /* @__PURE__ */ jsx("span", {
187
- "aria-live": "polite",
188
- className: "text-xs text-muted-foreground tabular-nums",
189
- children: selectedSet.size ? `${selectedSet.size} selected` : ""
190
- }),
191
- toolbar && /* @__PURE__ */ jsx("div", {
192
- className: "ml-auto flex flex-wrap items-center gap-2",
193
- children: toolbar
194
- })
195
- ]
196
- }),
197
- /* @__PURE__ */ jsx("div", {
230
+ toolbarNode,
231
+ cards ? /* @__PURE__ */ jsx(DataTableCards, {
232
+ "aria-label": ariaLabel,
233
+ columns,
234
+ rows,
235
+ getRowId,
236
+ getRowLabel,
237
+ selectedSet,
238
+ onToggle: selectable ? toggle : void 0,
239
+ onRowAction,
240
+ rowActions,
241
+ loading,
242
+ error,
243
+ onRetry,
244
+ empty
245
+ }) : /* @__PURE__ */ jsx("div", {
198
246
  ref: frameRef,
199
247
  className: "rounded-lg border bg-surface",
200
248
  children: /* @__PURE__ */ jsxs(Table, {
@@ -216,11 +264,11 @@ function DataTable({ "aria-label": ariaLabel, columns, rows, getRowId, getRowLab
216
264
  "aria-label": "Select all rows on this page"
217
265
  })
218
266
  }),
219
- columns.map((col) => {
267
+ tableColumns.map((col) => {
220
268
  const sorted = sort?.columnId === col.id ? sort.direction : void 0;
221
269
  return /* @__PURE__ */ jsx(TableHead, {
222
270
  numeric: col.numeric,
223
- className: col.className,
271
+ className: cn(col.hideBelow && hideBelowClass[col.hideBelow], col.className),
224
272
  "aria-sort": col.sortable ? sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none" : void 0,
225
273
  children: col.sortable && onSortChange ? /* @__PURE__ */ jsxs("button", {
226
274
  type: "button",
@@ -247,18 +295,204 @@ function DataTable({ "aria-label": ariaLabel, columns, rows, getRowId, getRowLab
247
295
  })
248
296
  })
249
297
  ]
250
- }) }), /* @__PURE__ */ jsx(TableBody, { children: body })]
298
+ }) }), /* @__PURE__ */ jsx(TableBody, { children: error ? /* @__PURE__ */ jsx(TableRow, {
299
+ className: "hover:bg-transparent",
300
+ children: /* @__PURE__ */ jsx(TableCell, {
301
+ colSpan: colCount,
302
+ className: "h-auto py-8 text-center whitespace-normal",
303
+ children: /* @__PURE__ */ jsx(ErrorMessage, {
304
+ error,
305
+ onRetry
306
+ })
307
+ })
308
+ }) : loading && rows.length === 0 ? Array.from({ length: 3 }, (_, r) => /* @__PURE__ */ jsx(TableRow, {
309
+ className: "hover:bg-transparent",
310
+ "aria-hidden": true,
311
+ children: Array.from({ length: colCount }, (_, c) => /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full max-w-40" }) }, c))
312
+ }, `loading-${r}`)) : rows.length === 0 ? /* @__PURE__ */ jsx(TableRow, {
313
+ className: "hover:bg-transparent",
314
+ children: /* @__PURE__ */ jsx(TableCell, {
315
+ colSpan: colCount,
316
+ className: "h-auto py-10 text-center whitespace-normal text-muted-foreground",
317
+ children: empty
318
+ })
319
+ }) : rows.map((row, i) => {
320
+ const id = getRowId(row);
321
+ const isSelected = selectedSet.has(id);
322
+ const label = getRowLabel?.(row) ?? id;
323
+ return /* @__PURE__ */ jsxs(TableRow, {
324
+ selected: isSelected,
325
+ tabIndex: interactive ? i === activeRow ? 0 : -1 : void 0,
326
+ onFocus: () => setActive(i),
327
+ onKeyDown: interactive ? (e) => onRowKeyDown(e, i, row) : void 0,
328
+ onClick: onRowAction ? (e) => {
329
+ if (e.target.closest(INTERACTIVE)) return;
330
+ onRowAction(row);
331
+ } : void 0,
332
+ className: cn(interactive && "outline-none focus-visible:bg-muted/60 focus-visible:shadow-[inset_2px_0_0_var(--ring)]", onRowAction && "cursor-pointer"),
333
+ children: [
334
+ selectable && /* @__PURE__ */ jsx(TableCell, {
335
+ className: "w-10",
336
+ children: /* @__PURE__ */ jsx(Checkbox, {
337
+ checked: isSelected,
338
+ onCheckedChange: () => toggle(id),
339
+ "aria-label": `Select ${label}`,
340
+ tabIndex: -1
341
+ })
342
+ }),
343
+ tableColumns.map((col) => /* @__PURE__ */ jsx(TableCell, {
344
+ numeric: col.numeric,
345
+ className: cn(col.hideBelow && hideBelowClass[col.hideBelow], col.className),
346
+ children: col.cell(row)
347
+ }, col.id)),
348
+ rowActions && /* @__PURE__ */ jsx(TableCell, {
349
+ className: "w-12 text-right",
350
+ children: rowActions(row)
351
+ })
352
+ ]
353
+ }, id);
354
+ }) })]
251
355
  })
252
356
  }),
253
- onPageChange && page !== void 0 && pageCount !== void 0 && pageCount > 0 && /* @__PURE__ */ jsx(Pagination, {
254
- page,
255
- pageCount,
256
- onPageChange,
257
- summary: pageSummary
258
- })
357
+ pager
259
358
  ]
260
359
  });
261
360
  }
361
+ function ErrorMessage({ error, onRetry }) {
362
+ return /* @__PURE__ */ jsxs("div", {
363
+ role: "alert",
364
+ className: "grid justify-items-center gap-3 text-sm",
365
+ children: [/* @__PURE__ */ jsx("span", {
366
+ className: "text-destructive",
367
+ children: error
368
+ }), onRetry && /* @__PURE__ */ jsx(Button, {
369
+ type: "button",
370
+ size: "sm",
371
+ variant: "outline",
372
+ onClick: onRetry,
373
+ children: "Try again"
374
+ })]
375
+ });
376
+ }
377
+ var cardLabelClass = "text-[11px] font-semibold tracking-[0.06em] text-muted-foreground uppercase";
378
+ var liftControls = "[&_a]:relative [&_a]:z-10 [&_button]:relative [&_button]:z-10 [&_input]:relative [&_input]:z-10 [&_select]:relative [&_select]:z-10 [&_textarea]:relative [&_textarea]:z-10";
379
+ /**
380
+ * The phone layout: a list of articles, one per row, each named by its title.
381
+ * The title is the row's open button (its hit area covers the card); the
382
+ * checkbox, row actions and footer controls sit above it and keep their own
383
+ * clicks. No roving focus: Tab walks the controls, as in any list of links.
384
+ */
385
+ function DataTableCards({ "aria-label": ariaLabel, columns, rows, getRowId, getRowLabel, selectedSet, onToggle, onRowAction, rowActions, loading, error, onRetry, empty }) {
386
+ const uid = React.useId();
387
+ if (error) return /* @__PURE__ */ jsx("div", {
388
+ className: "rounded-lg border bg-surface px-4 py-8",
389
+ children: /* @__PURE__ */ jsx(ErrorMessage, {
390
+ error,
391
+ onRetry
392
+ })
393
+ });
394
+ if (rows.length === 0 && !loading) return /* @__PURE__ */ jsx("div", {
395
+ className: "rounded-lg border bg-surface px-4 py-10 text-center text-sm text-muted-foreground",
396
+ children: empty
397
+ });
398
+ const visible = columns.filter((c) => c.card !== "hidden");
399
+ const firstUnplaced = visible.some((c) => c.card === "title") ? void 0 : visible.find((c) => c.card === void 0);
400
+ const slotOf = (c) => c.card ?? (c === firstUnplaced ? "title" : "field");
401
+ const inSlot = (s) => visible.filter((c) => slotOf(c) === s);
402
+ const title = inSlot("title");
403
+ const subtitle = inSlot("subtitle");
404
+ const aside = inSlot("aside");
405
+ const fields = inSlot("field");
406
+ const footer = inSlot("footer");
407
+ const show = (c, row) => (c.cardCell ?? c.cell)(row);
408
+ return /* @__PURE__ */ jsx("ul", {
409
+ role: "list",
410
+ "aria-label": ariaLabel,
411
+ "aria-busy": loading || void 0,
412
+ "data-slot": "data-table-cards",
413
+ className: cn("grid gap-2", loading && rows.length > 0 && "opacity-60 transition-opacity"),
414
+ children: loading && rows.length === 0 ? Array.from({ length: 3 }, (_, r) => /* @__PURE__ */ jsxs("li", {
415
+ "aria-hidden": true,
416
+ className: "grid gap-3 rounded-lg border bg-surface p-3.5",
417
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/2" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-3/4" })]
418
+ }, `loading-${r}`)) : rows.map((row, i) => {
419
+ const id = getRowId(row);
420
+ const label = getRowLabel?.(row) ?? id;
421
+ const isSelected = selectedSet.has(id);
422
+ const titleId = `${uid}-${i}`;
423
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("article", {
424
+ "aria-labelledby": title.length ? titleId : void 0,
425
+ "aria-label": title.length ? void 0 : label,
426
+ "data-slot": "data-table-card",
427
+ "data-state": isSelected ? "selected" : void 0,
428
+ className: cn("relative grid gap-3 rounded-lg border bg-surface p-3.5 shadow-xs transition-colors", onRowAction && "hover:bg-muted/40 has-[[data-row-action]:focus-visible]:border-ring has-[[data-row-action]:focus-visible]:inset-ring-1 has-[[data-row-action]:focus-visible]:inset-ring-ring", "data-[state=selected]:border-brand/40 data-[state=selected]:bg-brand-soft/60"),
429
+ children: [
430
+ /* @__PURE__ */ jsxs("div", {
431
+ className: "flex items-start gap-3",
432
+ children: [
433
+ onToggle && /* @__PURE__ */ jsx("label", {
434
+ htmlFor: `${titleId}-select`,
435
+ className: "relative z-10 -mx-3.5 -my-3 grid size-11 shrink-0 cursor-pointer place-items-center",
436
+ children: /* @__PURE__ */ jsx(Checkbox, {
437
+ id: `${titleId}-select`,
438
+ checked: isSelected,
439
+ onCheckedChange: () => onToggle(id),
440
+ "aria-label": `Select ${label}`
441
+ })
442
+ }),
443
+ /* @__PURE__ */ jsx("div", {
444
+ className: "grid min-w-0 flex-1 gap-0.5",
445
+ children: title.map((c, j) => /* @__PURE__ */ jsx("div", {
446
+ id: j === 0 ? titleId : void 0,
447
+ className: "min-w-0 text-[15px] leading-5 font-semibold [overflow-wrap:anywhere]",
448
+ children: onRowAction && j === 0 ? /* @__PURE__ */ jsx("button", {
449
+ type: "button",
450
+ "data-row-action": "",
451
+ onClick: () => onRowAction(row),
452
+ className: "text-left outline-hidden! after:absolute after:inset-0 after:rounded-lg",
453
+ children: show(c, row)
454
+ }) : show(c, row)
455
+ }, c.id))
456
+ }),
457
+ aside.length > 0 && /* @__PURE__ */ jsx("div", {
458
+ className: "relative z-10 flex shrink-0 flex-col items-end gap-1",
459
+ children: aside.map((c) => /* @__PURE__ */ jsx("div", { children: show(c, row) }, c.id))
460
+ }),
461
+ rowActions && /* @__PURE__ */ jsx("div", {
462
+ className: "relative z-10 -my-2.5 -mr-1.5 shrink-0",
463
+ children: rowActions(row)
464
+ })
465
+ ]
466
+ }),
467
+ subtitle.length > 0 && /* @__PURE__ */ jsx("div", {
468
+ className: cn("-mt-2 grid min-w-0 gap-0.5", liftControls, onToggle && "pl-7"),
469
+ children: subtitle.map((c) => /* @__PURE__ */ jsx("div", {
470
+ className: "min-w-0 text-sm text-muted-foreground [overflow-wrap:anywhere]",
471
+ children: show(c, row)
472
+ }, c.id))
473
+ }),
474
+ fields.length > 0 && /* @__PURE__ */ jsx("dl", {
475
+ className: cn("grid grid-cols-2 gap-x-4 gap-y-2.5 text-sm", liftControls),
476
+ children: fields.map((c) => /* @__PURE__ */ jsxs("div", {
477
+ className: "min-w-0",
478
+ children: [/* @__PURE__ */ jsx("dt", {
479
+ className: cardLabelClass,
480
+ children: c.cardLabel ?? c.header
481
+ }), /* @__PURE__ */ jsx("dd", {
482
+ className: cn("mt-0.5 min-w-0 [overflow-wrap:anywhere]", c.numeric && "tabular-nums"),
483
+ children: show(c, row)
484
+ })]
485
+ }, c.id))
486
+ }),
487
+ footer.map((c) => /* @__PURE__ */ jsx("div", {
488
+ className: "relative z-10 flex flex-wrap gap-2",
489
+ children: show(c, row)
490
+ }, c.id))
491
+ ]
492
+ }) }, id);
493
+ })
494
+ });
495
+ }
262
496
  //#endregion
263
497
  export { DataTable };
264
498