@drivenets/design-system 0.13.0 → 0.14.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 (64) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.cjs +791 -326
  3. package/dist/index.d.cts +265 -21
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +265 -21
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +789 -326
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +13 -11
  11. package/src/components/ds-button-v3/ds-button-v3.figma.ts +99 -0
  12. package/src/components/ds-button-v3/ds-button-v3.module.scss +22 -22
  13. package/src/components/ds-card/figma/ds-card-content.figma.ts +15 -0
  14. package/src/components/ds-card/figma/ds-card-footer.figma.ts +15 -0
  15. package/src/components/ds-card/figma/ds-card-header.figma.ts +15 -0
  16. package/src/components/ds-card/figma/ds-card.figma.ts +72 -0
  17. package/src/components/ds-date-picker/ds-date-picker.figma.ts +19 -0
  18. package/src/components/ds-form-control/ds-form-control.figma.ts +76 -0
  19. package/src/components/ds-icon/figma/ds-icon-material.figma.batch.ts +10 -0
  20. package/src/components/ds-icon/figma/ds-icon-special.figma.batch.ts +18 -0
  21. package/src/components/ds-icon/figma/ds-icon.figma.batch.json +2873 -0
  22. package/src/components/ds-modal/figma/ds-modal-content.figma.ts +18 -0
  23. package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +19 -0
  24. package/src/components/ds-modal/figma/ds-modal-header.figma.ts +21 -0
  25. package/src/components/ds-modal/figma/ds-modal.figma.ts +36 -0
  26. package/src/components/ds-popover/ds-popover.module.scss +58 -16
  27. package/src/components/ds-popover/ds-popover.tsx +148 -24
  28. package/src/components/ds-popover/ds-popover.types.ts +95 -12
  29. package/src/components/ds-popover/index.ts +1 -1
  30. package/src/components/ds-select/ds-select.figma.ts +53 -0
  31. package/src/components/ds-split-button/ds-split-button.figma.ts +95 -0
  32. package/src/components/ds-status-badge-v2/ds-status-badge-v2.figma.ts +41 -0
  33. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +8 -3
  34. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +31 -0
  35. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +48 -0
  36. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.types.ts +12 -0
  37. package/src/components/ds-table/components/ds-table-group-header-cell/index.ts +2 -0
  38. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +4 -0
  39. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +55 -39
  40. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +4 -0
  41. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +2 -0
  42. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +4 -0
  43. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +2 -0
  44. package/src/components/ds-table/context/ds-table-context.ts +4 -0
  45. package/src/components/ds-table/ds-table.tsx +20 -2
  46. package/src/components/ds-table/ds-table.types.ts +55 -0
  47. package/src/components/ds-table/figma/ds-table-expandable.figma.ts +17 -0
  48. package/src/components/ds-table/figma/ds-table.figma.ts +16 -0
  49. package/src/components/ds-table/grouping/hooks/use-column-groups.ts +73 -0
  50. package/src/components/ds-table/grouping/index.ts +5 -0
  51. package/src/components/ds-table/grouping/types/column-group-meta.augmentation.ts +26 -0
  52. package/src/components/ds-table/grouping/types/column-group.types.ts +35 -0
  53. package/src/components/ds-table/grouping/utils/column-group.ts +119 -0
  54. package/src/components/ds-table/index.ts +3 -0
  55. package/src/components/ds-table/styles/_variables.scss +1 -0
  56. package/src/components/ds-tag/ds-tag.module.scss +22 -1
  57. package/src/components/ds-tag/ds-tag.tsx +16 -3
  58. package/src/components/ds-tag/ds-tag.types.ts +22 -8
  59. package/src/components/ds-tag/ds-tag.unit.test.ts +32 -0
  60. package/src/components/ds-tag-filter/ds-tag-filter.tsx +14 -22
  61. package/src/components/ds-text-input/ds-text-input.figma.ts +60 -0
  62. package/src/components/ds-time-picker/ds-time-picker.figma.ts +18 -0
  63. package/src/styles/_root_new.scss +2 -0
  64. package/src/styles/_scrollbars.scss +27 -50
