@arqel-dev/ui 0.14.0 → 0.15.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.
Files changed (108) hide show
  1. package/README.md +1 -1
  2. package/dist/{AreaChart-NMQ2RQGA.js → AreaChart-JAZ6LUYG.js} +8 -4
  3. package/dist/AreaChart-JAZ6LUYG.js.map +1 -0
  4. package/dist/ArqelCreatePage-UZAHV4DK.js +8 -0
  5. package/dist/{ArqelCreatePage-O6KNXPAO.js.map → ArqelCreatePage-UZAHV4DK.js.map} +1 -1
  6. package/dist/ArqelDashboardPage-Z5R4XSH4.js +6 -0
  7. package/dist/{ArqelDashboardPage-PX3Y75AA.js.map → ArqelDashboardPage-Z5R4XSH4.js.map} +1 -1
  8. package/dist/ArqelEditPage-RRYFYOTC.js +8 -0
  9. package/dist/{ArqelShowPage-LGVTL6UZ.js.map → ArqelEditPage-RRYFYOTC.js.map} +1 -1
  10. package/dist/ArqelIndexPage-RGZWKKAX.js +12 -0
  11. package/dist/{ArqelIndexPage-GC33PNBL.js.map → ArqelIndexPage-RGZWKKAX.js.map} +1 -1
  12. package/dist/ArqelShowPage-TVCJAV2F.js +6 -0
  13. package/dist/{ArqelEditPage-NAMWJ5MC.js.map → ArqelShowPage-TVCJAV2F.js.map} +1 -1
  14. package/dist/{BarChart-KLHKTLO2.js → BarChart-E52NJSJE.js} +8 -4
  15. package/dist/BarChart-E52NJSJE.js.map +1 -0
  16. package/dist/{DonutChart-L56R4QF2.js → DonutChart-IGSF4LEB.js} +3 -3
  17. package/dist/{DonutChart-L56R4QF2.js.map → DonutChart-IGSF4LEB.js.map} +1 -1
  18. package/dist/{FormRenderer-BJBRIkfH.d.ts → FormRenderer-By8fZp4t.d.ts} +2 -0
  19. package/dist/{LineChart-GHMPHFZZ.js → LineChart-RRPGAZON.js} +8 -4
  20. package/dist/LineChart-RRPGAZON.js.map +1 -0
  21. package/dist/PieChart-SGN7V5LQ.js +4 -0
  22. package/dist/{PieChart-T2VQOFPP.js.map → PieChart-SGN7V5LQ.js.map} +1 -1
  23. package/dist/{RadarChart-CSWR5AO7.js → RadarChart-3AOM736J.js} +8 -4
  24. package/dist/RadarChart-3AOM736J.js.map +1 -0
  25. package/dist/action.d.ts +1 -1
  26. package/dist/action.js +5 -5
  27. package/dist/{chunk-IKJYLSTK.js → chunk-2JUWLJ3K.js} +90 -29
  28. package/dist/chunk-2JUWLJ3K.js.map +1 -0
  29. package/dist/chunk-3HN4ARGG.js +157 -0
  30. package/dist/chunk-3HN4ARGG.js.map +1 -0
  31. package/dist/{chunk-PXON56HL.js → chunk-3VSDQCFO.js} +3 -3
  32. package/dist/{chunk-PXON56HL.js.map → chunk-3VSDQCFO.js.map} +1 -1
  33. package/dist/{chunk-6UJQP7KU.js → chunk-4VWNBMMA.js} +34 -118
  34. package/dist/chunk-4VWNBMMA.js.map +1 -0
  35. package/dist/{chunk-4JJS2SVS.js → chunk-CMNZLA32.js} +83 -32
  36. package/dist/chunk-CMNZLA32.js.map +1 -0
  37. package/dist/{chunk-2LTZJ4R7.js → chunk-EG2DASKR.js} +16 -7
  38. package/dist/chunk-EG2DASKR.js.map +1 -0
  39. package/dist/{chunk-DSULVRB6.js → chunk-GJ2FKAOM.js} +5 -3
  40. package/dist/chunk-GJ2FKAOM.js.map +1 -0
  41. package/dist/{chunk-FHRQ7PWE.js → chunk-H23VX3RR.js} +4 -3
  42. package/dist/chunk-H23VX3RR.js.map +1 -0
  43. package/dist/{chunk-FV7RESKL.js → chunk-HNUUZEOL.js} +28 -16
  44. package/dist/chunk-HNUUZEOL.js.map +1 -0
  45. package/dist/{chunk-M6NI45DQ.js → chunk-IO6RMVUF.js} +9 -5
  46. package/dist/chunk-IO6RMVUF.js.map +1 -0
  47. package/dist/{chunk-I6MWOIFR.js → chunk-LLANMODS.js} +6 -5
  48. package/dist/chunk-LLANMODS.js.map +1 -0
  49. package/dist/{chunk-MTZJDFQ6.js → chunk-MZMYV43C.js} +11 -7
  50. package/dist/chunk-MZMYV43C.js.map +1 -0
  51. package/dist/{chunk-E2ORVQM5.js → chunk-Q7WGPKUN.js} +6 -4
  52. package/dist/chunk-Q7WGPKUN.js.map +1 -0
  53. package/dist/{chunk-PZEVKMZX.js → chunk-S5UNPCMQ.js} +315 -152
  54. package/dist/chunk-S5UNPCMQ.js.map +1 -0
  55. package/dist/{chunk-MTNFFSVA.js → chunk-SK6H7ARE.js} +5 -3
  56. package/dist/chunk-SK6H7ARE.js.map +1 -0
  57. package/dist/{chunk-WKWV36PG.js → chunk-WMOBLT3Z.js} +12 -5
  58. package/dist/chunk-WMOBLT3Z.js.map +1 -0
  59. package/dist/{chunk-C5H44NSP.js → chunk-WZRPGPLH.js} +10 -5
  60. package/dist/chunk-WZRPGPLH.js.map +1 -0
  61. package/dist/{chunk-QTUBBZVT.js → chunk-YQ6KXR3U.js} +9 -11
  62. package/dist/chunk-YQ6KXR3U.js.map +1 -0
  63. package/dist/{chunk-47HD7ALI.js → chunk-YYQ45W25.js} +37 -24
  64. package/dist/chunk-YYQ45W25.js.map +1 -0
  65. package/dist/flash.js +1 -1
  66. package/dist/form.d.ts +3 -1
  67. package/dist/form.js +3 -3
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +12 -12
  70. package/dist/pages.js +19 -19
  71. package/dist/palette.js +1 -1
  72. package/dist/resource.js +3 -3
  73. package/dist/shell.js +10 -6
  74. package/dist/shell.js.map +1 -1
  75. package/dist/table.js +2 -2
  76. package/dist/utility.js +1 -1
  77. package/dist/widgets.js +2 -2
  78. package/package.json +6 -6
  79. package/src/styles/globals.css +7 -0
  80. package/dist/AreaChart-NMQ2RQGA.js.map +0 -1
  81. package/dist/ArqelCreatePage-O6KNXPAO.js +0 -8
  82. package/dist/ArqelDashboardPage-PX3Y75AA.js +0 -6
  83. package/dist/ArqelEditPage-NAMWJ5MC.js +0 -8
  84. package/dist/ArqelIndexPage-GC33PNBL.js +0 -12
  85. package/dist/ArqelShowPage-LGVTL6UZ.js +0 -6
  86. package/dist/BarChart-KLHKTLO2.js.map +0 -1
  87. package/dist/LineChart-GHMPHFZZ.js.map +0 -1
  88. package/dist/PieChart-T2VQOFPP.js +0 -4
  89. package/dist/RadarChart-CSWR5AO7.js.map +0 -1
  90. package/dist/chunk-2LTZJ4R7.js.map +0 -1
  91. package/dist/chunk-47HD7ALI.js.map +0 -1
  92. package/dist/chunk-4JJS2SVS.js.map +0 -1
  93. package/dist/chunk-6UJQP7KU.js.map +0 -1
  94. package/dist/chunk-C5H44NSP.js.map +0 -1
  95. package/dist/chunk-DSULVRB6.js.map +0 -1
  96. package/dist/chunk-E2ORVQM5.js.map +0 -1
  97. package/dist/chunk-EYAUPJ6J.js +0 -64
  98. package/dist/chunk-EYAUPJ6J.js.map +0 -1
  99. package/dist/chunk-FHRQ7PWE.js.map +0 -1
  100. package/dist/chunk-FV7RESKL.js.map +0 -1
  101. package/dist/chunk-I6MWOIFR.js.map +0 -1
  102. package/dist/chunk-IKJYLSTK.js.map +0 -1
  103. package/dist/chunk-M6NI45DQ.js.map +0 -1
  104. package/dist/chunk-MTNFFSVA.js.map +0 -1
  105. package/dist/chunk-MTZJDFQ6.js.map +0 -1
  106. package/dist/chunk-PZEVKMZX.js.map +0 -1
  107. package/dist/chunk-QTUBBZVT.js.map +0 -1
  108. package/dist/chunk-WKWV36PG.js.map +0 -1
