@drivenets/design-system 0.12.1 → 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 (122) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.cjs +1989 -735
  3. package/dist/index.d.cts +1378 -998
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1378 -998
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1978 -735
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +43 -41
  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-input/ds-date-input.utils.ts +2 -2
  18. package/src/components/ds-date-picker/ds-date-picker.figma.ts +19 -0
  19. package/src/components/ds-drawer/ds-drawer.tsx +2 -0
  20. package/src/components/ds-form-control/ds-form-control.figma.ts +76 -0
  21. package/src/components/ds-form-control/ds-form-control.tsx +14 -11
  22. package/src/components/ds-form-control/ds-form-control.types.ts +4 -0
  23. package/src/components/ds-grid/ds-grid.tsx +1 -2
  24. package/src/components/ds-icon/figma/ds-icon-material.figma.batch.ts +10 -0
  25. package/src/components/ds-icon/figma/ds-icon-special.figma.batch.ts +18 -0
  26. package/src/components/ds-icon/figma/ds-icon.figma.batch.json +2873 -0
  27. package/src/components/ds-modal/ds-modal.tsx +3 -0
  28. package/src/components/ds-modal/figma/ds-modal-content.figma.ts +18 -0
  29. package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +19 -0
  30. package/src/components/ds-modal/figma/ds-modal-header.figma.ts +21 -0
  31. package/src/components/ds-modal/figma/ds-modal.figma.ts +36 -0
  32. package/src/components/ds-popover/ds-popover.module.scss +58 -16
  33. package/src/components/ds-popover/ds-popover.tsx +148 -24
  34. package/src/components/ds-popover/ds-popover.types.ts +95 -12
  35. package/src/components/ds-popover/index.ts +1 -1
  36. package/src/components/ds-select/ds-select.figma.ts +53 -0
  37. package/src/components/ds-split-button/ds-split-button.figma.ts +95 -0
  38. package/src/components/ds-status-badge-v2/ds-status-badge-v2.figma.ts +41 -0
  39. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +21 -2
  40. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +22 -14
  41. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +1 -6
  42. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +2 -2
  43. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +2 -0
  44. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +42 -11
  45. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +31 -0
  46. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +48 -0
  47. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.types.ts +12 -0
  48. package/src/components/ds-table/components/ds-table-group-header-cell/index.ts +2 -0
  49. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +28 -4
  50. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +65 -28
  51. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +26 -0
  52. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +3 -0
  53. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +23 -0
  54. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +4 -1
  55. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +1 -1
  56. package/src/components/ds-table/components/edit/cell-editors/cell-editors.module.scss +3 -0
  57. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-checkbox.tsx +27 -0
  58. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-date.tsx +50 -0
  59. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-number.tsx +51 -0
  60. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-select.tsx +36 -0
  61. package/src/components/ds-table/components/edit/cell-editors/ds-table-edit-cell-text.tsx +50 -0
  62. package/src/components/ds-table/components/edit/cell-editors/index.ts +14 -0
  63. package/src/components/ds-table/components/edit/cell-editors/table-edit-form-control.tsx +31 -0
  64. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.module.scss +28 -0
  65. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.tsx +39 -0
  66. package/src/components/ds-table/components/edit/ds-table-editable-cell/ds-table-editable-cell.types.ts +7 -0
  67. package/src/components/ds-table/components/edit/ds-table-editable-cell/index.ts +2 -0
  68. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.module.scss +48 -0
  69. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.tsx +76 -0
  70. package/src/components/ds-table/components/edit/ds-table-editing-cell/ds-table-editing-cell.types.ts +5 -0
  71. package/src/components/ds-table/components/edit/ds-table-editing-cell/index.ts +2 -0
  72. package/src/components/ds-table/context/ds-table-context.ts +83 -1
  73. package/src/components/ds-table/ds-table-column.types.ts +12 -0
  74. package/src/components/ds-table/ds-table.module.scss +100 -10
  75. package/src/components/ds-table/ds-table.tsx +38 -21
  76. package/src/components/ds-table/ds-table.types.ts +107 -0
  77. package/src/components/ds-table/figma/ds-table-expandable.figma.ts +17 -0
  78. package/src/components/ds-table/figma/ds-table.figma.ts +16 -0
  79. package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +24 -40
  80. package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +10 -31
  81. package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +6 -98
  82. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.module.scss +36 -0
  83. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +92 -13
  84. package/src/components/ds-table/filters/components/checkbox-filter/index.ts +1 -1
  85. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.module.scss +42 -0
  86. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.tsx +89 -0
  87. package/src/components/ds-table/filters/components/column-filter-popover/column-filter-popover.types.ts +25 -0
  88. package/src/components/ds-table/filters/components/column-filter-popover/index.ts +5 -0
  89. package/src/components/ds-table/filters/hooks/use-table-filters.ts +113 -78
  90. package/src/components/ds-table/filters/index.ts +2 -44
  91. package/src/components/ds-table/filters/types/column-meta.augmentation.ts +19 -0
  92. package/src/components/ds-table/filters/types/filter-adapter.types.ts +48 -81
  93. package/src/components/ds-table/grouping/hooks/use-column-groups.ts +73 -0
  94. package/src/components/ds-table/grouping/index.ts +5 -0
  95. package/src/components/ds-table/grouping/types/column-group-meta.augmentation.ts +26 -0
  96. package/src/components/ds-table/grouping/types/column-group.types.ts +35 -0
  97. package/src/components/ds-table/grouping/utils/column-group.ts +119 -0
  98. package/src/components/ds-table/hooks/use-cell-editor.ts +81 -0
  99. package/src/components/ds-table/index.ts +32 -0
  100. package/src/components/ds-table/styles/_variables.scss +1 -0
  101. package/src/components/ds-table/utils/column-size.ts +9 -8
  102. package/src/components/ds-tag/ds-tag.module.scss +22 -1
  103. package/src/components/ds-tag/ds-tag.tsx +18 -5
  104. package/src/components/ds-tag/ds-tag.types.ts +22 -8
  105. package/src/components/ds-tag/ds-tag.unit.test.ts +32 -0
  106. package/src/components/ds-tag-filter/ds-tag-filter.tsx +14 -22
  107. package/src/components/ds-text-input/ds-text-input.figma.ts +60 -0
  108. package/src/components/ds-time-picker/ds-time-picker.figma.ts +18 -0
  109. package/src/components/ds-workspace-layout/ds-workspace-layout.context.ts +19 -0
  110. package/src/components/ds-workspace-layout/ds-workspace-layout.module.scss +166 -0
  111. package/src/components/ds-workspace-layout/ds-workspace-layout.tsx +135 -0
  112. package/src/components/ds-workspace-layout/ds-workspace-layout.types.ts +52 -0
  113. package/src/components/ds-workspace-layout/index.ts +11 -0
  114. package/src/index.ts +1 -1
  115. package/src/styles/_root_new.scss +2 -0
  116. package/src/styles/_scrollbars.scss +36 -22
  117. package/src/utils/restore-document-body-modal-styles.ts +44 -0
  118. package/src/utils/use-ark-dialog-body-lock-cleanup.ts +36 -0
  119. package/src/components/ds-workspace/ds-workspace.module.scss +0 -55
  120. package/src/components/ds-workspace/ds-workspace.tsx +0 -37
  121. package/src/components/ds-workspace/ds-workspace.types.ts +0 -17
  122. package/src/components/ds-workspace/index.ts +0 -8
