@dbcdk/react-components 0.0.18 → 0.0.20

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.
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import type { HTMLAttributes, JSX, ReactNode } from 'react';
3
2
  import { Severity } from '../../constants/severity.types';
4
3
  import { PageChangeEvent } from '../../components/pagination/Pagination';
@@ -25,7 +24,7 @@ type HeaderExtrasArgs<T> = {
25
24
  index: number;
26
25
  };
27
26
  export type TableVariant = 'primary' | 'embedded';
28
- export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTMLTableElement>, 'onClick'> & {
27
+ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTMLDivElement>, 'onClick'> & {
29
28
  data: T[];
30
29
  dataKey: keyof T;
31
30
  columns: ColumnItem<T>[];
@@ -40,7 +39,13 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
40
39
  sortDirection?: SortDirection;
41
40
  loading?: boolean;
42
41
  headerExtras?: (args: HeaderExtrasArgs<T>) => ReactNode;
43
- columnStyles?: Partial<Record<string, React.CSSProperties>>;
42
+ /**
43
+ * Grid layout control
44
+ *
45
+ * Example:
46
+ * "34px minmax(160px, 2fr) minmax(120px, 1fr) minmax(120px, 1fr)"
47
+ */
48
+ gridTemplateColumns?: string;
44
49
  toolbar?: ReactNode;
45
50
  striped?: boolean;
46
51
  fillViewport?: boolean;
@@ -58,6 +63,6 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
58
63
  showFirstLast?: boolean;
59
64
  viewMode?: ViewMode;
60
65
  emptyConfig?: TableEmptyConfig;
61
- } & Omit<HTMLAttributes<HTMLTableElement>, 'onClick'>;
62
- export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles, toolbar, striped, fillViewport, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement, totalItemsCount, onPageChange, loading, variant, size, getRowSeverity, showFirstLast, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }: TableProps<T>): JSX.Element;
66
+ };
67
+ export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement, totalItemsCount, onPageChange, loading, variant, size, getRowSeverity, showFirstLast, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }: TableProps<T>): JSX.Element;
63
68
  export {};
@@ -9,8 +9,17 @@ import { Pagination } from '../../components/pagination/Pagination';
9
9
  import { SkeletonLoaderItem } from '../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
10
10
  import { TableEmptyState } from './components/empty-state/EmptyState';
11
11
  import styles from './Table.module.css';
12
- import { getAriaSort, getCellDisplayValue, getColumnStyle, getHeaderLabel, getNextSortDirection, getRowKey, getVisibleColumns, isModifierClick, shouldAllowWrap, shouldToggleOnKey, isActiveSort, } from './table.utils';
13
- export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles, toolbar, striped, fillViewport = false, viewportBottomOffset = 0, viewportMin = 120, viewportIncludeMarginTop = false, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }) {
12
+ import { getAriaSort, getCellDisplayValue, getHeaderLabel, getNextSortDirection, getVisibleColumns, isModifierClick, shouldAllowWrap, shouldToggleOnKey, isActiveSort, } from './table.utils';
13
+ function buildDefaultGridTemplate(args) {
14
+ const { hasSelection, colCount } = args;
15
+ const parts = [];
16
+ if (hasSelection)
17
+ parts.push('34px');
18
+ for (let i = 0; i < colCount; i++)
19
+ parts.push('minmax(120px, 1fr)');
20
+ return parts.join(' ');
21
+ }
22
+ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport = false, viewportBottomOffset = 0, viewportMin = 120, viewportIncludeMarginTop = false, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }) {
14
23
  const filteredColumns = useMemo(() => getVisibleColumns(columns), [columns]);
15
24
  const handlePageChange = useCallback((e) => onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e), [onPageChange]);
16
25
  const scrollRef = useRef(null);
@@ -19,64 +28,87 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
19
28
  min: viewportMin,
20
29
  includeMarginTop: viewportIncludeMarginTop,
21
30
  });