@@ -1,7 +1,8 @@
1
1
  import { resolveLucideIcon } from './chunk-RMPOR2YZ.js';
2
- import { Button } from './chunk-FHRQ7PWE.js';
2
+ import { Button } from './chunk-H23VX3RR.js';
3
3
  import { cn } from './chunk-B7ULUJVO.js';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { useArqelTranslations, useArqelLocale } from '@arqel-dev/react/utils';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
6
  import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
6
7
  import { useRef, useMemo, useCallback } from 'react';
7
8
 
@@ -94,46 +95,93 @@ function BadgeCell({ column, value }) {
94
95
  );
95
96
  }
96
97
  function BooleanCell({ column, value }) {
98
+ const t = useArqelTranslations();
97
99
  const truthy = Boolean(value);
98
- return /* @__PURE__ */ jsx("span", { role: "img", "aria-label": truthy ? "true" : "false", children: truthy ? column.props.trueIcon ?? "\u2713" : column.props.falseIcon ?? "\u2014" });
100
+ const ariaLabel = truthy ? t("table.boolean.true_label", "true") : t("table.boolean.false_label", "false");
101
+ return /* @__PURE__ */ jsx("span", { role: "img", "aria-label": ariaLabel, children: truthy ? column.props.trueIcon ?? "\u2713" : column.props.falseIcon ?? "\u2014" });
99
102
  }
