@dbcdk/react-components 0.0.24 → 0.0.25
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/filter-field/FilterField.module.css +4 -1
- package/dist/components/forms/input/Input.module.css +1 -0
- package/dist/components/overlay/modal/Modal.d.ts +2 -0
- package/dist/components/overlay/modal/Modal.js +1 -1
- package/dist/components/overlay/modal/Modal.module.css +1 -1
- package/dist/components/overlay/modal/provider/ModalProvider.d.ts +2 -0
- package/dist/components/overlay/modal/provider/ModalProvider.js +1 -2
- package/dist/components/table/Table.d.ts +2 -1
- package/dist/components/table/Table.js +12 -3
- package/dist/components/table/Table.module.css +19 -3
- package/dist/components/table/hooks/useAnimatedRowIds.d.ts +9 -0
- package/dist/components/table/hooks/useAnimatedRowIds.js +76 -0
- package/package.json +1 -1
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
border-color var(--transition-fast) var(--ease-standard),
|
|
16
16
|
box-shadow var(--transition-fast) var(--ease-standard),
|
|
17
17
|
color var(--transition-fast) var(--ease-standard);
|
|
18
|
-
line-height: 20px;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
/* =========================
|
|
@@ -311,6 +310,10 @@
|
|
|
311
310
|
font-size: var(--font-size-xs);
|
|
312
311
|
}
|
|
313
312
|
|
|
313
|
+
.filterField input {
|
|
314
|
+
height: 100%;
|
|
315
|
+
}
|
|
316
|
+
|
|
314
317
|
.filterField input::placeholder {
|
|
315
318
|
color: var(--color-fg-subtle);
|
|
316
319
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
+
import { ButtonVariant } from '../../../components/button/Button';
|
|
2
3
|
import { Severity } from '../../../constants/severity.types';
|
|
3
4
|
export type ModalActionConfig = {
|
|
4
5
|
label: string;
|
|
6
|
+
severity?: ButtonVariant;
|
|
5
7
|
onClick?: () => void;
|
|
6
8
|
icon?: ReactNode;
|
|
7
9
|
disabled?: boolean;
|
|
@@ -88,5 +88,5 @@ export function Modal({ isOpen, onRequestClose, header, content, children, prima
|
|
|
88
88
|
const resolvedWidth = typeof width === 'number' ? `${width}px` : typeof width === 'string' ? width : undefined;
|
|
89
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
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",
|
|
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", 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: primaryAction.severity || '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
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React, { type ReactNode } from 'react';
|
|
2
|
+
import { ButtonVariant } from '../../../../components/button/Button';
|
|
2
3
|
import { type ModalProps } from '../Modal';
|
|
3
4
|
type ModalConfig = Omit<ModalProps, 'isOpen' | 'onRequestClose'> & {
|
|
4
5
|
onRequestClose?: () => void;
|
|
5
6
|
};
|
|
6
7
|
type ConfirmModalConfig = Omit<ModalConfig, 'primaryAction' | 'secondaryAction' | 'onRequestClose'> & {
|
|
7
8
|
confirmLabel?: string;
|
|
9
|
+
confirmButtonSeverity?: ButtonVariant;
|
|
8
10
|
cancelLabel?: string;
|
|
9
11
|
};
|
|
10
12
|
type ModalContextValue = {
|
|
@@ -9,7 +9,6 @@ export function ModalProvider({ children }) {
|
|
|
9
9
|
const [config, setConfig] = useState(null);
|
|
10
10
|
const [mounted, setMounted] = useState(false);
|
|
11
11
|
useEffect(() => setMounted(true), []);
|
|
12
|
-
// Holds the resolver for the current "confirm" call, if any
|
|
13
12
|
const pendingResolverRef = useRef(null);
|
|
14
13
|
const resolvePending = useCallback((value) => {
|
|
15
14
|
if (pendingResolverRef.current) {
|
|
@@ -21,7 +20,6 @@ export function ModalProvider({ children }) {
|
|
|
21
20
|
setIsOpen(false);
|
|
22
21
|
}, []);
|
|
23
22
|
const openModal = useCallback((newConfig) => {
|
|
24
|
-
// if a confirm was in progress, resolve it as "false" (cancelled/overridden)
|
|
25
23
|
resolvePending(false);
|
|
26
24
|
setConfig(newConfig);
|
|
27
25
|
setIsOpen(true);
|
|
@@ -41,6 +39,7 @@ export function ModalProvider({ children }) {
|
|
|
41
39
|
...rest,
|
|
42
40
|
primaryAction: {
|
|
43
41
|
label: confirmLabel,
|
|
42
|
+
severity: confirmConfig.confirmButtonSeverity || 'primary',
|
|
44
43
|
onClick: () => {
|
|
45
44
|
resolvePending(true);
|
|
46
45
|
},
|
|
@@ -59,6 +59,7 @@ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTML
|
|
|
59
59
|
showFirstLast?: boolean;
|
|
60
60
|
viewMode?: ViewMode;
|
|
61
61
|
emptyConfig?: TableEmptyConfig;
|
|
62
|
+
animateNewRows?: boolean;
|
|
62
63
|
};
|
|
63
|
-
export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, onRowMouseEnter, 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;
|
|
64
|
+
export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, onRowMouseEnter, 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, animateNewRows, ...rest }: TableProps<T>): JSX.Element;
|
|
64
65
|
export {};
|
|
@@ -7,6 +7,7 @@ import { Pagination } from '../../components/pagination/Pagination';
|
|
|
7
7
|
import { SkeletonLoaderItem } from '../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
|
|
8
8
|
import { SeverityBgColor } from '../../constants/severity';
|
|
9
9
|
import { TableEmptyState } from './components/empty-state/EmptyState';
|
|
10
|
+
import { useAnimatedNewRowIds } from './hooks/useAnimatedRowIds';
|
|
10
11
|
import styles from './Table.module.css';
|
|
11
12
|
import { getAriaSort, getCellDisplayValue, getHeaderLabel, getNextSortDirection, getVisibleColumns, isModifierClick, shouldAllowWrap, shouldToggleOnKey, isActiveSort, } from './table.utils';
|
|
12
13
|
function buildDefaultGridTemplate(args) {
|
|
@@ -18,13 +19,19 @@ function buildDefaultGridTemplate(args) {
|
|
|
18
19
|
parts.push('minmax(120px, 1fr)');
|
|
19
20
|
return parts.join(' ');
|
|
20
21
|
}
|
|
21
|
-
export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, onRowMouseEnter, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport = false, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }) {
|
|
22
|
+
export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, onRowMouseEnter, sortById, sortDirection, dataKey, headerExtras, gridTemplateColumns, toolbar, striped, fillViewport = false, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, animateNewRows = true, ...rest }) {
|
|
22
23
|
void viewportBottomOffset;
|
|
23
24
|
void viewportMin;
|
|
24
25
|
void viewportIncludeMarginTop;
|
|
25
26
|
const filteredColumns = useMemo(() => getVisibleColumns(columns), [columns]);
|
|
26
27
|
const handlePageChange = useCallback((e) => onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e), [onPageChange]);
|
|
27
28
|
const hasSelection = Boolean(selectedRows && onRowSelect && dataKey);
|
|
29
|
+
const newRowIds = useAnimatedNewRowIds({
|
|
30
|
+
data,
|
|
31
|
+
dataKey,
|
|
32
|
+
enabled: animateNewRows,
|
|
33
|
+
animationDurationMs: 1000,
|
|
34
|
+
});
|
|
28
35
|
const template = useMemo(() => {
|
|
29
36
|
return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({ hasSelection, colCount: filteredColumns.length }));
|
|
30
37
|
}, [gridTemplateColumns, hasSelection, filteredColumns.length]);
|
|
@@ -94,13 +101,15 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
|
|
|
94
101
|
})] }, `loading-row-${rowIndex}`))) }));
|
|
95
102
|
const dataBodyEl = (_jsx("div", { className: `${styles.body} ${striped ? styles.striped : ''}`, role: "rowgroup", children: data.map(row => {
|
|
96
103
|
const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
|
|
97
|
-
const rowId = row[dataKey];
|
|
104
|
+
const rowId = String(row[dataKey]);
|
|
105
|
+
const isNewRow = animateNewRows && newRowIds.has(rowId);
|
|
98
106
|
const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
|
|
99
107
|
return (_jsxs("div", { className: [
|
|
100
108
|
styles.row,
|
|
101
109
|
onRowClick ? styles.clickableRow : '',
|
|
102
110
|
isSelected ? styles.selectedRow : '',
|
|
103
111
|
rowSeverity ? styles.severity : '',
|
|
112
|
+
isNewRow ? styles.newRow : '',
|
|
104
113
|
]
|
|
105
114
|
.filter(Boolean)
|
|
106
115
|
.join(' '), style: {
|
|
@@ -143,7 +152,7 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
|
|
|
143
152
|
]
|
|
144
153
|
.filter(Boolean)
|
|
145
154
|
.join(' '), role: "cell", "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', "data-divider": column.divider, children: _jsx("div", { className: styles.cellContent, children: allowWrap ? (cellValue) : (_jsx("div", { className: styles.cellValueEllipsis, children: cellValue })) }) }, column.id));
|
|
146
|
-
})] }, `gridRow-${
|
|
155
|
+
})] }, `gridRow-${rowId}`));
|
|
147
156
|
}) }));
|
|
148
157
|
const bodyContent = loading && !data.length ? loadingBodyEl : dataBodyEl;
|
|
149
158
|
const tableClassName = [
|
|
@@ -165,7 +165,6 @@
|
|
|
165
165
|
vertical-align: top;
|
|
166
166
|
min-width: 0;
|
|
167
167
|
overflow: hidden;
|
|
168
|
-
text-overflow: ellipsis;
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
.sm .cell {
|
|
@@ -346,8 +345,8 @@
|
|
|
346
345
|
min-width: 0;
|
|
347
346
|
max-inline-size: 100%;
|
|
348
347
|
white-space: nowrap;
|
|
349
|
-
overflow: hidden;
|
|
350
|
-
text-overflow: ellipsis;
|
|
348
|
+
/* overflow: hidden; */
|
|
349
|
+
/* text-overflow: ellipsis; */
|
|
351
350
|
}
|
|
352
351
|
|
|
353
352
|
.allowWrap .cellContent {
|
|
@@ -467,3 +466,20 @@
|
|
|
467
466
|
width: 1px;
|
|
468
467
|
background: var(--table-divider);
|
|
469
468
|
}
|
|
469
|
+
|
|
470
|
+
.newRow {
|
|
471
|
+
animation: tableRowFadeIn 1000ms ease-out;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.newRow {
|
|
475
|
+
animation: tableRowFadeIn 600ms ease-out;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
@keyframes tableRowFadeIn {
|
|
479
|
+
from {
|
|
480
|
+
opacity: 0;
|
|
481
|
+
}
|
|
482
|
+
to {
|
|
483
|
+
opacity: 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type RowId = string;
|
|
2
|
+
type UseAnimatedNewRowIdsArgs<T> = {
|
|
3
|
+
data: T[];
|
|
4
|
+
dataKey: keyof T;
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
animationDurationMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function useAnimatedNewRowIds<T extends Record<string, any>>({ data, dataKey, enabled, animationDurationMs, }: UseAnimatedNewRowIdsArgs<T>): Set<RowId>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
export function useAnimatedNewRowIds({ data, dataKey, enabled = true, animationDurationMs = 1000, }) {
|
|
4
|
+
const prevRowIdsRef = useRef(new Set());
|
|
5
|
+
const hasEstablishedBaselineRef = useRef(false);
|
|
6
|
+
const removeTimersRef = useRef(new Map());
|
|
7
|
+
const [newRowIds, setNewRowIds] = useState(new Set());
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const currentIds = new Set();
|
|
10
|
+
for (const row of data) {
|
|
11
|
+
currentIds.add(String(row[dataKey]));
|
|
12
|
+
}
|
|
13
|
+
if (!enabled) {
|
|
14
|
+
for (const timer of removeTimersRef.current.values()) {
|
|
15
|
+
window.clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
removeTimersRef.current.clear();
|
|
18
|
+
setNewRowIds(new Set());
|
|
19
|
+
prevRowIdsRef.current = currentIds;
|
|
20
|
+
hasEstablishedBaselineRef.current = currentIds.size > 0;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (currentIds.size === 0) {
|
|
24
|
+
prevRowIdsRef.current = currentIds;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!hasEstablishedBaselineRef.current) {
|
|
28
|
+
prevRowIdsRef.current = currentIds;
|
|
29
|
+
hasEstablishedBaselineRef.current = true;
|
|
30
|
+
setNewRowIds(new Set());
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const addedIds = [];
|
|
34
|
+
for (const id of currentIds) {
|
|
35
|
+
if (!prevRowIdsRef.current.has(id)) {
|
|
36
|
+
addedIds.push(id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (addedIds.length > 0) {
|
|
40
|
+
setNewRowIds(prev => {
|
|
41
|
+
const next = new Set(prev);
|
|
42
|
+
for (const id of addedIds)
|
|
43
|
+
next.add(id);
|
|
44
|
+
return next;
|
|
45
|
+
});
|
|
46
|
+
for (const id of addedIds) {
|
|
47
|
+
const existingTimer = removeTimersRef.current.get(id);
|
|
48
|
+
if (existingTimer) {
|
|
49
|
+
window.clearTimeout(existingTimer);
|
|
50
|
+
}
|
|
51
|
+
const timer = window.setTimeout(() => {
|
|
52
|
+
setNewRowIds(prev => {
|
|
53
|
+
if (!prev.has(id))
|
|
54
|
+
return prev;
|
|
55
|
+
const next = new Set(prev);
|
|
56
|
+
next.delete(id);
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
removeTimersRef.current.delete(id);
|
|
60
|
+
}, animationDurationMs);
|
|
61
|
+
removeTimersRef.current.set(id, timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
prevRowIdsRef.current = currentIds;
|
|
65
|
+
}, [data, dataKey, enabled, animationDurationMs]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const timers = removeTimersRef.current;
|
|
68
|
+
return () => {
|
|
69
|
+
for (const timer of timers.values()) {
|
|
70
|
+
window.clearTimeout(timer);
|
|
71
|
+
}
|
|
72
|
+
timers.clear();
|
|
73
|
+
};
|
|
74
|
+
}, []);
|
|
75
|
+
return newRowIds;
|
|
76
|
+
}
|