@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.
@@ -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
  }
@@ -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.24",
3
+ "version": "0.0.25",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",