@lerianstudio/sindarian-ui 2.0.0-beta.13 → 2.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/form/select-field/index.d.ts +15 -1
- package/dist/components/form/select-field/index.d.ts.map +1 -1
- package/dist/components/form/select-field/index.js +28 -3
- package/dist/components/page/index.d.ts +35 -1
- package/dist/components/page/index.d.ts.map +1 -1
- package/dist/components/page/index.js +6 -2
- package/dist/components/table/id-table-cell.d.ts +9 -1
- package/dist/components/table/id-table-cell.d.ts.map +1 -1
- package/dist/components/table/id-table-cell.js +57 -9
- package/dist/components/table/index.d.ts +1 -0
- package/dist/components/table/index.d.ts.map +1 -1
- package/dist/components/table/index.js +1 -0
- package/dist/components/table/locked-table-actions.d.ts +24 -0
- package/dist/components/table/locked-table-actions.d.ts.map +1 -1
- package/dist/components/table/locked-table-actions.js +31 -1
- package/dist/components/table/name-table-cell.d.ts +57 -4
- package/dist/components/table/name-table-cell.d.ts.map +1 -1
- package/dist/components/table/name-table-cell.js +50 -2
- package/dist/components/ui/select/index.d.ts.map +1 -1
- package/dist/components/ui/select/index.js +8 -4
- package/dist/enterprise/data-table/index.d.ts +77 -7
- package/dist/enterprise/data-table/index.d.ts.map +1 -1
- package/dist/enterprise/data-table/index.js +56 -11
- package/dist/esm/components/form/select-field/index.d.ts +15 -1
- package/dist/esm/components/form/select-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/select-field/index.js +29 -4
- package/dist/esm/components/page/index.d.ts +35 -1
- package/dist/esm/components/page/index.d.ts.map +1 -1
- package/dist/esm/components/page/index.js +6 -2
- package/dist/esm/components/table/id-table-cell.d.ts +9 -1
- package/dist/esm/components/table/id-table-cell.d.ts.map +1 -1
- package/dist/esm/components/table/id-table-cell.js +58 -10
- package/dist/esm/components/table/index.d.ts +1 -0
- package/dist/esm/components/table/index.d.ts.map +1 -1
- package/dist/esm/components/table/index.js +1 -0
- package/dist/esm/components/table/locked-table-actions.d.ts +24 -0
- package/dist/esm/components/table/locked-table-actions.d.ts.map +1 -1
- package/dist/esm/components/table/locked-table-actions.js +31 -1
- package/dist/esm/components/table/name-table-cell.d.ts +57 -4
- package/dist/esm/components/table/name-table-cell.d.ts.map +1 -1
- package/dist/esm/components/table/name-table-cell.js +50 -2
- package/dist/esm/components/ui/select/index.d.ts.map +1 -1
- package/dist/esm/components/ui/select/index.js +8 -4
- package/dist/esm/enterprise/data-table/index.d.ts +77 -7
- package/dist/esm/enterprise/data-table/index.d.ts.map +1 -1
- package/dist/esm/enterprise/data-table/index.js +57 -12
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- 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
|
|
5
|
-
*
|
|
6
|
-
* its
|
|
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
|
|
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,
|
|
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
|
-
/**
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
298
|
-
|
|
299
|
-
|
|
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,
|
|
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 {
|
|
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: [
|
|
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
|
|
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,
|
|
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",
|
|
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,
|
|
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,
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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 })] }) }),
|
|
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 +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,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,
|
|
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("
|
|
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
|
};
|