@lerianstudio/sindarian-ui 1.4.0-beta.5 → 1.4.0-beta.7

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.
@@ -27,8 +27,21 @@ export type PageHeaderInfoTitleProps = {
27
27
  /**
28
28
  * Seam for the wrapper element's own margins. Merged AFTER the hard-coded
29
29
  * `mb-12` so a consumer utility (e.g. `mb-0`) wins via tailwind-merge.
30
+ *
31
+ * It is also the way back to the old shrink-to-zero title box:
32
+ * `containerClassName="min-w-0"` simply lands, because the base string
33
+ * declares no `min-w-*` for tailwind-merge to weigh it against. Pass it only
34
+ * if a title box that a wide action slot can squeeze past its longest word
35
+ * is what the surface actually wants.
30
36
  */
31
37
  containerClassName?: string;
38
+ /**
39
+ * Rendered as their own row under the subtitle, wrapping, so a long identity
40
+ * tape never competes with the subtitle for the same line. This is the slot
41
+ * for things that IDENTIFY the record (short id, status, severity, counts),
42
+ * not for controls: buttons belong in the header's action slot, where the
43
+ * wrapper's `justify-between` keeps them right-aligned.
44
+ */
32
45
  children?: ReactNode;
33
46
  };
34
47
  export declare function PageHeaderInfoTitle({ title, subtitle, className, containerClassName, children, as: Heading }: PageHeaderInfoTitleProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/page-header/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAwB,MAAM,OAAO,CAAA;AAmBtE,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ7B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAc7B;AAED,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,SAAS,CAAA;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,aAAa,EACd,EAAE,eAAe,+BAmBjB;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,QAAQ,EACR,EAAE,EAAE,OAAc,EACnB,EAAE,wBAAwB,+BAsB1B;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACT,EAAE,0BAA0B,+BAqC5B;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACT,EAAE,qCAAqC,+BAavC;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACL,EAAE,8BAA8B,+BA+ChC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/page-header/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAA2B,SAAS,EAAwB,MAAM,OAAO,CAAA;AAmBhF,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ7B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAc7B;AAED,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,SAAS,CAAA;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,aAAa,EACd,EAAE,eAAe,+BAmBjB;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5C;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,QAAQ,EACR,EAAE,EAAE,OAAc,EACnB,EAAE,wBAAwB,+BA2D1B;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACT,EAAE,0BAA0B,+BAqC5B;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACT,EAAE,qCAAqC,+BAavC;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACL,EAAE,8BAA8B,+BA+ChC"}
@@ -29,7 +29,38 @@ function PageHeader({ children, className, rootClassName }) {
29
29
  return ((0, jsx_runtime_1.jsx)(PageHeaderContext.Provider, { value: { isOpen }, children: (0, jsx_runtime_1.jsx)("div", { "data-slot": "page-header", className: (0, utils_1.cn)('mt-12', isOpen && 'mb-12', rootClassName), children: (0, jsx_runtime_1.jsx)(collapsible_1.Collapsible, { open: isOpen, onOpenChange: setIsOpen, className: className, children: children }) }) }));
30
30
  }
31
31
  function PageHeaderInfoTitle({ title, subtitle, className, containerClassName, children, as: Heading = 'h1' }) {
32
- return ((0, jsx_runtime_1.jsxs)("div", { "data-slot": "page-header-info-title", className: (0, utils_1.cn)('mb-12 flex min-w-0 flex-1 flex-col gap-4', containerClassName), children: [(0, jsx_runtime_1.jsx)(Heading, { className: (0, utils_1.cn)('text-foreground text-4xl font-bold', className), "data-testid": "title", children: title }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-muted-foreground text-sm font-medium", children: subtitle }), children] })] }));
32
+ return ((0, jsx_runtime_1.jsxs)("div", { "data-slot": "page-header-info-title",
33
+ /*
34
+ Two utilities and one deliberate absence.
35
+
36
+ `flex-1` (basis 0) is #170: flex shrinking is shared in proportion to
37
+ flex-basis, so a title box at basis auto (its max-content width, which
38
+ a long subtitle inflates) absorbed almost none of the overflow and
39
+ starved the action row into one button per line. At basis 0 the line's
40
+ hypothetical size is the action row alone, so the row keeps its natural
41
+ width and the title grows into what is left, wrapping at spaces.
42
+
43
+ NO `min-w-0` is #174, and the absence is the fix. A flex item's
44
+ automatic minimum size is its own min-content, so left alone the box
45
+ cannot be squeezed below its longest word (of the title, the subtitle,
46
+ or a child chip) and nothing can overflow horizontally at any width.
47
+ beta.5 carried `min-w-0`, which waives that minimum: on Matcher's
48
+ exception-detail masthead the box collapsed to 68px and the `h1`
49
+ painted straight out of it and across the action row. That is gone.
50
+
51
+ A fixed floor was the other candidate and is wrong: any `sm:` gate keys
52
+ on the VIEWPORT while the overflow condition is the MASTHEAD width, so
53
+ a 1024px viewport with a 320px side panel open (a ~332px masthead)
54
+ would keep a 320px floor and scroll the body sideways.
55
+
56
+ The measured trade: on a masthead carrying a 520px action row the title
57
+ box shrinks to its longest word and the subtitle wraps to about three
58
+ lines. The remedy there is fewer controls in the action slot, not a
59
+ wider floor. A consumer that wants the collapse back passes
60
+ `containerClassName="min-w-0"`. jsdom does not lay out, so the geometry
61
+ is verified downstream.
62
+ */
63
+ className: (0, utils_1.cn)('mb-12 flex flex-1 flex-col gap-4', containerClassName), children: [(0, jsx_runtime_1.jsx)(Heading, { className: (0, utils_1.cn)('text-foreground text-4xl font-bold', className), "data-testid": "title", children: title }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-2", children: (0, jsx_runtime_1.jsx)("p", { className: "text-muted-foreground text-sm font-medium", children: subtitle }) }), react_1.Children.toArray(children).length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap items-center gap-2", children: children })) : null] }));
33
64
  }
34
65
  function PageHeaderInfoTooltip({ subtitle }) {
35
66
  const { toast } = (0, use_toast_1.useToast)();
@@ -88,7 +88,7 @@ function DropdownMenuRadioItem({ className, children, ...props }) {
88
88
  return ((0, jsx_runtime_1.jsxs)(DropdownMenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", className: (0, utils_1.cn)('focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50', className), ...props, children: [(0, jsx_runtime_1.jsx)("span", { className: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center", children: (0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.ItemIndicator, { children: (0, jsx_runtime_1.jsx)(lucide_react_1.Circle, { className: "size-2 fill-current" }) }) }), children] }));
89
89
  }
90
90
  function DropdownMenuLabel({ className, inset, ...props }) {
91
- return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: (0, utils_1.cn)('hover:bg-shadcn-100 text-muted-foreground flex items-center gap-3 px-3 py-2 text-sm font-bold transition-colors', className), ...props }));
91
+ return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: (0, utils_1.cn)('hover:bg-shadcn-100 hover:text-accent-foreground text-muted-foreground flex items-center gap-3 px-3 py-2 text-sm font-bold transition-colors', className), ...props }));
92
92
  }
93
93
  function DropdownMenuSeparator({ className, ...props }) {
94
94
  return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Separator, { "data-slot": "dropdown-menu-separator", className: (0, utils_1.cn)('bg-muted -mx-1 my-1 h-px', className), ...props }));
@@ -100,7 +100,7 @@ function DropdownMenuSub({ ...props }) {
100
100
  return (0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.Sub, { "data-slot": "dropdown-menu-sub", ...props });
101
101
  }
