@amboss/design-system 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/build/cjs/components/BulkActionsToolbar/BulkActionsToolbar.js +1 -1
  2. package/build/cjs/components/DataTable/DataTable.d.ts +18 -8
  3. package/build/cjs/components/DataTable/DataTable.js +1 -1
  4. package/build/cjs/components/DataTable/DataTableToolbar/DataTableToolbar.d.ts +97 -0
  5. package/build/cjs/components/DataTable/DataTableToolbar/DataTableToolbar.js +1 -0
  6. package/build/cjs/components/DataTable/TableCell.js +1 -1
  7. package/build/cjs/components/DataTable/TableColumnGroups.d.ts +15 -0
  8. package/build/cjs/components/DataTable/TableColumnGroups.js +1 -0
  9. package/build/cjs/components/DataTable/header/DataTableHeaderLabel.d.ts +28 -0
  10. package/build/cjs/components/DataTable/header/DataTableHeaderLabel.js +1 -0
  11. package/build/cjs/components/DataTable/header/TableHeader.js +1 -1
  12. package/build/cjs/components/DataTable/index.d.ts +1 -1
  13. package/build/cjs/components/DataTable/internal/buildColumnSegments.d.ts +18 -0
  14. package/build/cjs/components/DataTable/internal/buildColumnSegments.js +1 -0
  15. package/build/cjs/components/DataTable/internal/buildInternalColumns.d.ts +2 -2
  16. package/build/cjs/components/DataTable/internal/buildInternalColumns.js +1 -1
  17. package/build/cjs/components/DataTable/internal/getColumnSticky.js +1 -1
  18. package/build/cjs/components/DataTable/internal/types.d.ts +7 -2
  19. package/build/cjs/components/DataTable/isDataTableColumnGroup.d.ts +2 -0
  20. package/build/cjs/components/DataTable/isDataTableColumnGroup.js +1 -0
  21. package/build/cjs/components/DataTable/types.d.ts +52 -8
  22. package/build/cjs/components/DataTable/useDataTableSort.d.ts +4 -4
  23. package/build/cjs/components/DataTable/useDataTableSort.js +1 -1
  24. package/build/cjs/components/FilterToolbar/FilterToolbar.d.ts +103 -0
  25. package/build/cjs/components/FilterToolbar/FilterToolbar.js +1 -0
  26. package/build/cjs/components/Internal/Panel/Panel.d.ts +22 -1
  27. package/build/cjs/components/Internal/Panel/Panel.js +1 -1
  28. package/build/cjs/components/Internal/ToolbarItemCount/ToolbarItemCount.d.ts +16 -0
  29. package/build/cjs/components/Internal/ToolbarItemCount/ToolbarItemCount.js +1 -0
  30. package/build/cjs/components/Kbd/Kbd.d.ts +118 -0
  31. package/build/cjs/components/Kbd/Kbd.js +1 -0
  32. package/build/cjs/components/KbdGroup/KbdGroup.d.ts +7 -0
  33. package/build/cjs/components/KbdGroup/KbdGroup.js +1 -0
  34. package/build/cjs/components/Layout/-types.d.ts +6 -1
  35. package/build/cjs/components/Layout/Layout.js +1 -1
  36. package/build/cjs/components/Layout/LeftPanel/LeftPanelDocked.js +1 -1
  37. package/build/cjs/components/Layout/RightPanel/RightPanel.js +1 -1
  38. package/build/cjs/components/Layout/registry.d.ts +4 -0
  39. package/build/cjs/components/Layout/registry.js +1 -1
  40. package/build/cjs/components/Layout/usePanelDockedSlide.d.ts +4 -1
  41. package/build/cjs/components/Layout/usePanelDockedSlide.js +1 -1
  42. package/build/cjs/components/RangePopover/RangePopover.d.ts +7 -1
  43. package/build/cjs/components/RangePopover/RangePopover.js +1 -1
  44. package/build/cjs/components/Tooltip/BaseTooltip.js +1 -1
  45. package/build/cjs/components/Tooltip/Tooltip.d.ts +3 -1
  46. package/build/cjs/components/Tooltip/Tooltip.js +1 -1
  47. package/build/cjs/index.d.ts +1 -0
  48. package/build/cjs/index.js +1 -1
  49. package/build/cjs/shared/useIsMacOs.d.ts +1 -0
  50. package/build/cjs/shared/useIsMacOs.js +1 -0
  51. package/build/cjs/web-tokens/_colors.json +26 -0
  52. package/build/cjs/web-tokens/_sizes.json +10 -0
  53. package/build/cjs/web-tokens/visualConfig.d.ts +25 -0
  54. package/build/cjs/web-tokens/visualConfig.js +1 -1
  55. package/build/esm/components/BulkActionsToolbar/BulkActionsToolbar.js +1 -1
  56. package/build/esm/components/DataTable/DataTable.d.ts +18 -8
  57. package/build/esm/components/DataTable/DataTable.js +1 -1
  58. package/build/esm/components/DataTable/DataTableToolbar/DataTableToolbar.d.ts +97 -0
  59. package/build/esm/components/DataTable/DataTableToolbar/DataTableToolbar.js +1 -0
  60. package/build/esm/components/DataTable/TableCell.js +1 -1
  61. package/build/esm/components/DataTable/TableColumnGroups.d.ts +15 -0
  62. package/build/esm/components/DataTable/TableColumnGroups.js +1 -0
  63. package/build/esm/components/DataTable/header/DataTableHeaderLabel.d.ts +28 -0
  64. package/build/esm/components/DataTable/header/DataTableHeaderLabel.js +1 -0
  65. package/build/esm/components/DataTable/header/TableHeader.js +1 -1
  66. package/build/esm/components/DataTable/index.d.ts +1 -1
  67. package/build/esm/components/DataTable/internal/buildColumnSegments.d.ts +18 -0
  68. package/build/esm/components/DataTable/internal/buildColumnSegments.js +1 -0
  69. package/build/esm/components/DataTable/internal/buildInternalColumns.d.ts +2 -2
  70. package/build/esm/components/DataTable/internal/buildInternalColumns.js +1 -1
  71. package/build/esm/components/DataTable/internal/getColumnSticky.js +1 -1
  72. package/build/esm/components/DataTable/internal/types.d.ts +7 -2
  73. package/build/esm/components/DataTable/isDataTableColumnGroup.d.ts +2 -0
  74. package/build/esm/components/DataTable/isDataTableColumnGroup.js +1 -0
  75. package/build/esm/components/DataTable/types.d.ts +52 -8
  76. package/build/esm/components/DataTable/useDataTableSort.d.ts +4 -4
  77. package/build/esm/components/DataTable/useDataTableSort.js +1 -1
  78. package/build/esm/components/FilterToolbar/FilterToolbar.d.ts +103 -0
  79. package/build/esm/components/FilterToolbar/FilterToolbar.js +1 -0
  80. package/build/esm/components/Internal/Panel/Panel.d.ts +22 -1
  81. package/build/esm/components/Internal/Panel/Panel.js +1 -1
  82. package/build/esm/components/Internal/ToolbarItemCount/ToolbarItemCount.d.ts +16 -0
  83. package/build/esm/components/Internal/ToolbarItemCount/ToolbarItemCount.js +1 -0
  84. package/build/esm/components/Kbd/Kbd.d.ts +118 -0
  85. package/build/esm/components/Kbd/Kbd.js +1 -0
  86. package/build/esm/components/KbdGroup/KbdGroup.d.ts +7 -0
  87. package/build/esm/components/KbdGroup/KbdGroup.js +1 -0
  88. package/build/esm/components/Layout/-types.d.ts +6 -1
  89. package/build/esm/components/Layout/Layout.js +1 -1
  90. package/build/esm/components/Layout/LeftPanel/LeftPanelDocked.js +1 -1
  91. package/build/esm/components/Layout/RightPanel/RightPanel.js +1 -1
  92. package/build/esm/components/Layout/registry.d.ts +4 -0
  93. package/build/esm/components/Layout/registry.js +1 -1
  94. package/build/esm/components/Layout/usePanelDockedSlide.d.ts +4 -1
  95. package/build/esm/components/Layout/usePanelDockedSlide.js +1 -1
  96. package/build/esm/components/RangePopover/RangePopover.d.ts +7 -1
  97. package/build/esm/components/RangePopover/RangePopover.js +1 -1
  98. package/build/esm/components/Tooltip/BaseTooltip.js +1 -1
  99. package/build/esm/components/Tooltip/Tooltip.d.ts +3 -1
  100. package/build/esm/components/Tooltip/Tooltip.js +1 -1
  101. package/build/esm/index.d.ts +1 -0
  102. package/build/esm/index.js +1 -1
  103. package/build/esm/shared/useIsMacOs.d.ts +1 -0
  104. package/build/esm/shared/useIsMacOs.js +1 -0
  105. package/build/esm/web-tokens/_colors.json +26 -0
  106. package/build/esm/web-tokens/_sizes.json +10 -0
  107. package/build/esm/web-tokens/visualConfig.d.ts +25 -0
  108. package/build/esm/web-tokens/visualConfig.js +1 -1
  109. package/build/scss/_dark.scss +3 -0
  110. package/build/scss/_light.scss +3 -0
  111. package/build/scss/_variables.scss +4 -0
  112. package/build/scss/subthemes/_inverted.scss +6 -0
  113. package/package.json +1 -1