@@ -110,7 +110,7 @@ interface DsTableBodyProps<TData> {
110
110
  rowSelection: RowSelectionState;
111
111
  }
112
112
 
113
- function DsTableBody<TData>({
113
+ export function DsTableBody<TData>({
114
114
  ref,
115
115
  rowVirtualizer,
116
116
  rowsMapRef,
@@ -122,6 +122,11 @@ function DsTableBody<TData>({
122
122
  const virtualRows = rowVirtualizer.getVirtualItems();
123
123
  const totalSize = rowVirtualizer.getTotalSize();
124
124
 
125
+ // Gate the empty state on real data count, not on rendered virtual rows.
126
+ // getVirtualItems() transiently returns [] while the virtualizer re-measures
127
+ // after a `data` swap; rendering nothing for that frame avoids an empty-state flash.
128
+ const hasNoData = rowsAndExpandedRowContent.length === 0;
129
+
125
130
  return (
126
131
  <TableBody ref={ref} className={styles.body}>
127
132
  {virtualRows.length > 0 ? (
@@ -144,11 +149,11 @@ function DsTableBody<TData>({
144
149
  />
145
150
  );
146
151
  })
147
- ) : (
152
+ ) : hasNoData ? (
148
153
  <TableRow>
149
154
  <TableCell className={styles.emptyState}>{emptyState || EMPTY_TABLE_STATE_TEXT}</TableCell>
150
155
  </TableRow>
151
- )}
156
+ ) : null}
152
157
 
153
158
  <tr aria-hidden className={styles.sentinel} style={{ top: `${totalSize.toString()}px` }}>
154
159
  <td />
@@ -0,0 +1,31 @@
1
+ @use '../../../../styles/typography';
2
+ @use '../../styles/variables' as vars;
3
+
4
+ .groupHeaderCell {
5
+ @include typography.body-sm-md;
6
+ color: var(--font-main);
7
+ height: var(--ds-table-header-height);
8
+ padding: 0;
9
+ border-bottom: vars.$border-width solid var(--color-dap-gray-400);
10
+ border-left: vars.$group-border-width solid var(--border);
11
+ }
12
+
13
+ .groupHeaderContent {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ gap: vars.$header-sort-gap;
18
+ width: 100%;
19
+ height: 100%;
20
+ padding: 0 var(--standard);
21
+ }
22
+
23
+ .groupLabel {
24
+ overflow: hidden;
25
+ text-overflow: ellipsis;
26
+ white-space: nowrap;
27
+ }
28
+
29
+ .collapseToggle {
30
+ flex-shrink: 0;
31
+ }
@@ -0,0 +1,48 @@
1
+ import { flexRender } from '@tanstack/react-table';
2
+ import { DsButtonV3 } from '../../../ds-button-v3';
3
+ import { TableHead } from '../core-table';
4
+ import { useDsTableContext } from '../../context/ds-table-context';
5
+ import { getGroupColumnSizeStyle } from '../../grouping';
6
+ import { defaultDsTableLocale } from '../../ds-table.types';
7
+ import type { DsTableGroupHeaderCellProps } from './ds-table-group-header-cell.types';
8
+ import styles from './ds-table-group-header-cell.module.scss';
9
+
10
+ export const DsTableGroupHeaderCell = <TData, TValue>({
11
+ header,
12
+ }: DsTableGroupHeaderCellProps<TData, TValue>) => {
13
+ const { collapsedColumnGroups, onToggleColumnGroup, locale } = useDsTableContext<TData, TValue>();
14
+
15
+ const groupId = header.column.id;
16
+ const visibleLeafColumns = header.column.getLeafColumns().filter((leaf) => leaf.getIsVisible());
17
+ const groupStyle = getGroupColumnSizeStyle(visibleLeafColumns);
18
+
19
+ const isCollapsible = header.column.columnDef.meta?.group?.collapsible ?? false;
20
+ const isCollapsed = collapsedColumnGroups?.includes(groupId) ?? false;
21
+
22
+ const mergedLocale = { ...defaultDsTableLocale, ...locale };
23
+ const toggleLabel = isCollapsed ? mergedLocale.expandColumnGroup : mergedLocale.collapseColumnGroup;
24
+
25
+ const handleToggle = () => onToggleColumnGroup?.(groupId);
26
+
27
+ return (
28
+ <TableHead className={styles.groupHeaderCell} style={groupStyle} colSpan={header.colSpan}>
29
+ <div className={styles.groupHeaderContent}>
30
+ <span className={styles.groupLabel}>
31
+ {flexRender(header.column.columnDef.header, header.getContext())}
32
+ </span>
33
+ {isCollapsible && (
34
+ <DsButtonV3
35
+ variant="secondary"
36
+ size="tiny"
37
+ icon={isCollapsed ? 'chevron_right' : 'chevron_left'}
38
+ className={styles.collapseToggle}
39
+ aria-label={toggleLabel}
40
+ aria-expanded={!isCollapsed}
41
+ title={toggleLabel}
42
+ onClick={handleToggle}
43
+ />
44
+ )}
45
+ </div>
46
+ </TableHead>
47
+ );
48
+ };
@@ -0,0 +1,12 @@
1
+ import type { Header } from '@tanstack/react-table';
2
+
3
+ /**
4
+ * Props for the grouping column header cell.
5
+ */
6
+ export interface DsTableGroupHeaderCellProps<TData, TValue> {
7
+ /**
8
+ * The grouping header from `@tanstack/react-table` (a header whose column nests
9
+ * child columns).
10
+ */
11
+ header: Header<TData, TValue>;
12
+ }
@@ -0,0 +1,2 @@
1
+ export { DsTableGroupHeaderCell } from './ds-table-group-header-cell';
2
+ export type { DsTableGroupHeaderCellProps } from './ds-table-group-header-cell.types';
@@ -57,6 +57,10 @@
57
57
  justify-content: center;
58
58
  }
59
59
 
60
+ .groupStart {
61
+ border-left: vars.$group-border-width solid var(--border);
62
+ }
63
+
60
64
  .headerSortContainer {
61
65
  display: flex;
62
66
  align-items: center;
@@ -11,6 +11,8 @@ import { SELECT_COLUMN_ID } from '../../utils/constants';
11
11
  import { DsStack } from '../../../ds-stack';
12
12
  import { DsTableColumnFilterPopover } from '../../filters/components/column-filter-popover';
13
13
  import type { ResolvedColumnFilter } from '../../filters/types/filter-adapter.types';
14
+ import { DsTableGroupHeaderCell } from '../ds-table-group-header-cell';
15
+ import { isFirstLeafColumnOfGroup } from '../../grouping';
14
16
 
15
17
  const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
16
18
  const { stickyHeader, bordered, virtualized } = useDsTableContext<TData, unknown>();
@@ -29,9 +31,24 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
29
31
  )}
30
32
  >
31
33
  {headerGroup.headers.map((header) => {
34
+ if (header.isPlaceholder) {
35
+ return (
36
+ <TableHead
37
+ key={header.id}
38
+ className={styles.headerCell}
39
+ style={getColumnSizeStyle(header.getSize())}
40
+ />
41
+ );
42
+ }
43
+
44
+ if (header.subHeaders.length > 0) {
45
+ return <DsTableGroupHeaderCell key={header.id} header={header} />;
46
+ }
47
+
32
48
  const headerStyle = getColumnSizeStyle(header.column.getSize());
33
49
  const canSort = header.column.getCanSort();
34
50
  const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
51
+ const isGroupStart = isFirstLeafColumnOfGroup(header.column);
35
52
 
36
53
  const metaFilter = header.column.columnDef.meta?.filter;
37
54
  const resolvedFilter: ResolvedColumnFilter | undefined =
@@ -47,49 +64,48 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
47
64
  isSelectColumn && styles.selectHeaderCell,
48
65
  resolvedFilter && styles.filterableHeader,
49
66
  hasActiveColumnFilter && styles.filterActive,
67
+ isGroupStart && styles.groupStart,
50
68
  )}
51
69
  onClick={header.column.getToggleSortingHandler()}
52
70
  style={headerStyle}
53
71
  >
54
- {header.isPlaceholder ? null : (
55
- <div className={styles.headerSortContainer}>
56
- <DsStack direction="column" justifyContent="center" width="100%">
57
- {flexRender(header.column.columnDef.header, header.getContext())}
58
- </DsStack>
59
- {canSort && (
60
- <div className={styles.pageButtonIconContainer}>
61
- {{
62
- asc: (
63
- <DsIcon
64
- icon="arrow_drop_up"
65
- className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
66
- />
67
- ),
68
- desc: (
69
- <DsIcon
70
- icon="arrow_drop_down"
71
- className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
72
- />
73
- ),
74
- }[header.column.getIsSorted() as string] ?? (
75
- <div className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)} />
76
- )}
77
- </div>
78
- )}
79
- {resolvedFilter && (
80
- <div className={styles.filterTriggerContainer}>
81
- <DsTableColumnFilterPopover
82
- hasActiveFilter={resolvedFilter.hasActiveFilter}
83
- onApply={resolvedFilter.onApply}
84
- onClear={resolvedFilter.onClear}
85
- onCancel={resolvedFilter.onCancel}
86
- >
87
- {resolvedFilter.render()}
88
- </DsTableColumnFilterPopover>
89
- </div>
90
- )}
91
- </div>
92
- )}
72
+ <div className={styles.headerSortContainer}>
73
+ <DsStack direction="column" justifyContent="center" width="100%">
74
+ {flexRender(header.column.columnDef.header, header.getContext())}
75
+ </DsStack>
76
+ {canSort && (
77
+ <div className={styles.pageButtonIconContainer}>
78
+ {{
79
+ asc: (
80
+ <DsIcon
81
+ icon="arrow_drop_up"
82
+ className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
83
+ />
84
+ ),
85
+ desc: (
86
+ <DsIcon
87
+ icon="arrow_drop_down"
88
+ className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
89
+ />
90
+ ),
91
+ }[header.column.getIsSorted() as string] ?? (
92
+ <div className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)} />
93
+ )}
94
+ </div>
95
+ )}
96
+ {resolvedFilter && (
97
+ <div className={styles.filterTriggerContainer}>
98
+ <DsTableColumnFilterPopover
99
+ hasActiveFilter={resolvedFilter.hasActiveFilter}
100
+ onApply={resolvedFilter.onApply}
101
+ onClear={resolvedFilter.onClear}
102
+ onCancel={resolvedFilter.onCancel}
103
+ >
104
+ {resolvedFilter.render()}
105
+ </DsTableColumnFilterPopover>
106
+ </div>
107
+ )}
108
+ </div>
93
109
  </TableHead>