102
102
  function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
103
- return ((0, jsx_runtime_1.jsxs)(DropdownMenuPrimitive.SubTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: (0, utils_1.cn)('focus:bg-shadcn-100 data-[state=open]:bg-accent text-muted-foreground flex cursor-default items-center gap-3 px-3 py-2 text-sm font-medium outline-hidden select-none', className), ...props, children: [children, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "ml-auto h-4 w-4" })] }));
103
+ return ((0, jsx_runtime_1.jsxs)(DropdownMenuPrimitive.SubTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: (0, utils_1.cn)('focus:bg-shadcn-100 focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground text-muted-foreground flex cursor-default items-center gap-3 px-3 py-2 text-sm font-medium outline-hidden select-none', className), ...props, children: [children, (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "ml-auto h-4 w-4" })] }));
104
104
  }
105
105
  function DropdownMenuSubContent({ className, ...props }) {
106
106
  return ((0, jsx_runtime_1.jsx)(DropdownMenuPrimitive.SubContent, { "data-slot": "dropdown-menu-sub-content", className: (0, utils_1.cn)('bg-popover text-popover-foreground motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg', className), ...props }));
@@ -1,4 +1,4 @@
1
- import { type ColumnDef, type OnChangeFn, type RowData, type RowSelectionState } from '@tanstack/react-table';
1
+ import { type ColumnDef, type OnChangeFn, type RowData, type RowSelectionState, type SortingState } from '@tanstack/react-table';
2
2
  import { type EmptyStateProps } from '../empty-state';
3
3
  /**
4
4
  * Per-column opt-in for ledger numeric alignment. Set `meta: { numeric: true }`
@@ -38,6 +38,39 @@ type RowSelectionProps<TData> = {
38
38
  */
39
39
  getRowSelectionLabel?: (row: TData) => string;
40
40
  };
41
+ /**
42
+ * Sorting is CONTROLLED-ONLY and OPT-IN, mirroring the row-selection contract
43
+ * above: the table sets `manualSorting` and adds no sorted row model, so it
44
+ * never reorders rows. The consumer owns the order (it usually comes back
45
+ * sorted from the server); what the table owns is announcing the state in the
46
+ * one place ARIA defines for it, `aria-sort` on the `th`. Header content is
47
+ * typically a `<button>`, and `aria-sort` is not allowed on a button, so a
48
+ * consumer cannot put it there without failing axe `aria-allowed-attr`.
49
+ *
50
+ * GOTCHA, TanStack's own rule, not a new one: `getCanSort()` is
51
+ * `(columnDef.enableSorting ?? true) && (options.enableSorting ?? true) &&
52
+ * !!column.accessorFn`. A display column (an `id` plus a `cell`, no
53
+ * `accessorKey` or `accessorFn`) has no accessor, so it can never sort and
54
+ * gets no `aria-sort`. That is right for an actions column and surprising for
55
+ * a computed column: give a column you want announced an accessor, and give a
56
+ * column you want excluded `enableSorting: false`.
57
+ */
58
+ type SortingProps = {
59
+ enableSorting?: false;
60
+ sorting?: never;
61
+ onSortingChange?: never;
62
+ } | {
63
+ enableSorting: true;
64
+ /**
65
+ * Controlled sort state (TanStack `SortingState`). Single-entry:
66
+ * `aria-sort` is defined for one sorted header at a time, so a
67
+ * multi-entry `SortingState` is the consumer's responsibility and yields
68
+ * several non-`none` values.
69
+ */
70
+ sorting: SortingState;
71
+ /** Controlled sort updater (TanStack `OnChangeFn`). */
72
+ onSortingChange: OnChangeFn<SortingState>;
73
+ };
41
74
  type DataTableBaseProps<TData> = {
42
75
  columns: ColumnDef<TData, unknown>[];
43
76
  data: TData[];
@@ -94,7 +127,7 @@ type DataTableBaseProps<TData> = {
94
127
  */
95
128
  footer?: React.ReactNode;
96
129
  };
97
- export type DataTableProps<TData> = DataTableBaseProps<TData> & RowSelectionProps<TData>;
98
- export declare function DataTable<TData>({ columns, data, loading, skeletonRows, loadingLabel, empty, getRowId, flush, density, enableRowSelection, rowSelection, onRowSelectionChange, getRowSelectionLabel, onRowActivate, rowHref, className, headClassName, footer }: DataTableProps<TData>): import("react").JSX.Element;
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;
99
132
  export {};
100
133
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/enterprise/data-table/index.tsx"],"names":[],"mappings":"AAkDA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,UAAU,EAEf,KAAK,OAAO,EACZ,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAA;AAc9B,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAGjE;;;;GAIG;AACH,OAAO,QAAQ,uBAAuB,CAAC;IAIrC,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM;QAChD,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB;CACF;AAED;;;;;;;;GAQG;AACH,KAAK,iBAAiB,CAAC,KAAK,IACxB;IACE,kBAAkB,CAAC,EAAE,KAAK,CAAA;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,oBAAoB,CAAC,EAAE,KAAK,CAAA;IAC5B,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAC7B,GACD;IACE,kBAAkB,EAAE,IAAI,CAAA;IACxB,kFAAkF;IAClF,YAAY,EAAE,iBAAiB,CAAA;IAC/B,4DAA4D;IAC5D,oBAAoB,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAA;IACnD;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;CAC9C,CAAA;AAEL,KAAK,kBAAkB,CAAC,KAAK,IAAI;IAC/B,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAA;IACpC,IAAI,EAAE,KAAK,EAAE,CAAA;IACb,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IACnC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IACpC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,KAAK,IAAI,kBAAkB,CAAC,KAAK,CAAC,GAC3D,iBAAiB,CAAC,KAAK,CAAC,CAAA;AA4E1B,wBAAgB,SAAS,CAAC,KAAK,EAAE,EAC/B,OAAO,EACP,IAAI,EACJ,OAAe,EACf,YAAgB,EAChB,YAAmC,EACnC,KAAK,EACL,QAAQ,EACR,KAAa,EACb,OAAuB,EACvB,kBAA0B,EAC1B,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,OAAO,EACP,SAAS,EACT,aAAa,EACb,MAAM,EACP,EAAE,cAAc,CAAC,KAAK,CAAC,+BAySvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/enterprise/data-table/index.tsx"],"names":[],"mappings":"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"}
@@ -38,6 +38,11 @@ const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  * page (indeterminate for partial), checkbox-cell clicks never bubble to
39
39
  * row-level handlers. With the flag off (the default), nothing changes.
40
40
  *
41
+ * Sorting is opt-in and controlled too: pass `enableSorting` plus `sorting` /
42
+ * `onSortingChange`. Rows are never reordered client-side (`manualSorting`, no
43
+ * sorted row model); what the flag buys is `aria-sort` on each sortable `th`.
44
+ * With the flag off (the default), no header carries the attribute.
45
+ *
41
46
  * Keyboard navigation is opt-in via `onRowActivate`: when present, rows get
42
47
  * a roving tabindex (Tab enters the table once at the anchor row; the next
43
48
  * Tab moves to the row's first tabbable child if it has one, otherwise out
@@ -77,6 +82,22 @@ const ROW_FOCUS_CLASS = 'focus-visible:outline focus-visible:outline-2 focus-vis
77
82
  function stopPropagation(event) {
78
83
  event.stopPropagation();
79
84
  }
85
+ /**
86
+ * ARIA sort state for one header cell. `undefined` (attribute omitted) on a
87
+ * column that cannot sort, which covers both sorting-off tables and columns
88
+ * that opted out, so the attribute never claims a control the user does not
89
+ * have.
90
+ */
91
+ function ariaSortFor(column) {
92
+ if (!column.getCanSort())
93
+ return undefined;
94
+ const sorted = column.getIsSorted();
95
+ if (sorted === 'asc')
96
+ return 'ascending';
97
+ if (sorted === 'desc')
98
+ return 'descending';
99
+ return 'none';
100
+ }
80
101
  function SelectAllCheckbox({ table }) {
81
102
  const allSelected = table.getIsAllPageRowsSelected();
82
103
  const someSelected = table.getIsSomePageRowsSelected();
@@ -95,7 +116,7 @@ function createSelectionColumn(getLabel) {
95
116
  cell: ({ row }) => (0, jsx_runtime_1.jsx)(RowSelectionCheckbox, { row: row, getLabel: getLabel })
96
117
  };
97
118
  }
98
- function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLabel = 'Loading table rows', empty, getRowId, flush = false, density = 'comfortable', enableRowSelection = false, rowSelection, onRowSelectionChange, getRowSelectionLabel, onRowActivate, rowHref, className, headClassName, footer }) {
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 }) {
99
120
  const tableColumns = (0, react_1.useMemo)(() => enableRowSelection
100
121
  ? [createSelectionColumn(getRowSelectionLabel), ...columns]
101
122
  : columns, [columns, enableRowSelection, getRowSelectionLabel]);
@@ -114,8 +135,16 @@ function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLa
114
135
  // types make the TS path require the controlled pair whenever selection is
115
136
  // enabled, so the dead-checkbox configuration is now unrepresentable.
116
137
  onRowSelectionChange: enableRowSelection ? onRowSelectionChange : undefined,
138
+ // Controlled-only as well, and deliberately without `getSortedRowModel`:
139
+ // `manualSorting` tells TanStack the rows arrive in the order the consumer
140
+ // already chose, so enabling sorting changes what the header ANNOUNCES,
141
+ // never what the body shows.
142
+ manualSorting: true,
143
+ enableSorting,
144
+ onSortingChange: enableSorting ? onSortingChange : undefined,
117
145
  state: {
118
- rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined
146
+ rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined,
147
+ sorting: enableSorting ? (sorting ?? []) : undefined
119
148
  }
120
149
  });