@@ -0,0 +1,97 @@
1
+ import type { ReactElement } from "react";
2
+ import type { ConditionalAccessibleName } from "../../../types/a11y";
3
+ import type { ForbiddenProp } from "../../../types/type-helpers";
4
+ import type { SlotProps } from "../../../shared/slotProps";
5
+ import type { BulkActionsToolbarProps } from "../../BulkActionsToolbar/BulkActionsToolbar";
6
+ import type { FilterToolbarContentProps } from "../../FilterToolbar/FilterToolbar";
7
+ type DataTableToolbarFilterContent = Omit<FilterToolbarContentProps, "totalItems" | "searchInputProps">;
8
+ type NoAccessibleName = {
9
+ "aria-label"?: ForbiddenProp<"Fix: isSelectable={false} renders no toolbar row for aria-label to name. Remove it.">;
10
+ "aria-labelledby"?: ForbiddenProp<"Fix: isSelectable={false} renders no toolbar row for aria-labelledby to name. Remove it.">;
11
+ };
12
+ type DataTableToolbarFilterSlotProps = Required<SlotProps<{
13
+ /** Accessible name of the filter row's control group. */
14
+ filterToolbar: {
15
+ "aria-label": string;
16
+ };
17
+ }>>;
18
+ /**
19
+ * Each row keeps its own accessible name, required only while that row
20
+ * renders: `slotProps.filterToolbar` for the filter row's `role="group"` (an
21
+ * unnamed group is worse than none), and `aria-label`/`aria-labelledby` for
22
+ * the bulk row's `role="toolbar"`. The second arm drops the toolbar name
23
+ * because that row never renders there.
24
+ *
25
+ * The first arm keeps `isSelectable` a plain `boolean` on purpose: a caller
26
+ * deciding selection at runtime can't prove the bulk row is gone, so it still
27
+ * owes a name and still type-checks without branching its JSX.
28
+ */
29
+ type DataTableToolbarFilterSlice = ({
30
+ slotProps: DataTableToolbarFilterSlotProps;
31
+ /**
32
+ * Whether the rows can be selected. When `false`, the bulk-actions row is
33
+ * hidden and the filter row takes over the item count.
34
+ * @default true
35
+ */
36
+ isSelectable?: boolean;
37
+ } & DataTableToolbarFilterContent & ConditionalAccessibleName) | ({
38
+ slotProps: DataTableToolbarFilterSlotProps;
39
+ isSelectable: false;
40
+ } & NoAccessibleName & DataTableToolbarFilterContent) | ({
41
+ slotProps?: never;
42
+ isSelectable?: true;
43
+ segmentedControlProps?: never;
44
+ filters?: never;
45
+ } & ConditionalAccessibleName);
46
+ /**
47
+ * Forwarded to `BulkActionsToolbar`. Each prop keeps that component's own type,
48
+ * so the two cannot drift.
49
+ */
50
+ type DataTableToolbarBulkActionsSlice = {
51
+ /** Bulk action buttons, e.g. "Edit", "Export", "Delete". */
52
+ actions?: BulkActionsToolbarProps["actions"];
53
+ /**
54
+ * Select-all checkbox shown at the start of the bulk row. Omit it when the
55
+ * table renders its own select-all checkbox, to avoid a duplicate control.
56
+ */
57
+ checkboxProps?: BulkActionsToolbarProps["checkboxProps"];
58
+ /**
59
+ * Item count, e.g. `"50 items"`. Shown in the bulk row, or in the filter row
60
+ * when `isSelectable` is `false`. It is the toolbar's single live region.
61
+ */
62
+ totalItems: BulkActionsToolbarProps["totalItems"];
63
+ /** Content rendered at the end of the bulk row. Receives the toolbar's own id. */
64
+ renderRightContent?: BulkActionsToolbarProps["renderRightContent"];
65
+ /** Keeps the right content on one line on narrow viewports. */
66
+ rightContentNoWrapOnMobile?: BulkActionsToolbarProps["rightContentNoWrapOnMobile"];
67
+ /** Hides the divider below the toolbar, whichever row is the last one. */
68
+ hideDivider?: BulkActionsToolbarProps["hideDivider"];
69
+ };
70
+ export type DataTableToolbarProps = DataTableToolbarFilterSlice & DataTableToolbarBulkActionsSlice & {
71
+ /**
72
+ * Search field. It renders in the filter row when that row exists, and in
73
+ * the bulk row otherwise.
74
+ */
75
+ searchInputProps?: FilterToolbarContentProps["searchInputProps"];
76
+ "data-e2e-test-id"?: string;
77
+ };
78
+ /**
79
+ * The controls above a `DataTable`: a `FilterToolbar` (segmented control,
80
+ * filter and column triggers, search) above the existing `BulkActionsToolbar`
81
+ * row.
82
+ *
83
+ * - `isSelectable` (default `true`) decides whether the bulk-actions row
84
+ * renders. The item count sits in that row when it renders, and in the
85
+ * filter row otherwise.
86
+ * - The search field sits in the filter row when that row renders, and in the
87
+ * bulk-actions row otherwise.
88
+ *
89
+ * For a filter row on its own, import `FilterToolbar` directly. It has no
90
+ * dependency on `DataTable`.
91
+ *
92
+ * Render as a sibling of `BaseDataTable` inside a shared
93
+ * `<Container elevation={1}>`, never as a child of `<DataTable>`. `DataTable`
94
+ * renders its `children` inside the `<table>` itself.
95
+ */
96
+ export declare function DataTableToolbar({ slotProps, isSelectable, segmentedControlProps, filters, searchInputProps, actions, checkboxProps, totalItems, renderRightContent, rightContentNoWrapOnMobile, hideDivider, "data-e2e-test-id": dataE2eTestId, ...ariaAttributes }: DataTableToolbarProps): ReactElement;
97
+ export {};
@@ -0,0 +1 @@
1
+ import React from"react";import styled from"@emotion/styled";import{Divider}from"../../Divider/Divider";import{BulkActionsToolbar}from"../../BulkActionsToolbar/BulkActionsToolbar";import{FilterToolbar}from"../../FilterToolbar/FilterToolbar";let hasAccessibleName=ariaAttributes=>"aria-label"in ariaAttributes||"aria-labelledby"in ariaAttributes,StyledToolbar=styled("div",{target:"e116raau0",label:"StyledToolbar"})(({theme,hasFilterRow})=>({borderTopLeftRadius:"inherit",borderTopRightRadius:"inherit",backgroundColor:hasFilterRow?theme.values.color.background.secondary.default:theme.values.color.background.primary.default}));export function DataTableToolbar({slotProps,isSelectable=!0,segmentedControlProps,filters,searchInputProps,actions,checkboxProps,totalItems,renderRightContent,rightContentNoWrapOnMobile,hideDivider,"data-e2e-test-id":dataE2eTestId,...ariaAttributes}){let showFilterRow=!!segmentedControlProps||!!filters?.length||!isSelectable;return React.createElement(StyledToolbar,{hasFilterRow:showFilterRow,"data-ds-id":"DataTableToolbar","data-e2e-test-id":dataE2eTestId},showFilterRow&&void 0!==slotProps&&React.createElement(React.Fragment,null,React.createElement(FilterToolbar,{"aria-label":slotProps.filterToolbar["aria-label"],totalItems:isSelectable?void 0:totalItems,segmentedControlProps:segmentedControlProps,filters:filters,searchInputProps:searchInputProps}),(isSelectable||!hideDivider)&&React.createElement(Divider,null)),isSelectable&&hasAccessibleName(ariaAttributes)&&React.createElement(BulkActionsToolbar,{actions:actions,checkboxProps:checkboxProps,totalItems:totalItems,searchInputProps:showFilterRow?void 0:searchInputProps,renderRightContent:renderRightContent,rightContentNoWrapOnMobile:rightContentNoWrapOnMobile,hideDivider:hideDivider,...ariaAttributes}))}
@@ -1 +1 @@
1
- import styled from"@emotion/styled";import{useResponsiveValue}from"../../shared/mediaQueries";let TableCell=styled("td",{target:"e16jwji60",label:"TableCell"})(({theme,sticky,isTableScrolledToLeft,isTableScrolledToRight,alignColumn,columnWidth,isActive,isSelected})=>({borderBottom:`1px solid ${theme.values.color.divider.primary}`,backgroundColor:theme.values.color.background.primary.default,...isActive&&{backgroundColor:theme.values.color.background.secondary.default},...isSelected&&{backgroundColor:theme.values.color.background.accentSubtle.default},...useResponsiveValue({width:columnWidth,textAlign:alignColumn}),..."left"===sticky&&!isTableScrolledToLeft&&{position:"sticky",left:0,borderRight:`1px solid ${theme.values.color.divider.primary}`,zIndex:1},..."right"===sticky&&!isTableScrolledToRight&&{position:"sticky",right:0,borderLeft:`1px solid ${theme.values.color.divider.primary}`,zIndex:1}}));export default TableCell;
1
+ import styled from"@emotion/styled";import{useResponsiveValue}from"../../shared/mediaQueries";let TableCell=styled("td",{target:"e1tso8ln0",label:"TableCell"})(({theme,sticky,isTableScrolledToLeft,isTableScrolledToRight,alignColumn,columnWidth,isActive,isSelected})=>({borderBottom:`1px solid ${theme.values.color.divider.primary}`,backgroundColor:theme.values.color.background.primary.default,...isActive&&{backgroundColor:theme.values.color.background.secondary.default},...isSelected&&{backgroundColor:theme.values.color.background.accentSubtle.default},...useResponsiveValue({width:columnWidth,textAlign:alignColumn}),..."left"===sticky&&{borderRight:"1px solid transparent",...!isTableScrolledToLeft&&{position:"sticky",left:0,borderRightColor:theme.values.color.divider.primary,zIndex:1}},..."right"===sticky&&{borderLeft:"1px solid transparent",...!isTableScrolledToRight&&{position:"sticky",right:0,borderLeftColor:theme.values.color.divider.primary,zIndex:1}}}));export default TableCell;
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import type { ColumnSegment } from "./internal/buildColumnSegments";
3
+ type TableColumnGroupsProps = {
4
+ segments: ColumnSegment[];
5
+ };
6
+ /**
7
+ * The `<colgroup>` elements that back the header, one per segment.
8
+ *
9
+ * A group header carries `scope="colgroup"`, which names a real column group and
10
+ * nothing else — `colspan` alone does not create one. The `<col>` widths also give
11
+ * a grouped column a width under `layout="fixed"`, where a fixed layout reads the
12
+ * first header row and the grouped column sits in the second.
13
+ */
14
+ declare function TableColumnGroups({ segments, }: TableColumnGroupsProps): React.ReactElement;
15
+ export default TableColumnGroups;
@@ -0,0 +1 @@
1
+ import React from"react";import styled from"@emotion/styled";import{useResponsiveValue}from"../../shared/mediaQueries";let StyledCol=styled("col",{target:"e12f45xj0",label:"StyledCol"})(({columnWidth})=>({...useResponsiveValue({width:columnWidth})}));export default function({segments}){return React.createElement(React.Fragment,null,segments.map(segment=>React.createElement("colgroup",{key:segment.columns[0].column.name},segment.columns.map(({column})=>React.createElement(StyledCol,{key:column.name,columnWidth:column.width})))))}
@@ -0,0 +1,28 @@
1
+ import type { ReactElement } from "react";
2
+ import React from "react";
3
+ import type { SlotProps } from "../../../shared/slotProps";
4
+ import type { DataTableColumn, DataTableColumnDefinition, DataTableColumnGroup } from "../types";
5
+ /**
6
+ * `aria-label` is required inside the slot because the toggletip trigger is an
7
+ * icon-only button, which needs an accessible name (WCAG 2.1 SC 4.1.2).
8
+ */
9
+ type DataTableHeaderLabelSlotProps = SlotProps<{
10
+ toggletip: {
11
+ content: ReactElement;
12
+ "aria-label": string;
13
+ };
14
+ }>;
15
+ /**
16
+ * Builds the slot in one place, so both header call sites pair `content` with
17
+ * its `aria-label` the same way. The project runs without `strictNullChecks`,
18
+ * so nothing here would flag an `aria-label` that arrives undefined.
19
+ */
20
+ export declare const toToggletipSlotProps: (source: DataTableColumnDefinition | DataTableColumnGroup) => DataTableHeaderLabelSlotProps | undefined;
21
+ type DataTableHeaderLabelProps = {
22
+ label: string;
23
+ align: DataTableColumn["align"];
24
+ /** Props forwarded to internal sub-components */
25
+ slotProps?: DataTableHeaderLabelSlotProps;
26
+ };
27
+ export declare function DataTableHeaderLabel({ label, align, slotProps, }: Readonly<DataTableHeaderLabelProps>): React.ReactElement;
28
+ export {};
@@ -0,0 +1 @@
1
+ import React from"react";import{Inline}from"../../Inline/Inline";import{StyledLabel}from"./SortableCellContent";import{DataTableHeaderToggletip}from"./DataTableHeaderToggletip";export const toToggletipSlotProps=source=>source.toggletipContent?{toggletip:{content:source.toggletipContent,"aria-label":source.toggletipAriaLabel}}:void 0;export function DataTableHeaderLabel({label,align,slotProps}){return React.createElement(Inline,{space:"xs",noWrap:!0,vAlignItems:"center",alignItems:align},React.createElement(StyledLabel,{as:"span",size:"s",alignColumn:align,color:"tertiary",normalizeHeight:!0},label),slotProps?.toggletip&&React.createElement(DataTableHeaderToggletip,{content:slotProps.toggletip.content,"aria-label":slotProps.toggletip["aria-label"]}))}
@@ -1 +1 @@
1
- import React from"react";import styled from"@emotion/styled";import TableCell from"../TableCell";import{getColumnSticky}from"../internal/getColumnSticky";import{isControlColumn}from"../internal/types";import{SortableCellContent,StyledLabel}from"./SortableCellContent";import{Inline}from"../../Inline/Inline";import{DataTableHeaderToggletip}from"./DataTableHeaderToggletip";let SORT_DIRECTION_TEXT={asc:"ascending",desc:"descending"},StyledThead=styled("thead",{target:"eqohg680",label:"StyledThead"})(({maxHeight})=>({...maxHeight&&{position:"sticky",top:0,zIndex:2}})),StyledTh=styled(TableCell,{target:"eqohg681",label:"StyledTh"})(({theme})=>({padding:`${theme.variables.size.spacing.s} ${theme.variables.size.spacing.m}`,whiteSpace:"nowrap",backgroundColor:theme.values.color.background.primary.default}));export default React.memo(function({className,columns,isFirstColumnSticky,isLastColumnSticky,currentlySortedByColumn,isTableScrolledToLeft,isTableScrolledToRight,maxHeight,allRowsSelected,selectedRowIds,selectAllRowsCheckboxAriaLabel,onAllRowsSelectionChange,onSort}){let headerContext={onSort,currentlySortedByColumn,allRowsSelected,selectedRowIds,selectAllRowsCheckboxAriaLabel,onAllRowsSelectionChange},columnElements=columns.map((column,columnIndex)=>{let headerElm,sticky=getColumnSticky({column,columnIndex,columns,isFirstColumnSticky,isLastColumnSticky});if(isControlColumn(column))return React.createElement(StyledTh,{as:"th",alignColumn:"left",columnWidth:column.width,key:column.name,sticky:sticky,isTableScrolledToLeft:isTableScrolledToLeft,isTableScrolledToRight:isTableScrolledToRight,scope:"col"},column.renderHeader(headerContext));let{isSortable,label,align="left",width,name,sortDirection,toggletipContent,toggletipAriaLabel}=column;headerElm=isSortable?React.createElement(SortableCellContent,{onSort:onSort,name:name,label:label,sortDirection:sortDirection,align:align,currentlySortedByColumn:currentlySortedByColumn,toggletipContent:toggletipContent,toggletipAriaLabel:toggletipAriaLabel}):React.createElement(Inline,{space:"xs",noWrap:!0,vAlignItems:"center",alignItems:align},React.createElement(StyledLabel,{as:"span",size:"s",alignColumn:align,color:"tertiary",normalizeHeight:!0},label),toggletipContent&&React.createElement(DataTableHeaderToggletip,{content:toggletipContent,"aria-label":toggletipAriaLabel}));let ariaSortAttr=currentlySortedByColumn===name&&{"aria-sort":SORT_DIRECTION_TEXT[sortDirection]};return React.createElement(StyledTh,{as:"th",alignColumn:align,columnWidth:width,key:name,sticky:sticky,isTableScrolledToLeft:isTableScrolledToLeft,isTableScrolledToRight:isTableScrolledToRight,scope:"col",...ariaSortAttr},headerElm)});return React.createElement(StyledThead,{className:className,maxHeight:maxHeight},React.createElement("tr",null,columnElements))});
1
+ import React from"react";import styled from"@emotion/styled";import TableCell from"../TableCell";import{getColumnSticky}from"../internal/getColumnSticky";import{isControlColumn}from"../internal/types";import{buildColumnSegments}from"../internal/buildColumnSegments";import{SortableCellContent}from"./SortableCellContent";import{DataTableHeaderLabel,toToggletipSlotProps}from"./DataTableHeaderLabel";let SORT_DIRECTION_TEXT={asc:"ascending",desc:"descending"},StyledThead=styled("thead",{target:"elv391v0",label:"StyledThead"})(({maxHeight})=>({...maxHeight&&{position:"sticky",top:0,zIndex:2}})),StyledTh=styled(TableCell,{target:"elv391v1",label:"StyledTh"})(({theme,hasDivider})=>({padding:`${theme.variables.size.spacing.s} ${theme.variables.size.spacing.m}`,whiteSpace:"nowrap",backgroundColor:theme.values.color.background.primary.default,...hasDivider&&{borderRight:`1px solid ${theme.values.color.divider.primary}`}}));export default React.memo(function({className,columns,isFirstColumnSticky,isLastColumnSticky,currentlySortedByColumn,isTableScrolledToLeft,isTableScrolledToRight,maxHeight,allRowsSelected,selectedRowIds,selectAllRowsCheckboxAriaLabel,onAllRowsSelectionChange,onSort}){let headerContext={onSort,currentlySortedByColumn,allRowsSelected,selectedRowIds,selectAllRowsCheckboxAriaLabel,onAllRowsSelectionChange},renderColumnCell=({column,columnIndex},rowSpan)=>{let headerElm,isLastColumn=columnIndex===columns.length-1,sticky=getColumnSticky({column,columnIndex,columns,isFirstColumnSticky,isLastColumnSticky});if(isControlColumn(column))return React.createElement(StyledTh,{as:"th",alignColumn:"left",columnWidth:column.width,key:column.name,rowSpan:rowSpan,sticky:sticky,isTableScrolledToLeft:isTableScrolledToLeft,isTableScrolledToRight:isTableScrolledToRight,hasDivider:!1,scope:"col"},column.renderHeader(headerContext));let{isSortable,label,align="left",width,name,sortDirection,toggletipContent,toggletipAriaLabel}=column;headerElm=isSortable?React.createElement(SortableCellContent,{onSort:onSort,name:name,label:label,sortDirection:sortDirection,align:align,currentlySortedByColumn:currentlySortedByColumn,toggletipContent:toggletipContent,toggletipAriaLabel:toggletipAriaLabel}):React.createElement(DataTableHeaderLabel,{label:label,align:align,slotProps:toToggletipSlotProps(column)});let ariaSortAttr=currentlySortedByColumn===name&&{"aria-sort":SORT_DIRECTION_TEXT[sortDirection]};return React.createElement(StyledTh,{as:"th",alignColumn:align,columnWidth:width,key:name,rowSpan:rowSpan,sticky:sticky,isTableScrolledToLeft:isTableScrolledToLeft,isTableScrolledToRight:isTableScrolledToRight,hasDivider:!isLastColumn,scope:"col",...ariaSortAttr},headerElm)},renderGroupCell=(group,groupColumns)=>{let{name,label,align="center"}=group,isLastColumn=groupColumns[groupColumns.length-1].columnIndex===columns.length-1;return React.createElement(StyledTh,{as:"th",alignColumn:align,key:name,colSpan:groupColumns.length,sticky:!1,isTableScrolledToLeft:isTableScrolledToLeft,isTableScrolledToRight:isTableScrolledToRight,hasDivider:!isLastColumn,scope:"colgroup"},React.createElement(DataTableHeaderLabel,{label:label,align:align,slotProps:toToggletipSlotProps(group)}))},segments=buildColumnSegments(columns),hasColumnGroups=segments.some(({group})=>group),ungroupedRowSpan=hasColumnGroups?2:void 0;return React.createElement(StyledThead,{className:className,maxHeight:maxHeight},React.createElement("tr",null,segments.map(segment=>segment.group?renderGroupCell(segment.group,segment.columns):renderColumnCell(segment.columns[0],ungroupedRowSpan))),hasColumnGroups&&React.createElement("tr",null,segments.flatMap(segment=>segment.group?segment.columns.map(indexed=>renderColumnCell(indexed)):[])))});
@@ -1,4 +1,4 @@
1
- export type { DataTableColumn, DataTableRow, DataTableSlotProps, } from "./types";
1
+ export type { DataTableColumn, DataTableColumnGroup, DataTableColumnOrGroup, DataTableRow, DataTableSlotProps, } from "./types";
2
2
  export { DataTable, BaseDataTable } from "./DataTable";
