@wallarm-org/design-system 1.9.0 → 1.10.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.
@@ -12,9 +12,14 @@ const DropdownMenuContent = ({ className, children, ref, ...props })=>{
12
12
  const testId = useTestId('content');
13
13
  const cascadeBase = useTestId();
14
14
  const childArray = Children.toArray(children);
15
- const inputChildren = childArray.filter((child)=>/*#__PURE__*/ isValidElement(child) && child.type === DropdownMenuInput);
15
+ const isInputChild = (child)=>{
16
+ if (!/*#__PURE__*/ isValidElement(child)) return false;
17
+ if (child.type === DropdownMenuInput) return true;
18
+ return 'function' == typeof child.type && 'input' === child.type.dropdownMenuSlot;
19
+ };
20
+ const inputChildren = childArray.filter(isInputChild);
16
21
  const footerChildren = childArray.filter((child)=>/*#__PURE__*/ isValidElement(child) && child.type === DropdownMenuFooter);
17
- const menuChildren = childArray.filter((child)=>!/*#__PURE__*/ (isValidElement(child) && (child.type === DropdownMenuFooter || child.type === DropdownMenuInput)));
22
+ const menuChildren = childArray.filter((child)=>!isInputChild(child) && !/*#__PURE__*/ (isValidElement(child) && child.type === DropdownMenuFooter));
18
23
  return /*#__PURE__*/ jsx(Portal, {
19
24
  children: /*#__PURE__*/ jsx(Menu.Positioner, {
20
25
  children: /*#__PURE__*/ jsxs(Menu.Content, {
@@ -15,7 +15,7 @@ const toTanStackPinning = (p, pinnedLeft)=>({
15
15
  end: p?.right ?? []
16
16
  });
17
17
  const TableProvider = (props)=>{
18
- const { data, columns, isLoading = false, isLoadingPrevious = false, skeletonCount = TABLE_SKELETON_ROWS, children, getRowId, sorting: sortingProp, onSortingChange, manualSorting = false, rowSelection: rowSelectionProp, onRowSelectionChange, columnSizing: columnSizingProp, onColumnSizingChange, columnPinning: columnPinningProp, onColumnPinningChange, columnOrder: columnOrderProp, onColumnOrderChange, grouping: groupingProp, onGroupingChange, expanded: expandedProp, onExpandedChange, renderGroupRow, getSubRows, renderExpandedRow, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, defaultColumnVisibility, defaultColumnOrder, virtualized, estimateRowHeight, overscan = TABLE_VIRTUALIZATION_OVERSCAN, onEndReached, onEndReachedThreshold, onStartReached, onStartReachedThreshold, initialScrollToRowId, onMasterCellClick, activeRowId: activeRowIdProp, onSettingsOpenChange } = props;
18
+ const { data, columns, isLoading = false, isLoadingPrevious = false, skeletonCount = TABLE_SKELETON_ROWS, children, getRowId, sorting: sortingProp, onSortingChange, manualSorting = false, rowSelection: rowSelectionProp, onRowSelectionChange, columnSizing: columnSizingProp, onColumnSizingChange, columnPinning: columnPinningProp, onColumnPinningChange, columnOrder: columnOrderProp, onColumnOrderChange, grouping: groupingProp, onGroupingChange, expanded: expandedProp, onExpandedChange, renderGroupRow, getSubRows, renderExpandedRow, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, defaultColumnVisibility, defaultColumnOrder, columnGroups, virtualized, estimateRowHeight, overscan = TABLE_VIRTUALIZATION_OVERSCAN, onEndReached, onEndReachedThreshold, onStartReached, onStartReachedThreshold, initialScrollToRowId, onMasterCellClick, activeRowId: activeRowIdProp, onSettingsOpenChange } = props;
19
19
  const masterCellActiveRowId = activeRowIdProp ?? null;
20
20
  const sortingEnabled = !!onSortingChange;
21
21
  const selectionEnabled = !!onRowSelectionChange;
@@ -201,6 +201,7 @@ const TableProvider = (props)=>{
201
201
  allLeafColumns,
202
202
  defaultColumnVisibility,
203
203
  defaultColumnOrder,
204
+ columnGroups,
204
205
  setColumnOrder,
205
206
  alwaysPinnedLeft,
206
207
  masterColumnId,
@@ -236,6 +237,7 @@ const TableProvider = (props)=>{
236
237
  allLeafColumns,
237
238
  defaultColumnVisibility,
238
239
  defaultColumnOrder,
240
+ columnGroups,
239
241
  setColumnOrder,
240
242
  alwaysPinnedLeft,
241
243
  masterColumnId,
@@ -2,7 +2,7 @@ import type { ReactNode, RefObject } from 'react';
2
2
  import type { Column, ColumnVisibilityState, ReactTable, Row, RowData } from '@tanstack/react-table';
3
3
  import type { Virtualizer } from '@tanstack/react-virtual';
4
4
  import type { DSTableFeatures } from '../lib';
5
- import type { TableProps, TableVirtualized } from '../types';
5
+ import type { TableColumnGroup, TableProps, TableVirtualized } from '../types';
6
6
  /**
7
7
  * Union covers both virtualizer flavors used by the Table (`useVirtualizer`
8
8
  * with HTMLElement scroll container and `useWindowVirtualizer` with Window).
@@ -29,6 +29,7 @@ export interface TableContextValue<T extends RowData> {
29
29
  allLeafColumns: Column<DSTableFeatures, T, unknown>[];
30
30
  defaultColumnVisibility?: ColumnVisibilityState;
31
31
  defaultColumnOrder?: string[];
32
+ columnGroups?: TableColumnGroup[];
32
33
  setColumnOrder: (order: string[]) => void;
33
34
  lastSelectedRowIndexRef: RefObject<number | null>;
34
35
  alwaysPinnedLeft: string[];
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cloneElement, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
@@ -24,7 +24,7 @@ const TableSettingsMenu = ({ 'data-testid': testIdProp, children, ...rest })=>{
24
24
  const testId = useTestId('settings-menu', testIdProp);
25
25
  const { anchorNode } = useTableSettingsMenuContext();
26
26
  const ctx = useTableContext();
27
- const { table, alwaysPinnedLeft, masterColumnId, onSettingsOpenChange } = ctx;
27
+ const { table, alwaysPinnedLeft, masterColumnId, columnGroups, onSettingsOpenChange } = ctx;
28
28
  const hasTextDescription = table.getAllLeafColumns().some((col)=>col.columnDef.meta?.description?.type === 'text');
29
29
  const [search, setSearch] = useState('');
30
30
  const [menuOpen, setMenuOpen] = useState(false);
@@ -59,6 +59,30 @@ const TableSettingsMenu = ({ 'data-testid': testIdProp, children, ...rest })=>{
59
59
  allColumns,
60
60
  search
61
61
  ]);
62
+ const labeledSections = useMemo(()=>{
63
+ if (!columnGroups) return null;
64
+ const byId = new Map(filteredColumns.map((col)=>[
65
+ col.id,
66
+ col
67
+ ]));
68
+ const claimed = new Set();
69
+ const sections = columnGroups.map((group)=>{
70
+ const cols = group.columns.map((id)=>byId.get(id)).filter((col)=>null != col);
71
+ for (const col of cols)claimed.add(col.id);
72
+ return {
73
+ label: group.label,
74
+ cols
75
+ };
76
+ }).filter((section)=>section.cols.length > 0);
77
+ const ungrouped = filteredColumns.filter((col)=>!claimed.has(col.id));
78
+ return {
79
+ sections,
80
+ ungrouped
81
+ };
82
+ }, [
83
+ columnGroups,
84
+ filteredColumns
85
+ ]);
62
86
  const { pinnedColumns, unpinnedColumns } = useMemo(()=>{
63
87
  const pinned = [];
64
88
  const unpinned = [];
@@ -147,15 +171,30 @@ const TableSettingsMenu = ({ 'data-testid': testIdProp, children, ...rest })=>{
147
171
  }),
148
172
  /*#__PURE__*/ jsxs(DropdownMenuContent, {
149
173
  ref: contentRef,
150
- className: cn('min-w-256 max-h-[430px]'),
151
- style: lockedWidth ? {
174
+ className: cn('min-w-256'),
175
+ style: {
176
+ maxHeight: 'min(430px, var(--available-height))',
152
177
  width: lockedWidth
153
- } : void 0,
178
+ },
154
179
  children: [
155
180
  searchOverride ?? /*#__PURE__*/ jsx(TableSettingsMenuSearch, {}),
156
181
  /*#__PURE__*/ jsx(VStack, {
157
182
  gap: 1,
158
- children: /*#__PURE__*/ jsx(DndContext, {
183
+ children: labeledSections ? /*#__PURE__*/ jsxs(Fragment, {
184
+ children: [
185
+ labeledSections.sections.map((section)=>/*#__PURE__*/ jsxs("div", {
186
+ className: "flex flex-col gap-1",
187
+ children: [
188
+ /*#__PURE__*/ jsx(DropdownMenuLabel, {
189
+ sticky: true,
190
+ children: section.label
191
+ }),
192
+ section.cols.map(renderColumnItem)
193
+ ]
194
+ }, section.label)),
195
+ labeledSections.ungrouped.map(renderColumnItem)
196
+ ]
197
+ }) : /*#__PURE__*/ jsx(DndContext, {
159
198
  sensors: sensors,
160
199
  collisionDetection: closestCenter,
161
200
  onDragEnd: handleDragEnd,
@@ -7,7 +7,8 @@ import { useTestId } from "../../../utils/testId.js";
7
7
  import { Switch, SwitchControl, SwitchLabel } from "../../Switch/index.js";
8
8
  import { useTableContext } from "../TableContext/index.js";
9
9
  const TableSettingsMenuItem = ({ columnId, className, 'data-testid': testIdProp, ...rest })=>{
10
- const { masterColumnId, columnDndEnabled, table } = useTableContext();
10
+ const { masterColumnId, columnDndEnabled, columnGroups, table } = useTableContext();
11
+ const grouped = !!columnGroups;
11
12
  const slotTestId = useTestId('settings-menu-item');
12
13
  const switchTestId = useTestId(`settings-menu-item-${columnId}`, testIdProp);
13
14
  const column = table.getColumn(columnId);
@@ -29,10 +30,10 @@ const TableSettingsMenuItem = ({ columnId, className, 'data-testid': testIdProp,
29
30
  ref: setNodeRef,
30
31
  style: style,
31
32
  "data-testid": slotTestId,
32
- className: cn('relative flex items-center w-full rounded-6 pl-20 pr-8 py-6', 'hover:bg-states-primary-hover transition-colors', 'data-disabled:opacity-50 data-disabled:pointer-events-none'),
33
+ className: cn('relative flex items-center w-full rounded-6 pr-8 py-6', grouped ? 'pl-8' : 'pl-20', 'hover:bg-states-primary-hover transition-colors', 'data-disabled:opacity-50 data-disabled:pointer-events-none'),
33
34
  "data-disabled": isMasterColumn || void 0,
34
35
  children: [
35
- /*#__PURE__*/ jsx("span", {
36
+ !grouped && /*#__PURE__*/ jsx("span", {
36
37
  className: cn('absolute left-4 top-1/2 -translate-y-1/2 cursor-grab active:cursor-grabbing text-icon-secondary hover:text-icon-primary data-disabled:opacity-50 data-disabled:pointer-events-none'),
37
38
  ...attributes,
38
39
  ...listeners,
@@ -14,4 +14,7 @@ const TableSettingsMenuSearch = ({ 'data-testid': testIdProp, placeholder = 'Sea
14
14
  });
15
15
  };
16
16
  TableSettingsMenuSearch.displayName = 'TableSettingsMenuSearch';
17
+ Object.assign(TableSettingsMenuSearch, {
18
+ dropdownMenuSlot: 'input'
19
+ });
17
20
  export { TableSettingsMenuSearch };
@@ -10,4 +10,4 @@ export { TableEmptyState } from './TableEmptyState';
10
10
  export { TableScrollHandler, TableScrollHandlerLeft, type TableScrollHandlerLeftProps, type TableScrollHandlerProps, TableScrollHandlerRight, type TableScrollHandlerRightProps, } from './TableScrollHandler';
11
11
  export { TableSettingsMenu, TableSettingsMenuItem, type TableSettingsMenuItemProps, type TableSettingsMenuProps, TableSettingsMenuReset, type TableSettingsMenuResetProps, TableSettingsMenuSearch, type TableSettingsMenuSearchProps, } from './TableSettingsMenu';
12
12
  export { TableSortTrigger, type TableSortTriggerProps } from './TableSortTrigger';
13
- export type { TableAccessorColumnDef, TableCellContext, TableColumnBase, TableColumnDef, TableColumnMeta, TableColumnPinningState, TableColumnSizingState, TableDisplayColumnDef, TableExpandedState, TableGroupingState, TableHandle, TableOnChangeFn, TableProps, TableRow, TableRowSelectionState, TableScrollToRowOptions, TableSortingState, TableUpdater, TableVirtualized, TableVisibilityState, } from './types';
13
+ export type { TableAccessorColumnDef, TableCellContext, TableColumnBase, TableColumnDef, TableColumnGroup, TableColumnMeta, TableColumnPinningState, TableColumnSizingState, TableDisplayColumnDef, TableExpandedState, TableGroupingState, TableHandle, TableOnChangeFn, TableProps, TableRow, TableRowSelectionState, TableScrollToRowOptions, TableSortingState, TableUpdater, TableVirtualized, TableVisibilityState, } from './types';
@@ -42,6 +42,19 @@ export type TableExpandedState = true | Record<string, boolean>;
42
42
  export type TableGroupingState = string[];
43
43
  /** Column visibility state: `{ [columnId]: boolean }` */
44
44
  export type TableVisibilityState = Record<string, boolean>;
45
+ /**
46
+ * Labeled grouping for the column-settings menu. When `columnGroups` is passed
47
+ * to the Table, the settings menu renders these labeled sections (in the given
48
+ * order) instead of the default pinned/unpinned + drag-reorder list — the same
49
+ * flat, non-reorderable pattern the FilterInput field menu uses. `columns` are
50
+ * column ids; ids absent from every group fall into a trailing headerless
51
+ * section, and empty groups (or groups whose columns are all filtered out by
52
+ * search) render nothing.
53
+ */
54
+ export interface TableColumnGroup {
55
+ label: string;
56
+ columns: string[];
57
+ }
45
58
  /** State updater — value or functional update */
46
59
  export type TableUpdater<T> = T | ((prev: T) => T);
47
60
  /** Options for the imperative `scrollToRow` method */
@@ -190,6 +203,13 @@ export interface TableProps<T> extends TestableProps {
190
203
  onColumnVisibilityChange?: TableOnChangeFn<TableVisibilityState>;
191
204
  defaultColumnVisibility?: TableVisibilityState;
192
205
  defaultColumnOrder?: string[];
206
+ /**
207
+ * Labeled sections for the column-settings menu. When set, the menu renders
208
+ * these groups (flat, non-reorderable — like the FilterInput field menu)
209
+ * instead of the default pinned/unpinned drag-reorder list. See
210
+ * {@link TableColumnGroup}.
211
+ */
212
+ columnGroups?: TableColumnGroup[];
193
213
  /**
194
214
  * Fired when the built-in column-settings menu opens (`true`) or closes
195
215
  * (`false`). The menu's open state otherwise lives inside the DS and is not
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.8.0",
3
- "generatedAt": "2026-08-26T13:19:26.738Z",
2
+ "version": "1.9.0",
3
+ "generatedAt": "2026-08-27T14:32:57.789Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -76989,6 +76989,12 @@
76989
76989
  "type": "string[] | undefined",
76990
76990
  "required": false
76991
76991
  },
76992
+ {
76993
+ "name": "columnGroups",
76994
+ "type": "TableColumnGroup[] | undefined",
76995
+ "required": false,
76996
+ "description": "Labeled sections for the column-settings menu. When set, the menu renders\nthese groups (flat, non-reorderable — like the FilterInput field menu)\ninstead of the default pinned/unpinned drag-reorder list. See\n{@link TableColumnGroup}."
76997
+ },
76992
76998
  {
76993
76999
  "name": "virtualized",
76994
77000
  "type": "TableVirtualized | undefined",
@@ -83091,6 +83097,11 @@
83091
83097
  "code": "() => {\n const [columnVisibility, setColumnVisibility] = useState<TableVisibilityState>({});\n const [columnOrder, setColumnOrder] = useState<string[]>([]);\n\n return (\n <Table\n data={securityEvents}\n columns={securityColumns}\n getRowId={row => row.id}\n columnVisibility={columnVisibility}\n onColumnVisibilityChange={setColumnVisibility}\n columnOrder={columnOrder}\n onColumnOrderChange={setColumnOrder}\n defaultColumnVisibility={{}}\n defaultColumnOrder={securityColumnIds}\n />\n );\n}",
83092
83098
  "description": "The gear at the end of the header — search the columns, show and hide them, reorder them,\nreset — writing the same visibility and order state as the header menu."
83093
83099
  },
83100
+ {
83101
+ "name": "SettingsMenuGrouped",
83102
+ "code": "() => {\n const [columnVisibility, setColumnVisibility] = useState<TableVisibilityState>({});\n\n const columnGroups: TableColumnGroup[] = [\n {\n label: 'Threat classification',\n columns: ['objectName', 'status', 'firstDetected', 'lastSeen'],\n },\n { label: 'Request features', columns: ['sourceIp', 'parameter', 'endpoint', 'requests'] },\n ];\n\n return (\n <Table\n data={securityEvents}\n columns={securityColumns}\n getRowId={row => row.id}\n columnVisibility={columnVisibility}\n onColumnVisibilityChange={setColumnVisibility}\n columnGroups={columnGroups}\n />\n );\n}",
83103
+ "description": "`columnGroups` turns the settings menu into labeled, non-reorderable sections\n(the FilterInput field-menu pattern): no drag handle, columns rendered under\ntheir group header in the given order. Column ids absent from every group\n(`cweId` here) fall into a trailing headerless section."
83104
+ },
83094
83105
  {
83095
83106
  "name": "Virtualization",
83096
83107
  "code": "() => {\n const largeData = useMemo(() => createLargeSecurityEvents(1000), []);\n\n return (\n <Table\n className='h-500'\n data={largeData}\n columns={securityColumns}\n getRowId={row => row.id}\n virtualized='container'\n />\n );\n}",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",