121
150
  const colCount = tableColumns.length;
@@ -237,7 +266,9 @@ function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLa
237
266
  ? 'No rows'
238
267
  : `${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) => {
239
268
  const numeric = header.column.columnDef.meta?.numeric;
240
- return ((0, jsx_runtime_1.jsx)(table_1.TableHead, { align: numeric ? 'right' : undefined,
269
+ return ((0, jsx_runtime_1.jsx)(table_1.TableHead, { align: numeric ? 'right' : undefined, "aria-sort": header.isPlaceholder
270
+ ? undefined
271
+ : ariaSortFor(header.column),
241
272
  // Only a column that DECLARED sizing gets an inline width;
242
273
  // everything else keeps the auto table layout it has today.
243
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
@@ -38,7 +38,18 @@ export type DateRangePickerProps = {
38
38
  clearLabel?: string;
39
39
  /** date-fns locale for the calendar. Defaults to en-US. */
40
40
  locale?: CalendarLocale;
41
+ /**
42
+ * Suspends the control. Both segments get the NATIVE `disabled` attribute,
43
+ * not an `aria-disabled` substitute: a trigger whose only job is to open a
44
+ * popover has no state worth keeping focusable, and native `disabled` is
45
+ * what takes it out of the tab order AND keeps the popover shut. That last
46
+ * part matters more than it looks: `PopoverContent` is portaled to
47
+ * `document.body`, so the calendar's Clear button is outside any wrapper a
48
+ * consumer could disable, and the closed popover is the only thing keeping
49
+ * it out of reach.
50
+ */
51
+ disabled?: boolean;
41
52
  };
42
- export declare function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid, required, errorId, placeholder, ariaLabel, clearLabel, locale }: DateRangePickerProps): import("react").JSX.Element;
53
+ export declare function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid, required, errorId, placeholder, ariaLabel, clearLabel, locale, disabled }: DateRangePickerProps): import("react").JSX.Element;
43
54
  export {};
44
55
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/enterprise/date-range-picker/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AASnD;;;;GAIG;AACH,KAAK,cAAc,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAA;AAE/D,MAAM,MAAM,cAAc,GAAG;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,cAAc,CAAA;IACrB,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC7C,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB,CAAA;AAkCD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,aAAa,EACb,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAe,EACf,QAAgB,EAChB,OAAO,EACP,WAAwB,EACxB,SAA+B,EAC/B,UAAoB,EACpB,MAAa,EACd,EAAE,oBAAoB,+BA8HtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/enterprise/date-range-picker/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AASnD;;;;GAIG;AACH,KAAK,cAAc,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAA;AAE/D,MAAM,MAAM,cAAc,GAAG;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,cAAc,CAAA;IACrB,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC7C,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAkCD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,aAAa,EACb,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAe,EACf,QAAgB,EAChB,OAAO,EACP,WAAwB,EACxB,SAA+B,EAC/B,UAAoB,EACpB,MAAa,EACb,QAAgB,EACjB,EAAE,oBAAoB,+BAgJtB"}
@@ -62,12 +62,22 @@ function toDayString(value) {
62
62
  const day = String(value.getDate()).padStart(2, '0');
63
63
  return `${year}-${month}-${day}`;
64
64
  }
65
- function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid = false, required = false, errorId, placeholder = 'Any date', ariaLabel = 'Select date range', clearLabel = 'Clear', locale = locale_1.enUS }) {
65
+ function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid = false, required = false, errorId, placeholder = 'Any date', ariaLabel = 'Select date range', clearLabel = 'Clear', locale = locale_1.enUS, disabled = false }) {
66
66
  // Which segment's popover is open, if any. One root per segment means one open
67
67
  // state per segment; holding the identity of the open one here keeps them
68
68
  // mutually exclusive, so clicking "to" while "from" is open swaps the anchor
69
69
  // instead of stacking two calendars.
70
70
  const [openSegment, setOpenSegment] = (0, react_1.useState)(null);
71
+ // Suspending the control must FORGET which segment was open, not just hide
72
+ // it. The `open` prop below already shuts the popover in the same frame
73
+ // `disabled` flips, but a controlled Radix root never calls `onOpenChange`
74
+ // for a prop-driven close, so the identity survived: lifting `disabled`
75
+ // re-satisfied the same condition and the portaled calendar reopened with no
76
+ // user gesture, pulling focus into it.
77
+ (0, react_1.useEffect)(() => {
78
+ if (disabled)
79
+ setOpenSegment(null);
80
+ }, [disabled]);
71
81
  const fromDate = parseDay(value.from);
72
82
  const toDate = parseDay(value.to);
73
83
  const selected = fromDate || toDate ? { from: fromDate, to: toDate } : undefined;
@@ -87,11 +97,19 @@ function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabe
87
97
  // Input and Select it sits with. font-sans, not font-mono: the dates are
88
98
  // figures in the body face with tabular figures, like every other figure in
89
99
  // the console.
90
- 'border-shadcn-400 bg-card hover:bg-secondary focus-visible:ring-ring flex h-9 items-center gap-2 rounded-md border px-3 text-left font-sans text-sm tabular-nums transition-colors duration-150 ease-out focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none sm:w-40', invalid && 'border-destructive');
100
+ 'border-shadcn-400 bg-card hover:bg-secondary focus-visible:ring-ring flex h-9 items-center gap-2 rounded-md border px-3 text-left font-sans text-sm tabular-nums transition-colors duration-150 ease-out focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none sm:w-40', invalid && 'border-destructive',
101
+ // Conditional rather than `disabled:` variants, matching how this file
102
+ // already applies `border-destructive`: with the prop absent the class
103
+ // string stays byte-identical to what consumers render today.
104
+ disabled && 'cursor-not-allowed opacity-50');
91
105
  // The one calendar, rendered inside whichever segment's popover is open. Both
92
106
  // segments read and write the SAME range selection, so which one you opened
93
107
  // changes only the anchor, never the state.
94
108
  const calendar = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(calendar_1.Calendar, { mode: "range", numberOfMonths: 2, defaultMonth: fromDate ?? toDate ?? new Date(), selected: selected, onSelect: onSelect, locale: locale }), (0, jsx_runtime_1.jsx)("div", { className: "border-border flex items-center justify-end border-t px-3 py-2", children: (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", variant: "plain", size: "small", disabled: !hasValue, onClick: () => onValueChange({ from: '', to: '' }), children: clearLabel }) })] }));
95
- const renderSegment = (segment, id, label, dateValue) => ((0, jsx_runtime_1.jsxs)(popover_1.Popover, { open: openSegment === segment, onOpenChange: (next) => setOpenSegment(next ? segment : null), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsxs)(label_1.Label, { htmlFor: id, children: [label, required ? ' *' : ''] }), (0, jsx_runtime_1.jsxs)(popover_1.PopoverTrigger, { type: "button", id: id, className: triggerClass, "aria-invalid": invalid || undefined, "aria-describedby": invalid ? errorId : undefined, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { className: "text-muted-foreground size-4 shrink-0", "aria-hidden": true }), dateValue ? ((0, jsx_runtime_1.jsx)("span", { children: dateValue })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted-foreground", children: placeholder }))] })] }), (0, jsx_runtime_1.jsx)(popover_1.PopoverContent, { align: "start", className: "w-auto p-0", "aria-label": ariaLabel, children: calendar })] }, segment));
109
+ const renderSegment = (segment, id, label, dateValue) => ((0, jsx_runtime_1.jsxs)(popover_1.Popover, {
110
+ // A disabled trigger cannot open a popover, but a surface that suspends
111
+ // itself WHILE one is open (a filter bar flipping to "show all") would
112
+ // otherwise leave the portaled calendar on screen with no way back to it.
113
+ open: !disabled && openSegment === segment, onOpenChange: (next) => setOpenSegment(next ? segment : null), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsxs)(label_1.Label, { htmlFor: id, children: [label, required ? ' *' : ''] }), (0, jsx_runtime_1.jsxs)(popover_1.PopoverTrigger, { type: "button", id: id, className: triggerClass, disabled: disabled, "aria-invalid": invalid || undefined, "aria-describedby": invalid ? errorId : undefined, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { className: "text-muted-foreground size-4 shrink-0", "aria-hidden": true }), dateValue ? ((0, jsx_runtime_1.jsx)("span", { children: dateValue })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-muted-foreground", children: placeholder }))] })] }), (0, jsx_runtime_1.jsx)(popover_1.PopoverContent, { align: "start", className: "w-auto p-0", "aria-label": ariaLabel, children: calendar })] }, segment));
96
114
  return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-end", children: [renderSegment('from', fromId, fromLabel, value.from), renderSegment('to', toId, toLabel, value.to)] }));
97
115
  }
@@ -27,8 +27,21 @@ export type PageHeaderInfoTitleProps = {
27
27
  /**
28
28
  * Seam for the wrapper element's own margins. Merged AFTER the hard-coded
29
29
  * `mb-12` so a consumer utility (e.g. `mb-0`) wins via tailwind-merge.
30
+ *
31
+ * It is also the way back to the old shrink-to-zero title box:
32
+ * `containerClassName="min-w-0"` simply lands, because the base string
33
+ * declares no `min-w-*` for tailwind-merge to weigh it against. Pass it only
34
+ * if a title box that a wide action slot can squeeze past its longest word
35
+ * is what the surface actually wants.
30
36
  */
31
37
  containerClassName?: string;
38
+ /**
39
+ * Rendered as their own row under the subtitle, wrapping, so a long identity
40
+ * tape never competes with the subtitle for the same line. This is the slot
41
+ * for things that IDENTIFY the record (short id, status, severity, counts),
42
+ * not for controls: buttons belong in the header's action slot, where the
43
+ * wrapper's `justify-between` keeps them right-aligned.
44
+ */
32
45
  children?: ReactNode;
33
46
  };
34
47
  export declare function PageHeaderInfoTitle({ title, subtitle, className, containerClassName, children, as: Heading }: PageHeaderInfoTitleProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/page-header/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAwB,MAAM,OAAO,CAAA;AAmBtE,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ7B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAc7B;AAED,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,SAAS,CAAA;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,aAAa,EACd,EAAE,eAAe,+BAmBjB;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,QAAQ,EACR,EAAE,EAAE,OAAc,EACnB,EAAE,wBAAwB,+BAsB1B;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACT,EAAE,0BAA0B,+BAqC5B;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACT,EAAE,qCAAqC,+BAavC;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACL,EAAE,8BAA8B,+BA+ChC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/page-header/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAA2B,SAAS,EAAwB,MAAM,OAAO,CAAA;AAmBhF,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAQ7B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,+BAc7B;AAED,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,SAAS,CAAA;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,aAAa,EACd,EAAE,eAAe,+BAmBjB;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5C;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,QAAQ,EACR,EAAE,EAAE,OAAc,EACnB,EAAE,wBAAwB,+BA2D1B;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACT,EAAE,0BAA0B,+BAqC5B;AAED,MAAM,MAAM,qCAAqC,GAAG;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,QAAQ,EACT,EAAE,qCAAqC,+BAavC;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACL,EAAE,8BAA8B,+BA+ChC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { cn } from '../../lib/utils.js';
3
- import { createContext, useContext, useState } from 'react';
3
+ import { Children, createContext, useContext, useState } from 'react';
4
4
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../components/ui/collapsible/index.js';
5
5
  import { Button } from '../ui/button/index.js';
6
6
  import { HelpCircle, ChevronUp, ExternalLink, Copy } from 'lucide-react';
@@ -20,7 +20,38 @@ export function PageHeader({ children, className, rootClassName }) {
20
20
  return (_jsx(PageHeaderContext.Provider, { value: { isOpen }, children: _jsx("div", { "data-slot": "page-header", className: cn('mt-12', isOpen && 'mb-12', rootClassName), children: _jsx(Collapsible, { open: isOpen, onOpenChange: setIsOpen, className: className, children: children }) }) }));
21
21
  }
22
22
  export function PageHeaderInfoTitle({ title, subtitle, className, containerClassName, children, as: Heading = 'h1' }) {
23
- return (_jsxs("div", { "data-slot": "page-header-info-title", className: cn('mb-12 flex min-w-0 flex-1 flex-col gap-4', containerClassName), children: [_jsx(Heading, { className: cn('text-foreground text-4xl font-bold', className), "data-testid": "title", children: title }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: subtitle }), children] })] }));
23
+ return (_jsxs("div", { "data-slot": "page-header-info-title",
24
+ /*
25
+ Two utilities and one deliberate absence.
26
+
27
+ `flex-1` (basis 0) is #170: flex shrinking is shared in proportion to
28
+ flex-basis, so a title box at basis auto (its max-content width, which
29
+ a long subtitle inflates) absorbed almost none of the overflow and
30
+ starved the action row into one button per line. At basis 0 the line's
31
+ hypothetical size is the action row alone, so the row keeps its natural
32
+ width and the title grows into what is left, wrapping at spaces.
33
+
34
+ NO `min-w-0` is #174, and the absence is the fix. A flex item's
35
+ automatic minimum size is its own min-content, so left alone the box
36
+ cannot be squeezed below its longest word (of the title, the subtitle,
37
+ or a child chip) and nothing can overflow horizontally at any width.
38
+ beta.5 carried `min-w-0`, which waives that minimum: on Matcher's
39
+ exception-detail masthead the box collapsed to 68px and the `h1`
40
+ painted straight out of it and across the action row. That is gone.
41
+
42
+ A fixed floor was the other candidate and is wrong: any `sm:` gate keys
43
+ on the VIEWPORT while the overflow condition is the MASTHEAD width, so
44
+ a 1024px viewport with a 320px side panel open (a ~332px masthead)
45
+ would keep a 320px floor and scroll the body sideways.
46
+
47
+ The measured trade: on a masthead carrying a 520px action row the title
48
+ box shrinks to its longest word and the subtitle wraps to about three
49
+ lines. The remedy there is fewer controls in the action slot, not a
50
+ wider floor. A consumer that wants the collapse back passes
51
+ `containerClassName="min-w-0"`. jsdom does not lay out, so the geometry
52
+ is verified downstream.
53
+ */
54
+ className: cn('mb-12 flex flex-1 flex-col gap-4', containerClassName), children: [_jsx(Heading, { className: cn('text-foreground text-4xl font-bold', className), "data-testid": "title", children: title }), _jsx("div", { className: "flex items-center gap-2", children: _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: subtitle }) }), Children.toArray(children).length > 0 ? (_jsx("div", { className: "flex flex-wrap items-center gap-2", children: children })) : null] }));
24
55
  }
25
56
  export function PageHeaderInfoTooltip({ subtitle }) {
26
57
  const { toast } = useToast();
@@ -37,7 +37,7 @@ function DropdownMenuRadioItem({ className, children, ...props }) {
37
37
  return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", className: cn('focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50', className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Circle, { className: "size-2 fill-current" }) }) }), children] }));
38
38
  }
39
39
  function DropdownMenuLabel({ className, inset, ...props }) {
40
- return (_jsx(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: cn('hover:bg-shadcn-100 text-muted-foreground flex items-center gap-3 px-3 py-2 text-sm font-bold transition-colors', className), ...props }));
40
+ return (_jsx(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: cn('hover:bg-shadcn-100 hover:text-accent-foreground text-muted-foreground flex items-center gap-3 px-3 py-2 text-sm font-bold transition-colors', className), ...props }));
41
41
  }
42
42
  function DropdownMenuSeparator({ className, ...props }) {
43
43
  return (_jsx(DropdownMenuPrimitive.Separator, { "data-slot": "dropdown-menu-separator", className: cn('bg-muted -mx-1 my-1 h-px', className), ...props }));
@@ -49,7 +49,7 @@ function DropdownMenuSub({ ...props }) {
49
49
  return _jsx(DropdownMenuPrimitive.Sub, { "data-slot": "dropdown-menu-sub", ...props });
50
50
  }
51
51
  function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
52
- return (_jsxs(DropdownMenuPrimitive.SubTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: cn('focus:bg-shadcn-100 data-[state=open]:bg-accent text-muted-foreground flex cursor-default items-center gap-3 px-3 py-2 text-sm font-medium outline-hidden select-none', className), ...props, children: [children, _jsx(ChevronRight, { className: "ml-auto h-4 w-4" })] }));
52
+ return (_jsxs(DropdownMenuPrimitive.SubTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: cn('focus:bg-shadcn-100 focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground text-muted-foreground flex cursor-default items-center gap-3 px-3 py-2 text-sm font-medium outline-hidden select-none', className), ...props, children: [children, _jsx(ChevronRight, { className: "ml-auto h-4 w-4" })] }));
53
53
  }
54
54
  function DropdownMenuSubContent({ className, ...props }) {
55
55
  return (_jsx(DropdownMenuPrimitive.SubContent, { "data-slot": "dropdown-menu-sub-content", className: cn('bg-popover text-popover-foreground motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg', className), ...props }));
@@ -1,4 +1,4 @@
1
- import { type ColumnDef, type OnChangeFn, type RowData, type RowSelectionState } from '@tanstack/react-table';
1
+ import { type ColumnDef, type OnChangeFn, type RowData, type RowSelectionState, type SortingState } from '@tanstack/react-table';
2
2
  import { type EmptyStateProps } from '../empty-state/index.js';
3
3
  /**
4
4
  * Per-column opt-in for ledger numeric alignment. Set `meta: { numeric: true }`
@@ -38,6 +38,39 @@ type RowSelectionProps<TData> = {
38
38
  */
39
39
  getRowSelectionLabel?: (row: TData) => string;
40
40
  };
41
+ /**
42
+ * Sorting is CONTROLLED-ONLY and OPT-IN, mirroring the row-selection contract
43
+ * above: the table sets `manualSorting` and adds no sorted row model, so it
44
+ * never reorders rows. The consumer owns the order (it usually comes back
45
+ * sorted from the server); what the table owns is announcing the state in the
46
+ * one place ARIA defines for it, `aria-sort` on the `th`. Header content is
47
+ * typically a `<button>`, and `aria-sort` is not allowed on a button, so a
48
+ * consumer cannot put it there without failing axe `aria-allowed-attr`.
49
+ *
50
+ * GOTCHA, TanStack's own rule, not a new one: `getCanSort()` is
51
+ * `(columnDef.enableSorting ?? true) && (options.enableSorting ?? true) &&
52
+ * !!column.accessorFn`. A display column (an `id` plus a `cell`, no
53
+ * `accessorKey` or `accessorFn`) has no accessor, so it can never sort and
54
+ * gets no `aria-sort`. That is right for an actions column and surprising for
55
+ * a computed column: give a column you want announced an accessor, and give a
56
+ * column you want excluded `enableSorting: false`.
57
+ */
58
+ type SortingProps = {
59
+ enableSorting?: false;
60
+ sorting?: never;
61
+ onSortingChange?: never;
62
+ } | {
63
+ enableSorting: true;
64
+ /**
65
+ * Controlled sort state (TanStack `SortingState`). Single-entry:
66
+ * `aria-sort` is defined for one sorted header at a time, so a
67
+ * multi-entry `SortingState` is the consumer's responsibility and yields
68
+ * several non-`none` values.
69
+ */
70
+ sorting: SortingState;
71
+ /** Controlled sort updater (TanStack `OnChangeFn`). */
72
+ onSortingChange: OnChangeFn<SortingState>;
73
+ };
41
74
  type DataTableBaseProps<TData> = {
42
75
  columns: ColumnDef<TData, unknown>[];
43
76
  data: TData[];
@@ -94,7 +127,7 @@ type DataTableBaseProps<TData> = {
94
127
  */
95
128
  footer?: React.ReactNode;
96
129
  };
97
- export type DataTableProps<TData> = DataTableBaseProps<TData> & RowSelectionProps<TData>;
98
- export declare function DataTable<TData>({ columns, data, loading, skeletonRows, loadingLabel, empty, getRowId, flush, density, enableRowSelection, rowSelection, onRowSelectionChange, getRowSelectionLabel, onRowActivate, rowHref, className, headClassName, footer }: DataTableProps<TData>): import("react").JSX.Element;
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;
99
132
  export {};
100
133
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/enterprise/data-table/index.tsx"],"names":[],"mappings":"AAkDA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,UAAU,EAEf,KAAK,OAAO,EACZ,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAA;AAc9B,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAGjE;;;;GAIG;AACH,OAAO,QAAQ,uBAAuB,CAAC;IAIrC,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM;QAChD,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB;CACF;AAED;;;;;;;;GAQG;AACH,KAAK,iBAAiB,CAAC,KAAK,IACxB;IACE,kBAAkB,CAAC,EAAE,KAAK,CAAA;IAC1B,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,oBAAoB,CAAC,EAAE,KAAK,CAAA;IAC5B,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAC7B,GACD;IACE,kBAAkB,EAAE,IAAI,CAAA;IACxB,kFAAkF;IAClF,YAAY,EAAE,iBAAiB,CAAA;IAC/B,4DAA4D;IAC5D,oBAAoB,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAA;IACnD;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;CAC9C,CAAA;AAEL,KAAK,kBAAkB,CAAC,KAAK,IAAI;IAC/B,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAA;IACpC,IAAI,EAAE,KAAK,EAAE,CAAA;IACb,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IACnC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IACpC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,KAAK,IAAI,kBAAkB,CAAC,KAAK,CAAC,GAC3D,iBAAiB,CAAC,KAAK,CAAC,CAAA;AA4E1B,wBAAgB,SAAS,CAAC,KAAK,EAAE,EAC/B,OAAO,EACP,IAAI,EACJ,OAAe,EACf,YAAgB,EAChB,YAAmC,EACnC,KAAK,EACL,QAAQ,EACR,KAAa,EACb,OAAuB,EACvB,kBAA0B,EAC1B,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,OAAO,EACP,SAAS,EACT,aAAa,EACb,MAAM,EACP,EAAE,cAAc,CAAC,KAAK,CAAC,+BAySvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/enterprise/data-table/index.tsx"],"names":[],"mappings":"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"}
@@ -35,6 +35,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
35
35
  * page (indeterminate for partial), checkbox-cell clicks never bubble to
36
36
  * row-level handlers. With the flag off (the default), nothing changes.
37
37
  *
38
+ * Sorting is opt-in and controlled too: pass `enableSorting` plus `sorting` /
39
+ * `onSortingChange`. Rows are never reordered client-side (`manualSorting`, no
40
+ * sorted row model); what the flag buys is `aria-sort` on each sortable `th`.
41
+ * With the flag off (the default), no header carries the attribute.
42
+ *
38
43
  * Keyboard navigation is opt-in via `onRowActivate`: when present, rows get
39
44
  * a roving tabindex (Tab enters the table once at the anchor row; the next
40
45
  * Tab moves to the row's first tabbable child if it has one, otherwise out
@@ -74,6 +79,22 @@ const ROW_FOCUS_CLASS = 'focus-visible:outline focus-visible:outline-2 focus-vis
74
79
  function stopPropagation(event) {
75
80
  event.stopPropagation();
76
81
  }
82
+ /**
83
+ * ARIA sort state for one header cell. `undefined` (attribute omitted) on a
84
+ * column that cannot sort, which covers both sorting-off tables and columns
85
+ * that opted out, so the attribute never claims a control the user does not
86
+ * have.
87
+ */
88
+ function ariaSortFor(column) {
89
+ if (!column.getCanSort())
90
+ return undefined;
91
+ const sorted = column.getIsSorted();
92
+ if (sorted === 'asc')
93
+ return 'ascending';
94
+ if (sorted === 'desc')
95
+ return 'descending';
96
+ return 'none';
97
+ }
77
98
  function SelectAllCheckbox({ table }) {
78
99
  const allSelected = table.getIsAllPageRowsSelected();
79
100
  const someSelected = table.getIsSomePageRowsSelected();
@@ -92,7 +113,7 @@ function createSelectionColumn(getLabel) {
92
113
  cell: ({ row }) => _jsx(RowSelectionCheckbox, { row: row, getLabel: getLabel })
93
114
  };
94
115
  }
95
- export function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLabel = 'Loading table rows', empty, getRowId, flush = false, density = 'comfortable', enableRowSelection = false, rowSelection, onRowSelectionChange, getRowSelectionLabel, onRowActivate, rowHref, className, headClassName, footer }) {
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 }) {
96
117
  const tableColumns = useMemo(() => enableRowSelection
97
118
  ? [createSelectionColumn(getRowSelectionLabel), ...columns]
98
119
  : columns, [columns, enableRowSelection, getRowSelectionLabel]);
@@ -111,8 +132,16 @@ export function DataTable({ columns, data, loading = false, skeletonRows = 8, lo
111
132
  // types make the TS path require the controlled pair whenever selection is
112
133
  // enabled, so the dead-checkbox configuration is now unrepresentable.
113
134
  onRowSelectionChange: enableRowSelection ? onRowSelectionChange : undefined,
135
+ // Controlled-only as well, and deliberately without `getSortedRowModel`:
136
+ // `manualSorting` tells TanStack the rows arrive in the order the consumer
137
+ // already chose, so enabling sorting changes what the header ANNOUNCES,
138
+ // never what the body shows.
139
+ manualSorting: true,
140
+ enableSorting,
141
+ onSortingChange: enableSorting ? onSortingChange : undefined,
114
142
  state: {
115
- rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined
143
+ rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined,
144
+ sorting: enableSorting ? (sorting ?? []) : undefined
116
145
  }
117
146
  });
118
147
  const colCount = tableColumns.length;
@@ -234,7 +263,9 @@ export function DataTable({ columns, data, loading = false, skeletonRows = 8, lo
234
263
  ? 'No rows'
235
264
  : `${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) => {
236
265
  const numeric = header.column.columnDef.meta?.numeric;
237
- return (_jsx(TableHead, { align: numeric ? 'right' : undefined,
266
+ return (_jsx(TableHead, { align: numeric ? 'right' : undefined, "aria-sort": header.isPlaceholder
267
+ ? undefined
268
+ : ariaSortFor(header.column),
238
269
  // Only a column that DECLARED sizing gets an inline width;
239
270
  // everything else keeps the auto table layout it has today.
240
271
  style: readPreferredColumnSize(header.column), className: cn(LABEL_VOICE_CLASS, headDensityClass, header.column.id === SELECTION_COLUMN_ID && 'w-10', headClassName), children: header.isPlaceholder
@@ -38,7 +38,18 @@ export type DateRangePickerProps = {
38
38
  clearLabel?: string;
39
39
  /** date-fns locale for the calendar. Defaults to en-US. */
40
40
  locale?: CalendarLocale;
41
+ /**
42
+ * Suspends the control. Both segments get the NATIVE `disabled` attribute,
43
+ * not an `aria-disabled` substitute: a trigger whose only job is to open a
44
+ * popover has no state worth keeping focusable, and native `disabled` is
45
+ * what takes it out of the tab order AND keeps the popover shut. That last
46
+ * part matters more than it looks: `PopoverContent` is portaled to
47
+ * `document.body`, so the calendar's Clear button is outside any wrapper a
48
+ * consumer could disable, and the closed popover is the only thing keeping
49
+ * it out of reach.
50
+ */
51
+ disabled?: boolean;
41
52
  };
42
- export declare function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid, required, errorId, placeholder, ariaLabel, clearLabel, locale }: DateRangePickerProps): import("react").JSX.Element;
53
+ export declare function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid, required, errorId, placeholder, ariaLabel, clearLabel, locale, disabled }: DateRangePickerProps): import("react").JSX.Element;
43
54
  export {};
44
55
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/enterprise/date-range-picker/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AASnD;;;;GAIG;AACH,KAAK,cAAc,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAA;AAE/D,MAAM,MAAM,cAAc,GAAG;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,cAAc,CAAA;IACrB,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC7C,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB,CAAA;AAkCD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,aAAa,EACb,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAe,EACf,QAAgB,EAChB,OAAO,EACP,WAAwB,EACxB,SAA+B,EAC/B,UAAoB,EACpB,MAAa,EACd,EAAE,oBAAoB,+BA8HtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/enterprise/date-range-picker/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AASnD;;;;GAIG;AACH,KAAK,cAAc,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAA;AAE/D,MAAM,MAAM,cAAc,GAAG;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,cAAc,CAAA;IACrB,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;IAC7C,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,SAAS,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAkCD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,aAAa,EACb,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAO,EACP,OAAe,EACf,QAAgB,EAChB,OAAO,EACP,WAAwB,EACxB,SAA+B,EAC/B,UAAoB,EACpB,MAAa,EACb,QAAgB,EACjB,EAAE,oBAAoB,+BAgJtB"}
@@ -20,7 +20,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
20
20
  * popover closed. Each segment is now a real `PopoverTrigger` button: focusable,
21
21
  * correctly named, and the element Radix returns focus to on dismissal.
22
22
  */
23
- import { useState } from 'react';
23
+ import { useEffect, useState } from 'react';
24
24
  import { CalendarDays } from 'lucide-react';
25
25
  import { enUS } from 'react-day-picker/locale';
26
26
  import { Button } from '../../components/ui/button/index.js';
@@ -59,12 +59,22 @@ function toDayString(value) {
59
59
  const day = String(value.getDate()).padStart(2, '0');
60
60
  return `${year}-${month}-${day}`;
61
61
  }
62
- export function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid = false, required = false, errorId, placeholder = 'Any date', ariaLabel = 'Select date range', clearLabel = 'Clear', locale = enUS }) {
62
+ export function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid = false, required = false, errorId, placeholder = 'Any date', ariaLabel = 'Select date range', clearLabel = 'Clear', locale = enUS, disabled = false }) {
63
63
  // Which segment's popover is open, if any. One root per segment means one open
64
64
  // state per segment; holding the identity of the open one here keeps them
65
65
  // mutually exclusive, so clicking "to" while "from" is open swaps the anchor
66
66
  // instead of stacking two calendars.
67
67
  const [openSegment, setOpenSegment] = useState(null);
68
+ // Suspending the control must FORGET which segment was open, not just hide
69
+ // it. The `open` prop below already shuts the popover in the same frame
70
+ // `disabled` flips, but a controlled Radix root never calls `onOpenChange`
71
+ // for a prop-driven close, so the identity survived: lifting `disabled`
72
+ // re-satisfied the same condition and the portaled calendar reopened with no
73
+ // user gesture, pulling focus into it.
74
+ useEffect(() => {
75
+ if (disabled)
76
+ setOpenSegment(null);
77
+ }, [disabled]);
68
78
  const fromDate = parseDay(value.from);
69
79
  const toDate = parseDay(value.to);
70
80
  const selected = fromDate || toDate ? { from: fromDate, to: toDate } : undefined;
@@ -84,11 +94,19 @@ export function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel,
84
94
  // Input and Select it sits with. font-sans, not font-mono: the dates are
85
95
  // figures in the body face with tabular figures, like every other figure in
86
96
  // the console.
87
- 'border-shadcn-400 bg-card hover:bg-secondary focus-visible:ring-ring flex h-9 items-center gap-2 rounded-md border px-3 text-left font-sans text-sm tabular-nums transition-colors duration-150 ease-out focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none sm:w-40', invalid && 'border-destructive');
97
+ 'border-shadcn-400 bg-card hover:bg-secondary focus-visible:ring-ring flex h-9 items-center gap-2 rounded-md border px-3 text-left font-sans text-sm tabular-nums transition-colors duration-150 ease-out focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none sm:w-40', invalid && 'border-destructive',
98
+ // Conditional rather than `disabled:` variants, matching how this file
99
+ // already applies `border-destructive`: with the prop absent the class
100
+ // string stays byte-identical to what consumers render today.
101
+ disabled && 'cursor-not-allowed opacity-50');
88
102
  // The one calendar, rendered inside whichever segment's popover is open. Both