100
103
  function DateCell({ column, value }) {
104
+ const locale = useArqelLocale();
101
105
  if (!value) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
102
106
  const raw = String(value);
103
107
  const date = new Date(raw);
104
108
  if (Number.isNaN(date.getTime())) return /* @__PURE__ */ jsx("span", { children: raw });
109
+ const timeZone = column.props.timezone || void 0;
105
110
  if (column.props.mode === "since") {
106
- return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: formatRelative(date) });
111
+ return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: formatRelative(date, locale) });
107
112
  }
108
113
  if (column.props.mode === "datetime") {
109
- return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: date.toLocaleString() });
114
+ return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: formatDate(date, locale, "datetime", timeZone) });
110
115
  }
111
- return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: date.toLocaleDateString() });
116
+ return /* @__PURE__ */ jsx("time", { dateTime: date.toISOString(), children: formatDate(date, locale, "date", timeZone) });
112
117
  }
113
- function formatRelative(date) {
118
+ function formatDate(date, locale, mode, timeZone) {
119
+ const options = mode === "datetime" ? { dateStyle: "medium", timeStyle: "short" } : { dateStyle: "medium" };
120
+ if (timeZone !== void 0) options.timeZone = timeZone;
121
+ try {
122
+ return new Intl.DateTimeFormat(locale, options).format(date);
123
+ } catch {
124
+ if (timeZone !== void 0) {
125
+ const { timeZone: _drop, ...rest } = options;
126
+ return new Intl.DateTimeFormat(locale, rest).format(date);
127
+ }
128
+ return mode === "datetime" ? date.toISOString() : date.toISOString().slice(0, 10);
129
+ }
130
+ }
131
+ function formatRelative(date, locale) {
114
132
  const seconds = Math.round((Date.now() - date.getTime()) / 1e3);
115
- if (seconds < 60) return `${seconds}s ago`;
133
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
134
+ const absSeconds = Math.abs(seconds);
135
+ if (absSeconds < 60) return rtf.format(-seconds, "second");
116
136
  const minutes = Math.round(seconds / 60);
117
- if (minutes < 60) return `${minutes}m ago`;
137
+ if (Math.abs(minutes) < 60) return rtf.format(-minutes, "minute");
118
138
  const hours = Math.round(minutes / 60);
119
- if (hours < 24) return `${hours}h ago`;
139
+ if (Math.abs(hours) < 24) return rtf.format(-hours, "hour");
120
140
  const days = Math.round(hours / 24);
121
- return `${days}d ago`;
141
+ return rtf.format(-days, "day");
122
142
  }
123
143
  function NumberCell({ column, value }) {
144
+ const locale = useArqelLocale();
124
145
  if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", { children: "\u2014" });
125
146
  const num = typeof value === "number" ? value : Number(value);
126
147
  if (Number.isNaN(num)) return /* @__PURE__ */ jsx("span", { children: asString(value) });
127
- const formatted = num.toFixed(column.props.decimals ?? 0);
128
- const [whole, dec] = formatted.split(".");
129
- const safeWhole = whole ?? "0";
148
+ if (column.props.currency) {
149
+ try {
150
+ const formatted = new Intl.NumberFormat(locale, {
151
+ style: "currency",
152
+ currency: column.props.currency,
153
+ ...column.props.decimals !== void 0 ? {
154
+ minimumFractionDigits: column.props.decimals,
155
+ maximumFractionDigits: column.props.decimals
156
+ } : {}
157
+ }).format(num);
158
+ return /* @__PURE__ */ jsxs("span", { children: [
159
+ column.props.prefix ?? "",
160
+ formatted,
161
+ column.props.suffix ?? ""
162
+ ] });
163
+ } catch {
164
+ }
165
+ }
166
+ const decimals = column.props.decimals ?? 0;
130
167
  const ts = column.props.thousandsSeparator;
131
- const finalWhole = ts ? safeWhole.replace(/\B(?=(\d{3})+(?!\d))/g, ts) : safeWhole;
132
- const ds = column.props.decimalSeparator ?? ".";
168
+ const ds = column.props.decimalSeparator;
169
+ let body;
170
+ if (ts === void 0 && ds === void 0) {
171
+ body = new Intl.NumberFormat(locale, {
172
+ minimumFractionDigits: decimals,
173
+ maximumFractionDigits: decimals
174
+ }).format(num);
175
+ } else {
176
+ const formatted = num.toFixed(decimals);
177
+ const [whole, dec] = formatted.split(".");
178
+ const safeWhole = whole ?? "0";
179
+ const finalWhole = ts ? safeWhole.replace(/\B(?=(\d{3})+(?!\d))/g, ts) : safeWhole;
180
+ body = dec ? `${finalWhole}${ds ?? "."}${dec}` : finalWhole;
181
+ }
133
182
  return /* @__PURE__ */ jsxs("span", { children: [
134
183
  column.props.prefix ?? "",
135
- finalWhole,
136
- dec ? `${ds}${dec}` : "",
184
+ body,
137
185
  column.props.suffix ?? ""
138
186
  ] });
139
187
  }
@@ -181,6 +229,7 @@ function DataTable({
181
229
  emptyState,
182
230
  className
183
231
  }) {
232
+ const t = useArqelTranslations();
184
233
  const lastClickedIndexRef = useRef(null);
185
234
  const visibleColumns = useMemo(() => columns.filter((col) => !col.hidden), [columns]);
186
235
  const tableColumns = useMemo(
@@ -233,114 +282,213 @@ function DataTable({
233
282
  if (!sort || sort.column !== columnName) return null;
234
283
  return sort.direction === "desc" ? "desc" : "asc";
235
284
  };
236
- return /* @__PURE__ */ jsx("div", { className: cn("w-full overflow-x-auto", className), children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
237
- /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-background", children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-border", children: [
238
- enableSelection && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-10 px-3 py-2 text-left", children: /* @__PURE__ */ jsx(
239
- "input",
240
- {
241
- type: "checkbox",
242
- "aria-label": "Select all rows",
243
- checked: allSelected,
244
- ref: (el) => {
245
- if (el) el.indeterminate = someSelected;
246
- },
247
- onChange: toggleAll
248
- }
249
- ) }),
250
- group.headers.map((header) => {
251
- const col = visibleColumns.find((c) => c.name === header.column.id);
252
- const sortable = col?.sortable && onSortChange;
253
- const direction = headerSortDirection(header.column.id);
254
- return /* @__PURE__ */ jsx(
285
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
286
+ /* @__PURE__ */ jsx("div", { className: cn("hidden w-full min-w-0 overflow-x-auto md:block", className), children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
287
+ /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-background", children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-border", children: [
288
+ enableSelection && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-10 px-3 py-2 text-left", children: /* @__PURE__ */ jsx(
289
+ "input",
290
+ {
291
+ type: "checkbox",
292
+ "aria-label": t("table.bulk.select_all_rows", "Select all rows"),
293
+ checked: allSelected,
294
+ ref: (el) => {
295
+ if (el) el.indeterminate = someSelected;
296
+ },
297
+ onChange: toggleAll
298
+ }
299
+ ) }),
300
+ group.headers.map((header) => {
301
+ const col = visibleColumns.find((c) => c.name === header.column.id);
302
+ const sortable = col?.sortable && onSortChange;
303
+ const direction = headerSortDirection(header.column.id);
304
+ return /* @__PURE__ */ jsx(
305
+ "th",
306
+ {
307
+ scope: "col",
308
+ className: cn(
309
+ "px-3 py-2 text-left font-medium text-muted-foreground",
310
+ col?.align === "center" && "text-center",
311
+ col?.align === "end" && "text-right",
312
+ col?.hiddenOnMobile && "hidden md:table-cell"
313
+ ),
314
+ "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none",
315
+ children: sortable ? /* @__PURE__ */ jsxs(
316
+ "button",
317
+ {
318
+ type: "button",
319
+ className: cn(
320
+ // -mx-3 px-3 stretches the button across the cell's
321
+ // horizontal padding; min-h-11 + w-full give a ≥44px
322
+ // touch target on mobile (WCAG 2.5.5) without changing
323
+ // the header's visual density.
324
+ "-mx-3 inline-flex min-h-11 w-full items-center gap-1 px-3 hover:text-foreground",
325
+ col?.align === "center" && "justify-center",
326
+ col?.align === "end" && "justify-end"
327
+ ),
328
+ onClick: () => onSortChange(header.column.id, direction === "asc" ? "desc" : "asc"),
329
+ children: [
330
+ flexRender(header.column.columnDef.header, header.getContext()),
331
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: direction === "asc" ? "\u25B2" : direction === "desc" ? "\u25BC" : "\u2195" })
332
+ ]
333
+ }
334
+ ) : flexRender(header.column.columnDef.header, header.getContext())
335
+ },
336
+ header.id
337
+ );
338
+ }),
339
+ rowActions && /* @__PURE__ */ jsx(
255
340
  "th",
256
341
  {
257
342
  scope: "col",
258
- className: cn(
259
- "px-3 py-2 text-left font-medium text-muted-foreground",
260
- col?.align === "center" && "text-center",
261
- col?.align === "end" && "text-right"
262
- ),
263
- "aria-sort": direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none",
264
- children: sortable ? /* @__PURE__ */ jsxs(
265
- "button",
266
- {
267
- type: "button",
268
- className: "inline-flex items-center gap-1 hover:text-foreground",
269
- onClick: () => onSortChange(header.column.id, direction === "asc" ? "desc" : "asc"),
270
- children: [
271
- flexRender(header.column.columnDef.header, header.getContext()),
272
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: direction === "asc" ? "\u25B2" : direction === "desc" ? "\u25BC" : "\u2195" })
273
- ]
274
- }
275
- ) : flexRender(header.column.columnDef.header, header.getContext())
276
- },
277
- header.id
278
- );
279
- }),
280
- rowActions && /* @__PURE__ */ jsx("th", { scope: "col", className: "w-24 px-3 py-2 text-right", "aria-label": "Actions" })
281
- ] }, group.id)) }),
282
- /* @__PURE__ */ jsxs("tbody", { children: [
283
- loading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
284
- "td",
285
- {
286
- colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
287
- className: "px-3 py-4 text-center text-muted-foreground",
288
- children: "Loading\u2026"
289
- }
290
- ) }),
291
- !loading && records.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
292
- "td",
293
- {
294
- colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
295
- className: "px-3 py-8 text-center text-muted-foreground",
296
- children: emptyState ?? "No records found."
297
- }
298
- ) }),
299
- !loading && table.getRowModel().rows.map((row, index) => {
300
- const record = row.original;
301
- const checked = selectedIds.includes(record.id);
302
- return /* @__PURE__ */ jsxs(
303
- "tr",
343
+ className: "w-24 px-3 py-2 text-right",
344
+ "aria-label": t("table.column.actions", "Actions")
345
+ }
346
+ )
347
+ ] }, group.id)) }),
348
+ /* @__PURE__ */ jsxs("tbody", { children: [
349
+ loading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
350
+ "td",
304
351
  {
305
- "data-selected": checked || void 0,
306
- className: cn("border-b border-border", checked && "bg-muted"),
307
- children: [
308
- enableSelection && /* @__PURE__ */ jsx("td", { className: "w-10 px-3 py-2", children: /* @__PURE__ */ jsx(
309
- "input",
310
- {
311
- type: "checkbox",
312
- "aria-label": `Select row ${record.id}`,
313
- checked,
314
- onChange: (event) => {
315
- const native = event.nativeEvent;
316
- toggleRow(record.id, index, native.shiftKey === true);
352
+ colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
353
+ className: "px-3 py-4 text-center text-muted-foreground",
354
+ children: t("table.loading", "Loading\u2026")
355
+ }
356
+ ) }),
357
+ !loading && records.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
358
+ "td",
359
+ {
360
+ colSpan: visibleColumns.length + (enableSelection ? 1 : 0) + (rowActions ? 1 : 0),
361
+ className: "px-3 py-8 text-center text-muted-foreground",
362
+ children: emptyState ?? t("table.empty", "No records found.")
363
+ }
364
+ ) }),
365
+ !loading && table.getRowModel().rows.map((row, index) => {
366
+ const record = row.original;
367
+ const checked = selectedIds.includes(record.id);
368
+ return /* @__PURE__ */ jsxs(
369
+ "tr",
370
+ {
371
+ "data-selected": checked || void 0,
372
+ className: cn("border-b border-border", checked && "bg-muted"),
373
+ children: [
374
+ enableSelection && /* @__PURE__ */ jsx("td", { className: "w-10 px-3 py-2", children: /* @__PURE__ */ jsx(
375
+ "input",
376
+ {
377
+ type: "checkbox",
378
+ "aria-label": t("table.bulk.select_row", `Select row ${record.id}`, {
379
+ id: String(record.id)
380
+ }),
381
+ checked,
382
+ onChange: (event) => {
383
+ const native = event.nativeEvent;
384
+ toggleRow(record.id, index, native.shiftKey === true);
385
+ }
317
386
  }
387
+ ) }),
388
+ row.getVisibleCells().map((cell) => {
389
+ const col = visibleColumns.find((c) => c.name === cell.column.id);
390
+ return /* @__PURE__ */ jsx(
391
+ "td",
392
+ {
393
+ className: cn(
394
+ "px-3 py-2",
395
+ col?.align === "center" && "text-center",
396
+ col?.align === "end" && "text-right",
397
+ col?.hiddenOnMobile && "hidden md:table-cell"
398
+ ),
399
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
400
+ },
401
+ cell.id
402
+ );
403
+ }),
404
+ rowActions && /* @__PURE__ */ jsx("td", { className: "w-24 px-3 py-2 text-right", children: rowActions(record) })
405
+ ]
406
+ },
407
+ row.id
408
+ );
409
+ })
410
+ ] })
411
+ ] }) }),
412
+ /* @__PURE__ */ jsx(
413
+ DataCards,
414
+ {
415
+ columns: visibleColumns,
416
+ rows: table.getRowModel().rows,
417
+ enableSelection,
418
+ selectedIds,
419
+ onToggleRow: toggleRow,
420
+ rowActions,
421
+ loading,
422
+ emptyState
423
+ }
424
+ )
425
+ ] });
426
+ }
427
+ function DataCards({
428
+ columns,
429
+ rows,
430
+ enableSelection,
431
+ selectedIds,
432
+ onToggleRow,
433
+ rowActions,
434
+ loading,
435
+ emptyState
436
+ }) {
437
+ const t = useArqelTranslations();
438
+ const cardColumns = columns.filter((col) => !col.hiddenOnMobile);
439
+ if (loading) {
440
+ return /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-muted-foreground md:hidden", children: t("table.loading", "Loading\u2026") });
441
+ }
442
+ if (rows.length === 0) {
443
+ return /* @__PURE__ */ jsx("div", { className: "px-3 py-8 text-center text-muted-foreground md:hidden", children: emptyState ?? t("table.empty", "No records found.") });
444
+ }
445
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3 md:hidden", children: rows.map((row, index) => {
446
+ const record = row.original;
447
+ const checked = selectedIds.includes(record.id);
448
+ return /* @__PURE__ */ jsxs(
449
+ "article",
450
+ {
451
+ "data-arqel-data-card": "",
452
+ "data-selected": checked || void 0,
453
+ className: cn("rounded-lg border border-border p-4 text-sm", checked && "bg-muted"),
454
+ children: [
455
+ (enableSelection || rowActions) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
456
+ enableSelection ? /* @__PURE__ */ jsx("label", { className: "-m-2.5 inline-flex size-11 cursor-pointer items-center justify-center p-2.5", children: /* @__PURE__ */ jsx(
457
+ "input",
458
+ {
459
+ type: "checkbox",
460
+ "aria-label": t("table.bulk.select_row", `Select row ${record.id}`, {
461
+ id: String(record.id)
462
+ }),
463
+ className: "size-5",
464
+ checked,
465
+ onChange: (event) => {
466
+ const native = event.nativeEvent;
467
+ onToggleRow(record.id, index, native.shiftKey === true);
318
468
  }
319
- ) }),
320
- row.getVisibleCells().map((cell) => {
321
- const col = visibleColumns.find((c) => c.name === cell.column.id);
322
- return /* @__PURE__ */ jsx(
323
- "td",
324
- {
325
- className: cn(
326
- "px-3 py-2",
327
- col?.align === "center" && "text-center",
328
- col?.align === "end" && "text-right",
329
- col?.hiddenOnMobile && "hidden md:table-cell"
330
- ),
331
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
332
- },
333
- cell.id
334
- );
335
- }),
336
- rowActions && /* @__PURE__ */ jsx("td", { className: "w-24 px-3 py-2 text-right", children: rowActions(record) })
337
- ]
338
- },
339
- row.id
340
- );
341
- })
342
- ] })
343
- ] }) });
469
+ }
470
+ ) }) : /* @__PURE__ */ jsx("span", {}),
471
+ rowActions ? /* @__PURE__ */ jsx("div", { className: "ml-auto", children: rowActions(record) }) : null
472
+ ] }),
473
+ /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[minmax(0,auto)_1fr] gap-x-3 gap-y-2", children: cardColumns.map((col) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
474
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: col.label ?? col.name }),
475
+ /* @__PURE__ */ jsx(
476
+ "dd",
477
+ {
478
+ className: cn(
479
+ "min-w-0 break-words",
480
+ col.align === "end" && "text-right",
481
+ col.align === "center" && "text-center"
482
+ ),
483
+ children: /* @__PURE__ */ jsx(TableCell, { column: col, value: record[col.name] })
484
+ }
485
+ )
486
+ ] }, col.name)) })
487
+ ]
488
+ },
489
+ row.id
490
+ );
491
+ }) });
344
492
  }