3
3
  export type { DataTableProps } from "./DataTable";
4
4
  export { useDataTableSort } from "./useDataTableSort";
@@ -0,0 +1,18 @@
1
+ import type { DataTableColumnGroup } from "../types";
2
+ import type { InternalColumn } from "./types";
3
+ export type IndexedColumn = {
4
+ column: InternalColumn;
5
+ columnIndex: number;
6
+ };
7
+ export type ColumnSegment = {
8
+ /** Set when the segment's columns share one `DataTableColumnGroup`. */
9
+ group?: DataTableColumnGroup;
10
+ columns: IndexedColumn[];
11
+ };
12
+ /**
13
+ * Splits the flat column list into segments: one segment per group, and a
14
+ * segment of one for every column that has no group. A segment is both a
15
+ * cell of the first header row and a `<colgroup>`, so the two always agree
16
+ * on where a group starts and ends.
17
+ */
18
+ export declare function buildColumnSegments(columns: InternalColumn[]): ColumnSegment[];
@@ -0,0 +1 @@
1
+ import{isDataColumn}from"./types";export function buildColumnSegments(columns){let segments=[];return columns.forEach((column,columnIndex)=>{let group=isDataColumn(column)?column.group:void 0,currentSegment=segments[segments.length-1];if(group&&currentSegment?.group===group)return void currentSegment.columns.push({column,columnIndex});segments.push({group,columns:[{column,columnIndex}]})}),segments}
@@ -1,10 +1,10 @@
1
- import type { DataTableColumn } from "../types";
1
+ import type { DataTableColumnOrGroup } from "../types";
2
2
  import type { InternalColumn } from "./types";