89
103
  // segments read and write the SAME range selection, so which one you opened
90
104
  // changes only the anchor, never the state.
91
105
  const calendar = (_jsxs(_Fragment, { children: [_jsx(Calendar, { mode: "range", numberOfMonths: 2, defaultMonth: fromDate ?? toDate ?? new Date(), selected: selected, onSelect: onSelect, locale: locale }), _jsx("div", { className: "border-border flex items-center justify-end border-t px-3 py-2", children: _jsx(Button, { type: "button", variant: "plain", size: "small", disabled: !hasValue, onClick: () => onValueChange({ from: '', to: '' }), children: clearLabel }) })] }));
92
- const renderSegment = (segment, id, label, dateValue) => (_jsxs(Popover, { open: openSegment === segment, onOpenChange: (next) => setOpenSegment(next ? segment : null), children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { htmlFor: id, children: [label, required ? ' *' : ''] }), _jsxs(PopoverTrigger, { type: "button", id: id, className: triggerClass, "aria-invalid": invalid || undefined, "aria-describedby": invalid ? errorId : undefined, children: [_jsx(CalendarDays, { className: "text-muted-foreground size-4 shrink-0", "aria-hidden": true }), dateValue ? (_jsx("span", { children: dateValue })) : (_jsx("span", { className: "text-muted-foreground", children: placeholder }))] })] }), _jsx(PopoverContent, { align: "start", className: "w-auto p-0", "aria-label": ariaLabel, children: calendar })] }, segment));
106
+ const renderSegment = (segment, id, label, dateValue) => (_jsxs(Popover, {
107
+ // A disabled trigger cannot open a popover, but a surface that suspends
108
+ // itself WHILE one is open (a filter bar flipping to "show all") would
109
+ // otherwise leave the portaled calendar on screen with no way back to it.
110
+ open: !disabled && openSegment === segment, onOpenChange: (next) => setOpenSegment(next ? segment : null), children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { htmlFor: id, children: [label, required ? ' *' : ''] }), _jsxs(PopoverTrigger, { type: "button", id: id, className: triggerClass, disabled: disabled, "aria-invalid": invalid || undefined, "aria-describedby": invalid ? errorId : undefined, children: [_jsx(CalendarDays, { className: "text-muted-foreground size-4 shrink-0", "aria-hidden": true }), dateValue ? (_jsx("span", { children: dateValue })) : (_jsx("span", { className: "text-muted-foreground", children: placeholder }))] })] }), _jsx(PopoverContent, { align: "start", className: "w-auto p-0", "aria-label": ariaLabel, children: calendar })] }, segment));
93
111
  return (_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-end", children: [renderSegment('from', fromId, fromLabel, value.from), renderSegment('to', toId, toLabel, value.to)] }));
94
112
  }