22
- const tableEl = (_jsxs(_Fragment, { children: [toolbar ? _jsx("div", { style: { marginBottom: 12 }, children: toolbar }) : null, _jsxs("table", { ...rest, className: `${styles.table} ${styles[variant]} ${styles[size]} ${getRowSeverity ? styles.severityTable : ''}`, children: [_jsx("thead", { children: _jsxs("tr", { children: [selectedRows && onRowSelect && dataKey && (_jsx("th", { className: `${styles.th} ${styles.selectionCell}`, children: selectionMode === 'multiple' ? (_jsx(Checkbox, { size: "sm", variant: "primary", checked: allRowsSelected, onChange: checked => onSelectAllRows === null || onSelectAllRows === void 0 ? void 0 : onSelectAllRows(checked) })) : null })), filteredColumns.map((column, index) => {
23
- const active = isActiveSort(sortById, column.id);
24
- const ariaSort = getAriaSort(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
25
- const toggleSort = () => {
26
- if (!onSortChange || !column.sortable)
27
- return;
28
- const nextDir = getNextSortDirection(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
29
- onSortChange(column, nextDir);
30
- };
31
- return (_jsx("th", { style: getColumnStyle(column.id, columnStyles, column.align, 'middle'), "aria-sort": ariaSort, className: `${styles.th}`, children: _jsxs("div", { className: styles.thInner, children: [column.sortable ? (_jsxs("button", { type: "button", className: [
32
- styles.thButton,
33
- column.align === 'right' ? styles.thButtonRight : '',
34
- column.align === 'center' ? styles.thButtonCenter : '',
35
- ].join(' '), onClick: toggleSort, onKeyDown: e => {
36
- if (shouldToggleOnKey(e.key)) {
37
- e.preventDefault();
38
- toggleSort();
39
- }
40
- }, children: [_jsx("span", { className: [
41
- styles.thLabel,
42
- column.align === 'right' ? styles.thLabelRight : '',
43
- column.align === 'center' ? styles.thLabelCenter : '',
44
- ].join(' '), children: getHeaderLabel(column.header) }), _jsxs("span", { className: styles.sortIndicator, "aria-hidden": "true", children: [active && sortDirection === 'asc' && _jsx(ArrowUp, {}), active && sortDirection === 'desc' && (_jsx(ArrowDown, { className: styles.descending })), !active && (_jsx(ArrowDown, { className: `${styles.descending} ${styles.inActiveSort}` }))] })] })) : (_jsx("span", { className: [
45
- styles.thLabel,
46
- column.align === 'right' ? styles.thLabelRight : '',
47
- column.align === 'center' ? styles.thLabelCenter : '',
48
- ].join(' '), children: getHeaderLabel(column.header) })), headerExtras ? (_jsx("div", { className: styles.thExtras, children: headerExtras({ column, index }) })) : null] }) }, column.id));
49
- })] }) }), loading && !data.length ? (_jsx("tbody", { className: `${styles.tBody} ${striped ? styles.striped : ''}`, children: Array.from({ length: take !== null && take !== void 0 ? take : 5 }).map((_, rowIndex) => (_jsx("tr", { children: filteredColumns.map((column, colIndex) => (_jsx("td", { style: getColumnStyle(column.id, columnStyles, column.align, 'middle'), className: `${styles.tableCell}`, children: _jsx(SkeletonLoaderItem, { height: 20, width: "100%" }) }, `${column.id}-${colIndex}`))) }, `loading-row-${rowIndex}`))) })) : (_jsx("tbody", { className: `${styles.tBody} ${striped ? styles.striped : ''}`, children: data === null || data === void 0 ? void 0 : data.map(row => {
50
- const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
51
- const rowId = row[dataKey];
52
- const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
53
- return (_jsxs("tr", { tabIndex: onRowClick ? 0 : -1, onKeyDown: e => {
54
- if (!onRowClick)
55
- return;
31
+ const hasSelection = Boolean(selectedRows && onRowSelect && dataKey);
32
+ const template = useMemo(() => {
33
+ return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({ hasSelection, colCount: filteredColumns.length }));
34
+ }, [gridTemplateColumns, hasSelection, filteredColumns.length]);
35
+ const gridStyle = useMemo(() => ({ ['--grid-template']: template }), [template]);
36
+ const headerEl = (_jsxs("div", { className: `${styles.headerRow}`, style: gridStyle, role: "row", children: [hasSelection && (_jsx("div", { className: `${styles.headerCell} ${styles.selectionCell}`, role: "columnheader", children: selectionMode === 'multiple' ? (_jsx(Checkbox, { size: "sm", variant: "primary", checked: allRowsSelected, onChange: checked => onSelectAllRows === null || onSelectAllRows === void 0 ? void 0 : onSelectAllRows(checked) })) : null })), filteredColumns.map((column, index) => {
37
+ var _a;
38
+ const active = isActiveSort(sortById, column.id);
39
+ const ariaSort = getAriaSort(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
40
+ const toggleSort = () => {
41
+ if (!onSortChange || !column.sortable)
42
+ return;
43
+ const nextDir = getNextSortDirection(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
44
+ onSortChange(column, nextDir);
45
+ };
46
+ return (_jsx("div", { className: styles.headerCell, role: "columnheader", "aria-sort": ariaSort, "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', children: _jsxs("div", { className: styles.thInner, children: [column.sortable ? (_jsxs("button", { type: "button", className: [
47
+ styles.thButton,
48
+ column.align === 'right' ? styles.thButtonRight : '',
49
+ column.align === 'center' ? styles.thButtonCenter : '',
50
+ ].join(' '), onClick: toggleSort, onKeyDown: e => {
56
51
  if (shouldToggleOnKey(e.key)) {
57
52
  e.preventDefault();
58
- onRowClick(row);
59
- }
60
- }, onClick: e => {
61
- const canSelect = Boolean(selectedRows && onRowSelect && dataKey);
62
- if (isModifierClick(e) && canSelect) {
63
- e.preventDefault();
64
- e.stopPropagation();
65
- onRowSelect(rowId, !selectedRows.has(rowId));
66
- return;
53
+ toggleSort();
67
54
  }
68
- onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row);
69
- }, style: {
70
- ['--row-severity-color']: rowSeverity
71
- ? SeverityBgColor[rowSeverity]
72
- : undefined,
73
- }, className: `${onRowClick ? styles.clickableRow : ''} ${isSelected ? styles.selectedRow : ''} ${rowSeverity ? styles.severity : ''}`, children: [selectedRows && onRowSelect && dataKey && (_jsx("td", { className: `${styles.selectionCell}`, children: _jsx(Checkbox, { variant: "primary", checked: selectedRows.has(rowId), size: "sm", onChange: (checked, e) => {
74
- e.stopPropagation();
75
- onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, checked);
76
- } }) })), filteredColumns.map(column => (_jsx("td", { style: getColumnStyle(column.id, columnStyles, column.align, column.verticalAlign), className: `${styles.tableCell} ${shouldAllowWrap(column.allowWrap, isSelected, viewMode)
77
- ? styles.allowWrap
78
- : styles.nowrap}`, children: getCellDisplayValue(row, column) }, column.id)))] }, getRowKey(rowId)));
79
- }) }))] }), !data.length && !loading && _jsx(TableEmptyState, { config: emptyConfig })] }));
55
+ }, children: [_jsx("span", { className: [
56
+ styles.thLabel,
57
+ column.align === 'right' ? styles.thLabelRight : '',
58
+ column.align === 'center' ? styles.thLabelCenter : '',
59
+ ].join(' '), children: getHeaderLabel(column.header) }), _jsxs("span", { className: styles.sortIndicator, "aria-hidden": "true", children: [active && sortDirection === 'asc' && _jsx(ArrowUp, {}), active && sortDirection === 'desc' && (_jsx(ArrowDown, { className: styles.descending })), !active && (_jsx(ArrowDown, { className: `${styles.descending} ${styles.inActiveSort}` }))] })] })) : (_jsx("span", { className: [
60
+ styles.thLabel,
61
+ column.align === 'right' ? styles.thLabelRight : '',
62
+ column.align === 'center' ? styles.thLabelCenter : '',
63
+ ].join(' '), children: getHeaderLabel(column.header) })), headerExtras ? (_jsx("div", { className: styles.thExtras, children: headerExtras({ column, index }) })) : null] }) }, column.id));
64
+ })] }));
65
+ const bodyEl = loading && !data.length ? (_jsx("div", { className: styles.body, role: "rowgroup", children: Array.from({ length: take !== null && take !== void 0 ? take : 5 }).map((_, rowIndex) => (_jsxs("div", { className: styles.row, style: gridStyle, role: "row", children: [hasSelection ? (_jsx("div", { className: `${styles.cell} ${styles.selectionCell}`, role: "cell" })) : null, filteredColumns.map(column => {
66
+ var _a;
67
+ return (_jsx("div", { className: styles.cell, role: "cell", "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', children: _jsx(SkeletonLoaderItem, { height: 20, width: "100%" }) }, column.id));
68
+ })] }, `loading-row-${rowIndex}`))) })) : (_jsx("div", { className: `${styles.body} ${striped ? styles.striped : ''}`, role: "rowgroup", children: data === null || data === void 0 ? void 0 : data.map(row => {
69
+ const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
70
+ const rowId = row[dataKey];
71
+ const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
72
+ return (_jsxs("div", { className: [
73
+ styles.row,
74
+ onRowClick ? styles.clickableRow : '',
75
+ isSelected ? styles.selectedRow : '',
76
+ rowSeverity ? styles.severity : '',
77
+ ].join(' '), style: {
78
+ ...gridStyle,
79
+ ['--row-severity-color']: rowSeverity
80
+ ? SeverityBgColor[rowSeverity]
81
+ : undefined,
82
+ }, role: "row", tabIndex: onRowClick ? 0 : -1, onKeyDown: e => {
83
+ if (!onRowClick)
84
+ return;
85
+ if (shouldToggleOnKey(e.key)) {
86
+ e.preventDefault();
87
+ onRowClick(row);
88
+ }
89
+ }, onClick: e => {
90
+ const canSelect = Boolean(selectedRows && onRowSelect && dataKey);
91
+ if (isModifierClick(e) && canSelect) {
92
+ e.preventDefault();
93
+ e.stopPropagation();
94
+ onRowSelect(rowId, !selectedRows.has(rowId));
95
+ return;
96
+ }
97
+ onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row);
98
+ }, children: [hasSelection && (_jsx("div", { className: `${styles.cell} ${styles.selectionCell}`, role: "cell", children: _jsx(Checkbox, { variant: "primary", checked: selectedRows.has(rowId), size: "sm", onChange: (checked, e) => {
99
+ e.stopPropagation();
100
+ onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, checked);
101
+ } }) })), filteredColumns.map(column => {
102
+ var _a;
103
+ return (_jsx("div", { className: [
104
+ styles.cell,
105
+ shouldAllowWrap(column.allowWrap, isSelected, viewMode)
106
+ ? styles.allowWrap
107
+ : styles.nowrap,
108
+ ].join(' '), role: "cell", "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', children: getCellDisplayValue(row, column) }, column.id));
109
+ })] }, `gridRow-${String(rowId)}`));
110
+ }) }));
111
+ const gridEl = (_jsxs(_Fragment, { children: [toolbar ? _jsx("div", { style: { marginBottom: 12 }, children: toolbar }) : null, _jsxs("div", { ...rest, className: `${styles.grid} ${styles[variant]} ${styles[size]} ${getRowSeverity ? styles.severityTable : ''}`, role: "table", "aria-rowcount": data.length, children: [_jsx("div", { className: styles.header, role: "rowgroup", children: headerEl }), bodyEl] }), !data.length && !loading && _jsx(TableEmptyState, { config: emptyConfig })] }));
80
112
  if (fillViewport) {
81
113
  return (_jsxs("div", { style: {
82
114
  display: 'flex',
@@ -84,10 +116,10 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
84
116
  gap: '20px',
85
117
  flexFlow: paginationPlacement === 'top' ? 'column-reverse' : 'column',
86
118
  position: 'relative',
87
- }, children: [_jsx("div", { ref: scrollRef, style: viewportStyle, className: styles.tableScroll, children: tableEl }), onPageChange && data.length > 0 && (_jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast }))] }));
119
+ }, children: [_jsx("div", { ref: scrollRef, style: viewportStyle, className: styles.tableScroll, children: gridEl }), onPageChange && data.length > 0 && (_jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast }))] }));
88
120
  }