345
493
  function TableFilters({
346
494
  filters,
@@ -349,13 +497,14 @@ function TableFilters({
349
497
  onClearAll,
350
498
  className
351
499
  }) {
500
+ const t = useArqelTranslations();
352
501
  if (filters.length === 0) return null;
353
502
  const activeCount = filters.reduce((acc, f) => {
354
503
  const v = values[f.name];
355
504
  return v !== void 0 && v !== null && v !== "" ? acc + 1 : acc;
356
505
  }, 0);
357
506
  return /* @__PURE__ */ jsxs("fieldset", { className: cn("flex flex-wrap items-end gap-2 border-0 p-0", className), children: [
358
- /* @__PURE__ */ jsx("legend", { className: "sr-only", children: "Filters" }),
507
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: t("table.filters.legend", "Filters") }),
359
508
  filters.map((filter) => /* @__PURE__ */ jsx(
360
509
  FilterControl,
361
510
  {
@@ -365,11 +514,7 @@ function TableFilters({
365
514
  },
366
515
  filter.name
367
516
  )),
368
- activeCount > 0 && onClearAll && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: onClearAll, children: [
369
- "Clear filters (",
370
- activeCount,
371
- ")"
372
- ] })
517
+ activeCount > 0 && onClearAll && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClearAll, children: t("table.filters.clear", `Clear filters (${activeCount})`, { count: activeCount }) })
373
518
  ] });
