@dbcdk/react-components 0.0.23 → 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.
@@ -43,6 +43,7 @@ type BaseProps = {
43
43
  locale: string;
44
44
  enableTime: boolean;
45
45
  }) => string;
46
+ onOpenChange?: (open: boolean) => void;
46
47
  };
47
48
  export type DateTimePickerProps = (BaseProps & {
48
49
  mode?: 'single';
@@ -52,7 +52,7 @@ function defaultFormatRange(s, e, opts) {
52
52
  return '';
53
53
  }
54
54
  const cx = (...classes) => classes.filter(Boolean).join(' ');
55
- export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'single', value, onChange, enableTime = false, timeStep = 15, min, max, locale = typeof navigator !== 'undefined' ? navigator.language : 'da-DK', weekStartsOn = 1, presets, inputProps, formatDate = defaultFormatDate, formatRange = defaultFormatRange, }, _ref) {
55
+ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'single', value, onChange, enableTime = false, timeStep = 15, min, max, locale = typeof navigator !== 'undefined' ? navigator.language : 'da-DK', weekStartsOn = 1, presets, inputProps, formatDate = defaultFormatDate, formatRange = defaultFormatRange, onOpenChange, }, _ref) {
56
56
  void formatDate;
57
57
  void formatRange;
58
58
  const popRef = useRef(null);
@@ -314,6 +314,19 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
314
314
  setDirty(false);
315
315
  }
316
316
  }, [text, mode, enableTime, onChangeDateOnly, onChangeIso, onChangeRange]);
317
+ const emitTimeChange = useCallback((nextHH, nextMM) => {
318
+ if (mode !== 'single' || !enableTime)
319
+ return;
320
+ if (typeof value !== 'string')
321
+ return;
322
+ const current = localDateFromIso(value);
323
+ if (!current)
324
+ return;
325
+ const iso = isoFromLocalParts(current.getFullYear(), current.getMonth(), current.getDate(), nextHH, nextMM);
326
+ if (!iso)
327
+ return;
328
+ onChangeIso(iso);
329
+ }, [mode, enableTime, value, onChangeIso]);
317
330
  const clear = useCallback(() => {
318
331
  if (mode === 'single') {
319
332
  if (enableTime)
@@ -328,7 +341,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
328
341
  setText('');
329
342
  }, [mode, enableTime, onChangeDateOnly, onChangeIso, onChangeRange]);
330
343
  const fallbackPlaceholder = mode === 'single' ? 'Vælg dato' : 'Vælg interval';
331
- return (_jsx(Popover, { matchTriggerWidth: false, ref: popRef, trigger: toggle => {
344
+ return (_jsx(Popover, { matchTriggerWidth: false, ref: popRef, onOpenChange: onOpenChange, trigger: toggle => {
332
345
  var _a, _b;
333
346
  return (_jsx("div", { onClick: toggle, className: styles.triggerWrap, children: _jsx(Input, { ...inputProps, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: "false", placeholder: (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder) !== null && _a !== void 0 ? _a : fallbackPlaceholder, value: dirty ? text : formatted, onInput: e => {
334
347
  setDirty(true);
@@ -374,6 +387,14 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
374
387
  }
375
388
  const dayNum = utcDay.getUTCDate();
376
389
  return (_jsx("button", { role: "gridcell", "aria-selected": selected, "aria-disabled": disabledDay, onMouseEnter: () => setHoverUTC(utcDay), onClick: () => selectDay(utcDay), disabled: disabledDay, className: cx(styles.dayCell, inThisMonth ? styles.dayInMonth : styles.dayOutside, selected && styles.daySelected, inRange && styles.dayInRange, isToday && !selected && styles.dayToday, disabledDay && styles.dayDisabled), title: new Date(utcDay).toLocaleDateString(locale), children: dayNum }, idx));
377
- }) }), enableTime && mode === 'single' && (_jsxs("div", { className: styles.timeRow, children: [_jsxs("div", { className: styles.timeLabel, children: [_jsx(Clock, { size: 14 }), " Tid"] }), _jsx("select", { value: timeHH, onChange: e => setTimeHH(parseInt(e.target.value, 10)), className: styles.timeSelect, children: hours.map(h => (_jsx("option", { value: h, children: String(h).padStart(2, '0') }, h))) }), _jsx("select", { value: timeMM, onChange: e => setTimeMM(parseInt(e.target.value, 10)), className: styles.timeSelect, children: minutes.map(m => (_jsx("option", { value: m, children: String(m).padStart(2, '0') }, m))) })] })), mode === 'range' && (_jsxs("div", { className: styles.footer, children: [_jsx(Button, { variant: "outlined", size: "sm", onClick: clear, icon: _jsx(X, { size: 14 }), children: "Ryd" }), _jsx(Button, { variant: "primary", size: "sm", onClick: () => { var _a; return (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close(); }, children: "OK" })] }))] })] }) }));
390
+ }) }), enableTime && mode === 'single' && (_jsxs("div", { className: styles.timeRow, children: [_jsxs("div", { className: styles.timeLabel, children: [_jsx(Clock, { size: 14 }), " Tid"] }), _jsx("select", { value: timeHH, onChange: e => {
391
+ const nextHH = parseInt(e.target.value, 10);
392
+ setTimeHH(nextHH);
393
+ emitTimeChange(nextHH, timeMM);
394
+ }, className: styles.timeSelect, children: hours.map(h => (_jsx("option", { value: h, children: String(h).padStart(2, '0') }, h))) }), _jsx("select", { value: timeMM, onChange: e => {
395
+ const nextMM = parseInt(e.target.value, 10);
396
+ setTimeMM(nextMM);
397
+ emitTimeChange(timeHH, nextMM);
398
+ }, className: styles.timeSelect, children: minutes.map(m => (_jsx("option", { value: m, children: String(m).padStart(2, '0') }, m))) })] })), mode === 'range' && (_jsxs("div", { className: styles.footer, children: [_jsx(Button, { variant: "outlined", size: "sm", onClick: clear, icon: _jsx(X, { size: 14 }), children: "Ryd" }), _jsx(Button, { variant: "primary", size: "sm", onClick: () => { var _a; return (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close(); }, children: "OK" })] }))] })] }) }));
378
399
  });
