@lerianstudio/sindarian-ui 2.0.0-beta.13 → 2.0.0-beta.15

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 (57) hide show
  1. package/dist/components/form/select-field/index.d.ts +15 -1
  2. package/dist/components/form/select-field/index.d.ts.map +1 -1
  3. package/dist/components/form/select-field/index.js +28 -3
  4. package/dist/components/page/index.d.ts +35 -1
  5. package/dist/components/page/index.d.ts.map +1 -1
  6. package/dist/components/page/index.js +6 -2
  7. package/dist/components/table/id-table-cell.d.ts +9 -1
  8. package/dist/components/table/id-table-cell.d.ts.map +1 -1
  9. package/dist/components/table/id-table-cell.js +57 -9
  10. package/dist/components/table/index.d.ts +1 -0
  11. package/dist/components/table/index.d.ts.map +1 -1
  12. package/dist/components/table/index.js +1 -0
  13. package/dist/components/table/locked-table-actions.d.ts +24 -0
  14. package/dist/components/table/locked-table-actions.d.ts.map +1 -1
  15. package/dist/components/table/locked-table-actions.js +31 -1
  16. package/dist/components/table/name-table-cell.d.ts +57 -4
  17. package/dist/components/table/name-table-cell.d.ts.map +1 -1
  18. package/dist/components/table/name-table-cell.js +50 -2
  19. package/dist/components/ui/copy-field/index.d.ts.map +1 -1
  20. package/dist/components/ui/copy-field/index.js +4 -1
  21. package/dist/components/ui/select/index.d.ts.map +1 -1
  22. package/dist/components/ui/select/index.js +8 -4
  23. package/dist/enterprise/data-table/index.d.ts +77 -7
  24. package/dist/enterprise/data-table/index.d.ts.map +1 -1
  25. package/dist/enterprise/data-table/index.js +56 -11
  26. package/dist/esm/components/form/select-field/index.d.ts +15 -1
  27. package/dist/esm/components/form/select-field/index.d.ts.map +1 -1
  28. package/dist/esm/components/form/select-field/index.js +29 -4
  29. package/dist/esm/components/page/index.d.ts +35 -1
  30. package/dist/esm/components/page/index.d.ts.map +1 -1
  31. package/dist/esm/components/page/index.js +6 -2
  32. package/dist/esm/components/table/id-table-cell.d.ts +9 -1
  33. package/dist/esm/components/table/id-table-cell.d.ts.map +1 -1
  34. package/dist/esm/components/table/id-table-cell.js +58 -10
  35. package/dist/esm/components/table/index.d.ts +1 -0
  36. package/dist/esm/components/table/index.d.ts.map +1 -1
  37. package/dist/esm/components/table/index.js +1 -0
  38. package/dist/esm/components/table/locked-table-actions.d.ts +24 -0
  39. package/dist/esm/components/table/locked-table-actions.d.ts.map +1 -1
  40. package/dist/esm/components/table/locked-table-actions.js +31 -1
  41. package/dist/esm/components/table/name-table-cell.d.ts +57 -4
  42. package/dist/esm/components/table/name-table-cell.d.ts.map +1 -1
  43. package/dist/esm/components/table/name-table-cell.js +50 -2
  44. package/dist/esm/components/ui/copy-field/index.d.ts.map +1 -1
  45. package/dist/esm/components/ui/copy-field/index.js +4 -1
  46. package/dist/esm/components/ui/select/index.d.ts.map +1 -1
  47. package/dist/esm/components/ui/select/index.js +8 -4
  48. package/dist/esm/enterprise/data-table/index.d.ts +77 -7
  49. package/dist/esm/enterprise/data-table/index.d.ts.map +1 -1
  50. package/dist/esm/enterprise/data-table/index.js +57 -12
  51. package/dist/esm/index.d.ts +2 -0
  52. package/dist/esm/index.d.ts.map +1 -1
  53. package/dist/esm/index.js +2 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -0
  57. package/package.json +1 -1
@@ -1,13 +1,53 @@
1
- import { type ColumnDef, type OnChangeFn, type RowData, type RowSelectionState, type SortingState } from '@tanstack/react-table';
1
+ import { type ColumnDef, type OnChangeFn, type RowData, type RowSelectionState, type SortingState, type VisibilityState } from '@tanstack/react-table';
2
2
  import { type EmptyStateProps } from '../empty-state';
3
3
  /**
4
- * Per-column opt-in for ledger numeric alignment. Set `meta: { numeric: true }`
5
- * on a money/number `ColumnDef` and DataTable right-aligns both the header and
6
- * its body cells (mono tabular figures).
4
+ * Per-column channel into the cells DataTable renders on the consumer's behalf:
5
+ * how a column aligns, what classes it carries on head and body, and whether it
6
+ * renders its own `<td>` at all. The member names are frozen by plan
7
+ * 2026-09-21-console-simplification C9.
7
8
  */
8
9
  declare module '@tanstack/react-table' {
9
10
  interface ColumnMeta<TData extends RowData, TValue> {
11
+ /**
12
+ * Ledger numeric treatment: right-aligns the header and its body cells and
13
+ * gives the body mono tabular figures. `align` overrides the alignment half
14
+ * on its own — the figures stay mono.
15
+ */
10
16
  numeric?: boolean;
17
+ /**
18
+ * Column alignment, header and body. Beats `numeric`'s implicit right, so a
19
+ * money column can read left or centre without losing its figure treatment.
20
+ * With `renderOwnCell` it reaches the header only — the body cell is the
21
+ * consumer's, and so is aligning it.
22
+ */
23
+ align?: 'left' | 'right' | 'center';
24
+ /**
25
+ * Extra classes on this column's `<th>`, merged AFTER the table-level
26
+ * `headClassName` so one column can override what the table set for all.
27
+ */
28
+ headerClassName?: string;
29
+ /**
30
+ * Extra classes on this column's body `<td>`, merged after the numeric and
31
+ * density treatments. IGNORED when `renderOwnCell` is set: there is no cell
32
+ * of the table's left to carry them. Use `headerClassName` for the `<th>`,
33
+ * which the table still owns either way.
34
+ */
35
+ className?: string;
36
+ /**
37
+ * The column's `cell` renderer emits its own `<td>`; DataTable renders it
38
+ * bare rather than wrapping it. The HEADER is still the table's `<th>` —
39
+ * nothing about owning a body cell makes a column own its header, and a
40
+ * table whose head and body disagree on cell count is broken markup.
41
+ *
42
+ * So the split is by element, not by column: the head still takes `align`,
43
+ * `numeric`, `headerClassName` and the declared width, because the `<th>`
44
+ * is the table's to style; the BODY takes none of them, because the `<td>`
45
+ * is the consumer's. A money column declaring `align: 'right'` therefore
46
+ * gets a right-aligned header and must right-align its own cell to match —
47
+ * leave the cell's alignment out and the figures sit under a heading that
48
+ * points somewhere else.
49
+ */
50
+ renderOwnCell?: boolean;
11
51
  }
12
52
  }