94
110
  );
95
111
  })}
@@ -85,6 +85,10 @@
85
85
  justify-content: center;
86
86
  }
87
87
 
88
+ .groupStart {
89
+ border-left: vars.$group-border-width solid var(--border);
90
+ }
91
+
88
92
  .tableRow .editableCell {
89
93
  padding: 1px;
90
94
 
@@ -10,6 +10,7 @@ import styles from './ds-table-row.module.scss';
10
10
  import { useDsTableContext } from '../../context/ds-table-context';
11
11
  import { mergeRefs } from '../../../../utils/merge-refs';
12
12
  import { getColumnSizeStyle } from '../../utils/column-size';
13
+ import { isFirstLeafColumnOfGroup } from '../../grouping';
13
14
  import { EXPANDER_COLUMN_ID, REORDER_COLUMN_ID, SELECT_COLUMN_ID } from '../../utils/constants';
14
15
 
15
16
  interface DsRowDragHandleProps {
@@ -118,6 +119,7 @@ const DsTableRow = <TData,>({ ref, row, isSelected }: DsTableRowProps<TData>) =>
118
119
  styles.tableCell,
119
120
  cell.column.id === EXPANDER_COLUMN_ID && styles.expandableCell,
120
121
  cell.column.id === SELECT_COLUMN_ID && styles.selectableCell,
122
+ isFirstLeafColumnOfGroup(cell.column) && styles.groupStart,
121
123
  typeof cell.column.columnDef.editCell === 'function' && styles.editableCell,
122
124
  )}
