@dbcdk/react-components 0.0.12 → 0.0.13
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.
- package/dist/components/accordion/Accordion.d.ts +2 -2
- package/dist/components/accordion/Accordion.js +34 -41
- package/dist/components/accordion/Accordion.module.css +13 -72
- package/dist/components/accordion/components/AccordionRow.d.ts +10 -0
- package/dist/components/accordion/components/AccordionRow.js +51 -0
- package/dist/components/accordion/components/AccordionRow.module.css +82 -0
- package/dist/components/breadcrumbs/Breadcrumbs.module.css +0 -1
- package/dist/components/button/Button.module.css +7 -7
- package/dist/components/card/Card.d.ts +9 -18
- package/dist/components/card/Card.js +34 -23
- package/dist/components/card/Card.module.css +22 -87
- package/dist/components/card/components/CardMeta.d.ts +15 -0
- package/dist/components/card/components/CardMeta.js +20 -0
- package/dist/components/card/components/CardMeta.module.css +51 -0
- package/dist/components/card-container/CardContainer.js +1 -1
- package/dist/components/card-container/CardContainer.module.css +3 -1
- package/dist/components/chip/Chip.module.css +7 -2
- package/dist/components/datetime-picker/DateTimePicker.d.ts +33 -8
- package/dist/components/datetime-picker/DateTimePicker.js +119 -78
- package/dist/components/datetime-picker/DateTimePicker.module.css +2 -0
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +15 -3
- package/dist/components/datetime-picker/dateTimeHelpers.js +137 -23
- package/dist/components/filter-field/FilterField.module.css +5 -5
- package/dist/components/forms/form-select/FormSelect.d.ts +35 -0
- package/dist/components/forms/form-select/FormSelect.js +86 -0
- package/dist/components/forms/form-select/FormSelect.module.css +236 -0
- package/dist/components/forms/input/Input.d.ts +0 -3
- package/dist/components/forms/input/Input.js +0 -3
- package/dist/components/forms/input/Input.module.css +7 -7
- package/dist/components/forms/radio-buttons/RadioButtons.module.css +1 -0
- package/dist/components/forms/select/Select.js +55 -16
- package/dist/components/interval-select/IntervalSelect.d.ts +9 -2
- package/dist/components/interval-select/IntervalSelect.js +21 -6
- package/dist/components/menu/Menu.d.ts +11 -14
- package/dist/components/menu/Menu.js +18 -33
- package/dist/components/menu/Menu.module.css +2 -2
- package/dist/components/overlay/modal/Modal.module.css +2 -1
- package/dist/components/overlay/modal/provider/ModalProvider.js +1 -3
- package/dist/components/overlay/side-panel/SidePanel.js +1 -1
- package/dist/components/overlay/side-panel/SidePanel.module.css +1 -1
- package/dist/components/page-layout/PageLayout.d.ts +16 -4
- package/dist/components/page-layout/PageLayout.js +57 -28
- package/dist/components/page-layout/PageLayout.module.css +153 -33
- package/dist/components/popover/Popover.d.ts +17 -4
- package/dist/components/popover/Popover.js +147 -65
- package/dist/components/popover/Popover.module.css +5 -0
- package/dist/components/split-pane/SplitPane.d.ts +10 -24
- package/dist/components/split-pane/SplitPane.js +83 -54
- package/dist/components/split-pane/SplitPane.module.css +11 -6
- package/dist/components/split-pane/provider/SplitPaneContext.js +5 -11
- package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +3 -8
- package/dist/components/sticky-footer-layout/StickyFooterLayout.js +57 -20
- package/dist/components/table/Table.d.ts +3 -8
- package/dist/components/table/Table.js +37 -76
- package/dist/components/table/Table.module.css +45 -42
- package/dist/components/table/{tanstack.d.ts → TanstackTable.d.ts} +5 -12
- package/dist/components/table/TanstackTable.js +84 -0
- package/dist/components/table/components/column-resizer/ColumnResizer.js +1 -1
- package/dist/components/table/components/column-resizer/ColumnResizer.module.css +17 -7
- package/dist/components/table/table.utils.d.ts +17 -0
- package/dist/components/table/table.utils.js +61 -0
- package/dist/components/table/tanstackTable.utils.d.ts +22 -0
- package/dist/components/table/tanstackTable.utils.js +104 -0
- package/dist/components/tabs/Tabs.d.ts +35 -12
- package/dist/components/tabs/Tabs.js +114 -26
- package/dist/components/tabs/Tabs.module.css +158 -71
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/src/styles/styles.css +0 -1
- package/dist/styles/styles.css +0 -1
- package/dist/styles/themes/dbc/base.css +136 -0
- package/dist/styles/themes/dbc/dark.css +39 -202
- package/dist/styles/themes/dbc/light.css +17 -174
- package/package.json +4 -4
- package/dist/components/table/tanstack.js +0 -214
|
@@ -2,18 +2,13 @@ import React, { JSX } from 'react';
|
|
|
2
2
|
type StickyFooterLayoutProps = {
|
|
3
3
|
children: React.ReactNode;
|
|
4
4
|
footer: React.ReactNode;
|
|
5
|
-
|
|
6
|
-
width?: number | string;
|
|
7
|
-
/** match the app chrome padding token (defaults to --spacing-md) */
|
|
5
|
+
maxWidth?: number | string;
|
|
8
6
|
chromePaddingVar?: string;
|
|
9
|
-
/** inner padding for content (defaults to "var(--spacing-md) 0") */
|
|
10
7
|
contentPadding?: string;
|
|
11
|
-
/** background used to “paint over” what scrolls behind */
|
|
12
8
|
background?: string;
|
|
13
|
-
/** extra space below content so last field doesn't sit
|
|
9
|
+
/** extra space below content so last field doesn't sit too close to footer */
|
|
14
10
|
contentBottomSpacer?: string | number;
|
|
15
|
-
/** optional classnames */
|
|
16
11
|
className?: string;
|
|
17
12
|
};
|
|
18
|
-
export declare function StickyFooterLayout({ children, footer,
|
|
13
|
+
export declare function StickyFooterLayout({ children, footer, maxWidth, chromePaddingVar, contentPadding, background, contentBottomSpacer, className, }: StickyFooterLayoutProps): JSX.Element;
|
|
19
14
|
export {};
|
|
@@ -1,27 +1,64 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
3
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
export function StickyFooterLayout({ children, footer, maxWidth = 550, chromePaddingVar = '--spacing-md', contentPadding = '0', background = 'var(--color-bg-surface)', contentBottomSpacer = '16px', className, }) {
|
|
4
5
|
const pad = `var(${chromePaddingVar})`;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const contentColRef = useRef(null);
|
|
7
|
+
const footerOverlayRef = useRef(null);
|
|
8
|
+
const [dock, setDock] = useState({
|
|
9
|
+
left: 0,
|
|
10
|
+
width: typeof maxWidth === 'number' ? maxWidth : 550,
|
|
11
|
+
});
|
|
12
|
+
const [overlayHeight, setOverlayHeight] = useState(0);
|
|
13
|
+
// Measure the LEFT + WIDTH of the content column (for aligning footer inner)
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
const el = contentColRef.current;
|
|
16
|
+
if (!el)
|
|
17
|
+
return;
|
|
18
|
+
const measure = () => {
|
|
19
|
+
const rect = el.getBoundingClientRect();
|
|
20
|
+
setDock({ left: rect.left, width: rect.width });
|
|
21
|
+
};
|
|
22
|
+
measure();
|
|
23
|
+
const ro = new ResizeObserver(measure);
|
|
24
|
+
ro.observe(el);
|
|
25
|
+
window.addEventListener('resize', measure);
|
|
26
|
+
return () => {
|
|
27
|
+
ro.disconnect();
|
|
28
|
+
window.removeEventListener('resize', measure);
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
// Measure the ACTUAL fixed footer overlay height (includes paddingBlock, borders, etc.)
|
|
32
|
+
useLayoutEffect(() => {
|
|
33
|
+
const el = footerOverlayRef.current;
|
|
34
|
+
if (!el)
|
|
35
|
+
return;
|
|
36
|
+
const measure = () => setOverlayHeight(el.getBoundingClientRect().height);
|
|
37
|
+
measure();
|
|
38
|
+
const ro = new ResizeObserver(measure);
|
|
39
|
+
ro.observe(el);
|
|
40
|
+
return () => ro.disconnect();
|
|
41
|
+
}, []);
|
|
42
|
+
const bottomPad = `calc(${overlayHeight}px + ${String(contentBottomSpacer)})`;
|
|
43
|
+
return (_jsxs("div", { className: className, style: { background }, children: [_jsx("div", { ref: contentColRef, style: {
|
|
44
|
+
maxWidth,
|
|
45
|
+
boxSizing: 'border-box',
|
|
10
46
|
padding: contentPadding,
|
|
11
|
-
paddingBottom:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
bottom: `calc(-1 * ${pad})`,
|
|
47
|
+
paddingBottom: bottomPad,
|
|
48
|
+
}, children: children }), _jsx("div", { ref: footerOverlayRef, style: {
|
|
49
|
+
position: 'fixed',
|
|
50
|
+
left: 0,
|
|
51
|
+
right: 0,
|
|
52
|
+
bottom: 0,
|
|
18
53
|
zIndex: 10,
|
|
19
54
|
background,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
paddingBlock: pad,
|
|
56
|
+
boxSizing: 'border-box',
|
|
57
|
+
}, children: _jsx("div", { style: {
|
|
58
|
+
position: 'relative',
|
|
59
|
+
left: dock.left,
|
|
60
|
+
width: dock.width,
|
|
61
|
+
boxSizing: 'border-box',
|
|
62
|
+
/* you said you removed padding-inline to match form edge exactly */
|
|
63
|
+
}, className: "dbc-flex dbc-justify-end", children: footer }) })] }));
|
|
27
64
|
}
|
|
@@ -4,7 +4,7 @@ import { Severity } from '../../constants/severity.types';
|
|
|
4
4
|
import { PageChangeEvent } from '../../components/pagination/Pagination';
|
|
5
5
|
import { ViewMode } from '../../hooks/useTableSettings';
|
|
6
6
|
import { TableEmptyConfig } from './components/empty-state/EmptyState';
|
|
7
|
-
|
|
7
|
+
import { SortDirection } from './table.utils';
|
|
8
8
|
export interface ColumnItem<T> {
|
|
9
9
|
id: string;
|
|
10
10
|
header: string | (() => ReactNode);
|
|
@@ -15,11 +15,10 @@ export interface ColumnItem<T> {
|
|
|
15
15
|
hidden?: boolean;
|
|
16
16
|
align?: 'left' | 'right' | 'center';
|
|
17
17
|
verticalAlign?: 'top' | 'middle' | 'bottom';
|
|
18
|
-
fitContent?: boolean;
|
|
19
18
|
allowWrap?: boolean;
|
|
20
19
|
emptyPlaceholder?: ReactNode;
|
|
21
|
-
width?: number | string;
|
|
22
20
|
canHide?: boolean;
|
|
21
|
+
severity?: any;
|
|
23
22
|
}
|
|
24
23
|
type HeaderExtrasArgs<T> = {
|
|
25
24
|
column: ColumnItem<T>;
|
|
@@ -42,10 +41,6 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
|
|
|
42
41
|
loading?: boolean;
|
|
43
42
|
headerExtras?: (args: HeaderExtrasArgs<T>) => ReactNode;
|
|
44
43
|
columnStyles?: Partial<Record<string, React.CSSProperties>>;
|
|
45
|
-
headerBelowRow?: ReactNode;
|
|
46
|
-
/**
|
|
47
|
-
* NEW: optional toolbar area above the table (right now used for column selector)
|
|
48
|
-
*/
|
|
49
44
|
toolbar?: ReactNode;
|
|
50
45
|
striped?: boolean;
|
|
51
46
|
fillViewport?: boolean;
|
|
@@ -64,5 +59,5 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
|
|
|
64
59
|
viewMode?: ViewMode;
|
|
65
60
|
emptyConfig?: TableEmptyConfig;
|
|
66
61
|
} & Omit<HTMLAttributes<HTMLTableElement>, 'onClick'>;
|
|
67
|
-
export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles,
|
|
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;
|
|
68
63
|
export {};
|
|
@@ -9,75 +9,48 @@ 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
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
}, [onPageChange]);
|
|
17
|
-
const getColStyle = (columnId, alignment, verticalAlignment, width) => {
|
|
18
|
-
const baseStyle = columnStyles === null || columnStyles === void 0 ? void 0 : columnStyles[columnId];
|
|
19
|
-
return {
|
|
20
|
-
...(baseStyle !== null && baseStyle !== void 0 ? baseStyle : {}),
|
|
21
|
-
...(alignment === 'right' && { fontVariantNumeric: 'tabular-nums' }),
|
|
22
|
-
verticalAlign: verticalAlignment !== null && verticalAlignment !== void 0 ? verticalAlignment : 'top',
|
|
23
|
-
textAlign: alignment !== null && alignment !== void 0 ? alignment : 'left',
|
|
24
|
-
width: width !== null && width !== void 0 ? width : baseStyle === null || baseStyle === void 0 ? void 0 : baseStyle.width,
|
|
25
|
-
minWidth: width !== null && width !== void 0 ? width : baseStyle === null || baseStyle === void 0 ? void 0 : baseStyle.minWidth,
|
|
26
|
-
};
|
|
27
|
-
};
|
|
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 }) {
|
|
14
|
+
const filteredColumns = useMemo(() => getVisibleColumns(columns), [columns]);
|
|
15
|
+
const handlePageChange = useCallback((e) => onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e), [onPageChange]);
|
|
28
16
|
const scrollRef = useRef(null);
|
|
29
17
|
const { style: viewportStyle } = useViewportFill(scrollRef, {
|
|
30
|
-
bottomOffset: viewportBottomOffset + 60,
|
|
18
|
+
bottomOffset: viewportBottomOffset + (onPageChange ? 60 : 0),
|
|
31
19
|
min: viewportMin,
|
|
32
20
|
includeMarginTop: viewportIncludeMarginTop,
|
|
33
21
|
});
|
|
34
|
-
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: [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
: null;
|
|
49
|
-
|
|
50
|
-
};
|
|
51
|
-
return (_jsx("th", { style: getColStyle(column.id, column.align, 'middle'), "aria-sort": ariaSort, className: `${styles.th} ${column.sortable ? styles.sortable : ''} `, onClick: e => {
|
|
52
|
-
if (!column.sortable)
|
|
53
|
-
return;
|
|
54
|
-
if (e.target instanceof HTMLElement && e.target.closest('.resizer'))
|
|
55
|
-
return;
|
|
56
|
-
toggleSort();
|
|
57
|
-
}, role: column.sortable ? 'button' : undefined, tabIndex: column.sortable ? 0 : undefined, onKeyDown: e => {
|
|
58
|
-
if (!column.sortable)
|
|
59
|
-
return;
|
|
60
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
toggleSort();
|
|
63
|
-
}
|
|
64
|
-
}, children: _jsx("div", { className: styles.thInner, children: _jsxs("span", { children: [_jsx("span", { className: styles.thLabel, children: typeof column.header === 'function' ? column.header() : column.header }), column.sortable && (_jsxs("span", { className: styles.sortIndicator, "aria-hidden": "true", children: [isActiveSort && sortDirection === 'asc' && _jsx(ArrowUp, {}), isActiveSort && sortDirection === 'desc' && (_jsx(ArrowDown, { className: styles.descending })), !isActiveSort && (_jsx(ArrowDown, { className: `${styles.descending} ${styles.inActiveSort}` }))] })), headerExtras === null || headerExtras === void 0 ? void 0 : headerExtras({ column, index })] }) }) }, column.id));
|
|
65
|
-
})] }), headerBelowRow ? (_jsx("tr", { className: styles.headerBelowRow, children: _jsx("th", { colSpan: filteredColumns.length, children: headerBelowRow }) })) : null] }), 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: getColStyle(column.id, column.align, 'middle', column.width), className: `${styles.tableCell} ${column.fitContent ? 'fitContent' : ''}`, 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, rowIndex) => {
|
|
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: styles.thButton, onClick: toggleSort, onKeyDown: e => {
|
|
32
|
+
if (shouldToggleOnKey(e.key)) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
toggleSort();
|
|
35
|
+
}
|
|
36
|
+
}, children: [_jsx("span", { className: styles.thLabel, 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: styles.thLabel, children: getHeaderLabel(column.header) })), headerExtras ? (_jsx("div", { className: styles.thExtras, children: headerExtras({ column, index }) })) : null] }) }, column.id));
|
|
37
|
+
})] }) }), 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 => {
|
|
66
38
|
const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
39
|
+
const rowId = row[dataKey];
|
|
40
|
+
const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
|
|
41
|
+
return (_jsxs("tr", { tabIndex: onRowClick ? 0 : -1, onKeyDown: e => {
|
|
42
|
+
if (!onRowClick)
|
|
43
|
+
return;
|
|
44
|
+
if (shouldToggleOnKey(e.key)) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
onRowClick(row);
|
|
47
|
+
}
|
|
48
|
+
}, onClick: e => {
|
|
70
49
|
const canSelect = Boolean(selectedRows && onRowSelect && dataKey);
|
|
71
|
-
if (isModifierClick && canSelect) {
|
|
50
|
+
if (isModifierClick(e) && canSelect) {
|
|
72
51
|
e.preventDefault();
|
|
73
52
|
e.stopPropagation();
|
|
74
|
-
|
|
75
|
-
if (selectionMode === 'single') {
|
|
76
|
-
onRowSelect(rowId, !isSelected);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
onRowSelect(rowId, !isSelected);
|
|
80
|
-
}
|
|
53
|
+
onRowSelect(rowId, !selectedRows.has(rowId));
|
|
81
54
|
return;
|
|
82
55
|
}
|
|
83
56
|
onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row);
|
|
@@ -85,18 +58,9 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
|
|
|
85
58
|
['--row-severity-color']: rowSeverity
|
|
86
59
|
? SeverityBgColor[rowSeverity]
|
|
87
60
|
: undefined,
|
|
88
|
-
}, className: `${onRowClick ? styles.clickableRow : ''} ${
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(row[dataKey])) ||
|
|
92
|
-
viewMode === 'wrapped'
|
|
93
|
-
? styles.allowWrap
|
|
94
|
-
: styles.nowrap} `, children: column.render
|
|
95
|
-
? column.render(row) || ((_a = column.emptyPlaceholder) !== null && _a !== void 0 ? _a : '')
|
|
96
|
-
: column.accessor
|
|
97
|
-
? row[column.accessor] || ((_b = column.emptyPlaceholder) !== null && _b !== void 0 ? _b : '')
|
|
98
|
-
: null }, column.id));
|
|
99
|
-
})] }, `tableRow-${String(row[dataKey])}-${rowIndex}`));
|
|
61
|
+
}, className: `${onRowClick ? styles.clickableRow : ''} ${isSelected ? styles.selectedRow : ''} ${rowSeverity ? styles.severity : ''}`, children: [selectedRows && onRowSelect && dataKey && (_jsx("td", { className: `${styles.selectionCell}`, onClick: e => e.stopPropagation(), children: _jsx(Checkbox, { variant: "primary", checked: selectedRows.has(rowId), size: "sm", onChange: () => onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, !selectedRows.has(rowId)) }) })), filteredColumns.map(column => (_jsx("td", { style: getColumnStyle(column.id, columnStyles, column.align, column.verticalAlign), className: `${styles.tableCell} ${shouldAllowWrap(column.allowWrap, isSelected, viewMode)
|
|
62
|
+
? styles.allowWrap
|
|
63
|
+
: styles.nowrap}`, children: getCellDisplayValue(row, column) }, column.id)))] }, getRowKey(rowId)));
|
|
100
64
|
}) }))] }), !data.length && !loading && _jsx(TableEmptyState, { config: emptyConfig })] }));
|
|
101
65
|
if (fillViewport) {
|
|
102
66
|
return (_jsxs("div", { style: {
|
|
@@ -107,10 +71,7 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
|
|
|
107
71
|
position: 'relative',
|
|
108
72
|
}, 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 }))] }));
|
|
109
73
|
}
|
|
110
|
-
return (_jsxs("div", { style: {
|
|
111
|
-
display: 'flex',
|
|
112
|
-
flexDirection: 'column',
|
|
113
|
-
gap: '20px',
|
|
74
|
+
return (_jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-md", style: {
|
|
114
75
|
flexFlow: paginationPlacement === 'top' ? 'column-reverse' : 'column',
|
|
115
76
|
position: 'relative',
|
|
116
77
|
}, children: [tableEl, onPageChange && data.length > 0 && (_jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange }))] }));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* Table.module.css (updated) */
|
|
2
|
+
|
|
1
3
|
/* =========================
|
|
2
4
|
Base table
|
|
3
5
|
========================= */
|
|
@@ -11,6 +13,7 @@
|
|
|
11
13
|
font-size: var(--font-size-sm);
|
|
12
14
|
color: var(--color-fg-default);
|
|
13
15
|
background: var(--color-bg-surface);
|
|
16
|
+
table-layout: auto;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
.tableScroll {
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
|
|
27
30
|
.table thead {
|
|
28
31
|
position: sticky;
|
|
29
|
-
|
|
32
|
+
top: 0;
|
|
30
33
|
z-index: 10;
|
|
31
34
|
background-color: var(--color-bg-surface);
|
|
32
35
|
}
|
|
@@ -62,7 +65,6 @@
|
|
|
62
65
|
|
|
63
66
|
/* Width control */
|
|
64
67
|
min-width: 0;
|
|
65
|
-
max-width: var(--card-label-width);
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/* Small variant: header padding */
|
|
@@ -70,28 +72,56 @@
|
|
|
70
72
|
padding-inline: var(--spacing-md);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
/*
|
|
74
|
-
.th.
|
|
75
|
+
/* Header layout */
|
|
76
|
+
.th > .thInner {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: var(--spacing-xxs);
|
|
80
|
+
inline-size: 100%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Actual interactive control for sorting */
|
|
84
|
+
.thButton {
|
|
85
|
+
all: unset;
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: var(--spacing-xxs);
|
|
89
|
+
inline-size: 100%;
|
|
90
|
+
|
|
75
91
|
cursor: pointer;
|
|
76
92
|
user-select: none;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
|
|
94
|
+
/* make it feel like a button */
|
|
95
|
+
border-radius: var(--border-radius-default);
|
|
96
|
+
padding-block: 2px;
|
|
97
|
+
padding-inline: 2px;
|
|
80
98
|
}
|
|
81
99
|
|
|
82
|
-
.
|
|
83
|
-
th.sortable:hover {
|
|
100
|
+
.thButton:hover {
|
|
84
101
|
background-color: var(--color-bg-contextual);
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
.
|
|
88
|
-
th.sortable:focus-visible {
|
|
104
|
+
.thButton:focus-visible {
|
|
89
105
|
outline: none;
|
|
90
106
|
box-shadow: var(--focus-ring);
|
|
91
107
|
}
|
|
92
108
|
|
|
109
|
+
.thLabel {
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
text-overflow: ellipsis;
|
|
112
|
+
white-space: nowrap;
|
|
113
|
+
flex-grow: 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.thExtras {
|
|
117
|
+
margin-inline-start: auto;
|
|
118
|
+
display: inline-flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
}
|
|
121
|
+
|
|
93
122
|
.inActiveSort {
|
|
94
123
|
color: var(--color-fg-subtle);
|
|
124
|
+
opacity: 0.4;
|
|
95
125
|
}
|
|
96
126
|
|
|
97
127
|
.sortIndicator {
|
|
@@ -103,17 +133,6 @@ th.sortable:focus-visible {
|
|
|
103
133
|
block-size: var(--icon-size-sm);
|
|
104
134
|
}
|
|
105
135
|
|
|
106
|
-
.th > .thInner {
|
|
107
|
-
display: inline-block;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.th > .thInner > span {
|
|
111
|
-
display: inline-flex;
|
|
112
|
-
align-items: center;
|
|
113
|
-
gap: var(--spacing-xxs);
|
|
114
|
-
inline-size: 100%;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
136
|
/* =========================
|
|
118
137
|
Body + cells
|
|
119
138
|
========================= */
|
|
@@ -150,37 +169,24 @@ th.sortable:focus-visible {
|
|
|
150
169
|
Selection column
|
|
151
170
|
========================= */
|
|
152
171
|
|
|
153
|
-
/*
|
|
154
|
-
Default (no severity rails):
|
|
155
|
-
Remove ALL inline padding for selection/checkbox cells in both header + body.
|
|
156
|
-
|
|
157
|
-
We deliberately use padding-inline (shorthand) so it reliably beats other
|
|
158
|
-
padding-inline shorthands (like .table.sm .tBody tr td).
|
|
159
|
-
*/
|
|
160
|
-
|
|
161
|
-
/* Body selection cells (covers td with .selectionCell even if markup differs) */
|
|
162
172
|
.tBody tr td.selectionCell,
|
|
163
173
|
.table .tBody tr td.selectionCell,
|
|
164
174
|
.table td.selectionCell {
|
|
165
175
|
padding-inline: var(--spacing-xxs);
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
/* Header selection cells:
|
|
169
|
-
- covers your .th class
|
|
170
|
-
- AND real <th> elements that might not have the .th class */
|
|
171
178
|
.table .th.selectionCell,
|
|
172
179
|
.table th.selectionCell,
|
|
173
180
|
th.selectionCell {
|
|
174
|
-
|
|
181
|
+
width: 34px !important;
|
|
175
182
|
}
|
|
176
183
|
|
|
177
|
-
/* Override the .table.sm .tBody tr td padding-inline
|
|
184
|
+
/* Override the .table.sm .tBody tr td padding-inline */
|
|
178
185
|
.table.sm .tBody tr td.selectionCell,
|
|
179
186
|
.table.table.sm .tBody tr td.selectionCell {
|
|
180
187
|
padding-inline: var(--spacing-xxs);
|
|
181
188
|
}
|
|
182
189
|
|
|
183
|
-
/* If severity rails are enabled, reserve a left gutter (still no right padding) */
|
|
184
190
|
.table.severityTable .tBody tr td.selectionCell,
|
|
185
191
|
.table.severityTable td.selectionCell,
|
|
186
192
|
.table.severityTable .th.selectionCell,
|
|
@@ -189,7 +195,6 @@ th.selectionCell {
|
|
|
189
195
|
padding-inline-start: 14px;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
/* Ensure severityTable also wins in sm */
|
|
193
198
|
.table.sm.severityTable .tBody tr td.selectionCell,
|
|
194
199
|
.table.table.sm.severityTable .tBody tr td.selectionCell,
|
|
195
200
|
.table.sm.severityTable .th.selectionCell,
|
|
@@ -227,8 +232,8 @@ th.selectionCell {
|
|
|
227
232
|
background-color: var(--color-bg-surface-subtle);
|
|
228
233
|
}
|
|
229
234
|
|
|
230
|
-
/* Focus ring */
|
|
231
|
-
.table .
|
|
235
|
+
/* Focus ring (fix selector: .tBody, not .tbody) */
|
|
236
|
+
.table .tBody tr:focus-within {
|
|
232
237
|
outline: none;
|
|
233
238
|
box-shadow:
|
|
234
239
|
inset 2px 0 0 var(--color-brand),
|
|
@@ -272,7 +277,6 @@ th.selectionCell {
|
|
|
272
277
|
position: relative;
|
|
273
278
|
}
|
|
274
279
|
|
|
275
|
-
/* Only render the rail when the table actually uses severity rails */
|
|
276
280
|
.table.severityTable .tBody tr.severity td:first-child::before {
|
|
277
281
|
content: '';
|
|
278
282
|
position: absolute;
|
|
@@ -288,7 +292,6 @@ th.selectionCell {
|
|
|
288
292
|
z-index: 0;
|
|
289
293
|
}
|
|
290
294
|
|
|
291
|
-
/* keep checkbox/content above the rail */
|
|
292
295
|
.table.severityTable .tBody tr.severity td:first-child > * {
|
|
293
296
|
position: relative;
|
|
294
297
|
z-index: 1;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnDef } from '@tanstack/react-table';
|
|
1
|
+
import { type ColumnDef, type SortingState } from '@tanstack/react-table';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { type TableProps, type TableVariant } from './Table';
|
|
4
4
|
import { ViewMode } from '../../hooks/useTableSettings';
|
|
@@ -6,19 +6,12 @@ type Filterable<T> = Array<keyof T>;
|
|
|
6
6
|
export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerBelowRow' | 'headerExtras' | 'columnStyles' | 'toolbar'> & {
|
|
7
7
|
columns: ReadonlyArray<ColumnDef<T, any>>;
|
|
8
8
|
filterable?: Filterable<T>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
sorting?: SortingState;
|
|
10
|
+
manualSorting?: boolean;
|
|
11
|
+
onSortingChange?: (next: SortingState) => void;
|
|
12
|
+
optimisticSortingUi?: boolean;
|
|
12
13
|
variant?: TableVariant;
|
|
13
14
|
viewMode?: ViewMode;
|
|
14
|
-
/**
|
|
15
|
-
* TanStack-agnostic column visibility input.
|
|
16
|
-
*
|
|
17
|
-
* If provided, this list is the single source of truth for which columns are visible.
|
|
18
|
-
* If not provided (or empty), defaults are derived from ColumnDef meta.hidden.
|
|
19
|
-
*
|
|
20
|
-
* NOTE: Passing [] is treated as "unset" and will fall back to defaults.
|
|
21
|
-
*/
|
|
22
15
|
visibleColumnIds?: string[];
|
|
23
16
|
};
|
|
24
17
|
export declare function TanstackTable<T extends Record<string, any>>(props: TanstackTableProps<T>): React.ReactNode;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, } from '@tanstack/react-table';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import ColumnResizer from './components/column-resizer/ColumnResizer';
|
|
6
|
+
import { Table } from './Table';
|
|
7
|
+
import { buildColumnVisibilityFromVisibleIds, mapDefsToColumnItems, sortingEqual, getSortPropsFromSorting, buildColumnStyles, columnItemsToIdSet, } from './tanstackTable.utils';
|
|
8
|
+
export function TanstackTable(props) {
|
|
9
|
+
const { data, dataKey, columns, filterable = [], sorting: controlledSorting, onSortingChange, optimisticSortingUi = true, visibleColumnIds, manualSorting, ...tableProps } = props;
|
|
10
|
+
const isControlledSorting = controlledSorting != null;
|
|
11
|
+
const [uiSorting, setUiSorting] = React.useState(controlledSorting !== null && controlledSorting !== void 0 ? controlledSorting : []);
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
if (!isControlledSorting)
|
|
14
|
+
return;
|
|
15
|
+
if (sortingEqual(uiSorting, controlledSorting))
|
|
16
|
+
return;
|
|
17
|
+
setUiSorting(controlledSorting);
|
|
18
|
+
}, [isControlledSorting, controlledSorting, uiSorting]);
|
|
19
|
+
const columnVisibility = React.useMemo(() => buildColumnVisibilityFromVisibleIds(columns, visibleColumnIds), [columns, visibleColumnIds]);
|
|
20
|
+
const [columnFilters, setColumnFilters] = React.useState([]);
|
|
21
|
+
const [columnSizing, setColumnSizing] = React.useState({});
|
|
22
|
+
const table = useReactTable({
|
|
23
|
+
data,
|
|
24
|
+
columns: columns,
|
|
25
|
+
state: {
|
|
26
|
+
sorting: uiSorting,
|
|
27
|
+
columnFilters,
|
|
28
|
+
columnSizing,
|
|
29
|
+
columnVisibility,
|
|
30
|
+
},
|
|
31
|
+
onSortingChange: updater => {
|
|
32
|
+
const next = typeof updater === 'function' ? updater(uiSorting) : updater;
|
|
33
|
+
if (optimisticSortingUi)
|
|
34
|
+
setUiSorting(next);
|
|
35
|
+
onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(next);
|
|
36
|
+
},
|
|
37
|
+
onColumnFiltersChange: setColumnFilters,
|
|
38
|
+
onColumnSizingChange: setColumnSizing,
|
|
39
|
+
getCoreRowModel: getCoreRowModel(),
|
|
40
|
+
getSortedRowModel: getSortedRowModel(),
|
|
41
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
42
|
+
manualSorting: manualSorting !== null && manualSorting !== void 0 ? manualSorting : false,
|
|
43
|
+
enableColumnResizing: true,
|
|
44
|
+
columnResizeMode: 'onChange',
|
|
45
|
+
defaultColumn: {
|
|
46
|
+
enableResizing: true,
|
|
47
|
+
minSize: 80,
|
|
48
|
+
size: 180,
|
|
49
|
+
maxSize: 300,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const columnItems = React.useMemo(() => mapDefsToColumnItems(columns, columnVisibility), [columns, columnVisibility]);
|
|
53
|
+
const allowedIds = React.useMemo(() => columnItemsToIdSet(columnItems), [columnItems]);
|
|
54
|
+
const visibleData = table.getRowModel().rows.map(r => r.original);
|
|
55
|
+
const { sortById, sortDirection } = getSortPropsFromSorting(uiSorting);
|
|
56
|
+
const columnStyles = React.useMemo(() => {
|
|
57
|
+
const leafCols = table.getAllLeafColumns();
|
|
58
|
+
return buildColumnStyles(leafCols, allowedIds, { minWidth: 80, maxWidth: 800 });
|
|
59
|
+
}, [table, columnSizing, allowedIds]);
|
|
60
|
+
const handleSortChange = React.useCallback((column, direction) => {
|
|
61
|
+
// Translate Table's direction -> TanStack SortingState
|
|
62
|
+
const next = direction == null ? [] : [{ id: column.id, desc: direction === 'desc' }];
|
|
63
|
+
// Mirror TanStack's onSortingChange behavior
|
|
64
|
+
if (optimisticSortingUi)
|
|
65
|
+
setUiSorting(next);
|
|
66
|
+
onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(next);
|
|
67
|
+
// If you are doing server-side sorting, you likely also want:
|
|
68
|
+
// table.resetPageIndex?.() or external pagination reset (depends on your setup)
|
|
69
|
+
}, [optimisticSortingUi, onSortingChange]);
|
|
70
|
+
const headerExtras = React.useCallback(({ index }) => {
|
|
71
|
+
var _a, _b, _c, _d;
|
|
72
|
+
const headerGroups = table.getHeaderGroups();
|
|
73
|
+
const leafHeaders = headerGroups.length > 0 ? headerGroups[headerGroups.length - 1].headers : [];
|
|
74
|
+
const header = leafHeaders[index];
|
|
75
|
+
if (!header)
|
|
76
|
+
return null;
|
|
77
|
+
const canResize = (_c = (_b = (_a = header.column).getCanResize) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : false;
|
|
78
|
+
const handler = (_d = header.getResizeHandler) === null || _d === void 0 ? void 0 : _d.call(header);
|
|
79
|
+
if (!canResize || !handler)
|
|
80
|
+
return null;
|
|
81
|
+
return _jsx(ColumnResizer, { id: header.column.id, handler: handler });
|
|
82
|
+
}, [table]);
|
|
83
|
+
return (_jsx(Table, { ...tableProps, onSortChange: handleSortChange, dataKey: dataKey, data: visibleData, columns: columnItems, sortById: sortById, sortDirection: sortDirection, columnStyles: columnStyles, headerExtras: headerExtras }));
|
|
84
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import styles from './ColumnResizer.module.css';
|
|
4
|
-
const ColumnResizer = ({ id, handler }) => (_jsx("span", { className: styles.resizer, onMouseDown: handler, onTouchStart: handler, role: "separator", "aria-label": `Resize ${id}` }));
|
|
4
|
+
const ColumnResizer = ({ id, handler }) => (_jsx("span", { className: styles.resizer, "data-resizer": "true", onMouseDown: handler, onTouchStart: handler, role: "separator", "aria-orientation": "vertical", "aria-label": `Resize ${id}` }));
|
|
5
5
|
export default ColumnResizer;
|