13
53
  /**
@@ -71,6 +111,35 @@ type SortingProps = {
71
111
  /** Controlled sort updater (TanStack `OnChangeFn`). */
72
112
  onSortingChange: OnChangeFn<SortingState>;
73
113
  };
114
+ /**
115
+ * Column visibility is controlled but, deliberately, NOT a required pair — a
116
+ * departure from the two unions above, and not drift. Those two are unions
117
+ * because each has an `enable*` flag whose "on" state is meaningless without
118
+ * the controlled pair. TanStack has no enable flag for visibility: the model is
119
+ * always running, and the only shipped consumer passes STATE WITH NO UPDATER,
120
+ * because a separate dropdown owns the state on the page. Requiring the pair
121
+ * would refuse that usage.
122
+ *
123
+ * What the union does forbid is the updater ALONE: TanStack reads an
124
+ * `on[State]Change` callback as a declaration of controlled state, so a
125
+ * callback with no `columnVisibility` freezes visibility at its initial value
126
+ * while the callback keeps firing. Prop names frozen by plan
127
+ * 2026-09-21-console-simplification C9.
128
+ *
129
+ * Omit both and the table keeps its own visibility state, exactly as before.
130
+ */
131
+ type ColumnVisibilityProps = {
132
+ /** Controlled column-visibility state (TanStack `VisibilityState`). */
133
+ columnVisibility: VisibilityState;
134
+ /**
135
+ * Controlled updater (TanStack `OnChangeFn`). Optional: a consumer may
136
+ * drive the state from outside the table entirely.
137
+ */
138
+ onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
139
+ } | {
140
+ columnVisibility?: undefined;
141
+ onColumnVisibilityChange?: undefined;
142
+ };
74
143
  type DataTableBaseProps<TData> = {
75
144
  columns: ColumnDef<TData, unknown>[];
76
145
  data: TData[];
@@ -117,7 +186,8 @@ type DataTableBaseProps<TData> = {
117
186
  /**
118
187
  * Extra classes merged onto every header cell, after the kit label voice —
119
188
  * so a caller can override the voice's size/tracking/color for one table
120
- * without restating the cluster. Per-column alignment stays `meta.numeric`.
189
+ * without restating the cluster. Per column, `meta.headerClassName` merges
190
+ * after this one and `meta.align` / `meta.numeric` own the alignment.
121
191
  */
122
192
  headClassName?: string;
123
193
  /**
@@ -127,7 +197,7 @@ type DataTableBaseProps<TData> = {
127
197
  */
128
198
  footer?: React.ReactNode;
129
199
  };
130
- export type DataTableProps<TData> = DataTableBaseProps<TData> & RowSelectionProps<TData> & SortingProps;
131
- export declare function DataTable<TData>({ columns, data, loading, skeletonRows, loadingLabel, empty, getRowId, flush, density, enableRowSelection, rowSelection, onRowSelectionChange, getRowSelectionLabel, enableSorting, sorting, onSortingChange, onRowActivate, rowHref, className, headClassName, footer }: DataTableProps<TData>): import("react").JSX.Element;
200
+ export type DataTableProps<TData> = DataTableBaseProps<TData> & RowSelectionProps<TData> & SortingProps & ColumnVisibilityProps;
201
+ export declare function DataTable<TData>({ columns, data, loading, skeletonRows, loadingLabel, empty, getRowId, flush, density, enableRowSelection, rowSelection, onRowSelectionChange, getRowSelectionLabel, enableSorting, sorting, onSortingChange, columnVisibility, onColumnVisibilityChange, onRowActivate, rowHref, className, headClassName, footer }: DataTableProps<TData>): import("react").JSX.Element;
132
202
  export {};
133
203
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/enterprise/data-table/index.tsx"],"names":[],"mappings":"AAuDA,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,UAAU,EAEf,KAAK,OAAO,EACZ,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAA;AAc9B,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAGjE;;;;GAIG;AACH,OAAO,QAAQ,uBAAuB,CAAC;IAIrC,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM;QAChD,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB;CACF;AAED;;;;;;;;GAQG;AACH,KAAK,iBAAiB,CAAC,KAAK,IACxB;IACE,kBAAkB,CAAC,EAAE,KAAK,CAAA;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,oBAAoB,CAAC,EAAE,KAAK,CAAA;IAC5B,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAC7B,GACD;IACE,kBAAkB,EAAE,IAAI,CAAA;IACxB,kFAAkF;IAClF,YAAY,EAAE,iBAAiB,CAAA;IAC/B,4DAA4D;IAC5D,oBAAoB,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAA;IACnD;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;CAC9C,CAAA;AAEL;;;;;;;;;;;;;;;;GAgBG;AACH,KAAK,YAAY,GACb;IACE,aAAa,CAAC,EAAE,KAAK,CAAA;IACrB,OAAO,CAAC,EAAE,KAAK,CAAA;IACf,eAAe,CAAC,EAAE,KAAK,CAAA;CACxB,GACD;IACE,aAAa,EAAE,IAAI,CAAA;IACnB;;;;;OAKG;IACH,OAAO,EAAE,YAAY,CAAA;IACrB,uDAAuD;IACvD,eAAe,EAAE,UAAU,CAAC,YAAY,CAAC,CAAA;CAC1C,CAAA;AAEL,KAAK,kBAAkB,CAAC,KAAK,IAAI;IAC/B,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAA;IACpC,IAAI,EAAE,KAAK,EAAE,CAAA;IACb,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IACnC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IACpC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,KAAK,IAAI,kBAAkB,CAAC,KAAK,CAAC,GAC3D,iBAAiB,CAAC,KAAK,CAAC,GACxB,YAAY,CAAA;AA8Fd,wBAAgB,SAAS,CAAC,KAAK,EAAE,EAC/B,OAAO,EACP,IAAI,EACJ,OAAe,EACf,YAAgB,EAChB,YAAmC,EACnC,KAAK,EACL,QAAQ,EACR,KAAa,EACb,OAAuB,EACvB,kBAA0B,EAC1B,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,aAAqB,EACrB,OAAO,EACP,eAAe,EACf,aAAa,EACb,OAAO,EACP,SAAS,EACT,aAAa,EACb,MAAM,EACP,EAAE,cAAc,CAAC,KAAK,CAAC,+BAyTvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/enterprise/data-table/index.tsx"],"names":[],"mappings":"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"}
@@ -65,8 +65,21 @@ const empty_state_1 = require("../empty-state");
65
65
  const column_size_1 = require("./column-size");
66
66
  const SELECTION_COLUMN_ID = '__select__';
67
67
  const CHECKBOX_CLASS = 'accent-primary block size-4';
68
- /** Right-aligned mono treatment for `meta: { numeric: true }` columns. */
69
- const NUMERIC_CELL_CLASS = 'text-right font-mono tabular-nums';
68
+ /** Figure treatment for `meta: { numeric: true }` columns; the alignment half
69
+ * lives in ALIGN_CELL_CLASS so `meta.align` can override one without the other. */
70
+ const NUMERIC_FIGURE_CLASS = 'font-mono tabular-nums';
71
+ /**
72
+ * Body-cell alignment, one class per direction rather than a single string with
73
+ * `text-right` baked in: an explicit `meta.align` has to beat `numeric` by
74
+ * choosing the class, not by out-ordering it — tailwind-merge resolving two
75
+ * conflicting alignment utilities by source order is not a contract worth
76
+ * relying on.
77
+ */
78
+ const ALIGN_CELL_CLASS = {
79
+ left: 'text-left',
80
+ center: 'text-center',
81
+ right: 'text-right'
82
+ };
70
83
  /**
71
84
  * Keydown events whose target sits inside one of these are never hijacked
72
85
  * by the row-level keyboard handler — Space in a checkbox and Enter on an
@@ -116,7 +129,7 @@ function createSelectionColumn(getLabel) {
116
129
  cell: ({ row }) => (0, jsx_runtime_1.jsx)(RowSelectionCheckbox, { row: row, getLabel: getLabel })
117
130
  };
118
131
  }
119
- function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLabel = 'Loading table rows', empty, getRowId, flush = false, density = 'comfortable', enableRowSelection = false, rowSelection, onRowSelectionChange, getRowSelectionLabel, enableSorting = false, sorting, onSortingChange, onRowActivate, rowHref, className, headClassName, footer }) {
132
+ function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLabel = 'Loading table rows', empty, getRowId, flush = false, density = 'comfortable', enableRowSelection = false, rowSelection, onRowSelectionChange, getRowSelectionLabel, enableSorting = false, sorting, onSortingChange, columnVisibility, onColumnVisibilityChange, onRowActivate, rowHref, className, headClassName, footer }) {
120
133
  const tableColumns = (0, react_1.useMemo)(() => enableRowSelection
121
134
  ? [createSelectionColumn(getRowSelectionLabel), ...columns]
122
135
  : columns, [columns, enableRowSelection, getRowSelectionLabel]);
@@ -142,12 +155,33 @@ function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLa
142
155
  manualSorting: true,
143
156
  enableSorting,
144
157
  onSortingChange: enableSorting ? onSortingChange : undefined,
158
+ // ⛔ EACH VISIBILITY ENTRY IS GUARDED ON ITS OWN PROP, NEVER ON THE OTHER.
159
+ //
160
+ // TanStack merges options as `{...defaultOptions, ...options}` and state as
161
+ // `{...internalState, ...options.state}`, both plain spreads — so a key
162
+ // present and holding `undefined` OVERWRITES what it was meant to leave
163
+ // alone. `onColumnVisibilityChange: undefined` replaces the feature's own
164
+ // `makeStateUpdater`, and `setColumnVisibility` reads
165
+ // `options.onColumnVisibilityChange == null ? void 0 : ...`, so it (and
166
+ // every `column.toggleVisibility` behind it) becomes a silent no-op.
167
+ // `columnVisibility: undefined` replaces the table's initial `{}`, so a
168
+ // consumer reading the slice gets `undefined` instead of an empty object.
169
+ //
170
+ // Guarding the UPDATER on the STATE put that `undefined` back for the
171
+ // union's first arm — state with no updater, which is the shipped console
172
+ // usage, where a separate dropdown owns the state. A table that passes
173
+ // neither prop is uncontrolled, exactly as before.
174
+ ...(onColumnVisibilityChange !== undefined && { onColumnVisibilityChange }),
145
175
  state: {
146
176
  rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined,
147
- sorting: enableSorting ? (sorting ?? []) : undefined
177
+ sorting: enableSorting ? (sorting ?? []) : undefined,
178
+ ...(columnVisibility !== undefined && { columnVisibility })
148
179
  }
149
180
  });
150
- const colCount = tableColumns.length;
181
+ // Visible, not declared: the skeleton grid and the empty row's colSpan have
182
+ // to match the header the table actually rendered, and a hidden column is in
183
+ // `tableColumns` but in no `<th>`.
184
+ const colCount = table.getVisibleLeafColumns().length;
151
185
  const dataRows = table.getRowModel().rows;
152
186
  const rowCount = dataRows.length;
153
187
  // Compact density overrides only THIS table's cell padding (tailwind-merge
@@ -265,13 +299,13 @@ function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLa
265
299
  : rowCount === 0
266
300
  ? 'No rows'
267
301
  : `${rowCount} rows` }), (0, jsx_runtime_1.jsxs)(table_1.Table, { "aria-busy": loading ? 'true' : undefined, children: [(0, jsx_runtime_1.jsx)(table_1.TableHeader, { children: table.getHeaderGroups().map((headerGroup) => ((0, jsx_runtime_1.jsx)(table_1.TableRow, { className: "hover:bg-transparent", children: headerGroup.headers.map((header) => {
268
- const numeric = header.column.columnDef.meta?.numeric;
269
- return ((0, jsx_runtime_1.jsx)(table_1.TableHead, { align: numeric ? 'right' : undefined, "aria-sort": header.isPlaceholder
302
+ const meta = header.column.columnDef.meta;
303
+ return ((0, jsx_runtime_1.jsx)(table_1.TableHead, { align: meta?.align ?? (meta?.numeric ? 'right' : undefined), "aria-sort": header.isPlaceholder
270
304
  ? undefined
271
305
  : ariaSortFor(header.column),
272
306
  // Only a column that DECLARED sizing gets an inline width;
273
307
  // everything else keeps the auto table layout it has today.
274
- style: (0, column_size_1.readPreferredColumnSize)(header.column), className: (0, utils_1.cn)(typography_1.LABEL_VOICE_CLASS, headDensityClass, header.column.id === SELECTION_COLUMN_ID && 'w-10', headClassName), children: header.isPlaceholder
308
+ style: (0, column_size_1.readPreferredColumnSize)(header.column), className: (0, utils_1.cn)(typography_1.LABEL_VOICE_CLASS, headDensityClass, header.column.id === SELECTION_COLUMN_ID && 'w-10', headClassName, meta?.headerClassName), children: header.isPlaceholder
275
309
  ? null
276
310
  : (0, react_table_1.flexRender)(header.column.columnDef.header, header.getContext()) }, header.id));
277
311
  }) }, headerGroup.id))) }), (0, jsx_runtime_1.jsx)(table_1.TableBody, { children: loading ? (Array.from({ length: skeletonRows }).map((_, rowIndex) => ((0, jsx_runtime_1.jsx)(table_1.TableRow, { className: "hover:bg-transparent", children: Array.from({ length: colCount }).map((__, cellIndex) => ((0, jsx_runtime_1.jsx)(table_1.TableCell, { className: cellDensityClass, children: (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, `skeleton-${rowIndex}-${cellIndex}`))) }, `skeleton-${rowIndex}`)))) : rowCount === 0 ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { className: "hover:bg-transparent", children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: colCount, className: "p-0", children: (0, jsx_runtime_1.jsx)(empty_state_1.EmptyState, { title: empty?.title ?? 'Nothing here yet', description: empty?.description, icon: empty?.icon, action: empty?.action,
@@ -294,7 +328,18 @@ function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLa
294
328
  ? (event) => handleRowKeyDown(event, row, rowIndex)
295
329
  : undefined, className: keyboardNav ? ROW_FOCUS_CLASS : undefined, "data-state": enableRowSelection && row.getIsSelected()
296
330
  ? 'selected'
297
- : undefined, children: row.getVisibleCells().map((cell) => ((0, jsx_runtime_1.jsx)(table_1.TableCell, { style: (0, column_size_1.readPreferredColumnSize)(cell.column), className: (0, utils_1.cn)(cell.column.columnDef.meta?.numeric && NUMERIC_CELL_CLASS, cellDensityClass, cell.column.id === SELECTION_COLUMN_ID && 'w-10'), onClick: cell.column.id === SELECTION_COLUMN_ID
298
- ? stopPropagation
299
- : undefined, children: (0, react_table_1.flexRender)(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id)))) }), footer ? (0, jsx_runtime_1.jsx)(table_1.TableFooter, { children: footer }) : null] })] }));
331
+ : undefined, children: row.getVisibleCells().map((cell) => {
332
+ const meta = cell.column.columnDef.meta;
333
+ const content = (0, react_table_1.flexRender)(cell.column.columnDef.cell, cell.getContext());
334
+ // The column owns its `<td>`: render the consumer's cell bare.
335
+ // The kit's selection column is built here and carries no
336
+ // meta, so it can never take this path.
337
+ if (meta?.renderOwnCell) {
338
+ return (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: content }, cell.id);
339
+ }
340
+ const align = meta?.align ?? (meta?.numeric ? 'right' : undefined);
341
+ return ((0, jsx_runtime_1.jsx)(table_1.TableCell, { style: (0, column_size_1.readPreferredColumnSize)(cell.column), className: (0, utils_1.cn)(meta?.numeric && NUMERIC_FIGURE_CLASS, align && ALIGN_CELL_CLASS[align], cellDensityClass, cell.column.id === SELECTION_COLUMN_ID && 'w-10', meta?.className), onClick: cell.column.id === SELECTION_COLUMN_ID
342
+ ? stopPropagation
343
+ : undefined, children: content }, cell.id));
344
+ }) }, row.id)))) }), footer ? (0, jsx_runtime_1.jsx)(table_1.TableFooter, { children: footer }) : null] })] }));
300
345
  }
@@ -15,6 +15,20 @@ type SelectFieldSharedProps<T extends FieldValues = FieldValues> = PropsWithChil
15
15
  required?: boolean;
16
16
  /** Copy for the "nothing to pick" row. Defaults to "No options found." */
17
17
  emptyMessage?: string;
18
+ /**
19
+ * Rendered inside the trigger, before the value — where a filter bar wants
20
+ * its glyph. On the single select it shares one flex box with the value so
21
+ * the trigger keeps exactly two children; on the multi select the trigger
22
+ * already groups them. The node itself is passed through untouched, with
23
+ * no `aria-hidden`: the prop takes an arbitrary node, and hiding it from
24
+ * the accessibility tree on the caller's behalf would silence a node the
25
+ * caller may have made meaningful on purpose. The trigger is named by its
26
+ * label and its value, so the node passed here should be decorative.
27
+ *
28
+ * Spacing is the caller's: the kit adds no margin, because a gap here
29
+ * would double with the one every existing call site already carries.
30
+ */
31
+ leadingIcon?: ReactNode;
18
32
  /**
19
33
  * Placed on the field's own root box, merged with its spacing rather than
20
34
  * replacing it. The root is the element a parent grid positions, so
@@ -47,6 +61,6 @@ type SelectFieldModeProps = {
47
61
  onChange?: (value: string) => void;
48
62
  };
49
63
  export type SelectFieldProps<T extends FieldValues = FieldValues> = SelectFieldSharedProps<T> & SelectFieldModeProps;
50
- export declare const SelectField: <T extends FieldValues = FieldValues>({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage, className, onChange, ...others }: SelectFieldProps<T>) => React.JSX.Element;
64
+ export declare const SelectField: <T extends FieldValues = FieldValues>({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage, leadingIcon, className, onChange, ...others }: SelectFieldProps<T>) => React.JSX.Element;
51
65
  export {};
52
66
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/select-field/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3D,OAAO,EACL,OAAO,EAEP,WAAW,EAEZ,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC7D,iBAAiB,GAAG;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;qCACiC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAEH;;;;;;;;GAQG;AACH,KAAK,oBAAoB,GACrB;IACE,KAAK,EAAE,IAAI,CAAA;IACX;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACrC,GACD;IACE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,CAAA;AAEL,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC9D,sBAAsB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAA;AAWlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,6KAkB9D,gBAAgB,CAAC,CAAC,CAAC,sBA6FrB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/select-field/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3D,OAAO,EACL,OAAO,EAEP,WAAW,EAEZ,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC7D,iBAAiB,GAAG;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;qCACiC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAEH;;;;;;;;GAQG;AACH,KAAK,oBAAoB,GACrB;IACE,KAAK,EAAE,IAAI,CAAA;IACX;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACrC,GACD;IACE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,CAAA;AAEL,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC9D,sBAAsB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAA;AAWlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,0LAmB9D,gBAAgB,CAAC,CAAC,CAAC,sBAgIrB,CAAA"}
@@ -1,22 +1,47 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, FormTooltip } from '../../../components/ui/form.js';
3
3
  import { MultipleSelect, MultipleSelectContent, MultipleSelectTrigger, MultipleSelectValue } from '../../../components/ui/multiple-select/index.js';
4
4
  import { Select, SelectContent, SelectEmpty, SelectTrigger, SelectValue } from '../../../components/ui/select/index.js';
5
5
  import { cn } from '../../../lib/utils.js';
6
- export const SelectField = ({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage = 'No options found.', className, onChange, ...others }) => {
6
+ export const SelectField = ({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage = 'No options found.', leadingIcon, className, onChange, ...others }) => {
7
7
  // The props union already pairs `multi` with the callback shape that branch
8
8
  // emits, but destructuring an intersection loses that correlation — TS sees
9
9
  // two unrelated callbacks. Both branches report through this one seam, each
10
10
  // with the payload its own union member declared.
11
11
  const emitChange = onChange;
12
+ /**
13
+ * ⛔ THE GLYPH AND THE VALUE ARE ONE FLEX CHILD, NEVER TWO.
14
+ *
15
+ * `.select-trigger` is `flex w-full items-center justify-between`, so a
16
+ * third child makes it split the free space into TWO gaps: on any trigger
17
+ * wider than its content — which is every filter bar — the selected value
18
+ * drifted to the middle of the box instead of sitting beside the glyph.
19
+ * Wrapped, the trigger is back to two children, content and chevron,
20
+ * whatever the caller passes.
21
+ *
22
+ * `min-w-0` keeps the value truncating: the wrapper is the flex item now,
23
+ * and a flex item never shrinks below its own min-content width. The
24
+ * trigger's `VALUE_OVERFLOW_CLASS` reaches through it by descendant
25
+ * selector.
26
+ *
27
+ * No `gap`: the prop's contract is that spacing is the caller's (the Tracer
28
+ * filter bars carry their own `mr-2`), and a kit gap would double with it.
29
+ *
30
+ * The MULTI trigger needs none of this — it already renders its children
31
+ * inside one `flex grow flex-wrap` box, so the icon already shares a parent
32
+ * with the value and the trigger root already has exactly two children.
33
+ * Wrapping there would put the selected-value badges in a non-wrapping row
34
+ * and stop the trigger growing to a second line.
35
+ */
36
+ const withLeadingIcon = (value) => leadingIcon ? (_jsxs("span", { className: "flex min-w-0 items-center", children: [leadingIcon, value] })) : (value);
12
37
  const renderItem = (field) => {
13
38
  return (_jsxs(FormItem, { required: required, className: className, children: [label && (_jsx(FormLabel, { extra: tooltip ? _jsx(FormTooltip, { children: tooltip }) : labelExtra, children: label })), multi ? (_jsxs(MultipleSelect, { onValueChange: (value) => {
14
39
  field.onChange(value);
15
40
  emitChange?.(value);
16
- }, disabled: disabled, ...field, children: [_jsx(MultipleSelectTrigger, { readOnly: readOnly, children: _jsx(MultipleSelectValue, { placeholder: placeholder }) }), _jsx(MultipleSelectContent, { children: children })] })) : (_jsxs(Select, { onValueChange: (value) => {
41
+ }, disabled: disabled, ...field, children: [_jsxs(MultipleSelectTrigger, { readOnly: readOnly, children: [leadingIcon, _jsx(MultipleSelectValue, { placeholder: placeholder })] }), _jsx(MultipleSelectContent, { children: children })] })) : (_jsxs(Select, { onValueChange: (value) => {
17
42
  field.onChange(value);
18
43
  emitChange?.(value);
19
- }, value: field.value, disabled: disabled, open: readOnly ? false : undefined, onOpenChange: readOnly ? () => { } : undefined, children: [_jsx(FormControl, { children: _jsx(SelectTrigger, { className: cn(disabled && 'bg-shadcn-100'), readOnly: readOnly, "data-testid": others['data-testid'], children: _jsx(SelectValue, { placeholder: placeholder }) }) }), _jsxs(SelectContent, { children: [_jsx(SelectEmpty, { children: emptyMessage }), children] })] })), _jsx(FormMessage, {}), description && _jsx(FormDescription, { children: description })] }));
44
+ }, value: field.value, disabled: disabled, open: readOnly ? false : undefined, onOpenChange: readOnly ? () => { } : undefined, children: [_jsx(FormControl, { children: _jsx(SelectTrigger, { className: cn(disabled && 'bg-shadcn-100'), readOnly: readOnly, "data-testid": others['data-testid'], children: withLeadingIcon(_jsx(SelectValue, { placeholder: placeholder })) }) }), _jsxs(SelectContent, { children: [_jsx(SelectEmpty, { children: emptyMessage }), children] })] })), _jsx(FormMessage, {}), description && _jsx(FormDescription, { children: description })] }));
20
45
  };