@@ -0,0 +1,41 @@
1
+ // url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=34428-16204
2
+ // source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-status-badge-v2
3
+ // component=DsStatusBadgeV2
4
+ import figma from 'figma';
5
+
6
+ const instance = figma.selectedInstance;
7
+
8
+ const phase = instance.getEnum('status', {
9
+ 'not-started': 'not-started',
10
+ temporary: 'temporary',
11
+ 'in-review': 'in-review',
12
+ pending: 'pending',
13
+ active: 'active',
14
+ execution: 'execution',
15
+ 'result-succeeded': 'result-succeeded',
16
+ 'result-warning': 'result-warning',
17
+ 'result-failed': 'result-failed',
18
+ deprecated: 'deprecated',
19
+ });
20
+ const variant = instance.getEnum('variant', { primary: 'primary', secondary: 'secondary' });
21
+ const size = instance.getEnum('Size', { medium: 'medium', small: 'small' });
22
+ const structure = instance.getEnum('structure', {
23
+ 'icon+text': 'icon+text',
24
+ 'icon-only': 'icon-only',
25
+ 'text-only': 'text-only',
26
+ });
27
+
28
+ const labelNode = instance.findText('Active', { traverseInstances: true });
29
+ const label = labelNode.type === 'TEXT' ? labelNode.textContent : 'Active';
30
+
31
+ export default {
32
+ example:
33
+ structure === 'icon-only'
34
+ ? figma.code`<DsStatusBadgeV2 phase="${phase}" label="${label}" variant="${variant}" size="${size}" iconOnly />`
35
+ : structure === 'text-only'
36
+ ? figma.code`<DsStatusBadgeV2 phase="${phase}" label="${label}" variant="${variant}" size="${size}" icon={null} />`
37
+ : figma.code`<DsStatusBadgeV2 phase="${phase}" label="${label}" variant="${variant}" size="${size}" />`,
38
+ imports: ["import { DsStatusBadgeV2 } from '@drivenets/design-system'"],
39
+ id: 'ds-status-badge-v2',
40
+ metadata: { nestable: true },
41
+ };
@@ -1,9 +1,28 @@
1
+ @use '../../../../styles/scrollbars' as scrollbars;
2
+
1
3
  .body {
2
- display: grid;
4
+ display: block;
5
+ position: relative;
6
+ overflow-y: auto;
7
+ flex: 1;
8
+ min-height: 0;
9
+ scrollbar-gutter: stable;
10
+
11
+ @include scrollbars.scrollbar-on-hover;
12
+ }
13
+
14
+ // 1px row pinned at `top: <totalSize>` (set inline) so absolutely-positioned
15
+ // rows extend the parent's scrollable overflow area.
16
+ .sentinel {
17
+ position: absolute;
18
+ left: 0;
19
+ width: 1px;
20
+ height: 1px;
21
+ pointer-events: none;
22
+ visibility: hidden;
3
23
  }