123
125
  style={cellStyle}
@@ -66,6 +66,10 @@
66
66
  justify-content: center;
67
67
  }
68
68
 
69
+ .groupStart {
70
+ border-left: vars.$group-border-width solid var(--border);
71
+ }
72
+
69
73
  .expandedRowContent {
70
74
  background-color: var(--background-secondary);
71
75
 
@@ -2,6 +2,7 @@ import classnames from 'classnames';
2
2
  import styles from './ds-table-row-virtualized.module.scss';
3
3
  import { useDsTableContext } from '../../context/ds-table-context';
4
4
  import { getColumnSizeStyle } from '../../utils/column-size';
5
+ import { isFirstLeafColumnOfGroup } from '../../grouping';
5
6
  import { TableCell, TableRow } from '../core-table';
6
7
  import type { DsTableRowVirtualizedProps } from './ds-table-row-virtualized.types';
7
8
  import { DsTableCell } from '../ds-table-cell';
@@ -77,6 +78,7 @@ export const DsTableRowVirtualized = <TData,>({
77
78
  styles.cell,
78
79
  cell.column.id === EXPANDER_COLUMN_ID && styles.expandableCell,
79
80
  cell.column.id === SELECT_COLUMN_ID && styles.selectableCell,
81
+ isFirstLeafColumnOfGroup(cell.column) && styles.groupStart,
80
82
  typeof cell.column.columnDef.editCell === 'function' && styles.editableCell,
81
83
  )}
82
84
  >
@@ -23,6 +23,10 @@ export interface DsTableContextType<TData, TValue> extends Partial<DsDataTablePr
23
23
  * ID of the currently active row
24
24
  */
25
25
  activeRowId?: string | null;
26
+ /**
27
+ * Toggles the collapsed state of a column group by its id.
28
+ */
29
+ onToggleColumnGroup?: (groupId: string) => void;
26
30
  /** The cell currently in edit mode, if any. */
27
31
  editing: EditingState<TData, TValue> | null;
28
32
  beginEdit: (cell: Cell<TData, TValue>) => void;
@@ -25,6 +25,7 @@ import { DsTableHeaderSelectableCell } from './components/ds-table-header-select
25
25
  import { useDragAndDrop } from './hooks/use-drag-and-drop';
26
26
  import { type DsTableContextType, DsTableContext, useEditingState } from './context/ds-table-context';
27
27
  import { DsTableBodyVirtualized } from './components/ds-table-body-virtualized';
28
+ import { useColumnGroups } from './grouping';
28
29
  import {
29
30
  EMPTY_TABLE_STATE_TEXT,
30
31
  EXPANDER_COLUMN_ID,
@@ -75,6 +76,9 @@ const DsTable = <TData extends { id: string }, TValue>({
75
76
  onColumnFiltersChange,
76
77
  columnVisibility: externalColumnVisibility,
77
78
  onColumnVisibilityChange,
79
+ collapsedColumnGroups: externalCollapsedColumnGroups,
80
+ onCollapsedColumnGroupsChange,
81
+ locale,
78
82
  activeRowId,
79
83
  infiniteScroll,
80
84
  onCellEdit,
@@ -109,12 +113,12 @@ const DsTable = <TData extends { id: string }, TValue>({
109
113
  }
110
114
  };
111
115
 
112
- const columnVisibility = externalColumnVisibility ?? internalColumnVisibility;
116
+ const baseColumnVisibility = externalColumnVisibility ?? internalColumnVisibility;
113
117
  const handleColumnVisibilityChange = (
114
118
  updaterOrValue: VisibilityState | ((old: VisibilityState) => VisibilityState),
115
119
  ) => {
116
120
  const newVisibility =
117
- typeof updaterOrValue === 'function' ? updaterOrValue(columnVisibility) : updaterOrValue;
121
+ typeof updaterOrValue === 'function' ? updaterOrValue(baseColumnVisibility) : updaterOrValue;
118
122
 
119
123
  if (onColumnVisibilityChange) {
120
124
  onColumnVisibilityChange(newVisibility);
@@ -123,6 +127,17 @@ const DsTable = <TData extends { id: string }, TValue>({
123
127
  }
124
128
  };
125
129
 
130
+ const { collapsedColumnGroups, toggleColumnGroup, collapsedVisibility } = useColumnGroups({
131
+ columns: columnsProp,
132
+ collapsedColumnGroups: externalCollapsedColumnGroups,
133
+ onCollapsedColumnGroupsChange,
134
+ });
135
+
136
+ const columnVisibility = useMemo(
137
+ () => ({ ...baseColumnVisibility, ...collapsedVisibility }),
138
+ [baseColumnVisibility, collapsedVisibility],
139
+ );
140
+
126
141
  const handleSortingChange = (updaterOrValue: SortingState | ((old: SortingState) => SortingState)) => {
127
142
  const newSorting = typeof updaterOrValue === 'function' ? updaterOrValue(sorting) : updaterOrValue;
128
143
  setSorting(newSorting);
@@ -287,6 +302,9 @@ const DsTable = <TData extends { id: string }, TValue>({
287
302
  renderExpandedRow,
288
303
  virtualized,
289
304
  activeRowId,
305
+ collapsedColumnGroups,
306
+ onToggleColumnGroup: toggleColumnGroup,
307
+ locale,
290
308
  onCellEdit,
291
309
  ...editingState,
292
310
  };
@@ -16,6 +16,31 @@ import type { InfiniteScrollConfig, ScrollParams } from './components/ds-table-b
16
16
  */
17
17
  export type DsTableRowSize = 'small' | 'medium' | 'large';
18
18
 
19
+ /**
20
+ * Overridable, user-facing strings used by the table (e.g. accessible labels for
21
+ * the column-group collapse toggle). Pass a partial object via the `locale` prop
22
+ * to localize.
23
+ */
24
+ export interface DsTableLocale {
25
+ /**
26
+ * Accessible label for the toggle that collapses a column group.
27
+ */
28
+ collapseColumnGroup: string;
29
+
30
+ /**
31
+ * Accessible label for the toggle that expands a column group.
32
+ */
33
+ expandColumnGroup: string;
34
+ }
35
+
36
+ /**
37
+ * Default English strings for {@link DsTableLocale}.
38
+ */
39
+ export const defaultDsTableLocale: DsTableLocale = Object.freeze({
40
+ collapseColumnGroup: 'Collapse column group',
41
+ expandColumnGroup: 'Expand column group',
42
+ });
43
+
19
44
  /**
20
45
  * API for programmatically controlling a DsTable component.
21
46
  * This interface provides methods to interact with the table's selection, sorting, filtering, and pagination features.
@@ -442,6 +467,36 @@ export interface DsDataTableProps<TData, TValue> {
442
467
  */
443
468
  onColumnVisibilityChange?: (visibility: VisibilityState) => void;
444
469
 
470
+ /**
471
+ * Controlled list of collapsed column-group ids. A group is collapsible when its
472
+ * column def sets `meta.group.collapsible`; collapsing hides every leaf column in
473
+ * the group except those marked `meta.keepVisibleWhenCollapsed`. Omit for
474
+ * uncontrolled behavior (seeded from `meta.group.defaultCollapsed`).
475
+ *
476
+ * @example
477
+ * ```tsx
478
+ * const [collapsed, setCollapsed] = useState<string[]>(['equipment']);
479
+ *
480
+ * <DsTable
481
+ * collapsedColumnGroups={collapsed}
482
+ * onCollapsedColumnGroupsChange={setCollapsed}
483
+ * />
484
+ * ```
485
+ */
486
+ collapsedColumnGroups?: string[];
487
+
488
+ /**
489
+ * Callback fired with the next list of collapsed group ids when a group's
490
+ * collapse toggle is clicked.
491
+ */
492
+ onCollapsedColumnGroupsChange?: (collapsedGroupIds: string[]) => void;
493
+
494
+ /**
495
+ * Overrides for user-facing strings (e.g. column-group toggle labels). Merged
496
+ * over the built-in English defaults.
497
+ */
498
+ locale?: Partial<DsTableLocale>;
499
+
445
500
  /**
446
501
  * Callback fired when an editable cell commits a new value via one of the
447
502
  * `DsTableEditCell*` wrappers or the `useCellEditor` hook. The parent is
@@ -0,0 +1,17 @@
1
+ // url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=36535-44213
2
+ // source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-table
3
+ // component=DsTable
4
+ //
5
+ // `DAP_Table Expandable_v03` is the expandable table on the "Table New (Data grid)"
6
+ // page. It maps to the same `DsTable` code component as the simple table, with the
7
+ // `expandable` prop enabled and a `renderExpandedRow` callback for the nested content.
8
+ // The Simple vs Expandable split exists only because of a Figma restriction (a
9
+ // component can't contain an instance of itself); in code it's one `DsTable`.
10
+ import figma from 'figma';
11
+
12
+ export default {
13
+ example: figma.code`<DsTable columns={columns} data={data} expandable renderExpandedRow={(row) => null} />`,
14
+ imports: ["import { DsTable } from '@drivenets/design-system'"],
15
+ id: 'ds-table-expandable',
16
+ metadata: { nestable: false },
17
+ };
@@ -0,0 +1,16 @@
1
+ // url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=36354-1965
2
+ // source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-table
3
+ // component=DsTable
4
+ //
5
+ // `DAP_Table Simple_v03` is the non-expandable table on the "Table New (Data grid)"
6
+ // page. Figma models the table as separate components (header/row/cell),
7
+ // while code exposes a single declarative `DsTable` driven by `columns`/`data`,
8
+ // so the snippet is a representative usage that references those consts.
9
+ import figma from 'figma';
10
+
11
+ export default {
12
+ example: figma.code`<DsTable columns={columns} data={data} />`,
13
+ imports: ["import { DsTable } from '@drivenets/design-system'"],
14
+ id: 'ds-table',
15
+ metadata: { nestable: false },
16
+ };
@@ -0,0 +1,73 @@
1
+ import { useMemo } from 'react';
2
+ import type { ColumnDef, VisibilityState } from '@tanstack/react-table';
3
+ import { useControlled } from '../../../../utils/use-controlled';
4
+ import { getCollapsedColumnVisibility, getInitialCollapsedGroups } from '../utils/column-group';
5
+
6
+ interface UseColumnGroupsParams<TData, TValue> {
7
+ /**
8
+ * Consumer-provided columns (before utility columns are injected). Group defs
9
+ * nest child columns via `columns`.
10
+ */
11
+ columns: ColumnDef<TData, TValue>[];
12
+
13
+ /**
14
+ * Controlled list of collapsed group ids. When provided, the table no longer
15
+ * manages collapse state internally.
16
+ */
17
+ collapsedColumnGroups?: string[];
18
+
19
+ /**
20
+ * Called with the next list of collapsed group ids whenever a group is toggled.
21
+ */
22
+ onCollapsedColumnGroupsChange?: (collapsedGroupIds: string[]) => void;
23
+ }
24
+
25
+ interface UseColumnGroupsResult {
26
+ /**
27
+ * The effective list of collapsed group ids (controlled or internal).
28
+ */
29
+ collapsedColumnGroups: string[];
30
+
31
+ /**
32
+ * Toggles a single group's collapsed state.
33
+ */
34
+ toggleColumnGroup: (groupId: string) => void;
35
+
36
+ /**
37
+ * Visibility overrides derived from the collapsed groups, to be layered on top
38
+ * of the consumer's own column visibility.
39
+ */
40
+ collapsedVisibility: VisibilityState;
41
+ }
42
+
43
+ /**
44
+ * Manages column-group collapse state. Supports both controlled
45
+ * (`collapsedColumnGroups` + `onCollapsedColumnGroupsChange`) and uncontrolled
46
+ * usage (seeded from `meta.group.defaultCollapsed`).
47
+ */
48
+ export const useColumnGroups = <TData, TValue>({
49
+ columns,
50
+ collapsedColumnGroups,
51
+ onCollapsedColumnGroupsChange,
52
+ }: UseColumnGroupsParams<TData, TValue>): UseColumnGroupsResult => {
53
+ const [collapsed, setCollapsed] = useControlled(
54
+ collapsedColumnGroups,
55
+ onCollapsedColumnGroupsChange,
56
+ getInitialCollapsedGroups(columns),
57
+ );
58
+
59
+ const toggleColumnGroup = (groupId: string) => {
60
+ const next = collapsed.includes(groupId)
61
+ ? collapsed.filter((id) => id !== groupId)
62
+ : [...collapsed, groupId];
63
+
64
+ setCollapsed(next);
65
+ };
66
+
67
+ const collapsedVisibility = useMemo(
68
+ () => getCollapsedColumnVisibility(columns, collapsed),
69
+ [columns, collapsed],
70
+ );
71
+
72
+ return { collapsedColumnGroups: collapsed, toggleColumnGroup, collapsedVisibility };
73
+ };
@@ -0,0 +1,5 @@
1
+ import './types/column-group-meta.augmentation';
2
+
3
+ export type { DsColumnGroupMeta } from './types/column-group.types';
4
+ export { useColumnGroups } from './hooks/use-column-groups';
5
+ export { getGroupColumnSizeStyle, isFirstLeafColumnOfGroup } from './utils/column-group';
@@ -0,0 +1,26 @@
1
+ import '@tanstack/react-table';
2
+ import type { DsColumnGroupMeta } from './column-group.types';
3
+
4
+ declare module '@tanstack/react-table' {
5
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
6
+ interface ColumnMeta<TData, TValue> {
7
+ /**
8
+ * Set on a grouping column def (one that nests child `columns`) to configure
9
+ * the group header and its collapse behavior.
10
+ */
11
+ group?: DsColumnGroupMeta;
12
+
13
+ /**
14
+ * Set on a leaf column to keep it visible while its parent group is collapsed
15
+ * (e.g. a key identifier column that should always stay on screen).
16
+ *
17
+ * Only affects the collapsed state. When `false` or omitted (the default),
18
+ * the column is hidden while the group is collapsed and shown again when the
19
+ * group is expanded — it is never permanently hidden by this flag.
20
+ * @default false
21
+ */
22
+ keepVisibleWhenCollapsed?: boolean;
23
+ }
24
+ }
25
+
26
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Configuration for a grouping column, set on a column definition that nests
3
+ * child columns via TanStack's `columns` array. Lives under `meta.group`.
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * const columns: ColumnDef<Row>[] = [
8
+ * {
9
+ * id: 'equipment',
10
+ * header: 'Equipment',
11
+ * meta: { group: { collapsible: true } },
12
+ * columns: [
13
+ * { accessorKey: 'cabinetType', header: 'Cabinet Type', meta: { keepVisibleWhenCollapsed: true } },
14
+ * { accessorKey: 'shelf', header: 'Shelf#' },
15
+ * ],
16
+ * },
17
+ * ];
18
+ * ```
19
+ */
20
+ export interface DsColumnGroupMeta {
21
+ /**
22
+ * Renders a collapse/expand toggle in the group header. When collapsed, every
23
+ * leaf column in the group is hidden except those marked
24
+ * `meta.keepVisibleWhenCollapsed`.
25
+ * @default false
26
+ */
27
+ collapsible?: boolean;
28
+
29
+ /**
30
+ * When the table manages collapse state internally (uncontrolled), start this
31
+ * group collapsed. Ignored once `collapsedColumnGroups` is provided.
32
+ * @default false
33
+ */
34
+ defaultCollapsed?: boolean;
35
+ }