21
46
  if (!control) {
22
47
  // `value` undefined leaves Radix to keep its own state, so an uncontrolled
@@ -1,4 +1,38 @@
1
1
  export declare function PageRoot({ className, ...props }: React.ComponentProps<'div'>): import("react").JSX.Element;
2
2
  export declare function PageView({ className, ...props }: React.ComponentProps<'div'>): import("react").JSX.Element;
3
- export declare function PageContent({ className, ...props }: React.ComponentProps<'div'>): import("react").JSX.Element;
3
+ export type PageContentProps = React.ComponentProps<'div'> & {
4
+ /**
5
+ * How much room the container spends on padding.
6
+ *
7
+ * `'default'` is 64px a side at every width, and it is what every consumer
8
+ * that does not ask for anything gets, class for class.
9
+ *
10
+ * `'compact'` adds 16px a side below `sm` (640px) and changes nothing above
11
+ * it. On a 390px phone the default spends 128px — a third of the screen —
12
+ * before the page has drawn anything: measured at 390 on ten Product Console
13
+ * screens across Midaz, Pix, Payments, Reporter, Tracer, Flowker and
14
+ * Settings, each was left with 262px of content, and the pages adding their
15
+ * own `px-24` inside it were down to 70px, which is where a transaction
16
+ * receipt's amount ran off its card.
17
+ *
18
+ * WHY `max-sm:p-4` AND NOT THE MOBILE-FIRST `p-4 sm:p-16`. A base `p-4`
19
+ * REPLACES `p-16` for every caller, and `sm:p-16` then re-imposes 64px at
20
+ * desktop on the pages that deliberately asked for something else — a
21
+ * full-bleed page would grow a 64px frame at 1440. `max-sm:` only ADDS a rule
22
+ * below `sm`, so every call site renders identically from 640px up, whatever
23
+ * padding it passes.
24
+ *
25
+ * HOW A CALLER OPTS OUT. Pass the phone value too: `className="p-0
26
+ * max-sm:p-0"`. `cn` is tailwind-merge, so the caller's `max-sm:p-0` replaces
27
+ * this rule rather than stacking with it.
28
+ *
29
+ * AND AN AXIS PADDING HAS TO REPEAT ITSELF: `className="px-6 max-sm:px-6"`,
30
+ * never `px-6` alone. tailwind-merge does not treat `max-sm:p-4` and `px-6`
31
+ * as the same group, so it drops neither, and CSS then gives the media-query
32
+ * rule the last word — a lone `px-6` is silently 16px on both axes on a
33
+ * phone.
34
+ */
35
+ padding?: 'default' | 'compact';
36
+ };
37
+ export declare function PageContent({ className, padding, ...props }: PageContentProps): import("react").JSX.Element;
4
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/page/index.tsx"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAW5E;AAED,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ5E;AAED,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ7B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/page/index.tsx"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAW5E;AAED,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ5E;AAED,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAC3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAChC,CAAA;AAED,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAmB,EACnB,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAelB"}
@@ -6,6 +6,10 @@ export function PageRoot({ className, ...props }) {
6
6
  export function PageView({ className, ...props }) {
7
7
  return (_jsx("div", { "data-slot": "page-view", className: cn('flex min-h-0 flex-1', className), ...props }));
8
8
  }
9
- export function PageContent({ className, ...props }) {
10
- return (_jsx("div", { "data-slot": "page-content", className: cn('flex grow flex-col overflow-y-auto p-16', className), ...props }));
9
+ export function PageContent({ className, padding = 'default', ...props }) {
10
+ return (_jsx("div", { "data-slot": "page-content",
11
+ // Order is the contract, not a style: `className` last is what lets a
12
+ // caller's `p-0` beat `p-16` and a caller's `max-sm:p-0` replace the
13
+ // compact rule instead of stacking with it.
14
+ className: cn('flex grow flex-col overflow-y-auto p-16', padding === 'compact' && 'max-sm:p-4', className), ...props }));
11
15
  }
@@ -6,6 +6,14 @@ export type IdTableCellProps = Omit<React.ComponentProps<typeof TableCell>, 'onC
6
6
  head?: number;
7
7
  /** Characters kept from the end of the id. */
8
8
  tail?: number;
9
+ /**
10
+ * Accessible name for the copy button. Override for localization; the kit
11
+ * ships no i18n. The id is deliberately NOT interpolated into it — a column
12
+ * of thirty rows would announce thirty different names for the same action,
13
+ * and the id is already the cell's text and its `title`.
14
+ * @defaultValue 'Copy id'
15
+ */
16
+ copyLabel?: string;
9
17
  };
10
- export declare const IdTableCell: ({ id, onCopy, head, tail, ...others }: IdTableCellProps) => import("react").JSX.Element;
18
+ export declare const IdTableCell: ({ id, onCopy, head, tail, copyLabel, ...others }: IdTableCellProps) => import("react").JSX.Element;
11
19
  //# sourceMappingURL=id-table-cell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"id-table-cell.d.ts","sourceRoot":"","sources":["../../../../src/components/table/id-table-cell.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAGV,MAAM,uBAAuB,CAAA;AAS9B,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,EACtC,QAAQ,CACT,GAAG;IACF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AA6CD,eAAO,MAAM,WAAW,GAAI,uCAMzB,gBAAgB,gCAiClB,CAAA"}
1
+ {"version":3,"file":"id-table-cell.d.ts","sourceRoot":"","sources":["../../../../src/components/table/id-table-cell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,uBAAuB,CAAA;AASnE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,EACtC,QAAQ,CACT,GAAG;IACF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AA6CD,eAAO,MAAM,WAAW,GAAI,kDAOzB,gBAAgB,gCAqFlB,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { TableCell, TableCellAction, TableCellWrapper } from '../../components/ui/table/index.js';
2
+ import { TableCell, TableCellWrapper } from '../../components/ui/table/index.js';
3
3
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/ui/tooltip/index.js';
4
4
  import { Copy } from 'lucide-react';
5
5
  /**
@@ -41,17 +41,65 @@ function truncateMiddle(id, head, tail) {
41
41
  ? id
42
42
  : `${id.slice(0, start)}…${id.slice(id.length - end)}`;
43
43
  }
44
- export const IdTableCell = ({ id, onCopy, head = DEFAULT_HEAD, tail = DEFAULT_TAIL, ...others }) => {
45
- const handleCopyToClipboard = () => {
46
- // The WHOLE id, never what is rendered. The cell is the only place most
47
- // consoles expose it, and a truncated id pasted into a query matches
48
- // nothing.
49
- navigator.clipboard.writeText(id);
50
- onCopy?.(id);
44
+ export const IdTableCell = ({ id, onCopy, head = DEFAULT_HEAD, tail = DEFAULT_TAIL, copyLabel = 'Copy id', ...others }) => {
45
+ /**
46
+ * ⛔ THE COPY ACTION IS A NAMED BUTTON, NEVER A HANDLER ON THE `<td>`.
47
+ *
48
+ * On the cell it had no role, no tab stop and no accessible name: an
49
+ * operator who does not use a pointer could not copy an id from any table
50
+ * built on this kit, and a screen reader announced the cell as plain text.
51
+ * It also made the id column the one cell in a row-activating table that
52
+ * silently swallowed the click that opens the record.
53
+ */
54
+ const handleCopyToClipboard = async (event) => {
55
+ // A row that opens a record on click must not also open it on a copy.
56
+ event.stopPropagation();
57
+ // The same guard `CopyField` uses: an insecure context, and a browser
58
+ // without the API, both leave `navigator.clipboard` undefined, and the
59
+ // only thing this cell would otherwise do about it is throw an unhandled
60
+ // rejection out of a click handler. There is no input here to select as a
61
+ // fallback, so a silent no-op is the whole of what it can honestly offer.
62
+ if (!id || typeof navigator.clipboard?.writeText !== 'function') {
63
+ return;
64
+ }
65
+ try {
66
+ // The WHOLE id, never what is rendered. The cell is the only place most
67
+ // consoles expose it, and a truncated id pasted into a query matches
68
+ // nothing.
69
+ await navigator.clipboard.writeText(id);
70
+ onCopy?.(id);
71
+ }
72
+ catch {
73
+ // The browser refused the write: nothing reached the clipboard, so
74
+ // `onCopy` — which consumers hang their "copied" toast off — must not
75
+ // claim otherwise.
76
+ }
51
77
  };
52
- return (_jsx(TableCell, { onClick: handleCopyToClipboard,
78
+ return (_jsx(TableCell
79
+ // The tooltip is the styled affordance; `title` is the fallback that
80
+ // survives without it — a printed page, a portal that has not mounted,
81
+ // and the cell padding outside the trigger.
82
+ , {
53
83
  // The tooltip is the styled affordance; `title` is the fallback that
54
84
  // survives without it — a printed page, a portal that has not mounted,
55
85
  // and the cell padding outside the trigger.
56
- title: id, ...others, children: _jsxs(TableCellWrapper, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { delayDuration: 300, children: [_jsx(TooltipTrigger, { children: id ? truncateMiddle(id, head, tail) : id }), _jsx(TooltipContent, { children: id })] }) }), _jsx(TableCellAction, { children: _jsx(Copy, { className: "size-3.5" }) })] }) }));
86
+ title: id, ...others, children: _jsxs(TableCellWrapper, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { delayDuration: 300, children: [_jsx(TooltipTrigger, { children: id ? truncateMiddle(id, head, tail) : id }), _jsx(TooltipContent, { children: id })] }) }), id ? (_jsx("button", { type: "button", "aria-label": copyLabel, onClick: handleCopyToClipboard,
87
+ // ⛔ THREE RULES HERE, AND EVERY ONE IS A WAY THE CONTROL
88
+ // DISAPPEARS RATHER THAN A DECORATION.
89
+ //
90
+ // `focus-visible:opacity-100`: a keyboard stop the operator cannot
91
+ // see is the same dead end the `<td>` handler was.
92
+ //
93
+ // `[@media(hover:none)]:opacity-100`: a phone or a tablet has no
94
+ // pointer to hover with, so `group-hover` never fires and the only
95
+ // control that copies an id stayed fully transparent — invisible
96
+ // AND unexplained on the one device where the id is hardest to
97
+ // read off the screen. Written as an arbitrary variant because the
98
+ // kit's Tailwind theme defines no named one.
99
+ //
100
+ // `size-6` with `-my-0.5`: WCAG 2.5.8 wants 24×24 CSS px, and the
101
+ // glyph alone is 14. The negative margin pulls the button's outer
102
+ // box back to the 20px line box of the cell's `text-sm` text, so
103
+ // the target grows without the row growing under it.
104
+ className: "focus-visible:ring-ring -my-0.5 ml-4 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm opacity-0 transition-opacity group-hover/table-cell:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:outline-none [@media(hover:none)]:opacity-100", children: _jsx(Copy, { className: "size-3.5" }) })) : null] }) }));
57
105
  };
