@dbcdk/react-components 0.0.19 → 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.
- package/dist/components/button/Button.module.css +8 -4
- package/dist/components/code-block/CodeBlock.js +28 -19
- package/dist/components/code-block/CodeBlock.module.css +69 -67
- package/dist/components/overlay/modal/Modal.d.ts +2 -1
- package/dist/components/overlay/modal/Modal.js +6 -6
- package/dist/components/overlay/modal/Modal.module.css +52 -19
- package/dist/components/table/Table.d.ts +10 -5
- package/dist/components/table/Table.js +91 -59
- package/dist/components/table/Table.module.css +107 -172
- package/dist/components/table/TanstackTable.d.ts +1 -1
- package/dist/components/table/TanstackTable.js +37 -11
- package/dist/components/table/table.utils.js +0 -2
- package/dist/src/styles/styles.css +4 -1
- package/dist/styles/styles.css +4 -1
- package/package.json +1 -1
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/* ==========================================================================
|
|
2
|
-
* BASE BUTTON
|
|
3
|
-
* ======================================================================= */
|
|
4
|
-
|
|
5
1
|
.button {
|
|
6
2
|
display: inline-flex;
|
|
7
3
|
align-items: center;
|
|
@@ -226,6 +222,14 @@
|
|
|
226
222
|
border-color: var(--color-border-selected);
|
|
227
223
|
}
|
|
228
224
|
|
|
225
|
+
.inline.active {
|
|
226
|
+
color: var(--button-bg-primary);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
229
|
.active:hover {
|
|
230
230
|
background-color: var(--button-bg-primary-hover);
|
|
231
231
|
}
|
|
232
|
+
|
|
233
|
+
.inline.active:hover {
|
|
234
|
+
color: var(--button-bg-primary-hover);
|
|
235
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TextWrap } from 'lucide-react';
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
2
4
|
import styles from './CodeBlock.module.css';
|
|
5
|
+
import { Button } from '../button/Button';
|
|
3
6
|
import { CopyButton } from '../copy-button/CopyButton';
|
|
4
7
|
const looksLikeStackFrame = (line) => {
|
|
5
8
|
const t = line.trim();
|
|
@@ -16,24 +19,30 @@ export function CodeBlock({ code, children, copyButton, copyText, size = 'md', s
|
|
|
16
19
|
var _a;
|
|
17
20
|
const text = typeof code === 'string' ? code : undefined;
|
|
18
21
|
const copy = (_a = copyText !== null && copyText !== void 0 ? copyText : text) !== null && _a !== void 0 ? _a : '';
|
|
19
|
-
// If children are provided, render them as-is (no line processing).
|
|
20
22
|
const hasChildren = children !== undefined && children !== null;
|
|
21
|
-
|
|
22
|
-
const lines = smart && !hasChildren && typeof text === 'string' ? text.split('\n') : null;
|
|
23
|
-
return (_jsxs("
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
const [isWrapped, setIsWrapped] = useState(wrap);
|
|
24
|
+
const lines = useMemo(() => (smart && !hasChildren && typeof text === 'string' ? text.split('\n') : null), [smart, hasChildren, text]);
|
|
25
|
+
return (_jsxs("div", { className: [
|
|
26
|
+
styles.wrapper,
|
|
27
|
+
styles[size],
|
|
28
|
+
isWrapped ? styles.wrap : styles.noWrap,
|
|
29
|
+
copyButton ? styles.hasActions : '',
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(' '), children: [copyButton && (_jsxs("span", { className: styles.actions, "aria-hidden": false, children: [_jsx(Button, { type: "button", variant: "inline", size: "sm", shape: "round", onClick: () => setIsWrapped(v => !v), "aria-pressed": isWrapped, active: isWrapped, title: isWrapped ? 'Ombryd ikke tekst' : 'Ombryd tekst', children: _jsx(TextWrap, { size: 16 }) }), _jsx(CopyButton, { size: "sm", shape: "round", variant: "inline", text: copy })] })), _jsx("pre", { className: styles.container, tabIndex: 0, children: _jsx("code", { className: styles.code, children: hasChildren
|
|
33
|
+
? children
|
|
34
|
+
: lines
|
|
35
|
+
? lines.map((line, i) => {
|
|
36
|
+
const isFirst = i === 0;
|
|
37
|
+
const isFrame = looksLikeStackFrame(line);
|
|
38
|
+
const cls = [
|
|
39
|
+
styles.line,
|
|
40
|
+
isFirst ? styles.lineFirst : '',
|
|
41
|
+
isFrame ? styles.lineFrame : '',
|
|
42
|
+
]
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.join(' ');
|
|
45
|
+
return (_jsxs("span", { className: cls, children: [line, '\n'] }, i));
|
|
46
|
+
})
|
|
47
|
+
: text }) })] }));
|
|
39
48
|
}
|
|
@@ -1,79 +1,80 @@
|
|
|
1
|
+
.wrapper {
|
|
2
|
+
position: relative;
|
|
3
|
+
--code-actions-h: var(--component-size-sm);
|
|
4
|
+
--code-actions-inset: var(--spacing-xs);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* <pre> */
|
|
1
8
|
.container {
|
|
2
9
|
position: relative;
|
|
3
10
|
margin-block: 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
border: var(--border-width-thin) solid var(--color-border-default);
|
|
11
|
+
background: var(--color-bg-surface-strong);
|
|
12
|
+
border: var(--border-width-thin) solid var(--color-border-subtle);
|
|
7
13
|
border-radius: var(--border-radius-lg);
|
|
8
14
|
box-shadow: var(--shadow-xs);
|
|
9
|
-
|
|
10
15
|
padding: var(--spacing-sm);
|
|
11
|
-
padding-inline-end: calc(var(--spacing-sm) + 40px);
|
|
12
|
-
|
|
13
16
|
font-family: var(--font-family-mono);
|
|
14
|
-
line-height:
|
|
15
|
-
|
|
16
|
-
overflow-x: auto;
|
|
17
|
-
overflow-y: hidden;
|
|
18
|
-
|
|
19
|
-
/* Nice: avoids layout shift if/when scrollbars appear (supported in modern browsers) */
|
|
17
|
+
line-height: 1.35;
|
|
18
|
+
overflow: auto;
|
|
20
19
|
scrollbar-gutter: stable;
|
|
21
|
-
}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
box-shadow: var(--shadow-xs), var(--focus-ring);
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
/* Sizes */
|
|
26
|
+
.sm .container {
|
|
29
27
|
padding: var(--spacing-xs);
|
|
30
|
-
padding-inline-end: calc(var(--spacing-xs) + 40px);
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
.
|
|
30
|
+
.sm .code {
|
|
34
31
|
font-size: var(--font-size-xs);
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
.
|
|
34
|
+
.md .code {
|
|
38
35
|
font-size: var(--font-size-sm);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
.
|
|
38
|
+
.lg .code {
|
|
42
39
|
font-size: var(--font-size-base);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
margin: 0;
|
|
48
|
-
font-family: var(--font-family-mono);
|
|
49
|
-
color: var(--color-fg-default);
|
|
50
|
-
white-space: pre-wrap;
|
|
51
|
-
overflow-wrap: anywhere;
|
|
52
|
-
word-break: normal; /* <- not break-all */
|
|
42
|
+
.hasActions .container {
|
|
43
|
+
min-block-size: calc(var(--code-actions-h) + var(--spacing-sm) + var(--spacing-sm));
|
|
53
44
|
}
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
top: var(--spacing-xs);
|
|
59
|
-
right: var(--spacing-xs);
|
|
60
|
-
z-index: 2;
|
|
46
|
+
.sm.hasActions .container {
|
|
47
|
+
min-block-size: calc(var(--code-actions-h) + var(--spacing-xs) + var(--spacing-xs));
|
|
48
|
+
}
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
transition: opacity var(--transition-fast) var(--ease-standard);
|
|
50
|
+
.md.hasActions .container {
|
|
51
|
+
min-block-size: calc(var(--code-actions-h) + var(--spacing-sm) + var(--spacing-sm));
|
|
65
52
|
}
|
|
66
53
|
|
|
67
|
-
.container
|
|
68
|
-
|
|
69
|
-
opacity: 1;
|
|
70
|
-
pointer-events: auto;
|
|
54
|
+
.lg.hasActions .container {
|
|
55
|
+
min-block-size: calc(var(--code-actions-h) + var(--spacing-sm) + var(--spacing-sm));
|
|
71
56
|
}
|
|
72
57
|
|
|
73
|
-
/*
|
|
58
|
+
/* Focus ring */
|
|
59
|
+
.wrapper:focus-within .container {
|
|
60
|
+
border-color: var(--color-border-selected);
|
|
61
|
+
box-shadow: var(--shadow-xs), var(--focus-ring);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* <code> */
|
|
65
|
+
.code {
|
|
66
|
+
display: block;
|
|
67
|
+
margin: 0;
|
|
68
|
+
font-family: var(--font-family-mono);
|
|
69
|
+
color: var(--color-fg-default);
|
|
70
|
+
flex: 1 1 auto;
|
|
71
|
+
min-width: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Wrapping modes */
|
|
74
75
|
.wrap .code {
|
|
75
76
|
white-space: pre-wrap;
|
|
76
|
-
overflow-wrap:
|
|
77
|
+
overflow-wrap: break-word;
|
|
77
78
|
word-break: normal;
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -83,9 +84,31 @@
|
|
|
83
84
|
word-break: normal;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
.actions {
|
|
88
|
+
position: absolute;
|
|
89
|
+
top: var(--code-actions-inset);
|
|
90
|
+
right: var(--code-actions-inset);
|
|
91
|
+
z-index: 3;
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
gap: var(--spacing-xs);
|
|
94
|
+
align-items: center;
|
|
95
|
+
padding: var(--spacing-2xs);
|
|
96
|
+
border-radius: var(--border-radius-lg);
|
|
97
|
+
background: color-mix(in oklab, var(--color-bg-surface) 70%, transparent);
|
|
98
|
+
backdrop-filter: blur(6px);
|
|
99
|
+
opacity: 0;
|
|
100
|
+
pointer-events: none;
|
|
101
|
+
transition: opacity var(--transition-fast) var(--ease-standard);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.wrapper:hover .actions,
|
|
105
|
+
.wrapper:focus-within .actions {
|
|
106
|
+
opacity: 1;
|
|
107
|
+
pointer-events: auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
87
110
|
.line {
|
|
88
|
-
display: inline;
|
|
111
|
+
display: inline;
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
.lineFirst {
|
|
@@ -94,27 +117,6 @@
|
|
|
94
117
|
color: var(--color-fg-default);
|
|
95
118
|
}
|
|
96
119
|
|
|
97
|
-
/* Common stack frames are “noise”; deemphasize without hiding */
|
|
98
120
|
.lineFrame {
|
|
99
121
|
color: var(--color-fg-subtle);
|
|
100
122
|
}
|
|
101
|
-
|
|
102
|
-
/* Optional: make the container a bit denser for logs */
|
|
103
|
-
.container {
|
|
104
|
-
/* keep your existing properties ... */
|
|
105
|
-
|
|
106
|
-
/* Easy win: stacktraces feel less tall */
|
|
107
|
-
line-height: 1.35; /* instead of relaxed */
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/* Optional: differentiate the code area slightly from surrounding UI */
|
|
111
|
-
.container {
|
|
112
|
-
background: var(--color-bg-surface-strong); /* a touch more neutral than contextual */
|
|
113
|
-
border-color: var(--color-border-subtle);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* Keep focus ring behavior */
|
|
117
|
-
.container:focus-within {
|
|
118
|
-
border-color: var(--color-border-selected);
|
|
119
|
-
box-shadow: var(--shadow-xs), var(--focus-ring);
|
|
120
|
-
}
|
|
@@ -19,5 +19,6 @@ export type ModalProps = {
|
|
|
19
19
|
severity?: Severity;
|
|
20
20
|
disableContentSpacing?: boolean;
|
|
21
21
|
dataCy?: string;
|
|
22
|
+
width?: number | string;
|
|
22
23
|
};
|
|
23
|
-
export declare function Modal({ isOpen, onRequestClose, header, content, children, primaryAction, secondaryAction, closeOnOverlayClick, severity, disableContentSpacing, isLoading, dataCy, }: ModalProps): React.ReactNode;
|
|
24
|
+
export declare function Modal({ isOpen, onRequestClose, header, content, children, primaryAction, secondaryAction, closeOnOverlayClick, severity, disableContentSpacing, isLoading, dataCy, width, }: ModalProps): React.ReactNode;
|
|
@@ -5,7 +5,7 @@ import { useEffect, useId, useRef } from 'react';
|
|
|
5
5
|
import { Button } from '../../../components/button/Button';
|
|
6
6
|
import { Headline } from '../../../components/headline/Headline';
|
|
7
7
|
import styles from './Modal.module.css';
|
|
8
|
-
export function Modal({ isOpen, onRequestClose, header, content, children, primaryAction, secondaryAction, closeOnOverlayClick = true, severity, disableContentSpacing = false, isLoading, dataCy, }) {
|
|
8
|
+
export function Modal({ isOpen, onRequestClose, header, content, children, primaryAction, secondaryAction, closeOnOverlayClick = true, severity, disableContentSpacing = false, isLoading, dataCy, width, }) {
|
|
9
9
|
const titleId = useId();
|
|
10
10
|
const dialogRef = useRef(null);
|
|
11
11
|
const lastActiveElementRef = useRef(null);
|
|
@@ -30,7 +30,6 @@ export function Modal({ isOpen, onRequestClose, header, content, children, prima
|
|
|
30
30
|
const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
31
31
|
const focusable = dialog.querySelectorAll(focusableSelectors);
|
|
32
32
|
if (focusable.length > 0) {
|
|
33
|
-
// Prefer focusing the first input/select/textarea if present
|
|
34
33
|
const preferred = (_a = dialog.querySelector('input, select, textarea')) !== null && _a !== void 0 ? _a : focusable[0];
|
|
35
34
|
preferred.focus();
|
|
36
35
|
}
|
|
@@ -68,13 +67,11 @@ export function Modal({ isOpen, onRequestClose, header, content, children, prima
|
|
|
68
67
|
document.addEventListener('keydown', handleKeyDown);
|
|
69
68
|
return () => {
|
|
70
69
|
document.removeEventListener('keydown', handleKeyDown);
|
|
71
|
-
// Restore focus only when closing (true -> false) happens elsewhere
|
|
72
|
-
// so we do it when modal unmounts while open.
|
|
73
70
|
if (lastActiveElementRef.current) {
|
|
74
71
|
lastActiveElementRef.current.focus();
|
|
75
72
|
}
|
|
76
73
|
};
|
|
77
|
-
}, [isOpen]);
|
|
74
|
+
}, [isOpen]);
|
|
78
75
|
if (!isOpen)
|
|
79
76
|
return null;
|
|
80
77
|
const handleOverlayClick = () => {
|
|
@@ -88,5 +85,8 @@ export function Modal({ isOpen, onRequestClose, header, content, children, prima
|
|
|
88
85
|
const resolvedSecondaryAction = secondaryAction !== null && secondaryAction !== void 0 ? secondaryAction : (primaryAction ? { label: 'Luk', onClick: onRequestCloseRef.current } : undefined);
|
|
89
86
|
const shouldRenderFooter = Boolean(primaryAction || resolvedSecondaryAction);
|
|
90
87
|
const body = children !== null && children !== void 0 ? children : content;
|
|
91
|
-
|
|
88
|
+
const resolvedWidth = typeof width === 'number' ? `${width}px` : typeof width === 'string' ? width : undefined;
|
|
89
|
+
return (_jsx("div", { className: styles.overlay, onClick: handleOverlayClick, children: _jsxs("div", { "data-cy": dataCy, ref: dialogRef, className: `${styles.modal} ${disableContentSpacing ? '' : styles.contentSpacing}`, style: resolvedWidth
|
|
90
|
+
? { ['--modal-width']: resolvedWidth }
|
|
91
|
+
: undefined, onClick: stopPropagation, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, tabIndex: -1, children: [_jsxs("div", { className: styles.header, children: [header && (_jsx(Headline, { severity: severity, size: 3, disableMargin: true, children: header })), _jsx(Button, { type: "button", variant: "inline", onClick: () => onRequestCloseRef.current(), "aria-label": "Luk", shape: "round", icon: _jsx(X, {}) })] }), _jsx("div", { className: styles.body, children: body }), shouldRenderFooter && (_jsxs("div", { className: styles.footer, children: [resolvedSecondaryAction && (_jsxs(Button, { type: "button", variant: "outlined", onClick: resolvedSecondaryAction.onClick, disabled: isLoading, children: [resolvedSecondaryAction.icon && (_jsx("span", { className: styles.icon, children: resolvedSecondaryAction.icon })), _jsx("span", { children: resolvedSecondaryAction.label })] })), primaryAction && (_jsxs(Button, { type: "button", variant: "primary", onClick: primaryAction.onClick, disabled: primaryAction.disabled || isLoading, loading: isLoading, children: [primaryAction.icon && _jsx("span", { className: styles.icon, children: primaryAction.icon }), _jsx("span", { children: primaryAction.label })] }))] }))] }) }));
|
|
92
92
|
}
|
|
@@ -2,62 +2,87 @@
|
|
|
2
2
|
position: fixed;
|
|
3
3
|
inset: 0;
|
|
4
4
|
background: var(--overlay-scrim);
|
|
5
|
+
|
|
5
6
|
display: flex;
|
|
6
|
-
align-items: flex-start;
|
|
7
7
|
justify-content: center;
|
|
8
|
-
|
|
9
|
-
padding
|
|
8
|
+
|
|
9
|
+
padding: clamp(var(--spacing-sm), 10vh, var(--spacing-xl));
|
|
10
10
|
z-index: var(--z-backdrop-modal);
|
|
11
|
+
|
|
12
|
+
/* Overlay can scroll if modal is taller than viewport */
|
|
11
13
|
overflow-y: auto;
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
/* Default width can be overridden by --modal-width from props */
|
|
14
17
|
.modal {
|
|
18
|
+
--modal-width: 700px;
|
|
19
|
+
|
|
15
20
|
background: var(--color-bg-surface);
|
|
16
21
|
border-radius: var(--border-radius-lg);
|
|
17
|
-
min-width: 320px;
|
|
18
|
-
max-width: 700px;
|
|
19
|
-
max-height: calc(100vh - (2 * var(--spacing-md)));
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
22
|
box-shadow: var(--shadow-lg);
|
|
23
23
|
font-family: var(--font-family);
|
|
24
|
-
min-width: 500px;
|
|
25
|
-
z-index: var(--z-modal);
|
|
26
24
|
color: var(--color-fg-default);
|
|
27
|
-
|
|
25
|
+
z-index: var(--z-modal);
|
|
26
|
+
|
|
27
|
+
/* Responsive width: never exceed viewport */
|
|
28
|
+
width: min(
|
|
29
|
+
var(--modal-width),
|
|
30
|
+
calc(100vw - 2 * clamp(var(--spacing-sm), 4vw, var(--spacing-lg)))
|
|
31
|
+
);
|
|
32
|
+
min-width: 320px;
|
|
33
|
+
|
|
34
|
+
/* Critical: prevent “below bottom of screen”
|
|
35
|
+
Prefer svh on mobile; fallback to vh. */
|
|
36
|
+
max-height: calc(100svh - 2 * clamp(var(--spacing-sm), 10vh, var(--spacing-xl)));
|
|
37
|
+
max-height: calc(100vh - 2 * clamp(var(--spacing-sm), 10vh, var(--spacing-xl)));
|
|
38
|
+
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
28
41
|
|
|
29
|
-
/*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
42
|
+
/* If you want it slightly lower than top padding, keep it aligned start */
|
|
43
|
+
align-self: flex-start;
|
|
44
|
+
|
|
45
|
+
/* Helps on iOS when address bar changes */
|
|
46
|
+
overscroll-behavior: contain;
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
/* Header/footer pinned; body scrolls */
|
|
37
50
|
.header {
|
|
38
51
|
font-size: var(--font-size-md);
|
|
39
52
|
font-weight: var(--font-weight-semibold);
|
|
53
|
+
|
|
40
54
|
display: flex;
|
|
41
55
|
justify-content: space-between;
|
|
42
56
|
align-items: center;
|
|
57
|
+
|
|
43
58
|
padding: var(--spacing-md);
|
|
44
59
|
padding-bottom: 0;
|
|
60
|
+
|
|
61
|
+
/* Keeps header readable if body scrolls underneath */
|
|
62
|
+
flex: 0 0 auto;
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
.body {
|
|
48
|
-
|
|
66
|
+
flex: 1 1 auto;
|
|
67
|
+
overflow: auto;
|
|
68
|
+
|
|
49
69
|
font-size: var(--font-size-sm);
|
|
50
70
|
line-height: var(--line-height-normal);
|
|
51
71
|
color: var(--color-fg-muted);
|
|
72
|
+
|
|
52
73
|
padding: var(--spacing-md);
|
|
74
|
+
min-height: 0; /* IMPORTANT: allows flex child to actually scroll */
|
|
53
75
|
}
|
|
54
76
|
|
|
55
77
|
.footer {
|
|
78
|
+
flex: 0 0 auto;
|
|
79
|
+
|
|
56
80
|
display: flex;
|
|
57
81
|
justify-content: flex-end;
|
|
58
82
|
gap: var(--spacing-xs);
|
|
59
|
-
|
|
83
|
+
|
|
60
84
|
padding: var(--spacing-md);
|
|
85
|
+
padding-top: 0;
|
|
61
86
|
}
|
|
62
87
|
|
|
63
88
|
.icon {
|
|
@@ -65,3 +90,11 @@
|
|
|
65
90
|
align-items: center;
|
|
66
91
|
justify-content: center;
|
|
67
92
|
}
|
|
93
|
+
|
|
94
|
+
.contentSpacing .body > :first-child {
|
|
95
|
+
margin-top: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.contentSpacing .body > :last-child {
|
|
99
|
+
margin-bottom: 0;
|
|
100
|
+
}
|
|
@@ -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<
|
|
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
|
-
|
|
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
|
-
}
|
|
62
|
-
export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras,
|
|
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,
|
|
13
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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:
|
|
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: [
|
|
124
|
+
}, children: [gridEl, onPageChange && data.length > 0 && (_jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange }))] }));
|
|
93
125
|
}
|
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
25
|
+
.primary .header {
|
|
38
26
|
box-shadow: var(--shadow-md);
|
|
39
27
|
}
|
|
40
28
|
|
|
41
|
-
|
|
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
|
-
|
|
70
|
-
.table.sm .th {
|
|
60
|
+
.sm .headerCell {
|
|
71
61
|
padding-inline: var(--spacing-md);
|
|
72
62
|
}
|
|
73
63
|
|
|
74
|
-
/*
|
|
75
|
-
.
|
|
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
|
-
|
|
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
|
|
162
|
-
|
|
79
|
+
min-width: 0;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
text-overflow: ellipsis;
|
|
163
82
|
}
|
|
164
83
|
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
.
|
|
191
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
206
|
-
.
|
|
207
|
-
|
|
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
|
-
.
|
|
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
|
|
243
|
-
.
|
|
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
|
-
|
|
264
|
-
overflow-wrap:
|
|
154
|
+
white-space: normal;
|
|
155
|
+
overflow-wrap: break-word;
|
|
156
|
+
word-break: normal;
|
|
265
157
|
}
|
|
266
158
|
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
272
|
-
|
|
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
|
-
.
|
|
276
|
-
|
|
181
|
+
.thButtonRight {
|
|
182
|
+
justify-content: flex-end;
|
|
277
183
|
}
|
|
278
184
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
185
|
+
.thButtonCenter {
|
|
186
|
+
justify-content: center;
|
|
187
|
+
}
|
|
282
188
|
|
|
283
|
-
.
|
|
284
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
230
|
+
z-index: 2;
|
|
231
|
+
pointer-events: none;
|
|
298
232
|
}
|
|
299
233
|
|
|
300
|
-
|
|
234
|
+
/* Ensure content is above the rail */
|
|
235
|
+
.severityTable .row.severity > * {
|
|
301
236
|
position: relative;
|
|
302
|
-
z-index:
|
|
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' | '
|
|
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;
|
|
@@ -4,9 +4,33 @@ import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel,
|
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import ColumnResizer from './components/column-resizer/ColumnResizer';
|
|
6
6
|
import { Table } from './Table';
|
|
7
|
-
import { buildColumnVisibilityFromVisibleIds, mapDefsToColumnItems, sortingEqual, getSortPropsFromSorting,
|
|
7
|
+
import { buildColumnVisibilityFromVisibleIds, mapDefsToColumnItems, sortingEqual, getSortPropsFromSorting, columnItemsToIdSet, } from './tanstackTable.utils';
|
|
8
|
+
function buildGridTemplateColumns(args) {
|
|
9
|
+
var _a, _b, _c, _d;
|
|
10
|
+
const { table, allowedIds, hasSelection, selectionPx, defaultMinPx } = args;
|
|
11
|
+
const parts = [];
|
|
12
|
+
if (hasSelection)
|
|
13
|
+
parts.push(`${selectionPx}px`);
|
|
14
|
+
const leaf = table.getVisibleLeafColumns().filter(c => allowedIds.has(c.id));
|
|
15
|
+
for (const c of leaf) {
|
|
16
|
+
const def = c.columnDef;
|
|
17
|
+
const min = Number((_a = def.minSize) !== null && _a !== void 0 ? _a : defaultMinPx);
|
|
18
|
+
const maxRaw = def.maxSize;
|
|
19
|
+
const max = maxRaw == null ? Number.POSITIVE_INFINITY : Number(maxRaw);
|
|
20
|
+
const size = Number((_d = (_c = (_b = c.getSize) === null || _b === void 0 ? void 0 : _b.call(c)) !== null && _c !== void 0 ? _c : def.size) !== null && _d !== void 0 ? _d : min);
|
|
21
|
+
if (Number.isFinite(min) && Number.isFinite(max) && min > 0 && max === min) {
|
|
22
|
+
parts.push(`${Math.round(min)}px`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
// Weight based on size so wide columns keep more room
|
|
26
|
+
const weight = Math.max(1, Math.min(6, Math.round(size / 100)));
|
|
27
|
+
// Respect minSize; scroll if mins don't fit
|
|
28
|
+
parts.push(`minmax(${Math.round(min)}px, ${weight}fr)`);
|
|
29
|
+
}
|
|
30
|
+
return parts.join(' ');
|
|
31
|
+
}
|
|
8
32
|
export function TanstackTable(props) {
|
|
9
|
-
const { data, dataKey, columns,
|
|
33
|
+
const { data, dataKey, columns, sorting: controlledSorting, onSortingChange, optimisticSortingUi = true, visibleColumnIds, manualSorting, selectedRows, onRowSelect, ...tableProps } = props;
|
|
10
34
|
const isControlledSorting = controlledSorting != null;
|
|
11
35
|
const [uiSorting, setUiSorting] = React.useState(controlledSorting !== null && controlledSorting !== void 0 ? controlledSorting : []);
|
|
12
36
|
React.useEffect(() => {
|
|
@@ -53,19 +77,11 @@ export function TanstackTable(props) {
|
|
|
53
77
|
const allowedIds = React.useMemo(() => columnItemsToIdSet(columnItems), [columnItems]);
|
|
54
78
|
const visibleData = table.getRowModel().rows.map(r => r.original);
|
|
55
79
|
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
80
|
const handleSortChange = React.useCallback((column, direction) => {
|
|
61
|
-
// Translate Table's direction -> TanStack SortingState
|
|
62
81
|
const next = direction == null ? [] : [{ id: column.id, desc: direction === 'desc' }];
|
|
63
|
-
// Mirror TanStack's onSortingChange behavior
|
|
64
82
|
if (optimisticSortingUi)
|
|
65
83
|
setUiSorting(next);
|
|
66
84
|
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
85
|
}, [optimisticSortingUi, onSortingChange]);
|
|
70
86
|
const headerExtras = React.useCallback(({ index }) => {
|
|
71
87
|
var _a, _b, _c, _d;
|
|
@@ -80,5 +96,15 @@ export function TanstackTable(props) {
|
|
|
80
96
|
return null;
|
|
81
97
|
return _jsx(ColumnResizer, { id: header.column.id, handler: handler });
|
|
82
98
|
}, [table]);
|
|
83
|
-
|
|
99
|
+
const hasSelection = Boolean(selectedRows && onRowSelect && dataKey);
|
|
100
|
+
const gridTemplateColumns = React.useMemo(() => {
|
|
101
|
+
return buildGridTemplateColumns({
|
|
102
|
+
table,
|
|
103
|
+
allowedIds,
|
|
104
|
+
hasSelection,
|
|
105
|
+
selectionPx: 36,
|
|
106
|
+
defaultMinPx: 80,
|
|
107
|
+
});
|
|
108
|
+
}, [table, allowedIds, hasSelection, columnSizing]);
|
|
109
|
+
return (_jsx(Table, { ...tableProps, onSortChange: handleSortChange, dataKey: dataKey, data: visibleData, columns: columnItems, sortById: sortById, sortDirection: sortDirection, gridTemplateColumns: gridTemplateColumns, headerExtras: headerExtras, selectedRows: selectedRows, onRowSelect: onRowSelect }));
|
|
84
110
|
}
|
|
@@ -2,14 +2,12 @@ export function getVisibleColumns(columns) {
|
|
|
2
2
|
return columns.filter(c => !c.hidden);
|
|
3
3
|
}
|
|
4
4
|
export function getColumnStyle(columnId, columnStyles, alignment, verticalAlignment) {
|
|
5
|
-
var _a;
|
|
6
5
|
const baseStyle = columnStyles === null || columnStyles === void 0 ? void 0 : columnStyles[columnId];
|
|
7
6
|
return {
|
|
8
7
|
...(baseStyle !== null && baseStyle !== void 0 ? baseStyle : {}),
|
|
9
8
|
...(alignment === 'right' ? { fontVariantNumeric: 'tabular-nums' } : null),
|
|
10
9
|
verticalAlign: verticalAlignment !== null && verticalAlignment !== void 0 ? verticalAlignment : 'top',
|
|
11
10
|
textAlign: alignment !== null && alignment !== void 0 ? alignment : 'left',
|
|
12
|
-
minWidth: (_a = baseStyle === null || baseStyle === void 0 ? void 0 : baseStyle.minWidth) !== null && _a !== void 0 ? _a : 0,
|
|
13
11
|
};
|
|
14
12
|
}
|
|
15
13
|
export function getHeaderLabel(header) {
|
|
@@ -60,12 +60,15 @@ body.dbc-app {
|
|
|
60
60
|
|
|
61
61
|
.dbc-table {
|
|
62
62
|
--card-label-width: 260px;
|
|
63
|
-
|
|
64
63
|
border-collapse: collapse;
|
|
65
64
|
font-size: var(--font-size-sm);
|
|
66
65
|
line-height: var(--line-height-normal);
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
.dbc-table--hover tr:hover {
|
|
69
|
+
background-color: var(--color-bg-contextual);
|
|
70
|
+
}
|
|
71
|
+
|
|
69
72
|
.dbc-table tr + tr th,
|
|
70
73
|
.dbc-table tr + tr td {
|
|
71
74
|
padding-block: var(--spacing-xxs);
|
package/dist/styles/styles.css
CHANGED
|
@@ -60,12 +60,15 @@ body.dbc-app {
|
|
|
60
60
|
|
|
61
61
|
.dbc-table {
|
|
62
62
|
--card-label-width: 260px;
|
|
63
|
-
|
|
64
63
|
border-collapse: collapse;
|
|
65
64
|
font-size: var(--font-size-sm);
|
|
66
65
|
line-height: var(--line-height-normal);
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
.dbc-table--hover tr:hover {
|
|
69
|
+
background-color: var(--color-bg-contextual);
|
|
70
|
+
}
|
|
71
|
+
|
|
69
72
|
.dbc-table tr + tr th,
|
|
70
73
|
.dbc-table tr + tr td {
|
|
71
74
|
padding-block: var(--spacing-xxs);
|