379
400
  DateTimePicker.displayName = 'DateTimePicker';
@@ -56,7 +56,7 @@ function OperatorDropdown({ value, onChange, operators, size = 'sm', disabled, }
56
56
  setActiveIndex(operators.indexOf(op));
57
57
  (_a = popRef.current) === null || _a === void 0 ? void 0 : _a.close();
58
58
  };
59
- return (_jsx(Popover, { ref: popRef, minWidth: "220px", trigger: (toggle, icon) => (_jsxs("button", { type: "button", onClick: toggle, disabled: disabled, "aria-label": "Change operator", className: `${styles.operatorTrigger} ${styles[size]}`, children: [_jsx("span", { className: styles.operatorText, children: LABELS[value] }), icon] })), children: _jsx(Menu, { children: operators.map(op => {
59
+ return (_jsx(Popover, { ref: popRef, minWidth: "220px", trigger: (toggle, icon) => (_jsxs("button", { type: "button", onClick: toggle, disabled: disabled, "aria-label": "Skift operator", className: `${styles.operatorTrigger} ${styles[size]}`, children: [_jsx("span", { className: styles.operatorText, children: LABELS[value] }), icon] })), children: _jsx(Menu, { children: operators.map(op => {
60
60
  const selected = op === value;
61
61
  return (_jsx(Menu.Item, { active: selected, children: _jsxs("button", { type: "button", onClick: () => handleSelect(op), disabled: disabled, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: selected ? _jsx(Check, { size: 16 }) : null }), LABELS[op]] }) }, op));
62
62
  }) }) }));
@@ -264,6 +264,10 @@
264
264
  );
265
265
  }
266
266
 
267
+ .operatorTrigger svg {
268
+ height: var(--component-size-xxs);
269
+ width: var(--component-size-xxs);
270
+ }
267
271
  /* =========================
268
272
  VALUE WRAPPER
269
273
  ========================= */
@@ -302,6 +306,12 @@
302
306
 
303
307
  .filterField .operatorText {
304
308
  white-space: nowrap;
309
+ font-family: var(--font-family-mono);
310
+ font-size: var(--font-size-xs);
311
+ }
312
+
313
+ .filterField input {
314
+ height: 100%;
305
315
  }
306
316
 
307
317
  .filterField input::placeholder {
@@ -139,6 +139,7 @@
139
139
  box-shadow: none;
140
140
  padding-inline: var(--spacing-xs);
141
141
  padding-block: 0;
142
+ block-size: 100%;
142
143
  }
143
144
 
144
145
  .embedded:hover:not(:disabled),
@@ -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", 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 })] }))] }))] }) }));
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
  }
@@ -15,7 +15,7 @@
15
15
 
16
16
  /* Default width can be overridden by --modal-width from props */
17
17
  .modal {
18
- --modal-width: 700px;
18
+ --modal-width: 500px;
19
19
 
20
20
  background: var(--color-bg-surface);
21
21
  border-radius: var(--border-radius-lg);
@@ -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-${String(rowId)}`));
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",