@@ -1,3 +1,4 @@
1
1
  export * from './id-table-cell.js';
2
+ export * from './locked-table-actions.js';
2
3
  export * from './name-table-cell.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/table/index.tsx"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/table/index.tsx"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA"}
@@ -1,2 +1,3 @@
1
1
  export * from './id-table-cell.js';
2
+ export * from './locked-table-actions.js';
2
3
  export * from './name-table-cell.js';
@@ -1,7 +1,31 @@
1
1
  import React from 'react';
2
2
  export type LockedTableActionsProps = {
3
+ /** Why this record cannot be changed. It is the control's accessible name as well as the tooltip's text. */
3
4
  message: string;
4
5
  className?: string;
5
6
  };
7
+ /**
8
+ * Where a row's action menu would be, on a record the ledger will not let
9
+ * anyone change — an external account, in double-entry terms.
10
+ *
11
+ * ⛔ THE TRIGGER IS A BUTTON. It used to be a `<div>`, so the ONLY explanation
12
+ * of why the menu is gone lived in a tooltip that a keyboard could not open and
13
+ * a screen reader had nothing to attach to: the row simply had one control
14
+ * fewer, unexplained.
15
+ *
16
+ * `aria-disabled`, not the `disabled` attribute: a truly disabled button takes
17
+ * no focus and fires no pointer events, so it would announce as unavailable and
18
+ * then refuse to open the tooltip that says WHY — the same dead end in a
19
+ * different shape. This one is reachable, announces its reason as its name,
20
+ * opens the tooltip on focus as well as hover, and does nothing when pressed.
21
+ *
22
+ * ⛔ DOING NOTHING TAKES A HANDLER. `aria-disabled` is a promise to the
23
+ * accessibility tree, not to the DOM: the press really does dispatch a click,
24
+ * and a click bubbles. Consumers put this where a row's action menu would be,
25
+ * in rows that open the record — so with no handler of its own the one control
26
+ * that says "you cannot change this" would open the record it refuses to
27
+ * change, from a pointer and, because a button turns Enter and Space into
28
+ * clicks, from a keyboard too.
29
+ */
6
30
  export declare const LockedTableActions: ({ message, className }: LockedTableActionsProps) => React.JSX.Element;