89
121
  return (_jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-md", style: {
90
122
  flexFlow: paginationPlacement === 'top' ? 'column-reverse' : 'column',
91
123
  position: 'relative',
92
- }, children: [tableEl, onPageChange && data.length > 0 && (_jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange }))] }));
124
+ }, children: [gridEl, onPageChange && data.length > 0 && (_jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange }))] }));
93
125
  }
@@ -1,19 +1,10 @@
1
- /* Table.module.css (updated) */
2
-
3
- /* =========================
4
- Base table
5
- ========================= */
6
-
7
- .table {
1
+ .grid {
8
2
  inline-size: 100%;
9
3
  max-inline-size: 100%;
10
- overflow: visible;
11
- border-collapse: collapse;
12
4
  font-family: var(--font-family);
13
5
  font-size: var(--font-size-sm);
14
6
  color: var(--color-fg-default);
15
7
  background: var(--color-bg-surface);
16
- table-layout: fixed;
17
8
  }
18
9
 
19
10
  .tableScroll {
@@ -23,38 +14,39 @@
23
14
  -webkit-overflow-scrolling: touch;
24
15
  }
25
16
 
26
- /* =========================
27
- Header
28
- ========================= */
29
-
30
- .table thead {
17
+ /* Header wrapper sticky */
18
+ .header {
31
19
  position: sticky;
32
20
  top: 0;
33
21
  z-index: 10;
34
22
  background-color: var(--color-bg-surface);
35
23
  }
36
24
 
37
- .table.primary thead {
25
+ .primary .header {
38
26
  box-shadow: var(--shadow-md);
39
27
  }
40
28
 
41
- .table .th {
29
+ /* Shared row grid template */
30
+ .headerRow,
31
+ .row {
42
32
  position: relative;
33
+ display: grid;
34
+ grid-template-columns: var(--grid-template);
35
+ align-items: stretch;
36
+ inline-size: 100%;
37
+ }
43
38
 
39
+ /* Header cells */
40
+ .headerCell {
41
+ position: relative;
44
42
  padding-block: var(--spacing-xs);
45
43
  padding-inline: var(--spacing-sm);
46
44
 
47
- text-align: left;
48
- vertical-align: middle;
49
-
50
- background: inherit;
51
-
52
45
  /* Typography */
53
46
  font-size: var(--font-size-xs);
54
47
  font-weight: var(--font-weight-normal);
55
48
  letter-spacing: var(--letter-spacing-wide);
56
49
  text-transform: uppercase;
57
-
58
50
  color: var(--color-fg-subtle);
59
51
 
60
52
  /* Truncation */
@@ -62,159 +54,65 @@
62
54
  overflow: hidden;
63
55
  text-overflow: ellipsis;
64
56
 
65
- /* Width control */
66
57
  min-width: 0;
67
58
  }
68
59
 
69
- /* Small variant: header padding */
70
- .table.sm .th {
60
+ .sm .headerCell {
71
61
  padding-inline: var(--spacing-md);
72
62
  }
73
63
 
74
- /* Header layout */
75
- .th > .thInner {
76
- display: flex;
77
- align-items: center;
78
- inline-size: 100%;
79
- }
80
-
81
- /* Sort button (full width click target, but content aligns via modifiers) */
82
- .thButton {
83
- all: unset;
84
- display: flex;
85
- align-items: center;
86
- gap: var(--spacing-xxs);
87
- inline-size: 100%;
88
- cursor: pointer;
89
- user-select: none;
90
- border-radius: var(--border-radius-default);
91
- padding-block: 2px;
92
- padding-inline: 2px;
93
-
94
- /* default */
95
- justify-content: flex-start;
96
- }
97
-
98
- /* IMPORTANT: use consistent PascalCase suffixes */
99
- .thButtonRight {
100
- justify-content: flex-end;
101
- }
102
-
103
- .thButtonCenter {
104
- justify-content: center;
105
- }
106
-
107
- /* Label should NOT grow to fill space (it breaks alignment) */
108
- .thLabel {
109
- overflow: hidden;
110
- text-overflow: ellipsis;
111
- white-space: nowrap;
112
- flex: 0 1 auto;
113
- min-width: 0;
114
- }
115
-
116
- /* Match label text alignment to column alignment */
117
- .thLabelRight {
118
- text-align: right;
119
- }
120
-
121
- .thLabelCenter {
122
- text-align: center;
123
- }
124
-
125
- .thExtras {
126
- display: inline-flex;
127
- align-items: center;
128
- }
129
-
130
- .sortIndicator {
131
- display: inline-flex;
132
- flex: 0 0 auto;
133
- align-items: center;
134
- }
135
-
136
- .sortIndicator svg {
137
- inline-size: var(--icon-size-sm);
138
- block-size: var(--icon-size-sm);
139
- }
140
-
141
- /* =========================
142
- Body + cells
143
- ========================= */
144
-
145
- .tBody::before {
64
+ /* Body */
65
+ .body::before {
146
66
  content: '';
147
67
  height: var(--spacing-xs);
148
68
  display: block;
149
69
  }
150
70
 
151
- .tBody tr td {
71
+ /* Data cells */
72
+ .cell {
152
73
  position: relative;
153
74
  padding-block: var(--spacing-xs);
154
75
  padding-inline: var(--spacing-sm);
155
-
156
76
  border-block-end: var(--border-width-thin) solid var(--color-border-default);
157
-
158
77
  text-align: start;
159
78
  vertical-align: top;
160
-
161
- overflow-wrap: break-word;
162
- word-break: normal;
79
+ min-width: 0;
80
+ overflow: hidden;
81
+ text-overflow: ellipsis;
163
82
  }
164
83
 
165
- /* Small variant applies to all cells by default */
166
- .table.sm .tBody tr td {
84
+ .sm .cell {
167
85
  padding-block: var(--spacing-xxs);
168
86
  padding-inline: var(--spacing-md);
169
87
  font-size: var(--font-size-xs);
170
88
  line-height: var(--line-height-normal);
171
89
  }
172
90
 
173
- /* =========================
174
- Selection column
175
- ========================= */
176
-
177
- .tBody tr td.selectionCell,
178
- .table .tBody tr td.selectionCell,
179
- .table td.selectionCell {
180
- padding-inline: var(--spacing-xxs);
181
- }
182
-
183
- .table .th.selectionCell,
184
- .table th.selectionCell,
185
- th.selectionCell {
186
- width: 34px !important;
91
+ .headerCell[data-align='right'],
92
+ .cell[data-align='right'] {
93
+ text-align: end;
94
+ font-variant-numeric: tabular-nums;
187
95
  }
188
96
 
189
- /* Override the .table.sm .tBody tr td padding-inline */
190
- .table.sm .tBody tr td.selectionCell,
191
- .table.table.sm .tBody tr td.selectionCell {
192
- padding-inline: var(--spacing-xxs);
97
+ .headerCell[data-align='center'],
98
+ .cell[data-align='center'] {
99
+ text-align: center;
193
100
  }
194
101
 
195
- .table.severityTable .tBody tr td:first-child {
196
- padding-inline-start: var(--spacing-md);
197
- }
198
- .table.severityTable .tBody tr td.selectionCell,
199
- .table.severityTable td.selectionCell,
200
- .table.severityTable .th.selectionCell,
201
- .table.severityTable th.selectionCell {
202
- padding-inline: var(--spacing-xxs);
102
+ /* Selection column */
103
+ .selectionCell {
104
+ padding: inherit 0 !important;
105
+ overflow: visible !important;
106
+ display: flex;
107
+ align-items: flex-start;
108
+ justify-content: center;
203
109
  }
204
110
 
205
- .table.sm.severityTable .tBody tr td.selectionCell,
206
- .table.table.sm.severityTable .tBody tr td.selectionCell,
207
- .table.sm.severityTable .th.selectionCell,
208
- .table.table.sm.severityTable .th.selectionCell,
209
- .table.sm.severityTable th.selectionCell {
210
- padding-inline: var(--spacing-xxs);
211
- padding-inline-start: 14px;
111
+ /* Rows (interaction + states) */
112
+ .row {
113
+ background: transparent;
212
114
  }
213
115
 
214
- /* =========================
215
- Rows (interaction + states)
216
- ========================= */
217
-
218
116
  .clickableRow {
219
117
  cursor: pointer;
220
118
  }
@@ -231,16 +129,12 @@ th.selectionCell {
231
129
  background-color: var(--color-bg-selected-hover);
232
130
  }
233
131
 
234
- .tr--hover:hover {
235
- background-color: var(--color-bg-contextual);
236
- }
237
-
238
- .striped tr:nth-child(even):not(.selectedRow):not(:hover) {
132
+ .striped .row:nth-child(even):not(.selectedRow):not(:hover) {
239
133
  background-color: var(--color-bg-surface-subtle);
240
134
  }
241
135
 
242
- /* Focus ring (fix selector: .tBody, not .tbody) */
243
- .table .tBody tr:focus-within {
136
+ /* Focus ring: apply when something inside row is focused */
137
+ .row:focus-within {
244
138
  outline: none;
245
139
  box-shadow:
246
140
  inset 2px 0 0 var(--color-brand),
@@ -249,10 +143,7 @@ th.selectionCell {
249
143
  inset 0 -2px 0 var(--color-brand);
250
144
  }
251
145
 
252
- /* =========================
253
- Content utilities
254
- ========================= */
255
-
146
+ /* Content utilities */
256
147
  .nowrap {
257
148
  white-space: nowrap;
258
149
  overflow: hidden;
@@ -260,31 +151,74 @@ th.selectionCell {
260
151
  }
261
152
 
262
153
  .allowWrap {
263
- word-break: break-all !important;
264
- overflow-wrap: anywhere !important;
154
+ white-space: normal;
155
+ overflow-wrap: break-word;
156
+ word-break: normal;
265
157
  }
266
158
 
267
- .td--numeric {
268
- text-align: end;
159
+ /* Header inner layout (same as before) */
160
+ .thInner {
161
+ display: flex;
162
+ align-items: center;
163
+ inline-size: 100%;
269
164
  }
270
165
 
271
- .td--muted {
272
- color: var(--color-fg-muted);
166
+ /* Sort button */
167
+ .thButton {
168
+ all: unset;
169
+ display: flex;
170
+ align-items: center;
171
+ gap: var(--spacing-xxs);
172
+ inline-size: 100%;
173
+ cursor: pointer;
174
+ user-select: none;
175
+ border-radius: var(--border-radius-default);
176
+ padding-block: 2px;
177
+ padding-inline: 2px;
178
+ justify-content: flex-start;
273
179
  }
274
180
 
275
- .td .error {
276
- color: var(--color-danger);
181
+ .thButtonRight {
182
+ justify-content: flex-end;
277
183
  }
278
184
 
279
- /* =========================
280
- Severity rail
281
- ========================= */
185
+ .thButtonCenter {
186
+ justify-content: center;
187
+ }
282
188
 
283
- .tBody tr.severity td:first-child {
284
- position: relative;
189
+ .thLabel {
190
+ overflow: hidden;
191
+ text-overflow: ellipsis;
192
+ white-space: nowrap;
193
+ flex: 0 1 auto;
194
+ min-width: 0;
195
+ }
196
+
197
+ .thLabelRight {
198
+ text-align: right;
199
+ }
200
+
201
+ .thLabelCenter {
202
+ text-align: center;
203
+ }
204
+
205
+ .thExtras {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ }
209
+
210
+ .sortIndicator {
211
+ display: inline-flex;
212
+ flex: 0 0 auto;
213
+ align-items: center;
285
214
  }
286
215
 
287
- .table.severityTable .tBody tr.severity td:first-child::before {
216
+ .sortIndicator svg {
217
+ inline-size: var(--icon-size-sm);
218
+ block-size: var(--icon-size-sm);
219
+ }
220
+
221
+ .severityTable .row.severity::before {
288
222
  content: '';
289
223
  position: absolute;
290
224
  left: 2px;
@@ -293,11 +227,12 @@ th.selectionCell {
293
227
  width: 3px;
294
228
  background-color: var(--row-severity-color);
295
229
  border-radius: 1px;
296
-
297
- z-index: 0;
230
+ z-index: 2;
231
+ pointer-events: none;
298
232
  }
299
233
 
300
- .table.severityTable .tBody tr.severity td:first-child > * {
234
+ /* Ensure content is above the rail */
235
+ .severityTable .row.severity > * {
301
236
  position: relative;
302
- z-index: 1;
237
+ z-index: 3;
303
238
  }
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { type TableProps, type TableVariant } from './Table';
4
4
  import { ViewMode } from '../../hooks/useTableSettings';
5
5
  type Filterable<T> = Array<keyof T>;
6
- export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerBelowRow' | 'headerExtras' | 'columnStyles' | 'toolbar'> & {
6
+ export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerExtras' | 'gridTemplateColumns' | 'toolbar'> & {
7
7
  columns: ReadonlyArray<ColumnDef<T, any>>;
8
8
  filterable?: Filterable<T>;
9
9
  sorting?: SortingState;