3
3
  export type DataTableFeatures = {
4
4
  selectable?: boolean;
5
5
  expandable?: boolean;
6
6
  };
7
7
  export declare function buildInternalColumns({ columns, features: { selectable, expandable }, }: {
8
- columns: DataTableColumn[];
8
+ columns: DataTableColumnOrGroup[];
9
9
  features: DataTableFeatures;
10
10
  }): InternalColumn[];
@@ -1 +1 @@
1
- import{createCheckboxColumn}from"./controlColumns/checkboxColumn";import{createExpandColumn}from"./controlColumns/expandColumn";export function buildInternalColumns({columns,features:{selectable,expandable}}){let dataColumns=columns.map(column=>({...column,kind:"data"})),controlColumns=[];return expandable&&controlColumns.push(createExpandColumn()),selectable&&controlColumns.push(createCheckboxColumn()),[...controlColumns,...dataColumns]}
1
+ import{isDataTableColumnGroup}from"../isDataTableColumnGroup";import{createCheckboxColumn}from"./controlColumns/checkboxColumn";import{createExpandColumn}from"./controlColumns/expandColumn";let toDataColumns=column=>isDataTableColumnGroup(column)?column.children.map(child=>({...child,kind:"data",group:column})):[{...column,kind:"data"}];export function buildInternalColumns({columns,features:{selectable,expandable}}){let dataColumns=columns.flatMap(toDataColumns),controlColumns=[];return expandable&&controlColumns.push(createExpandColumn()),selectable&&controlColumns.push(createCheckboxColumn()),[...controlColumns,...dataColumns]}
@@ -1 +1 @@
1
- import{isControlColumn,isDataColumn}from"./types";export function getColumnSticky({column,columnIndex,columns,isFirstColumnSticky,isLastColumnSticky}){if(isControlColumn(column))return!1;let dataColumnIndices=columns.reduce((indices,col,index)=>(isDataColumn(col)&&indices.push(index),indices),[]),firstDataIndex=dataColumnIndices[0],lastDataIndex=dataColumnIndices[dataColumnIndices.length-1];return isFirstColumnSticky&&columnIndex===firstDataIndex?"left":!!isLastColumnSticky&&columnIndex===lastDataIndex&&"right"}
1
+ import{isControlColumn,isDataColumn}from"./types";export function getColumnSticky({column,columnIndex,columns,isFirstColumnSticky,isLastColumnSticky}){if(isControlColumn(column)||column.group)return!1;let dataColumnIndices=columns.reduce((indices,col,index)=>(isDataColumn(col)&&indices.push(index),indices),[]),firstDataIndex=dataColumnIndices[0],lastDataIndex=dataColumnIndices[dataColumnIndices.length-1];return isFirstColumnSticky&&columnIndex===firstDataIndex?"left":!!isLastColumnSticky&&columnIndex===lastDataIndex&&"right"}
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { DataTableProps } from "../DataTable";
3
- import type { DataTableColumn, DataTableRow, DataTableRowProps } from "../types";
3
+ import type { DataTableColumnDefinition, DataTableColumnGroup, DataTableRow, DataTableRowProps } from "../types";
4
4
  export type ControlColumnKind = "checkbox" | "expand";
