@dbcdk/react-components 0.0.12 → 0.0.14
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.js +21 -6
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
import { SplitPaneProvider, useSplitPaneContext } from './provider/SplitPaneContext';
|
|
5
5
|
import styles from './SplitPane.module.css';
|
|
6
6
|
function clamp(n, min, max) {
|
|
@@ -11,68 +11,97 @@ export function SplitPane({ children, initialPrimarySize = 300, minPrimarySize =
|
|
|
11
11
|
}
|
|
12
12
|
function SplitPaneContainer({ children, showDivider, gutterSize, }) {
|
|
13
13
|
const { direction, primarySize, containerRef } = useSplitPaneContext();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const style = useMemo(() => ({
|
|
15
|
+
'--split-pane-primary-size': `${primarySize}px`,
|
|
16
|
+
'--split-pane-gutter': `${gutterSize}px`,
|
|
17
|
+
}), [primarySize, gutterSize]);
|
|
18
|
+
return (_jsx("div", { ref: containerRef, className: styles.container, "data-direction": direction, "data-divider": showDivider, style: style, children: children }));
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
-
* IMPORTANT:
|
|
21
|
-
* This component now renders ONLY the primary content (scrollable).
|
|
22
|
-
* The resizer lives in a dedicated <SplitPaneGutter /> so it never overlaps scrollbars.
|
|
23
|
-
*/
|
|
24
20
|
export function SplitPanePrimary({ children }) {
|
|
25
21
|
return _jsx("div", { className: styles.primary, children: children });
|
|
26
22
|
}
|
|
23
|
+
export function SplitPaneSecondary({ children }) {
|
|
24
|
+
return _jsx("div", { className: styles.secondary, children: children });
|
|
25
|
+
}
|
|
27
26
|
export function SplitPaneGutter() {
|
|
28
27
|
const { direction, primarySize, setPrimarySize, minPrimarySize, minSecondarySize, containerRef } = useSplitPaneContext();
|
|
29
|
-
const
|
|
28
|
+
const draggingRef = useRef(false);
|
|
29
|
+
const pointerIdRef = useRef(null);
|
|
30
30
|
const startPosRef = useRef(0);
|
|
31
31
|
const startSizeRef = useRef(primarySize);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const maxPrimaryRef = useRef(Infinity);
|
|
33
|
+
const getClientPos = useCallback((e) => (direction === 'horizontal' ? e.clientX : e.clientY), [direction]);
|
|
34
|
+
const computeClamp = useCallback(() => {
|
|
35
|
+
const el = containerRef.current;
|
|
36
|
+
if (!el)
|
|
37
|
+
return { maxPrimary: Infinity, total: 0 };
|
|
38
|
+
const rect = el.getBoundingClientRect();
|
|
39
|
+
const total = direction === 'horizontal' ? rect.width : rect.height;
|
|
40
|
+
const maxPrimary = Math.max(minPrimarySize, total - minSecondarySize);
|
|
41
|
+
return { maxPrimary, total };
|
|
42
|
+
}, [containerRef, direction, minPrimarySize, minSecondarySize]);
|
|
43
|
+
const onPointerDown = useCallback((e) => {
|
|
44
|
+
const el = containerRef.current;
|
|
45
|
+
if (!el)
|
|
34
46
|
return;
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return;
|
|
42
|
-
const rect = el.getBoundingClientRect();
|
|
43
|
-
const total = direction === 'horizontal' ? rect.width : rect.height;
|
|
44
|
-
const clientPos = direction === 'horizontal' ? e.clientX : e.clientY;
|
|
45
|
-
const delta = clientPos - startPosRef.current;
|
|
46
|
-
// Note: total includes gutter. That's fine because max clamps against secondary min.
|
|
47
|
-
const next = startSizeRef.current + delta;
|
|
48
|
-
const maxPrimary = Math.max(minPrimarySize, total - minSecondarySize);
|
|
49
|
-
setPrimarySize(clamp(next, minPrimarySize, maxPrimary));
|
|
50
|
-
};
|
|
51
|
-
const onUp = () => {
|
|
52
|
-
if (!isDraggingRef.current)
|
|
53
|
-
return;
|
|
54
|
-
isDraggingRef.current = false;
|
|
55
|
-
document.body.style.cursor = '';
|
|
56
|
-
document.body.style.userSelect = '';
|
|
57
|
-
};
|
|
58
|
-
window.addEventListener('mousemove', onMove);
|
|
59
|
-
window.addEventListener('mouseup', onUp);
|
|
60
|
-
return () => {
|
|
61
|
-
if (window) {
|
|
62
|
-
window.removeEventListener('mousemove', onMove);
|
|
63
|
-
window.removeEventListener('mouseup', onUp);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}, [containerRef, direction, minPrimarySize, minSecondarySize, setPrimarySize]);
|
|
67
|
-
const handleMouseDown = (event) => {
|
|
68
|
-
isDraggingRef.current = true;
|
|
69
|
-
startPosRef.current = direction === 'horizontal' ? event.clientX : event.clientY;
|
|
47
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
48
|
+
const { maxPrimary } = computeClamp();
|
|
49
|
+
maxPrimaryRef.current = maxPrimary;
|
|
50
|
+
draggingRef.current = true;
|
|
51
|
+
pointerIdRef.current = e.pointerId;
|
|
52
|
+
startPosRef.current = getClientPos(e);
|
|
70
53
|
startSizeRef.current = primarySize;
|
|
71
|
-
|
|
54
|
+
// UX: prevent text selection during drag
|
|
72
55
|
document.body.style.userSelect = 'none';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
document.body.style.cursor = direction === 'horizontal' ? 'col-resize' : 'row-resize';
|
|
57
|
+
}, [computeClamp, containerRef, direction, getClientPos, primarySize]);
|
|
58
|
+
const onPointerMove = useCallback((e) => {
|
|
59
|
+
if (!draggingRef.current)
|
|
60
|
+
return;
|
|
61
|
+
if (pointerIdRef.current !== null && e.pointerId !== pointerIdRef.current)
|
|
62
|
+
return;
|
|
63
|
+
const delta = getClientPos(e) - startPosRef.current;
|
|
64
|
+
const next = startSizeRef.current + delta;
|
|
65
|
+
const maxPrimary = maxPrimaryRef.current;
|
|
66
|
+
setPrimarySize(clamp(next, minPrimarySize, maxPrimary));
|
|
67
|
+
}, [getClientPos, minPrimarySize, setPrimarySize]);
|
|
68
|
+
const endDrag = useCallback(() => {
|
|
69
|
+
if (!draggingRef.current)
|
|
70
|
+
return;
|
|
71
|
+
draggingRef.current = false;
|
|
72
|
+
pointerIdRef.current = null;
|
|
73
|
+
document.body.style.cursor = '';
|
|
74
|
+
document.body.style.userSelect = '';
|
|
75
|
+
}, []);
|
|
76
|
+
const onPointerUp = useCallback(() => endDrag(), [endDrag]);
|
|
77
|
+
const onPointerCancel = useCallback(() => endDrag(), [endDrag]);
|
|
78
|
+
// Keyboard: arrows adjust size; Shift = bigger step; Home/End = min/max
|
|
79
|
+
const onKeyDown = useCallback((e) => {
|
|
80
|
+
const { maxPrimary } = computeClamp();
|
|
81
|
+
const step = e.shiftKey ? 32 : 8;
|
|
82
|
+
let next = null;
|
|
83
|
+
if (direction === 'horizontal') {
|
|
84
|
+
if (e.key === 'ArrowLeft')
|
|
85
|
+
next = primarySize - step;
|
|
86
|
+
if (e.key === 'ArrowRight')
|
|
87
|
+
next = primarySize + step;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
if (e.key === 'ArrowUp')
|
|
91
|
+
next = primarySize - step;
|
|
92
|
+
if (e.key === 'ArrowDown')
|
|
93
|
+
next = primarySize + step;
|
|
94
|
+
}
|
|
95
|
+
if (e.key === 'Home')
|
|
96
|
+
next = minPrimarySize;
|
|
97
|
+
if (e.key === 'End')
|
|
98
|
+
next = maxPrimary;
|
|
99
|
+
if (next === null)
|
|
100
|
+
return;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
setPrimarySize(clamp(next, minPrimarySize, maxPrimary));
|
|
103
|
+
}, [computeClamp, direction, minPrimarySize, primarySize, setPrimarySize]);
|
|
104
|
+
const ariaOrientation = direction === 'horizontal' ? 'vertical' : 'horizontal';
|
|
105
|
+
const { maxPrimary } = computeClamp();
|
|
106
|
+
return (_jsx("div", { className: styles.gutter, children: _jsx("div", { className: styles.resizer, role: "separator", "aria-orientation": ariaOrientation, "aria-valuemin": Math.round(minPrimarySize), "aria-valuemax": Number.isFinite(maxPrimary) ? Math.round(maxPrimary) : undefined, "aria-valuenow": Math.round(primarySize), tabIndex: 0, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerCancel, onKeyDown: onKeyDown }) }));
|
|
78
107
|
}
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
min-inline-size: 0;
|
|
21
21
|
min-block-size: 0;
|
|
22
22
|
overflow: auto;
|
|
23
|
-
flex-direction: column;
|
|
24
23
|
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/* ===== Secondary pane ===== */
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
flex-direction: column;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/* ===== Gutter (spacing + hit area) ===== */
|
|
37
|
+
/* ===== Gutter (spacing + hit area wrapper) ===== */
|
|
38
38
|
.gutter {
|
|
39
39
|
position: relative;
|
|
40
40
|
flex: 0 0 var(--split-pane-gutter, 8px);
|
|
@@ -42,32 +42,37 @@
|
|
|
42
42
|
z-index: 1;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/* Vertical mode gutter */
|
|
46
45
|
.container[data-direction='vertical'] .gutter {
|
|
47
46
|
inline-size: 100%;
|
|
48
47
|
block-size: var(--split-pane-gutter, 8px);
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
/* ===== Resizer (interaction
|
|
50
|
+
/* ===== Resizer (interaction element) ===== */
|
|
52
51
|
.resizer {
|
|
53
52
|
position: absolute;
|
|
54
53
|
inset: 0;
|
|
55
54
|
cursor: col-resize;
|
|
56
55
|
user-select: none;
|
|
57
56
|
touch-action: none;
|
|
57
|
+
outline: none;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
.container[data-direction='vertical'] .resizer {
|
|
61
61
|
cursor: row-resize;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/* Focus ring for keyboard resizing */
|
|
65
|
+
.resizer:focus-visible {
|
|
66
|
+
box-shadow: var(--focus-ring);
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
/* ===== Divider line ===== */
|
|
65
70
|
.resizer::after {
|
|
66
71
|
content: '';
|
|
67
72
|
position: absolute;
|
|
68
73
|
inset-block: 0;
|
|
69
74
|
inset-inline: 50%;
|
|
70
|
-
inline-size: var(--border-width-
|
|
75
|
+
inline-size: var(--border-width-thin);
|
|
71
76
|
background-color: var(--color-border-subtle);
|
|
72
77
|
opacity: 0;
|
|
73
78
|
transform: translateX(-50%);
|
|
@@ -81,7 +86,7 @@
|
|
|
81
86
|
inset-inline: 0;
|
|
82
87
|
inset-block: 50%;
|
|
83
88
|
inline-size: 100%;
|
|
84
|
-
block-size: var(--border-width-
|
|
89
|
+
block-size: var(--border-width-thin);
|
|
85
90
|
transform: translateY(-50%);
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
3
|
export const SplitPaneContext = React.createContext(null);
|
|
@@ -22,7 +21,7 @@ function writeStoredSize(key, value) {
|
|
|
22
21
|
localStorage.setItem(key, String(Math.round(value)));
|
|
23
22
|
}
|
|
24
23
|
catch {
|
|
25
|
-
// ignore
|
|
24
|
+
// ignore
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
export function useSplitPaneContext() {
|
|
@@ -33,13 +32,8 @@ export function useSplitPaneContext() {
|
|
|
33
32
|
}
|
|
34
33
|
export function SplitPaneProvider({ children, direction, initialPrimarySize, minPrimarySize, minSecondarySize, storageKey, }) {
|
|
35
34
|
const containerRef = useRef(null);
|
|
36
|
-
|
|
37
|
-
* IMPORTANT (Next.js hydration):
|
|
38
|
-
* Always start with initialPrimarySize so server HTML and first client render match.
|
|
39
|
-
* Then, after hydration, read localStorage and update.
|
|
40
|
-
*/
|
|
35
|
+
// Start with initial to avoid SSR mismatch, then hydrate from storage
|
|
41
36
|
const [primarySize, setPrimarySize] = useState(initialPrimarySize);
|
|
42
|
-
// Apply persisted size AFTER hydration (prevents SSR/client mismatch warnings)
|
|
43
37
|
useEffect(() => {
|
|
44
38
|
if (!storageKey)
|
|
45
39
|
return;
|
|
@@ -48,11 +42,12 @@ export function SplitPaneProvider({ children, direction, initialPrimarySize, min
|
|
|
48
42
|
return;
|
|
49
43
|
setPrimarySize(stored);
|
|
50
44
|
}, [storageKey]);
|
|
51
|
-
// Clamp after mount / when container is measurable; re-clamp on resize
|
|
52
45
|
useEffect(() => {
|
|
53
46
|
const el = containerRef.current;
|
|
54
47
|
if (!el)
|
|
55
48
|
return;
|
|
49
|
+
if (typeof ResizeObserver === 'undefined')
|
|
50
|
+
return;
|
|
56
51
|
const clampToContainer = () => {
|
|
57
52
|
const rect = el.getBoundingClientRect();
|
|
58
53
|
const total = direction === 'horizontal' ? rect.width : rect.height;
|
|
@@ -62,11 +57,10 @@ export function SplitPaneProvider({ children, direction, initialPrimarySize, min
|
|
|
62
57
|
setPrimarySize(prev => clamp(prev, minPrimarySize, maxPrimary));
|
|
63
58
|
};
|
|
64
59
|
clampToContainer();
|
|
65
|
-
const ro = new ResizeObserver(
|
|
60
|
+
const ro = new ResizeObserver(clampToContainer);
|
|
66
61
|
ro.observe(el);
|
|
67
62
|
return () => ro.disconnect();
|
|
68
63
|
}, [direction, minPrimarySize, minSecondarySize]);
|
|
69
|
-
// Persist on change
|
|
70
64
|
useEffect(() => {
|
|
71
65
|
if (!storageKey)
|
|
72
66
|
return;
|
|
@@ -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 }))] }));
|