package/dist/globals.css CHANGED
@@ -216,10 +216,33 @@
216
216
 
217
217
  /* Application tokens (from Figma ring-tokens) */
218
218
  --body-surface: 240 5% 96%; /* base/100 = #F4F4F5 */
219
- --body-text: 240 4% 46%; /* base/500 = #71717A */
219
+ /*
220
+ --body-text and --container-text below both step one notch off base/500.
221
+ base/500 clears AA on white (4.86:1) but not on --body-surface, the
222
+ recessed page ground, where it read 4.43:1. That ground is exactly where
223
+ the components carrying these two tokens sit: the resting tabs trigger
224
+ (text-sm font-medium, and its hover fill is --card precisely because the
225
+ resting ground is not white), the breadcrumb page and separator, the
226
+ stepper item text and description. An axe crawl of the br-sfn cockpit
227
+ flagged .text-body-text on 2026-09-06; the ratio it printed does not
228
+ reproduce here, and the measured pair on this token's own ground is
229
+ 4.40:1 at 8-bit precision (4.43:1 in float).
230
+
231
+ 45% is the smallest step that clears it: 4.59:1 on --body-surface, up
232
+ from 4.43:1, and 5.04:1 on white. Both stay eleven notches clear of
233
+ --container-title (base/600), so the text-versus-title step holds.
234
+
235
+ Deliberately not stepped far enough to clear --muted (#E4E4E7), which
236
+ would take base/500 minus five notches and visibly darken tabs,
237
+ breadcrumbs and steppers. No component in THIS kit paints either token on
238
+ that fill. A consumer that does is a call-site fix on its side, not a
239
+ clean bill from here: product-console's extract-table pairs bg-muted with
240
+ text-body-text and reads 3.98:1 in light.
241
+ */
242
+ --body-text: 240 4% 45%; /* one notch off base/500: #6E6E77, AA on body-surface */
220
243
  --body-title: 240 5% 26%; /* base/700 = #3F3F46 */