4
24
 
5
25
  .emptyState {
6
26
  position: absolute;
7
27
  inset: 0;
8
- top: var(--ds-table-header-height);
9
28
  }
@@ -10,7 +10,6 @@ import { useInfiniteScroll } from './use-infinite-scroll';
10
10
 
11
11
  export const DsTableBodyVirtualized = <TData,>({
12
12
  table,
13
- tableContainerRef,
14
13
  emptyState,
15
14
  estimateSize,
16
15
  overscan,
@@ -20,6 +19,7 @@ export const DsTableBodyVirtualized = <TData,>({
20
19
  }: DsTableBodyVirtualizedProps<TData>) => {
21
20
  const rowsMapRef = useRef(new Map<string, HTMLTableRowElement>());
22
21
  const rowHeightsMapRef = useRef(new Map<string, number>());
22
+ const tbodyRef = useRef<HTMLTableSectionElement>(null);
23
23
 
24
24
  const { rows } = table.getRowModel();
25
25
 
@@ -32,16 +32,16 @@ export const DsTableBodyVirtualized = <TData,>({
32
32
  return item ? `${item.row.id}${item.isExpandedRowContent ? '-expanded-content' : ''}` : String(index);
33
33
  };
34
34
 
35
- const { loadDataIfNeeded } = useInfiniteScroll(tableContainerRef, rows.length, infiniteScroll);
35
+ const { loadDataIfNeeded } = useInfiniteScroll(tbodyRef, rows.length, infiniteScroll);
36
36
 
37
- const rowVirtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
37
+ const rowVirtualizer = useVirtualizer<HTMLTableSectionElement, HTMLTableRowElement>({
38
38
  count: rowsAndExpandedRowContent.length,
39
39
  estimateSize: (index) => {
40
40
  const cachedHeight = rowHeightsMapRef.current.get(getItemKey(index));
41
41
  return cachedHeight || estimateSize;
42
42
  }, // estimate row height for accurate scrollbar dragging
43
43
  getItemKey,
44
- getScrollElement: () => tableContainerRef.current,
44
+ getScrollElement: () => tbodyRef.current,
45
45
  // measure dynamic row height, except in firefox because it measures table border height incorrectly
46
46
  measureElement:
47
47
  typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1
@@ -86,6 +86,7 @@ export const DsTableBodyVirtualized = <TData,>({
86
86
 
87
87
  return (
88
88
  <DsTableBody
89
+ ref={tbodyRef}
89
90
  rowsMapRef={rowsMapRef}
90
91
  rowHeightsMapRef={rowHeightsMapRef}
91
92
  rowVirtualizer={rowVirtualizer}
@@ -97,7 +98,8 @@ export const DsTableBodyVirtualized = <TData,>({
97
98
  };
98
99
 
99
100
  interface DsTableBodyProps<TData> {
100
- rowVirtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement>;
101
+ ref: RefObject<HTMLTableSectionElement | null>;
102
+ rowVirtualizer: Virtualizer<HTMLTableSectionElement, HTMLTableRowElement>;
101
103
  rowsMapRef: RefObject<Map<string, HTMLTableRowElement>>;
102
104
  rowHeightsMapRef: RefObject<Map<string, number>>;
103
105
  rowsAndExpandedRowContent: {
@@ -108,7 +110,8 @@ interface DsTableBodyProps<TData> {
108
110
  rowSelection: RowSelectionState;
109
111
  }
110
112
 
111
- function DsTableBody<TData>({
113
+ export function DsTableBody<TData>({
114
+ ref,
112
115
  rowVirtualizer,
113
116
  rowsMapRef,
114
117
  rowHeightsMapRef,
@@ -117,14 +120,15 @@ function DsTableBody<TData>({
117
120
  rowSelection,
118
121
  }: DsTableBodyProps<TData>) {
119
122
  const virtualRows = rowVirtualizer.getVirtualItems();
123
+ const totalSize = rowVirtualizer.getTotalSize();
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;
120
129
 
121
130
  return (
122
- <TableBody
123
- className={styles.body}
124
- style={{
125
- height: `${rowVirtualizer.getTotalSize().toString()}px`, // tells scrollbar how big the table is
126
- }}
127
- >
131
+ <TableBody ref={ref} className={styles.body}>
128
132
  {virtualRows.length > 0 ? (
129
133
  virtualRows.map((virtualRow) => {
130
134
  const row = rowsAndExpandedRowContent[virtualRow.index];
@@ -145,11 +149,15 @@ function DsTableBody<TData>({
145
149
  />
146
150
  );
147
151
  })
148
- ) : (
152
+ ) : hasNoData ? (
149
153
  <TableRow>
150
154
  <TableCell className={styles.emptyState}>{emptyState || EMPTY_TABLE_STATE_TEXT}</TableCell>
151
155
  </TableRow>
152
- )}
156
+ ) : null}
157
+
158
+ <tr aria-hidden className={styles.sentinel} style={{ top: `${totalSize.toString()}px` }}>
159
+ <td />
160
+ </tr>
153
161
  </TableBody>
154
162
  );
155
163
  }
@@ -1,4 +1,4 @@
1
- import { type RefObject, type ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  import { type RowSelectionState, type Table } from '@tanstack/react-table';
3
3
 
4
4
  export interface DsTableBodyVirtualizedProps<TData> {
@@ -7,11 +7,6 @@ export interface DsTableBodyVirtualizedProps<TData> {
7
7
  * and column state.
8
8
  */
9
9
  table: Table<TData>;
10
- /**
11
- * Ref to the scrollable container that hosts the virtualized rows. Required by
12
- * the virtualizer to measure viewport size and listen for scroll events.
13
- */
14
- tableContainerRef: RefObject<HTMLDivElement | null>;
15
10
  /**
16
11
  * Optional content rendered in place of rows when the table has no data.
17
12
  */
@@ -1,14 +1,14 @@
1
1
  @use '../../../../styles/typography';
2
2
 
3
3
  .bulkActionsContainer {
4
- position: fixed;
4
+ position: absolute;
5
5
  bottom: 20px;
6
6
  left: 50%;
7
7
  transform: translateX(-50%);
8
8
  background-color: var(--color-dap-gray-050);
9
9
  border-radius: 8px;
10
10
  box-shadow: 0 4px 12px #0445cc66;
11
- z-index: 1000;
11
+ z-index: 1;
12
12
  display: flex;
13
13
  align-items: center;
14
14
  border-radius: 8px;
@@ -1,6 +1,8 @@
1
1
  @use '../../../../styles/typography';
2
2
 
3
3
  .tableCellEllipsis {
4
+ flex: 1;
5
+ min-width: 0;
4
6
  display: -webkit-box;
5
7
  -webkit-line-clamp: 2;
6
8
  -webkit-box-orient: vertical;
@@ -2,31 +2,31 @@ import { type Cell, flexRender } from '@tanstack/react-table';
2
2
  import classnames from 'classnames';
3
3
  import { DsIcon } from '../../../ds-icon';
4
4
  import { DsDropdownMenu } from '../../../ds-dropdown-menu';
5
+ import { useDsTableContext } from '../../context/ds-table-context';
6
+ import { DsTableEditableCell } from '../edit/ds-table-editable-cell';
7
+ import { DsTableEditingCell } from '../edit/ds-table-editing-cell';
5
8
  import styles from './ds-table-cell.module.scss';
6
9
  import type { DsTableCellProps } from './ds-table-cell.types';
7
10
 
8
- const DsDefaultTableCell = <TData, TValue>({ cell }: { cell: Cell<TData, TValue> }) => {
9
- return (
10
- <div className={styles.tableCellEllipsis}>
11
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
12
- </div>
13
- );
14
- };
15
-
16
11
  export const DsTableCell = <TData, TValue>({
17
12
  row,
18
13
  cell,
19
14
  primaryRowActions = [],
20
15
  secondaryRowActions = [],
21
16
  }: DsTableCellProps<TData, TValue>) => {
17
+ const { editing } = useDsTableContext<TData, TValue>();
18
+ const isCellBeingEdited =
19
+ editing !== null && editing.cell.row.id === row.id && editing.cell.column.id === cell.column.id;
20
+
22
21
  const visiblePrimary = primaryRowActions.filter((action) => !action.hidden?.(row.original));
23
22
  const visibleSecondary = secondaryRowActions.filter((action) => !action.hidden?.(row.original));
24
23
 
25
- if (visiblePrimary.length || visibleSecondary.length) {
24
+ if (!isCellBeingEdited && (visiblePrimary.length || visibleSecondary.length)) {
26
25
  const hasSecondaryRowActions = visibleSecondary.length > 0;
26
+
27
27
  return (
28
28
  <div className={styles.lastCell}>
29
- <DsDefaultTableCell cell={cell} />
29
+ <CellContent cell={cell} isCellBeingEdited={isCellBeingEdited} />
30
30
  <div className={styles.cellActions}>
31
31
  {visiblePrimary.map((action, i) => {
32
32
  const isDisabled = action.disabled?.(row.original);
@@ -95,5 +95,36 @@ export const DsTableCell = <TData, TValue>({
95
95
  </div>
96
96
  );
97
97
  }
98
- return <DsDefaultTableCell cell={cell} />;
98
+
99
+ return <CellContent cell={cell} isCellBeingEdited={isCellBeingEdited} />;
100
+ };
101
+
102
+ interface CellContentProps<TData, TValue> {
103
+ cell: Cell<TData, TValue>;
104
+ isCellBeingEdited: boolean;
105
+ }
106
+
107
+ const CellContent = <TData, TValue>({ cell, isCellBeingEdited }: CellContentProps<TData, TValue>) => {
108
+ const columnDef = cell.column.columnDef;
109
+ const isEditable = typeof columnDef.editCell === 'function';
110
+
111
+ if (isCellBeingEdited) {
112
+ return <DsTableEditingCell cell={cell} />;
113
+ }
114
+
115
+ if (isEditable) {
116
+ return (
117
+ <DsTableEditableCell cell={cell}>
118
+ <div className={styles.tableCellEllipsis}>
119
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
120
+ </div>
121
+ </DsTableEditableCell>
122
+ );
123
+ }
124
+
125
+ return (
126
+ <div className={styles.tableCellEllipsis}>
127
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
128
+ </div>
129
+ );
99
130
  };
@@ -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';
@@ -2,14 +2,11 @@
2
2
  @use '../../styles/variables' as vars;
3
3
 
4
4
  .stickyHeader {
5
- position: sticky;
6
- top: 0;
7
- z-index: 10;
8
5
  background-color: var(--background);
9
6
  }
10
7
 
11
8
  .virtualizedHeader {
12
- display: grid;
9
+ display: block;
13
10
  }
14
11
 
15
12
  .headerRow {
@@ -57,6 +54,11 @@
57
54
 
58
55
  .selectHeaderCell {
59
56
  padding: 0;
57
+ justify-content: center;
58
+ }
59
+
60
+ .groupStart {
61
+ border-left: vars.$group-border-width solid var(--border);
60
62
  }
61
63
 
62
64
  .headerSortContainer {
@@ -66,3 +68,25 @@
66
68
  gap: vars.$header-sort-gap;
67
69
  height: 100%;
68
70
  }
71
+
72
+ .filterTriggerContainer {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ margin-left: vars.$header-sort-gap;
76
+ }
77
+
78
+ .filterableHeader {
79
+ &:hover .filterTriggerContainer > * {
80
+ opacity: 1;
81
+ }
82
+
83
+ &:focus-within .filterTriggerContainer > * {
84
+ opacity: 1;
85
+ }
86
+ }
87
+
88
+ .filterActive {
89
+ .filterTriggerContainer > * {
90
+ opacity: 1;
91
+ }
92
+ }
@@ -9,6 +9,10 @@ import { useDsTableContext } from '../../context/ds-table-context';
9
9
  import { getColumnSizeStyle } from '../../utils/column-size';
10
10
  import { SELECT_COLUMN_ID } from '../../utils/constants';
11
11
  import { DsStack } from '../../../ds-stack';
12
+ import { DsTableColumnFilterPopover } from '../../filters/components/column-filter-popover';
13
+ import type { ResolvedColumnFilter } from '../../filters/types/filter-adapter.types';
14
+ import { DsTableGroupHeaderCell } from '../ds-table-group-header-cell';
15
+ import { isFirstLeafColumnOfGroup } from '../../grouping';
12
16
 
13
17
  const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
14
18
  const { stickyHeader, bordered, virtualized } = useDsTableContext<TData, unknown>();
@@ -27,9 +31,29 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
27
31
  )}
28
32
  >
29
33
  {headerGroup.headers.map((header) => {
30
- const headerStyle = getColumnSizeStyle(header.column.getSize(), virtualized);
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
+
48
+ const headerStyle = getColumnSizeStyle(header.column.getSize());
31
49
  const canSort = header.column.getCanSort();
32
50
  const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
51
+ const isGroupStart = isFirstLeafColumnOfGroup(header.column);
52
+
53
+ const metaFilter = header.column.columnDef.meta?.filter;
54
+ const resolvedFilter: ResolvedColumnFilter | undefined =
55
+ metaFilter && 'render' in metaFilter ? metaFilter : undefined;
56
+ const hasActiveColumnFilter = resolvedFilter?.hasActiveFilter ?? false;
33
57
 
34
58
  return (
35
59
  <TableHead
@@ -38,37 +62,50 @@ const DsTableHeader = <TData,>({ table }: DsTableHeaderProps<TData>) => {
38
62
  styles.headerCell,
39
63
  canSort && styles.sortableHeader,
40
64
  isSelectColumn && styles.selectHeaderCell,
65
+ resolvedFilter && styles.filterableHeader,
66
+ hasActiveColumnFilter && styles.filterActive,
67
+ isGroupStart && styles.groupStart,
41
68
  )}
42
69
  onClick={header.column.getToggleSortingHandler()}
43
70
  style={headerStyle}
44
71
  >
45
- {header.isPlaceholder ? null : (
46
- <div className={styles.headerSortContainer}>
47
- <DsStack direction="column" justifyContent="center" width="100%">
48
- {flexRender(header.column.columnDef.header, header.getContext())}
49
- </DsStack>
50
- {canSort && (
51
- <div className={styles.pageButtonIconContainer}>
52
- {{
53
- asc: (
54
- <DsIcon
55
- icon="arrow_drop_up"
56
- className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
57
- />
58
- ),
59
- desc: (
60
- <DsIcon
61
- icon="arrow_drop_down"
62
- className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)}
63
- />
64
- ),
65
- }[header.column.getIsSorted() as string] ?? (
66
- <div className={classnames(styles.pageButtonIcon, stylesShared.pageButtonIcon)} />
67
- )}
68
- </div>
69
- )}
70
- </div>
71
- )}
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>
72
109
  </TableHead>
73
110
  );
74
111
  })}
@@ -50,6 +50,8 @@
50
50
  background-color: var(--background-secondary);
51
51
 
52
52
  & > td {
53
+ flex: 1;
54
+ min-width: 0;
53
55
  padding: var(--standard);
54
56
  }
55
57
 
@@ -80,4 +82,28 @@
80
82
  .selectableCell {
81
83
  padding: 0;
82
84
  text-align: center;
85
+ justify-content: center;
86
+ }
87
+
88
+ .groupStart {
89
+ border-left: vars.$group-border-width solid var(--border);
90
+ }
91
+
92
+ .tableRow .editableCell {
93
+ padding: 1px;
94
+
95
+ &:hover {
96
+ border: 1px solid var(--border-secondary-hover);
97
+ }
98
+
99
+ &:focus-within {
100
+ border: 1px solid var(--border-action-primary);
101
+ }
102
+
103
+ &:hover,
104
+ &:focus-within {
105
+ padding: 0;
106
+ border-bottom-width: 2px;
107
+ background: var(--background);
108
+ }
83
109
  }
@@ -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,8 @@ 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,
123
+ typeof cell.column.columnDef.editCell === 'function' && styles.editableCell,
121
124
  )}
122
125
  style={cellStyle}
123
126
  >
@@ -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
 
@@ -79,3 +83,22 @@
79
83
 
80
84
  @include table-mixins.expanded-row-border('bottom');
81
85
  }
86
+
87
+ .row td.editableCell {
88
+ padding: 1px;
89
+
90
+ &:hover {
91
+ border: 1px solid var(--border-secondary-hover);
92
+ }
93
+
94
+ &:focus-within {
95
+ border: 1px solid var(--border-action-primary);
96
+ }
97
+
98
+ &:hover,
99
+ &:focus-within {
100
+ padding: 0;
101
+ border-bottom-width: 2px;
102
+ background: var(--background);
103
+ }
104
+ }