374
519
  }
375
520
  function FilterControl({
@@ -393,7 +538,8 @@ function FilterControl({
393
538
  }
394
539
  function controlClasses() {
395
540
  return cn(
396
- "h-9 rounded-sm border border-[var(--input)]",
541
+ // 44px touch target on mobile (WCAG 2.5.5); dense 36px on >=md.
542
+ "h-11 rounded-sm border border-[var(--input)] md:h-9",
397
543
  "bg-background px-3 text-sm",
398
544
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
399
545
  );
@@ -403,6 +549,7 @@ function SelectFilter({
403
549
  value,
404
550
  onChange
405
551
  }) {
552
+ const t = useArqelTranslations();
406
553
  return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
407
554
  filter.label ?? filter.name,
408
555
  /* @__PURE__ */ jsxs(
@@ -412,7 +559,7 @@ function SelectFilter({
412
559
  value: value === void 0 || value === null ? "" : String(value),
413
560
  onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
414
561
  children: [
415
- /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
562
+ /* @__PURE__ */ jsx("option", { value: "", children: t("table.filters.all", "All") }),
416
563
  filter.props.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
417
564
  ]
418
565
  }
@@ -431,7 +578,7 @@ function MultiSelectFilter({
431
578
  "select",
432
579
  {
433
580
  multiple: true,
434
- className: cn(controlClasses(), "h-auto min-h-[2.25rem] py-1"),
581
+ className: cn(controlClasses(), "h-auto min-h-11 py-1 md:min-h-[2.25rem]"),
435
582
  value: arr.map(String),
436
583
  onChange: (e) => {
437
584
  const next = Array.from(e.target.selectedOptions, (o) => o.value);
@@ -465,6 +612,7 @@ function TernaryFilter({
465
612
  value,
466
613
  onChange
467
614
  }) {
615
+ const t = useArqelTranslations();
468
616
  const current = value === void 0 || value === null ? "all" : String(value);
469
617
  return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-muted-foreground", children: [
470
618
  filter.label ?? filter.name,
@@ -478,9 +626,9 @@ function TernaryFilter({
478
626
  onChange(v === "all" ? null : v);
479
627
  },
480
628
  children: [
481
- /* @__PURE__ */ jsx("option", { value: "all", children: filter.props.allLabel ?? "All" }),
482
- /* @__PURE__ */ jsx("option", { value: "true", children: filter.props.trueLabel ?? "Yes" }),
483
- /* @__PURE__ */ jsx("option", { value: "false", children: filter.props.falseLabel ?? "No" })
629
+ /* @__PURE__ */ jsx("option", { value: "all", children: filter.props.allLabel ?? t("table.filters.all", "All") }),
630
+ /* @__PURE__ */ jsx("option", { value: "true", children: filter.props.trueLabel ?? t("table.filters.yes", "Yes") }),
631
+ /* @__PURE__ */ jsx("option", { value: "false", children: filter.props.falseLabel ?? t("table.filters.no", "No") })
484
632
  ]
485
633
  }
486
634
  )
@@ -493,8 +641,14 @@ function TablePagination({
493
641
  perPageOptions = [10, 25, 50, 100],
494
642
  className
495
643
  }) {
496
- const from = (meta.currentPage - 1) * meta.perPage + 1;
497
- const to = Math.min(meta.currentPage * meta.perPage, meta.total);
644
+ const t = useArqelTranslations();
645
+ const locale = useArqelLocale();
646
+ const numberFormatter = useMemo(() => new Intl.NumberFormat(locale), [locale]);
647
+ const fromRaw = (meta.currentPage - 1) * meta.perPage + 1;
648
+ const toRaw = Math.min(meta.currentPage * meta.perPage, meta.total);
649
+ const from = numberFormatter.format(fromRaw);
650
+ const to = numberFormatter.format(toRaw);
651
+ const total = numberFormatter.format(meta.total);
498
652
  const isFirst = meta.currentPage <= 1;
499
653
  const isLast = meta.currentPage >= meta.lastPage;
500
654
  return /* @__PURE__ */ jsxs(
@@ -504,16 +658,23 @@ function TablePagination({
504
658
  "flex flex-wrap items-center justify-between gap-2 border-t border-border px-3 py-2 text-sm",
505
659
  className
506
660
  ),
507
- "aria-label": "Pagination",
661
+ "aria-label": t("table.pagination.label", "Pagination"),
508
662
  children: [
509
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: meta.total === 0 ? "No results" : `${from}\u2013${to} of ${meta.total}` }),
663
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: meta.total === 0 ? t("table.empty", "No records found.") : t(
664
+ "table.pagination.showing",
665
+ // Fallback is rendered verbatim when the key is missing, so it must
666
+ // already carry the interpolated values (the replacements map only
667
+ // applies to a resolved dictionary string).
668
+ `Showing ${from} to ${to} of ${total} results`,
669
+ { from, to, total }
670
+ ) }),
510
671
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
511
672
  onPerPageChange && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
512
- "Per page",
673
+ t("table.per_page", "Per page"),
513
674
  /* @__PURE__ */ jsx(
514
675
  "select",
515
676
  {
516
- className: "h-8 rounded-sm border border-[var(--input)] bg-background px-2",
677
+ className: "h-11 rounded-sm border border-[var(--input)] bg-background px-2 md:h-8",
517
678
  value: meta.perPage,
518
679
  onChange: (e) => onPerPageChange(Number(e.target.value)),
519
680
  children: perPageOptions.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
@@ -525,26 +686,28 @@ function TablePagination({
525
686
  {
526
687
  variant: "outline",
527
688
  size: "sm",
689
+ className: "h-11 md:h-8",
528
690
  disabled: isFirst,
529
691
  onClick: () => onPageChange(meta.currentPage - 1),
530
- "aria-label": "Previous page",
531
- children: "Prev"
692
+ "aria-label": t("table.pagination.previous_page", "Previous page"),
693
+ children: t("table.pagination.previous", "Prev")
532
694
  }
533
695
  ),
534
696
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
535
- meta.currentPage,
697
+ numberFormatter.format(meta.currentPage),
536
698
  " / ",
537
- meta.lastPage
699
+ numberFormatter.format(meta.lastPage)
538
700
  ] }),
539
701
  /* @__PURE__ */ jsx(
540
702
  Button,
541
703
  {
542
704
  variant: "outline",
543
705
  size: "sm",
706
+ className: "h-11 md:h-8",
544
707
  disabled: isLast,
545
708
  onClick: () => onPageChange(meta.currentPage + 1),
546
- "aria-label": "Next page",
547
- children: "Next"
709
+ "aria-label": t("table.pagination.next_page", "Next page"),
710
+ children: t("table.pagination.next", "Next")
548
711
  }
549
712
  )
550
713
  ] })
@@ -560,6 +723,7 @@ function TableToolbar({
560
723
  onClearSelection,
561
724
  className
562
725
  }) {
726
+ const t = useArqelTranslations();
563
727
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3 px-3 py-3", className), children: [
564
728
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [
565
729
  search,
@@ -569,12 +733,11 @@ function TableToolbar({
569
733
  "section",
570
734
  {
571
735
  className: "flex flex-wrap items-center gap-2 rounded-sm border border-border bg-muted px-3 py-2",
572
- "aria-label": "Bulk actions",
736
+ "aria-label": t("table.bulk.label", "Bulk actions"),
573
737
  children: [
574
- /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
575
- selectedCount,
576
- " selected"
577
- ] }),
738
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("table.bulk.selected", "{one} :count selected|{other} :count selected", {
739
+ count: selectedCount
740
+ }) }),
578
741
  /* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: [
579
742
  bulkActions,
580
743
  onClearSelection && /* @__PURE__ */ jsx(
@@ -583,7 +746,7 @@ function TableToolbar({
583
746
  type: "button",
584
747
  className: "text-sm text-muted-foreground hover:underline",
585
748
  onClick: onClearSelection,
586
- children: "Clear"
749
+ children: t("table.bulk.clear", "Clear")
587
750
  }
588
751
  )
589
752
  ] })
@@ -594,5 +757,5 @@ function TableToolbar({
594
757
  }
595
758
 
596
759
  export { DataTable, TableCell, TableFilters, TablePagination, TableToolbar };
597
- //# sourceMappingURL=chunk-PZEVKMZX.js.map
598
- //# sourceMappingURL=chunk-PZEVKMZX.js.map
760
+ //# sourceMappingURL=chunk-S5UNPCMQ.js.map
761
+ //# sourceMappingURL=chunk-S5UNPCMQ.js.map