5
5
  export type HeaderRenderContext = Pick<DataTableProps, "onSort" | "currentlySortedByColumn" | "allRowsSelected" | "selectedRowIds" | "selectAllRowsCheckboxAriaLabel" | "onAllRowsSelectionChange">;
6
6
  export type RowCellRenderContext = {
@@ -14,8 +14,13 @@ export type RowCellRenderContext = {
14
14
  onRowExpansionChange: (expandedRowIds: DataTableRow["id"][]) => void;
15
15
  emptyCellContent: string;
16
16
  };
17
- export type DataColumn = DataTableColumn & {
17
+ export type DataColumn = DataTableColumnDefinition & {
18
18
  kind: "data";
19
+ /**
20
+ * Set when the column came from a `DataTableColumnGroup`. The header collapses
21
+ * neighbours that share this reference into one spanning cell.
22
+ */
23
+ group?: DataTableColumnGroup;
19
24
  };
20
25
  export type ControlColumn = {
21
26
  kind: ControlColumnKind;
@@ -0,0 +1,2 @@
1
+ import type { DataTableColumnGroup, DataTableColumnOrGroup } from "./types";
2
+ export declare function isDataTableColumnGroup(column: DataTableColumnOrGroup): column is DataTableColumnGroup;
@@ -0,0 +1 @@
1
+ export function isDataTableColumnGroup(column){return"group"===column.kind}
@@ -7,6 +7,16 @@ export type DataTableRow = {
7
7
  id: string;
8
8
  [key: string]: any;
9
9
  };
10
+ /** A toggletip needs both fields or neither. A column and a group share the pair. */
11
+ type WithToggletip = {
12
+ toggletipContent: ReactElement;
13
+ /** Accessible name for the toggletip trigger button. Required when `toggletipContent` is set. */
14
+ toggletipAriaLabel: string;
15
+ };
16
+ type WithoutToggletip = {
17
+ toggletipContent?: never;
18
+ toggletipAriaLabel?: never;
19
+ };
10
20
  type DataTableColumnBase = {
11
21
  name: string;
12
22
  label: string;
@@ -16,16 +26,50 @@ type DataTableColumnBase = {
16
26
  sortDirection?: "asc" | "desc";
17
27
  renderCell?: (row: DataTableRow) => React.ReactElement;
18
28
  };
19
- type DataTableColumnWithToggletip = DataTableColumnBase & {
20
- toggletipContent: ReactElement;
21
- /** Accessible name for the toggletip trigger button. Required when `toggletipContent` is set. */
22
- toggletipAriaLabel: string;
29
+ /**
30
+ * A column before a discriminant marks it public or internal. The public
31
+ * `DataTableColumn` adds `kind?: "column"`; the internal `DataColumn` adds
32
+ * `kind: "data"`. Each adds its own, so neither has to strip the other's.
33
+ */
34
+ export type DataTableColumnDefinition = (DataTableColumnBase & WithToggletip) | (DataTableColumnBase & WithoutToggletip);
35
+ export type DataTableColumn = DataTableColumnDefinition & {
36
+ /** Leave it out on a column. Only a group sets it, to `"group"`. */
37
+ kind?: "column";
38
+ /** Not on a column — `children` makes a group, and a group is one level deep. */
39
+ children?: never;
23
40
  };
24
- type DataTableColumnWithoutToggletip = DataTableColumnBase & {
25
- toggletipContent?: never;
26
- toggletipAriaLabel?: never;
41
+ type DataTableColumnGroupBase = {
42
+ /**
43
+ * Marks the entry as a group. Required: it also makes TypeScript match the
44
+ * literal against the group alone, so a rejected field is reported where you
45
+ * wrote it instead of blaming `children`.
46
+ */
47
+ kind: "group";
48
+ name: string;
49
+ label: string;
50
+ /** Defaults to `"center"`. It aligns the group label only, not the children. */
51
+ align?: TextAlignment | MQ<TextAlignment>;
52
+ /** The columns under the shared label. One level only — a column cannot itself be a group. */
53
+ children: DataTableColumn[];
54
+ /** Not on a group — a group spans its children and has no width of its own. */
55
+ width?: never;
56
+ /** Not on a group — a group label has no sort control. Sort a child instead. */
57
+ isSortable?: never;
58
+ /** Not on a group — sorting belongs to a child column. */
59
+ sortDirection?: never;
60
+ /** Not on a group — a group renders a label, not a cell of data. */
61
+ renderCell?: never;
27
62
  };
28
- export type DataTableColumn = DataTableColumnWithToggletip | DataTableColumnWithoutToggletip;
63
+ /**
64
+ * A shared label above its `children`, rendered in the first header row.
65
+ *
66
+ * A group labels columns and owns no data, so it rejects `width`,
67
+ * `isSortable`, `sortDirection` and `renderCell`. Passing one is a type error
68
+ * rather than a silent no-op. They stay readable on `DataTableColumnOrGroup`,
69
+ * so reading a column field off the union still needs no narrowing step.
70
+ */
71
+ export type DataTableColumnGroup = (DataTableColumnGroupBase & WithToggletip) | (DataTableColumnGroupBase & WithoutToggletip);
72
+ export type DataTableColumnOrGroup = DataTableColumn | DataTableColumnGroup;
29
73
  export type DataTableRowProps = {
30
74
  verticalAlign?: Property.VerticalAlign;
31
75
  isActive?: boolean;
@@ -1,15 +1,15 @@
1
1
  import type { Dispatch, SetStateAction } from "react";
2
- import type { DataTableColumn, DataTableRow } from "./types";
2
+ import type { DataTableColumn, DataTableColumnOrGroup, DataTableRow } from "./types";
3
3
  import type { DataTableProps } from "./DataTable";
4
4
  type SortCompareFn = (a: DataTableRow, b: DataTableRow, columnName: DataTableColumn["name"], desiredSortDirection: DataTableColumn["sortDirection"]) => number;
5
5
  export declare const defaultSortCompareFn: (a: DataTableRow, b: DataTableRow, columnName: DataTableColumn["name"], desiredSortDirection: DataTableColumn["sortDirection"]) => number;
6
- export declare function useDataTableSort(initialColumns: DataTableColumn[], initialRows: DataTableRow[], initialSortedByColumn?: DataTableColumn["name"], sortCompareFn?: SortCompareFn): {
7
- columns: DataTableColumn[];
6
+ export declare function useDataTableSort(initialColumns: DataTableColumnOrGroup[], initialRows: DataTableRow[], initialSortedByColumn?: DataTableColumn["name"], sortCompareFn?: SortCompareFn): {
7
+ columns: DataTableColumnOrGroup[];
8
8
  rows: DataTableRow[];
9
9
  currentlySortedByColumn: DataTableColumn["name"];
10
10
  handleSort: DataTableProps["onSort"];
11
11
  resetColumns: VoidFunction;
12
- setColumns: Dispatch<SetStateAction<DataTableColumn[]>>;
12
+ setColumns: Dispatch<SetStateAction<DataTableColumnOrGroup[]>>;
13
13
  setRows: Dispatch<SetStateAction<DataTableRow[]>>;
14
14
  setCurrentlySortedByColumn: Dispatch<SetStateAction<DataTableColumn["name"]>>;
15
15
  };
@@ -1 +1 @@
1
- import{useState}from"react";export const defaultSortCompareFn=(a,b,columnName,desiredSortDirection)=>a[columnName]<b[columnName]?"asc"===desiredSortDirection?-1:1:a[columnName]>b[columnName]?"asc"===desiredSortDirection?1:-1:0;export function useDataTableSort(initialColumns,initialRows,initialSortedByColumn,sortCompareFn=defaultSortCompareFn){let[columns,setColumns]=useState(initialColumns),[currentlySortedByColumn,setCurrentlySortedByColumn]=useState(initialSortedByColumn),[rows,setRows]=useState(initialRows);return{columns,rows,currentlySortedByColumn,handleSort:(columnName,desiredSortDirection)=>{let newColumns=[...columns],newRows=[...rows],column=newColumns.find(item=>item.name===columnName);column&&(column.sortDirection=desiredSortDirection),newRows.sort((a,b)=>sortCompareFn(a,b,columnName,desiredSortDirection)),setRows(newRows),setColumns(newColumns),setCurrentlySortedByColumn(columnName)},setColumns,resetColumns:()=>{setColumns(initialColumns),setCurrentlySortedByColumn(initialSortedByColumn)},setRows,setCurrentlySortedByColumn}}
1
+ import{useState}from"react";import{isDataTableColumnGroup}from"./isDataTableColumnGroup";export const defaultSortCompareFn=(a,b,columnName,desiredSortDirection)=>a[columnName]<b[columnName]?"asc"===desiredSortDirection?-1:1:a[columnName]>b[columnName]?"asc"===desiredSortDirection?1:-1:0;let withUpdatedSortDirection=({columns,columnName,desiredSortDirection})=>{let updateColumn=column=>column.name===columnName?{...column,sortDirection:desiredSortDirection}:column;return columns.map(column=>isDataTableColumnGroup(column)?{...column,children:column.children.map(updateColumn)}:updateColumn(column))};export function useDataTableSort(initialColumns,initialRows,initialSortedByColumn,sortCompareFn=defaultSortCompareFn){let[columns,setColumns]=useState(initialColumns),[currentlySortedByColumn,setCurrentlySortedByColumn]=useState(initialSortedByColumn),[rows,setRows]=useState(initialRows);return{columns,rows,currentlySortedByColumn,handleSort:(columnName,desiredSortDirection)=>{let newColumns=withUpdatedSortDirection({columns,columnName,desiredSortDirection}),newRows=[...rows];newRows.sort((a,b)=>sortCompareFn(a,b,columnName,desiredSortDirection)),setRows(newRows),setColumns(newColumns),setCurrentlySortedByColumn(columnName)},setColumns,resetColumns:()=>{setColumns(initialColumns),setCurrentlySortedByColumn(initialSortedByColumn)},setRows,setCurrentlySortedByColumn}}
@@ -0,0 +1,103 @@
1
+ import type { ReactElement } from "react";
2
+ import React from "react";
3
+ import type { ConditionalAccessibleName } from "../../types/a11y";
4
+ import type { SegmentedControlProps } from "../Form/SegmentedControl/SegmentedControl";
5
+ import type { SearchInputProps } from "../Form/SearchInput/SearchInput";
6
+ import type { IconName } from "../Icon/Icon";
7
+ import type { PopupRole } from "../Sheet/Sheet";
8
+ export type FilterToolbarFilter = {
9
+ /** Visible label and accessible name of the trigger button. */
10
+ label: string;
11
+ /** Count shown in the trigger's badge. Pass `null` to hide the badge. */
12
+ count: number | null;
13
+ /** Icon shown on the trigger button, e.g. `"filter"`, `"columns3"`. */
14
+ leftIcon?: IconName;
15
+ /** Whether this filter's popup (e.g. a `FilterDialog` or `Popover`) is open. Wired into the trigger's `aria-expanded`. */
16
+ isOpen: boolean;
17
+ /**
18
+ * Id of the popup this trigger controls, wired into `aria-controls` while
19
+ * `isOpen`. Leave it out for `FilterDialog`/`Popover`, since neither
20
+ * exposes an `id`.
21
+ */
22
+ popupId?: string;
23
+ /**
24
+ * Popup type opened by the trigger. Wired into `aria-haspopup`. Leave it out
25
+ * when the trigger opens no popup, so the button claims none.
26
+ */
27
+ role?: Extract<PopupRole, "dialog" | "menu" | "listbox">;
28
+ /**
29
+ * Ref to this trigger's button. A `Popover` needs it as its own
30
+ * `externalTriggerRef`, to anchor to the trigger and to manage its click and
31
+ * focus-return behavior. It also writes `aria-expanded`, `aria-haspopup`, and
32
+ * `aria-controls` on the button itself, so keep `isOpen` in sync with the
33
+ * popover and leave `role` and `popupId` out. `FilterDialog` needs no ref: it
34
+ * opens as a modal or sheet, not anchored to the trigger.
35
+ */
36
+ triggerRef?: React.RefObject<HTMLButtonElement>;
37
+ /**
38
+ * Called when the trigger is clicked, for a popup the consumer opens itself,
39
+ * e.g. a `FilterDialog`. Leave it out with `triggerRef`: a `Popover` adds its
40
+ * own click listener to that button, so a second handler here toggles the
41
+ * open state twice on one click.
42
+ */
43
+ onClick?: () => void;
44
+ };
45
+ /** The visible content of a filter toolbar, without its accessible name or test id. */
46
+ export type FilterToolbarContentProps = {
47
+ /**
48
+ * Visible item count shown at the start of the row, e.g. `"50 items"`. It is
49
+ * a live region, so a count change is announced without a move of focus.
50
+ * Use it when no other row shows the count.
51
+ */
52
+ totalItems?: string;
53
+ /**
54
+ * A segmented control rendered at the start of the row, e.g. a year toggle.
55
+ * `label` is required because `FormFieldGroup` always renders a `<legend>`,
56
+ * even when the label is visually hidden. `size` is set by the toolbar, so
57
+ * the row keeps one height.
58
+ */
59
+ segmentedControlProps?: Omit<SegmentedControlProps, "size"> & {
60
+ label: string;
61
+ };
62
+ /**
63
+ * Filter and column trigger buttons, e.g. "Filter 4", "Columns 9". Each one
64
+ * renders as a `CountButton` whose `aria-expanded`, `aria-haspopup`, and
65
+ * `aria-controls` come from the entry's state. The toolbar does not render or
66
+ * manage the popup (`FilterDialog`, `Popover`) itself.
67
+ *
68
+ * Always wrap in `useMemo` to avoid unnecessary re-renders.
69
+ */
70
+ filters?: FilterToolbarFilter[];
71
+ /**
72
+ * Search field rendered at the end of the row. Always wrap in `useMemo` to
73
+ * avoid unnecessary re-renders. `slotProps.clearButton.ariaLabel` is
74
+ * required so the icon-only clear button has an accessible name.
75
+ */
76
+ searchInputProps?: Omit<SearchInputProps, "slotProps"> & {
77
+ slotProps: {
78
+ clearButton: {
79
+ ariaLabel: string;
80
+ };
81
+ };
82
+ };
83
+ };
84
+ export type FilterToolbarProps = FilterToolbarContentProps & {
85
+ "data-e2e-test-id"?: string;
86
+ } & ConditionalAccessibleName;
87
+ /**
88
+ * A row of filter controls: an optional item count, an optional segmented
89
+ * control, any number of count-badged filter triggers, and an optional search
90
+ * field pinned to the end. Use it above a table, a list, a grid, or any other
91
+ * filterable collection. It renders the triggers only — the popups behind
92
+ * them (`FilterDialog`, `Popover`) stay with the consumer; see `filters`.
93
+ *
94
+ * The root is `role="group"`, not `role="toolbar"`: the APG toolbar pattern
95
+ * assumes one arrow-key widget, placed last, but this row has two (the native
96
+ * radio group inside `SegmentedControl`, and the search textbox), and a
97
+ * roving tabindex would break both (WCAG SC 2.1.1). `role="group"` gives the
98
+ * same programmatic grouping with no custom keyboard code, so every control
99
+ * keeps its native behavior.
100
+ */
101
+ declare function FilterToolbarUI({ totalItems, segmentedControlProps, filters, searchInputProps, "data-e2e-test-id": dataE2eTestId, ...ariaAttributes }: FilterToolbarProps): ReactElement;
102
+ export declare const FilterToolbar: React.MemoExoticComponent<typeof FilterToolbarUI>;
103
+ export {};
@@ -0,0 +1 @@
1
+ import React from"react";import styled from"@emotion/styled";import breakpoints from"../../web-tokens/_breakpoints.json";import{Inline}from"../Inline/Inline";import{ToolbarItemCount}from"../Internal/ToolbarItemCount/ToolbarItemCount";import{SegmentedControl}from"../Form/SegmentedControl/SegmentedControl";import{SearchInput}from"../Form/SearchInput/SearchInput";import{CountButton}from"../CountButton/CountButton";let containerMediumSize=breakpoints.m.value,toolbarContainerName="filterToolbar",containerBelowMedium=`@container ${toolbarContainerName} (min-width: 0px) and (max-width:${containerMediumSize}px)`,StyledFilterToolbar=styled("div",{target:"e1pn7li40",label:"StyledFilterToolbar"})(({theme})=>({containerType:"inline-size",containerName:toolbarContainerName,backgroundColor:theme.values.color.background.primary.default,borderTopLeftRadius:"inherit",borderTopRightRadius:"inherit"})),StyledFilterRow=styled("div",{target:"e1pn7li41",label:"StyledFilterRow"})(({theme})=>({padding:theme.variables.size.spacing.xs,display:"flex",flexWrap:"wrap",alignItems:"center",gap:theme.variables.size.spacing.s,[containerBelowMedium]:{flexDirection:"column",alignItems:"stretch"}})),CustomInline=styled(Inline,{target:"e1pn7li42",label:"CustomInline"})({[containerBelowMedium]:{order:2}}),StyledSearchSlot=styled("div",{target:"e1pn7li43",label:"StyledSearchSlot"})({display:"flex",flexGrow:1,justifyContent:"flex-end",[containerBelowMedium]:{order:1,flexGrow:0,display:"block"}}),CustomToolbarItemCount=styled(ToolbarItemCount,{target:"e1pn7li44",label:"CustomToolbarItemCount"})(({theme})=>({paddingInline:theme.variables.size.spacing.xxs}));export const FilterToolbar=React.memo(function({totalItems,segmentedControlProps,filters,searchInputProps,"data-e2e-test-id":dataE2eTestId,...ariaAttributes}){let hasLeadingContent=!!totalItems||!!segmentedControlProps||!!filters?.length;return React.createElement(StyledFilterToolbar,{role:"group","data-ds-id":"FilterToolbar","data-e2e-test-id":dataE2eTestId,...ariaAttributes},React.createElement(StyledFilterRow,null,hasLeadingContent&&React.createElement(CustomInline,{vAlignItems:"center",space:"xs"},totalItems&&React.createElement(CustomToolbarItemCount,null,totalItems),segmentedControlProps&&React.createElement(SegmentedControl,{...segmentedControlProps,size:"s"}),filters?.map(({label,count,leftIcon,isOpen,popupId,role,onClick,triggerRef})=>React.createElement(CountButton,{key:`filter-${label}`,ref:triggerRef,size:"s",leftIcon:leftIcon,count:count,onClick:onClick,"aria-haspopup":role,"aria-expanded":isOpen,"aria-controls":isOpen?popupId:void 0},label))),searchInputProps&&React.createElement(StyledSearchSlot,null,React.createElement(SearchInput,searchInputProps))))});
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import type { LayoutStorage, PanelImperativeHandle } from "react-resizable-panels";
2
+ import type { LayoutStorage, PanelImperativeHandle, PanelProps as ResizablePanelProps } from "react-resizable-panels";
3
3
  /** A size: plain number = pixels; unitless string = percentage ("10%"). */
4
4
  type Size = number | string;
5
5
  export type PanelItem = {
@@ -19,6 +19,16 @@ export type PanelItem = {
19
19
  * neighbour. Separators beside it stay usable as long as a resizable panel
20
20
  * remains on each of their sides - the drag just passes this panel by. */
21
21
  resizable?: boolean;
22
+ /** Hold this panel's width when the window (or the containing group) is
23
+ * resized, rather than its share of the group - so the change lands on the
24
+ * panels without it. Orthogonal to `resizable`, which governs drags only,
25
+ * and to the unit of `defaultSize`: a panel sized in `%` starts at that
26
+ * share and then holds the width it resolved to.
27
+ *
28
+ * Ignored on a panel with no `defaultSize` (nothing was asked for, so there
29
+ * is no width worth holding) and on every panel of a row where no panel is
30
+ * left to absorb the change. */
31
+ lockWidthOnWindowResize?: boolean;
22
32
  };
23
33
  export type PanelProps = {
24
34
  /** Visible panels, left-to-right. Conditionally rendering items toggles a
@@ -45,6 +55,17 @@ export type PanelProps = {
45
55
  * it lifts (~300ms). */
46
56
  shouldSuppressLayoutCallback?: () => boolean;
47
57
  };
58
+ type GroupResizeBehavior = ResizablePanelProps["groupResizeBehavior"];
59
+ /**
60
+ * A panel's `groupResizeBehavior` - what a resize of the GROUP
61
+ * (window/container) holds constant; a separator drag is unaffected either way.
62
+ * `"preserve-relative-size"`, the library's default, keeps a panel's share of
63
+ * the group, so its pixel width scales with the window. A panel that locks its
64
+ * width takes `"preserve-pixel-size"` and the rest absorb the change, which
65
+ * takes at least one panel without the lock - so a row of nothing else stays
66
+ * relative throughout rather than leaving the change nowhere to go.
67
+ */
68
+ export declare const groupResizeBehaviorFor: (item: PanelItem, panels: PanelItem[]) => GroupResizeBehavior;
48
69
  /**
49
70
  * Internal resizable panel row — the only wrapper around react-resizable-panels.
50
71
  * Renders one Panel per item with that item's `child` in a full-bleed host,
@@ -1 +1 @@
1
- import React from"react";import styled from"@emotion/styled";import{Group,Panel as ResizablePanel,Separator,useDefaultLayout}from"react-resizable-panels";let CustomGroup=styled(Group,{target:"e1vzb9ge0",label:"CustomGroup"})({height:"100%",width:"100%"}),StyledPanelChild=styled("div",{target:"e1vzb9ge1",label:"StyledPanelChild"})({position:"relative",height:"100%",width:"100%"}),NOOP_STORAGE={getItem:()=>null,setItem:()=>{}},isResizable=item=>!1!==item.resizable,canSeparatorResize=({panels,index})=>panels.slice(0,index).some(isResizable)&&panels.slice(index).some(isResizable);export function Panel({panels,persistenceKey,storage,onResize,onCollapse,panelHandleFor,panelElementFor,shouldSuppressLayoutCallback}){let{defaultLayout,onLayoutChanged:persistSizes}=useDefaultLayout({id:persistenceKey??"ds-layout",panelIds:panels.map(p=>p.id),storage:persistenceKey&&storage?storage:NOOP_STORAGE});return React.createElement(CustomGroup,{orientation:"horizontal",defaultLayout:defaultLayout,onLayoutChanged:layout=>{shouldSuppressLayoutCallback?.()||(persistSizes(layout),panels.forEach(({id,collapsible})=>{let pct=layout[id];if("number"==typeof pct){if(collapsible&&pct<.5)return void onCollapse(id);pct>=.5&&onResize(id,`${pct}%`)}}))}},panels.map((item,index)=>{let isSeparatorDisabled=!canSeparatorResize({panels,index});return React.createElement(React.Fragment,{key:item.id},index>0&&React.createElement(Separator,{disabled:isSeparatorDisabled}),React.createElement(ResizablePanel,{id:item.id,defaultSize:item.defaultSize,collapsible:item.collapsible,disabled:!1===item.resizable,minSize:item.minSize,maxSize:item.maxSize,panelRef:panelHandleFor?.(item.id),elementRef:panelElementFor?.(item.id)},React.createElement(StyledPanelChild,null,item.child)))}))}
1
+ import React from"react";import styled from"@emotion/styled";import{Group,Panel as ResizablePanel,Separator,useDefaultLayout}from"react-resizable-panels";let CustomGroup=styled(Group,{target:"e1q4b9dw0",label:"CustomGroup"})({height:"100%",width:"100%"}),StyledPanelChild=styled("div",{target:"e1q4b9dw1",label:"StyledPanelChild"})({position:"relative",height:"100%",width:"100%"}),NOOP_STORAGE={getItem:()=>null,setItem:()=>{}},isResizable=item=>!1!==item.resizable,locksWidth=item=>!0===item.lockWidthOnWindowResize&&void 0!==item.defaultSize;export const groupResizeBehaviorFor=(item,panels)=>locksWidth(item)&&panels.some(panel=>!locksWidth(panel))?"preserve-pixel-size":"preserve-relative-size";let canSeparatorResize=({panels,index})=>panels.slice(0,index).some(isResizable)&&panels.slice(index).some(isResizable);export function Panel({panels,persistenceKey,storage,onResize,onCollapse,panelHandleFor,panelElementFor,shouldSuppressLayoutCallback}){let{defaultLayout,onLayoutChanged:persistSizes}=useDefaultLayout({id:persistenceKey??"ds-layout",panelIds:panels.map(p=>p.id),storage:persistenceKey&&storage?storage:NOOP_STORAGE});return React.createElement(CustomGroup,{orientation:"horizontal",defaultLayout:defaultLayout,onLayoutChanged:layout=>{shouldSuppressLayoutCallback?.()||(persistSizes(layout),panels.forEach(({id,collapsible})=>{let pct=layout[id];if("number"==typeof pct){if(collapsible&&pct<.5)return void onCollapse(id);pct>=.5&&onResize(id,`${pct}%`)}}))}},panels.map((item,index)=>{let isSeparatorDisabled=!canSeparatorResize({panels,index});return React.createElement(React.Fragment,{key:item.id},index>0&&React.createElement(Separator,{disabled:isSeparatorDisabled}),React.createElement(ResizablePanel,{id:item.id,defaultSize:item.defaultSize,collapsible:item.collapsible,disabled:!1===item.resizable,groupResizeBehavior:groupResizeBehaviorFor(item,panels),minSize:item.minSize,maxSize:item.maxSize,panelRef:panelHandleFor?.(item.id),elementRef:panelElementFor?.(item.id)},React.createElement(StyledPanelChild,null,item.child)))}))}
@@ -0,0 +1,16 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ type ToolbarItemCountProps = {
3
+ /** The count itself, e.g. `"50 items"`. */
4
+ children: ReactNode;
5
+ /** Published on the text, so a toolbar's `aria-labelledby`/`aria-describedby` can point at it. */
6
+ id?: string;
7
+ "data-e2e-test-id"?: string;
8
+ };
9
+ /**
10
+ * The live region shown by `BulkActionsToolbar` and `FilterToolbar`, so a
11
+ * count change announces without moving focus. `DataTableToolbar` moves it
12
+ * between the two rows as `isSelectable` changes, so one definition covers
13
+ * both instead of two copies drifting apart.
14
+ */
15
+ export declare function ToolbarItemCount({ children, id, "data-e2e-test-id": dataE2eTestId, ...rest }: ToolbarItemCountProps): ReactElement;
16
+ export {};
@@ -0,0 +1 @@
1
+ import React from"react";import{Text}from"../../Typography/Text/Text";export function ToolbarItemCount({children,id,"data-e2e-test-id":dataE2eTestId,...rest}){return React.createElement("div",{...rest,role:"status","aria-atomic":"true","data-e2e-test-id":dataE2eTestId},React.createElement(Text,{size:"s",color:"secondary",weight:"bold",id:id},children))}