221
244
  --container-surface: 0 0% 100%; /* base/white = #FFFFFF */
222
- --container-text: 240 4% 46%; /* base/500 = #71717A */
245
+ --container-text: 240 4% 45%; /* one notch off base/500: #6E6E77, AA on body-surface */
223
246
  --container-title: 240 5% 34%; /* base/600 = #52525B */
224
247
  --container-border: 0 0% 100%; /* #FFFFFF */
225
248
  --link: 240 4% 16%; /* base/800 = #27272A */
@@ -232,7 +255,14 @@
232
255
  --background: 0 0% 100%; /* base/white = #FFFFFF */
233
256
  --foreground: 240 5% 26%; /* base/700 = #3F3F46 */
234
257
  --card: 0 0% 100%; /* = container-surface (white) */
235
- --card-foreground: 240 4% 46%; /* = container-text (base/500) */
258
+ /* Held at base/500, the step --container-text just moved off: this token's
259
+ own ground is --card (white), where base/500 clears AA at 4.86:1. It
260
+ measures 3.84:1 over --muted, but no component in this kit renders it
261
+ there: the data table pins its own inks rather than inheriting this one
262
+ (`ui/table/index.tsx`: cells take text-foreground, heads take
263
+ text-muted-foreground), so the hovered-row ground never sits under this
264
+ token. */
265
+ --card-foreground: 240 4% 46%; /* base/500 = #71717A */
236
266
  --popover: 0 0% 100%; /* base/white */