7
31
  //# sourceMappingURL=locked-table-actions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"locked-table-actions.d.ts","sourceRoot":"","sources":["../../../../src/components/table/locked-table-actions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,wBAGhC,uBAAuB,sBAkBzB,CAAA"}
1
+ {"version":3,"file":"locked-table-actions.d.ts","sourceRoot":"","sources":["../../../../src/components/table/locked-table-actions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,MAAM,MAAM,uBAAuB,GAAG;IACpC,4GAA4G;IAC5G,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,GAAI,wBAGhC,uBAAuB,sBA6BzB,CAAA"}
@@ -2,6 +2,36 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LockIcon } from 'lucide-react';
3
3
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/ui/tooltip/index.js';
4
4
  import { cn } from '../../lib/utils.js';
5
+ /**
6
+ * Where a row's action menu would be, on a record the ledger will not let
7
+ * anyone change — an external account, in double-entry terms.
8
+ *
9
+ * ⛔ THE TRIGGER IS A BUTTON. It used to be a `<div>`, so the ONLY explanation
10
+ * of why the menu is gone lived in a tooltip that a keyboard could not open and
11
+ * a screen reader had nothing to attach to: the row simply had one control
12
+ * fewer, unexplained.
13
+ *
14
+ * `aria-disabled`, not the `disabled` attribute: a truly disabled button takes
15
+ * no focus and fires no pointer events, so it would announce as unavailable and
16
+ * then refuse to open the tooltip that says WHY — the same dead end in a
17
+ * different shape. This one is reachable, announces its reason as its name,
18
+ * opens the tooltip on focus as well as hover, and does nothing when pressed.
19
+ *
20
+ * ⛔ DOING NOTHING TAKES A HANDLER. `aria-disabled` is a promise to the
21
+ * accessibility tree, not to the DOM: the press really does dispatch a click,
22
+ * and a click bubbles. Consumers put this where a row's action menu would be,
23
+ * in rows that open the record — so with no handler of its own the one control
24
+ * that says "you cannot change this" would open the record it refuses to
25
+ * change, from a pointer and, because a button turns Enter and Space into
26
+ * clicks, from a keyboard too.
27
+ */
5
28
  export const LockedTableActions = ({ message, className }) => {
6
- return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { className: cn('border-border bg-muted flex size-9 items-center justify-center rounded-md border', className), children: _jsx(LockIcon, { size: 14, className: "text-muted-foreground" }) }) }), _jsx(TooltipContent, { side: "left", children: message })] }) }));
29
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-disabled": "true", "aria-label": message, onClick: (event) => {
30
+ event.preventDefault();
31
+ event.stopPropagation();
32
+ }, className: cn('border-border bg-muted flex size-9 items-center justify-center rounded-md border',
33
+ // Reachable is half the fix: a `border` over a `bg-muted` can
34
+ // swallow the browser's default outline entirely, and the other
35
+ // two cells in this table carry the same ring.
36
+ 'focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', className), children: _jsx(LockIcon, { size: 14, className: "text-muted-foreground" }) }) }), _jsx(TooltipContent, { side: "left", children: message })] }) }));
7
37
  };