@lerianstudio/sindarian-ui 2.0.0-beta.12 → 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/copy-field/index.d.ts +25 -1
- package/dist/components/ui/copy-field/index.d.ts.map +1 -1
- package/dist/components/ui/copy-field/index.js +12 -7
- 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/copy-field/index.d.ts +25 -1
- package/dist/esm/components/ui/copy-field/index.d.ts.map +1 -1
- package/dist/esm/components/ui/copy-field/index.js +12 -7
- 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
|
@@ -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
|
};
|
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
import { TableCell } from '../ui/table/index.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/** Opens the record. Lands on a real button, never on the `<td>`. */
|
|
3
|
+
type OpenRecord = React.MouseEventHandler<HTMLButtonElement>;
|
|
4
|
+
export type NameTableCellProps = Omit<React.ComponentProps<typeof TableCell>, 'onClick'> &
|
|
5
|
+
/**
|
|
6
|
+
* Three shapes, and the only one missing is an interactive cell that can
|
|
7
|
+
* carry no accessible name.
|
|
8
|
+
*
|
|
9
|
+
* 1. A text `name` IS the button's accessible name, so the handler is free
|
|
10
|
+
* and `buttonLabel` merely overrides what the button announces.
|
|
11
|
+
* 2. Anything else rendered inside the button — a badge, an icon, an italic
|
|
12
|
+
* placeholder — may carry no text at all, so it has to name its button.
|
|
13
|
+
* 3. No handler, no control: text in a table has nothing to name.
|
|
14
|
+
*/
|
|
15
|
+
({
|
|
16
|
+
name: string;
|
|
17
|
+
onClick?: OpenRecord;
|
|
18
|
+
buttonLabel?: string;
|
|
19
|
+
} | {
|
|
20
|
+
name?: React.ReactNode;
|
|
21
|
+
onClick: OpenRecord;
|
|
22
|
+
buttonLabel: string;
|
|
23
|
+
} | {
|
|
24
|
+
name?: React.ReactNode;
|
|
25
|
+
onClick?: never;
|
|
26
|
+
buttonLabel?: never;
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* The name column, which is how an operator opens a record from a list.
|
|
30
|
+
*
|
|
31
|
+
* ⛔ THE CLICK IS A BUTTON, NEVER THE `<td>`. The cell had no click prop at
|
|
32
|
+
* all, so a consumer wanting a clickable name put `onClick` on the `<td>`
|
|
33
|
+
* through the spread props: a pointer user saw an affordance under a
|
|
34
|
+
* `<p class="cursor-pointer">`, and a keyboard or screen-reader user got a
|
|
35
|
+
* table cell with no control in it — the row's only route to the record was
|
|
36
|
+
* unreachable. `onClick` is now `Omit`ed from the inherited cell props, so the
|
|
37
|
+
* `<td>` handler is unrepresentable and the compiler says so at the call site.
|
|
38
|
+
*
|
|
39
|
+
* And with no handler there is no pointer cursor: text that says it is
|
|
40
|
+
* clickable and is not is worse than plain text.
|
|
41
|
+
*
|
|
42
|
+
* ⛔ AN INTERACTIVE CELL CANNOT BE ANONYMOUS. `name` renders INSIDE the button,
|
|
43
|
+
* so whatever it renders is the button's accessible name — and a `ReactNode` is
|
|
44
|
+
* free to render no text at all, which announces as a bare "button" and leaves
|
|
45
|
+
* the row's only route to its record as unreachable as the `<td>` handler
|
|
46
|
+
* above. The props are a union rather than a comment: a text `name` names the
|
|
47
|
+
* button by itself, anything else has to pass `buttonLabel`, and a cell with no
|
|
48
|
+
* handler is exempt because it holds no control.
|
|
49
|
+
*
|
|
50
|
+
* `buttonLabel` and not an inherited `aria-label`, which the spread puts on the
|
|
51
|
+
* `<td>` — naming the CELL, never the control inside it.
|
|
52
|
+
*
|
|
53
|
+
* ⛔ AND THE UNION STILL LETS THE EMPTY STRING THROUGH, because every string
|
|
54
|
+
* type does. `name=""` satisfies the text arm and `buttonLabel=" "` satisfies
|
|
55
|
+
* the labelled one, so the last word is the component's, below.
|
|
56
|
+
*/
|
|
57
|
+
export declare const NameTableCell: ({ name, onClick, buttonLabel, ...props }: NameTableCellProps) => import("react").JSX.Element;
|
|
58
|
+
export {};
|
|
6
59
|
//# sourceMappingURL=name-table-cell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"name-table-cell.d.ts","sourceRoot":"","sources":["../../../../src/components/table/name-table-cell.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAqC,MAAM,aAAa,CAAA;AAE1E,MAAM,MAAM,kBAAkB,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"name-table-cell.d.ts","sourceRoot":"","sources":["../../../../src/components/table/name-table-cell.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAqC,MAAM,aAAa,CAAA;AAE1E,qEAAqE;AACrE,KAAK,UAAU,GAAG,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;AAE5D,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,EACtC,SAAS,CACV;AACC;;;;;;;;;GASG;AACH,CACI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,OAAO,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CACnE,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,aAAa,GAAI,0CAK3B,kBAAkB,gCAgDpB,CAAA"}
|
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Search } from 'lucide-react';
|
|
3
3
|
import { TableCell, TableCellWrapper, TableCellAction } from '../ui/table/index.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The name column, which is how an operator opens a record from a list.
|
|
6
|
+
*
|
|
7
|
+
* ⛔ THE CLICK IS A BUTTON, NEVER THE `<td>`. The cell had no click prop at
|
|
8
|
+
* all, so a consumer wanting a clickable name put `onClick` on the `<td>`
|
|
9
|
+
* through the spread props: a pointer user saw an affordance under a
|
|
10
|
+
* `<p class="cursor-pointer">`, and a keyboard or screen-reader user got a
|
|
11
|
+
* table cell with no control in it — the row's only route to the record was
|
|
12
|
+
* unreachable. `onClick` is now `Omit`ed from the inherited cell props, so the
|
|
13
|
+
* `<td>` handler is unrepresentable and the compiler says so at the call site.
|
|
14
|
+
*
|
|
15
|
+
* And with no handler there is no pointer cursor: text that says it is
|
|
16
|
+
* clickable and is not is worse than plain text.
|
|
17
|
+
*
|
|
18
|
+
* ⛔ AN INTERACTIVE CELL CANNOT BE ANONYMOUS. `name` renders INSIDE the button,
|
|
19
|
+
* so whatever it renders is the button's accessible name — and a `ReactNode` is
|
|
20
|
+
* free to render no text at all, which announces as a bare "button" and leaves
|
|
21
|
+
* the row's only route to its record as unreachable as the `<td>` handler
|
|
22
|
+
* above. The props are a union rather than a comment: a text `name` names the
|
|
23
|
+
* button by itself, anything else has to pass `buttonLabel`, and a cell with no
|
|
24
|
+
* handler is exempt because it holds no control.
|
|
25
|
+
*
|
|
26
|
+
* `buttonLabel` and not an inherited `aria-label`, which the spread puts on the
|
|
27
|
+
* `<td>` — naming the CELL, never the control inside it.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ AND THE UNION STILL LETS THE EMPTY STRING THROUGH, because every string
|
|
30
|
+
* type does. `name=""` satisfies the text arm and `buttonLabel=" "` satisfies
|
|
31
|
+
* the labelled one, so the last word is the component's, below.
|
|
32
|
+
*/
|
|
33
|
+
export const NameTableCell = ({ name, onClick, buttonLabel, ...props }) => {
|
|
34
|
+
// Whitespace names nothing. An `aria-label` of " " is worse than none: it
|
|
35
|
+
// OVERRIDES the text inside the button, so a perfectly good name is replaced
|
|
36
|
+
// by silence. Drop it and the text content names the control again.
|
|
37
|
+
const label = buttonLabel?.trim() ? buttonLabel : undefined;
|
|
38
|
+
// Only a text `name` can name the button by itself. Any other node is opaque
|
|
39
|
+
// here — which is why the type demands a label for it in the first place.
|
|
40
|
+
const textName = typeof name === 'string' ? name.trim() : '';
|
|
41
|
+
const named = Boolean(label) || textName !== '';
|
|
42
|
+
// ⛔ AN UNNAMED BUTTON LOSES ITS HANDLER, IT DOES NOT SHIP. The two failures
|
|
43
|
+
// are not symmetric: a button announcing "" is invisible to the developer who
|
|
44
|
+
// wrote it and fatal to the operator who cannot use a mouse, while a name
|
|
45
|
+
// that does not open its record shows itself on the very first render. So the
|
|
46
|
+
// cell degrades to the plain text it can still display correctly, and says so
|
|
47
|
+
// where a developer will see it. Never throws: one nameless row must not take
|
|
48
|
+
// the whole table down.
|
|
49
|
+
if (process.env.NODE_ENV !== 'production' && onClick && !named) {
|
|
50
|
+
console.error(`NameTableCell has no accessible name: \`${typeof name === 'string' ? 'name' : 'buttonLabel'}\` is empty, so its click handler was dropped rather than render a button announcing "".`);
|
|
51
|
+
}
|
|
52
|
+
const opensRecord = onClick && named;
|
|
53
|
+
return (_jsx(TableCell, { ...props, children: _jsxs(TableCellWrapper, { children: [opensRecord ? (_jsx("button", { type: "button", onClick: onClick, "aria-label": label, className: "focus-visible:ring-ring cursor-pointer rounded-sm text-left focus-visible:ring-2 focus-visible:outline-none", children: name })) : (_jsx("p", { children: name })), _jsx(TableCellAction, { children: _jsx(Search, { className: "size-3.5" }) })] }) }));
|
|
6
54
|
};
|
|
@@ -39,6 +39,30 @@ export type CopyFieldProps = {
|
|
|
39
39
|
* @defaultValue 'Value to copy'
|
|
40
40
|
*/
|
|
41
41
|
valueLabel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Accessible name for the copy button. Override for localization. Defaults to
|
|
44
|
+
* English, derived from `label` when there is one.
|
|
45
|
+
* @defaultValue `Copy ${label}`, or 'Copy value' without a label
|
|
46
|
+
*/
|
|
47
|
+
copyLabel?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Toast text on the fallback path, when the clipboard API is unavailable or
|
|
50
|
+
* refuses the write and the value is selected for a manual Ctrl/Cmd+C
|
|
51
|
+
* instead. Override for localization. Defaults to English.
|
|
52
|
+
* @defaultValue 'Copy not available — text selected, press Ctrl/Cmd+C'
|
|
53
|
+
*/
|
|
54
|
+
fallbackLabel?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Renders the value in the monospace face. For identifiers, tokens and
|
|
57
|
+
* recovery codes, where character shapes must be told apart.
|
|
58
|
+
* @defaultValue false
|
|
59
|
+
*/
|
|
60
|
+
mono?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* One-line hint rendered under the field and announced with the input via
|
|
63
|
+
* `aria-describedby`. Omit for no element and no attribute.
|
|
64
|
+
*/
|
|
65
|
+
description?: React.ReactNode;
|
|
42
66
|
/**
|
|
43
67
|
* Milliseconds after a *successful* copy before the clipboard is wiped, for
|
|
44
68
|
* sensitive values that shouldn't linger there (TOTP secrets, recovery
|
|
@@ -52,5 +76,5 @@ export type CopyFieldProps = {
|
|
|
52
76
|
*/
|
|
53
77
|
clearClipboardAfter?: number;
|
|
54
78
|
};
|
|
55
|
-
export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
|
|
79
|
+
export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, copyLabel, fallbackLabel, mono, description, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
|
|
56
80
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/copy-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/copy-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAmCD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,KAAK,EACL,MAAc,EACd,WAAW,EACX,WAA0B,EAC1B,SAAwB,EACxB,UAA4B,EAC5B,SAAS,EACT,aAAa,EACb,IAAY,EACZ,WAAW,EACX,mBAAmB,EACpB,EAAE,cAAc,qBAqLhB"}
|
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { Check, Copy, Eye, EyeOff } from 'lucide-react';
|
|
5
5
|
import { IconButton } from '../../../components/ui/icon-button/index.js';
|
|
6
6
|
import { useToast } from '../../../hooks/use-toast.js';
|
|
7
|
+
import { cn } from '../../../lib/utils.js';
|
|
7
8
|
const DEFAULT_COPY_LABEL = 'Copied to clipboard!';
|
|
8
9
|
const FALLBACK_COPY_LABEL = 'Copy not available — text selected, press Ctrl/Cmd+C';
|
|
9
10
|
const COPIED_ICON_TIMEOUT = 1500;
|
|
@@ -31,17 +32,18 @@ const clearClipboardIfUnchanged = async (copiedValue) => {
|
|
|
31
32
|
// Read or write refused; the clipboard stays as-is.
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
export function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', clearClipboardAfter }) {
|
|
35
|
+
export function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', copyLabel, fallbackLabel, mono = false, description, clearClipboardAfter }) {
|
|
35
36
|
const { toast } = useToast();
|
|
36
37
|
const generatedId = React.useId();
|
|
37
38
|
const inputId = `${generatedId}-copy-field`;
|
|
39
|
+
const descriptionId = `${inputId}-description`;
|
|
38
40
|
const inputRef = React.useRef(null);
|
|
39
41
|
const copiedTimerRef = React.useRef(null);
|
|
40
42
|
const clearTimerRef = React.useRef(null);
|
|
41
43
|
const [revealed, setRevealed] = React.useState(false);
|
|
42
44
|
const [copied, setCopied] = React.useState(false);
|
|
43
45
|
const showAsText = !masked || revealed;
|
|
44
|
-
const copyAccessibleName = label ? `Copy ${label}` : 'Copy value';
|
|
46
|
+
const copyAccessibleName = copyLabel ?? (label ? `Copy ${label}` : 'Copy value');
|
|
45
47
|
React.useEffect(() => {
|
|
46
48
|
return () => {
|
|
47
49
|
if (copiedTimerRef.current) {
|
|
@@ -73,7 +75,7 @@ export function CopyField({ value, label, masked = false, onCopyLabel, revealLab
|
|
|
73
75
|
};
|
|
74
76
|
const handleFallback = () => {
|
|
75
77
|
selectFieldText();
|
|
76
|
-
toast({ title: FALLBACK_COPY_LABEL });
|
|
78
|
+
toast({ title: fallbackLabel ?? FALLBACK_COPY_LABEL });
|
|
77
79
|
};
|
|
78
80
|
const handleCopy = async () => {
|
|
79
81
|
if (!isClipboardAvailable()) {
|
|
@@ -110,10 +112,13 @@ export function CopyField({ value, label, masked = false, onCopyLabel, revealLab
|
|
|
110
112
|
// codes. While masked it renders as type="password", which invites
|
|
111
113
|
// browsers and password managers to autofill it or offer to SAVE its
|
|
112
114
|
// value, and spellcheck can ship the text to a remote service.
|
|
113
|
-
autoComplete: "off", spellCheck: false, "aria-label": label ? undefined : valueLabel,
|
|
115
|
+
autoComplete: "off", spellCheck: false, "aria-label": label ? undefined : valueLabel, "aria-describedby": description ? descriptionId : undefined,
|
|
114
116
|
// `min-w-0` lets this flex item shrink below its content width so a
|
|
115
117
|
// long value (e.g. a UUID recovery code) scrolls inside the field
|
|
116
|
-
// instead of overflowing its container.
|
|
117
|
-
//
|
|
118
|
-
|
|
118
|
+
// instead of overflowing its container. Sans by default, inheriting
|
|
119
|
+
// the app font (Inter) so the field matches every other input;
|
|
120
|
+
// `mono` opts identifiers, tokens and recovery codes into the
|
|
121
|
+
// monospace face, where telling 0 from O decides whether the value
|
|
122
|
+
// read back is the value.
|
|
123
|
+
className: cn('text-input-foreground h-full min-w-0 flex-1 cursor-text border-none bg-transparent text-sm outline-none select-text focus:ring-0 focus:ring-offset-0', mono && 'font-mono') }), masked ? (_jsx(IconButton, { type: "button", variant: "outline", size: "small", rounded: true, "aria-label": revealed ? hideLabel : revealLabel, onClick: () => setRevealed((prev) => !prev), children: revealed ? _jsx(EyeOff, {}) : _jsx(Eye, {}) })) : null, _jsx(IconButton, { type: "button", variant: "outline", size: "small", rounded: true, "aria-label": copyAccessibleName, onClick: handleCopy, children: copied ? _jsx(Check, {}) : _jsx(Copy, {}) })] }), description ? (_jsx("p", { "data-slot": "copy-field-description", id: descriptionId, className: "text-muted-foreground text-xs", children: description })) : null] }));
|
|
119
124
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/select/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAKzD,iBAAS,MAAM,CAAC,EACd,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAEpD;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAEpD;AAED,KAAK,kBAAkB,GAAG,KAAK,CAAC,cAAc,CAC5C,OAAO,eAAe,CAAC,OAAO,CAC/B,GAAG;IACF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/select/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAKzD,iBAAS,MAAM,CAAC,EACd,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAEpD;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAEpD;AAED,KAAK,kBAAkB,GAAG,KAAK,CAAC,cAAc,CAC5C,OAAO,eAAe,CAAC,OAAO,CAC/B,GAAG;IACF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AA0BD,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,kBAAkB,qBAsBpB;AAED,iBAAS,oBAAoB,CAAC,EAC5B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,cAAc,CAAC,qBAU7D;AACD,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,gBAAgB,CAAC,qBAU/D;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAmB,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,qBA0CtD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,qBAQvC;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAQpD;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,qBAgBnD;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,SAAS,CAAC,qBAQxD;AAED,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,WAAW,EACX,WAAW,EACX,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACvB,CAAA"}
|
|
@@ -27,15 +27,19 @@ function SelectValue({ ...props }) {
|
|
|
27
27
|
* slot shrink, `truncate` ellipsises what no longer fits — the chevron keeps its
|
|
28
28
|
* place because the value absorbs all the shrinkage.
|
|
29
29
|
*
|
|
30
|
-
* Written as a
|
|
31
|
-
* `className` off `Select.Value` itself
|
|
32
|
-
*
|
|
30
|
+
* Written as a descendant-scoped variant on the TRIGGER because Radix strips
|
|
31
|
+
* `className` off `Select.Value` itself. DESCENDANT, not `>`: a trigger that
|
|
32
|
+
* carries a leading glyph groups the glyph and the value into one flex child
|
|
33
|
+
* (see `SelectField`'s `leadingIcon`), so the value span is no longer the
|
|
34
|
+
* trigger's direct child and a `>` combinator stopped truncating exactly where
|
|
35
|
+
* the trigger has the least room. The wrapper carries `min-w-0` itself, which
|
|
36
|
+
* is what lets the shrinkage reach the value.
|
|
33
37
|
*
|
|
34
38
|
* Truncation hides text, so a trigger showing a long value should also carry
|
|
35
39
|
* `title` — on the trigger, never on the value slot, which Radix renders
|
|
36
40
|
* `pointer-events: none`.
|
|
37
41
|
*/
|
|
38
|
-
const VALUE_OVERFLOW_CLASS = '[
|
|
42
|
+
const VALUE_OVERFLOW_CLASS = '[&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate';
|
|
39
43
|
function SelectTrigger({ className, readOnly, children, ...props }) {
|
|
40
44
|
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": "default", className: cn('select-trigger select-read-only select-disabled', VALUE_OVERFLOW_CLASS, className), "aria-readonly": readOnly, "data-read-only": readOnly ? '' : undefined, ...props, children: [children, !readOnly && (_jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDown, { className: "select-chevron" }) }))] }));
|
|
41
45
|
}
|
|
@@ -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/index.js';
|
|
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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"}
|
|
@@ -52,7 +52,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
52
52
|
* activation. Keydown events originating from interactive children (links,
|
|
53
53
|
* buttons, checkboxes, form fields) are never hijacked.
|
|
54
54
|
*/
|
|
55
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
55
|
+
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
|
56
56
|
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
|
57
57
|
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '../../components/ui/table/index.js';
|
|
58
58
|
import { Skeleton } from '../../components/ui/skeleton/index.js';
|
|
@@ -62,8 +62,21 @@ import { EmptyState } from '../empty-state/index.js';
|
|
|
62
62
|
import { readPreferredColumnSize, UNSIZED_DEFAULT_COLUMN } from './column-size.js';
|
|
63
63
|
const SELECTION_COLUMN_ID = '__select__';
|
|
64
64
|
const CHECKBOX_CLASS = 'accent-primary block size-4';
|
|
65
|
-
/**
|
|
66
|
-
|
|
65
|
+
/** Figure treatment for `meta: { numeric: true }` columns; the alignment half
|
|
66
|
+
* lives in ALIGN_CELL_CLASS so `meta.align` can override one without the other. */
|
|
67
|
+
const NUMERIC_FIGURE_CLASS = 'font-mono tabular-nums';
|
|
68
|
+
/**
|
|
69
|
+
* Body-cell alignment, one class per direction rather than a single string with
|
|
70
|
+
* `text-right` baked in: an explicit `meta.align` has to beat `numeric` by
|
|
71
|
+
* choosing the class, not by out-ordering it — tailwind-merge resolving two
|
|
72
|
+
* conflicting alignment utilities by source order is not a contract worth
|
|
73
|
+
* relying on.
|
|
74
|
+
*/
|
|
75
|
+
const ALIGN_CELL_CLASS = {
|
|
76
|
+
left: 'text-left',
|
|
77
|
+
center: 'text-center',
|
|
78
|
+
right: 'text-right'
|
|
79
|
+
};
|
|
67
80
|
/**
|
|
68
81
|
* Keydown events whose target sits inside one of these are never hijacked
|
|
69
82
|
* by the row-level keyboard handler — Space in a checkbox and Enter on an
|
|
@@ -113,7 +126,7 @@ function createSelectionColumn(getLabel) {
|
|
|
113
126
|
cell: ({ row }) => _jsx(RowSelectionCheckbox, { row: row, getLabel: getLabel })
|
|
114
127
|
};
|
|
115
128
|
}
|
|
116
|
-
export 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 }) {
|
|
129
|
+
export 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 }) {
|
|
117
130
|
const tableColumns = useMemo(() => enableRowSelection
|
|
118
131
|
? [createSelectionColumn(getRowSelectionLabel), ...columns]
|
|
119
132
|
: columns, [columns, enableRowSelection, getRowSelectionLabel]);
|
|
@@ -139,12 +152,33 @@ export function DataTable({ columns, data, loading = false, skeletonRows = 8, lo
|
|
|
139
152
|
manualSorting: true,
|
|
140
153
|
enableSorting,
|
|
141
154
|
onSortingChange: enableSorting ? onSortingChange : undefined,
|
|
155
|
+
// ⛔ EACH VISIBILITY ENTRY IS GUARDED ON ITS OWN PROP, NEVER ON THE OTHER.
|
|
156
|
+
//
|
|
157
|
+
// TanStack merges options as `{...defaultOptions, ...options}` and state as
|
|
158
|
+
// `{...internalState, ...options.state}`, both plain spreads — so a key
|
|
159
|
+
// present and holding `undefined` OVERWRITES what it was meant to leave
|
|
160
|
+
// alone. `onColumnVisibilityChange: undefined` replaces the feature's own
|
|
161
|
+
// `makeStateUpdater`, and `setColumnVisibility` reads
|
|
162
|
+
// `options.onColumnVisibilityChange == null ? void 0 : ...`, so it (and
|
|
163
|
+
// every `column.toggleVisibility` behind it) becomes a silent no-op.
|
|
164
|
+
// `columnVisibility: undefined` replaces the table's initial `{}`, so a
|
|
165
|
+
// consumer reading the slice gets `undefined` instead of an empty object.
|
|
166
|
+
//
|
|
167
|
+
// Guarding the UPDATER on the STATE put that `undefined` back for the
|
|
168
|
+
// union's first arm — state with no updater, which is the shipped console
|
|
169
|
+
// usage, where a separate dropdown owns the state. A table that passes
|
|
170
|
+
// neither prop is uncontrolled, exactly as before.
|
|
171
|
+
...(onColumnVisibilityChange !== undefined && { onColumnVisibilityChange }),
|
|
142
172
|
state: {
|
|
143
173
|
rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined,
|
|
144
|
-
sorting: enableSorting ? (sorting ?? []) : undefined
|
|
174
|
+
sorting: enableSorting ? (sorting ?? []) : undefined,
|
|
175
|
+
...(columnVisibility !== undefined && { columnVisibility })
|
|
145
176
|
}
|
|
146
177
|
});
|
|
147
|
-
|
|
178
|
+
// Visible, not declared: the skeleton grid and the empty row's colSpan have
|
|
179
|
+
// to match the header the table actually rendered, and a hidden column is in
|
|
180
|
+
// `tableColumns` but in no `<th>`.
|
|
181
|
+
const colCount = table.getVisibleLeafColumns().length;
|
|
148
182
|
const dataRows = table.getRowModel().rows;
|
|
149
183
|
const rowCount = dataRows.length;
|
|
150
184
|
// Compact density overrides only THIS table's cell padding (tailwind-merge
|
|
@@ -262,13 +296,13 @@ export function DataTable({ columns, data, loading = false, skeletonRows = 8, lo
|
|
|
262
296
|
: rowCount === 0
|
|
263
297
|
? 'No rows'
|
|
264
298
|
: `${rowCount} rows` }), _jsxs(Table, { "aria-busy": loading ? 'true' : undefined, children: [_jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: "hover:bg-transparent", children: headerGroup.headers.map((header) => {
|
|
265
|
-
const
|
|
266
|
-
return (_jsx(TableHead, { align: numeric ? 'right' : undefined, "aria-sort": header.isPlaceholder
|
|
299
|
+
const meta = header.column.columnDef.meta;
|
|
300
|
+
return (_jsx(TableHead, { align: meta?.align ?? (meta?.numeric ? 'right' : undefined), "aria-sort": header.isPlaceholder
|
|
267
301
|
? undefined
|
|
268
302
|
: ariaSortFor(header.column),
|
|
269
303
|
// Only a column that DECLARED sizing gets an inline width;
|
|
270
304
|
// everything else keeps the auto table layout it has today.
|
|
271
|
-
style: readPreferredColumnSize(header.column), className: cn(LABEL_VOICE_CLASS, headDensityClass, header.column.id === SELECTION_COLUMN_ID && 'w-10', headClassName), children: header.isPlaceholder
|
|
305
|
+
style: readPreferredColumnSize(header.column), className: cn(LABEL_VOICE_CLASS, headDensityClass, header.column.id === SELECTION_COLUMN_ID && 'w-10', headClassName, meta?.headerClassName), children: header.isPlaceholder
|
|
272
306
|
? null
|
|
273
307
|
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
274
308
|
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loading ? (Array.from({ length: skeletonRows }).map((_, rowIndex) => (_jsx(TableRow, { className: "hover:bg-transparent", children: Array.from({ length: colCount }).map((__, cellIndex) => (_jsx(TableCell, { className: cellDensityClass, children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, `skeleton-${rowIndex}-${cellIndex}`))) }, `skeleton-${rowIndex}`)))) : rowCount === 0 ? (_jsx(TableRow, { className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: colCount, className: "p-0", children: _jsx(EmptyState, { title: empty?.title ?? 'Nothing here yet', description: empty?.description, icon: empty?.icon, action: empty?.action,
|
|
@@ -291,7 +325,18 @@ export function DataTable({ columns, data, loading = false, skeletonRows = 8, lo
|
|
|
291
325
|
? (event) => handleRowKeyDown(event, row, rowIndex)
|
|
292
326
|
: undefined, className: keyboardNav ? ROW_FOCUS_CLASS : undefined, "data-state": enableRowSelection && row.getIsSelected()
|
|
293
327
|
? 'selected'
|
|
294
|
-
: undefined, children: row.getVisibleCells().map((cell) =>
|
|
295
|
-
|
|
296
|
-
|
|
328
|
+
: undefined, children: row.getVisibleCells().map((cell) => {
|
|
329
|
+
const meta = cell.column.columnDef.meta;
|
|
330
|
+
const content = flexRender(cell.column.columnDef.cell, cell.getContext());
|
|
331
|
+
// The column owns its `<td>`: render the consumer's cell bare.
|
|
332
|
+
// The kit's selection column is built here and carries no
|
|
333
|
+
// meta, so it can never take this path.
|
|
334
|
+
if (meta?.renderOwnCell) {
|
|
335
|
+
return _jsx(Fragment, { children: content }, cell.id);
|
|
336
|
+
}
|
|
337
|
+
const align = meta?.align ?? (meta?.numeric ? 'right' : undefined);
|
|
338
|
+
return (_jsx(TableCell, { style: readPreferredColumnSize(cell.column), className: 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
|
|
339
|
+
? stopPropagation
|
|
340
|
+
: undefined, children: content }, cell.id));
|
|
341
|
+
}) }, row.id)))) }), footer ? _jsx(TableFooter, { children: footer }) : null] })] }));
|
|
297
342
|
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './components/ui/alert/index.js';
|
|
2
2
|
export * from './components/ui/autocomplete/index.js';
|
|
3
|
+
export * from './hooks/use-click-away.js';
|
|
3
4
|
export * from './components/ui/autosize-textarea/index.js';
|
|
4
5
|
export * from './components/ui/avatar/index.js';
|
|
5
6
|
export * from './components/ui/badge/index.js';
|
|
@@ -39,6 +40,7 @@ export * from './components/ui/toast/index.js';
|
|
|
39
40
|
export * from './components/ui/toast/toaster.js';
|
|
40
41
|
export * from './hooks/use-toast.js';
|
|
41
42
|
export * from './components/ui/time-picker/index.js';
|
|
43
|
+
export * from './hooks/use-time.js';
|
|
42
44
|
export * from './components/ui/tooltip/index.js';
|
|
43
45
|
export * from './components/ui/form.js';
|
|
44
46
|
export { Breadcrumb as ApplicationBreadcrumb } from './components/breadcrumb/index.js';
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAGA,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,0BAA0B,CAAA;AACxC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,uBAAuB,CAAA;AACrC,cAAc,mCAAmC,CAAA;AACjD,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AAGpC,OAAO,EAAE,UAAU,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,cAAc,8CAA8C,CAAA;AAC5D,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAGlC,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AAGxB,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,8BAA8B,CAAA;AAG5C,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAGA,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,wBAAwB,CAAA;AACtC,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,0BAA0B,CAAA;AACxC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,uBAAuB,CAAA;AACrC,cAAc,mCAAmC,CAAA;AACjD,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AAGpC,OAAO,EAAE,UAAU,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,cAAc,8CAA8C,CAAA;AAC5D,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAGlC,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AAGxB,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,8BAA8B,CAAA;AAG5C,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,cAAc,kBAAkB,CAAA"}
|
package/dist/esm/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Primitives
|
|
3
3
|
export * from './components/ui/alert/index.js';
|
|
4
4
|
export * from './components/ui/autocomplete/index.js';
|
|
5
|
+
export * from './hooks/use-click-away.js';
|
|
5
6
|
export * from './components/ui/autosize-textarea/index.js';
|
|
6
7
|
export * from './components/ui/avatar/index.js';
|
|
7
8
|
export * from './components/ui/badge/index.js';
|
|
@@ -41,6 +42,7 @@ export * from './components/ui/toast/index.js';
|
|
|
41
42
|
export * from './components/ui/toast/toaster.js';
|
|
42
43
|
export * from './hooks/use-toast.js';
|
|
43
44
|
export * from './components/ui/time-picker/index.js';
|
|
45
|
+
export * from './hooks/use-time.js';
|
|
44
46
|
export * from './components/ui/tooltip/index.js';
|
|
45
47
|
export * from './components/ui/form.js';
|
|
46
48
|
// Components
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './components/ui/alert';
|
|
2
2
|
export * from './components/ui/autocomplete';
|
|
3
|
+
export * from './hooks/use-click-away';
|
|
3
4
|
export * from './components/ui/autosize-textarea';
|
|
4
5
|
export * from './components/ui/avatar';
|
|
5
6
|
export * from './components/ui/badge';
|
|
@@ -39,6 +40,7 @@ export * from './components/ui/toast';
|
|
|
39
40
|
export * from './components/ui/toast/toaster';
|
|
40
41
|
export * from './hooks/use-toast';
|
|
41
42
|
export * from './components/ui/time-picker';
|
|
43
|
+
export * from './hooks/use-time';
|
|
42
44
|
export * from './components/ui/tooltip';
|
|
43
45
|
export * from './components/ui/form';
|
|
44
46
|
export { Breadcrumb as ApplicationBreadcrumb } from './components/breadcrumb';
|