237
267
  --popover-foreground: 240 5% 34%; /* base/600 = #52525B */
238
268
  --primary: 240 4% 16%; /* base/800 = #27272A */
@@ -254,10 +284,16 @@
254
284
  --destructive-foreground: 0 0% 100%; /* white */
255
285
  --border: 240 5% 90%; /* base/200 = #E4E4E7 */
256
286
  --input: 0 0% 100%; /* base/white */
257
- /* One step lighter than --muted-foreground, which is also the label colour:
258
- a placeholder that matches its own label has no hierarchy left to give.
259
- Still 4.4:1 on the shadcn-100 field, so it stays legible as a hint. */
260
- --input-placeholder: 240 4% 46%; /* base/500 = #71717A */
287
+ /* Lighter than --muted-foreground, which is also the label colour: a
288
+ placeholder that matches its own label has no hierarchy left to give.
289
+ Stepped off base/500 with --body-text, and for the same reason. The
290
+ field paints no fill of its own (`input/styles.css`: .input-wrapper is
291
+ border-only, .input-base is bg-transparent), so the hint sits on
292
+ whatever ground contains the form, and on --body-surface base/500 read
293
+ 4.40:1. 45% brings that to 4.59:1 and 5.04:1 on white. The retired note
294
+ here claimed 4.4:1 was acceptable "on the shadcn-100 field", which named
295
+ a fill the component never paints. */
296
+ --input-placeholder: 240 4% 45%; /* one notch off base/500: #6E6E77, AA on body-surface */
261
297
  --ring: 240 5% 65%; /* focus ring */
262
298
  --radius: 0.5rem;
263
299
 
@@ -349,7 +385,36 @@
349
385
  --background: 240 4% 16%; /* base/800 = #27272A */
350
386
  --foreground: 240 5% 96%; /* base/100 = #F4F4F5 */
351
387
  --muted: 240 5% 34%; /* base/600 = #52525B */
352
- --muted-foreground: 240 5% 65%; /* base/400 = #A1A1AA */
388
+ /*
389
+ Lifted off the base scale. base/400 read 4.09:1 on --container-surface
390
+ (which --card aliases) and 3.01:1 on --muted, both under the 4.5 AA floor
391
+ for normal text, and this is the kit's universal secondary ink: every
392
+ TableHead column label (text-sm font-medium inside a text-sm Table),
393
+ every empty-state line, every form label through LABEL_VOICE_CLASS, every
394
+ MoneyText caption. An axe crawl of the br-sfn cockpit measured both pairs
395
+ on 2026-09-06; every console on this library inherited them.
396
+
397
+ 80% is the smallest round step that clears both: 6.36:1 on
398
+ --container-surface and 4.68:1 on --muted. base/300 is the nearest scale
399
+ step that would also clear, but it is byte-identical to this theme's
400
+ --body-text, so adopting it would collapse the muted-versus-body step the
401
+ two tokens exist to express. Staying on the scale is worth less than
402
+ keeping that step, so the value leaves the scale instead.
403
+
404
+ That step is now thin, and the trade was made with eyes open: 80% sits at
405
+ L* 81 against --body-text at L* 85, so the two inks are 1.12:1 apart and
406
+ barely separable side by side. Legibility won because the weight is all
407
+ on one side: this token has 86 call sites and is read as prose on every
408
+ surface, while --body-text has one (an uppercase sidebar group label) plus
409
+ two derived theme keys. A reader who needs the step to be visible should
410
+ move --body-text, which is cheap to reposition, rather than push this one
411
+ back under the AA floor.
412
+
413
+ Repairs a fourth pair on the way: the disabled chip in autocomplete and
414
+ multiple-select paints text-muted on bg-muted-foreground, 3.01:1 before
415
+ and 4.68:1 now.
416
+ */
417
+ --muted-foreground: 240 5% 80%; /* between base/400 and base/300: #C9C9CF, AA on container-surface and muted */
353
418
  --card: var(--container-surface);
354
419
  --card-foreground: var(--container-text);
355
420
  --popover: 240 4% 16%; /* base/800 = #27272A */
@@ -359,7 +424,7 @@
359
424
  /* Held at base/400 rather than stepped down with the light theme: on the
360
425
  dark field that is already only 5.8:1, and going lighter here means
361
426
  darker, which would push the hint under the AA floor. */
362
- --input-placeholder: 240 5% 65%; /* base/400 = #A1A1AA */
427
+ --input-placeholder: 240 5% 80%; /* between base/400 and base/300: #C9C9CF, AA on card (6.3:1); base/400 read 4.07:1 there */
363
428
  --primary: 240 5% 96%; /* base/100 = #F4F4F5 */
364
429
  --primary-foreground: 240 4% 16%; /* base/800 = #27272A */
365
430
  --secondary: 240 4% 16%; /* base/800 = #27272A */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lerianstudio/sindarian-ui",
3
- "version": "1.4.0-beta.5",
3
+ "version": "1.4.0-beta.7",
4
4
  "description": "Sindarian UI - A UI library for Midaz Console",
5
5
  "license": "ISC",
6
6
  "author": {