@dbcdk/react-components 0.0.25 → 0.0.26

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.
@@ -4,7 +4,7 @@ import { Check } from 'lucide-react';
4
4
  import { useId, useState } from 'react';
5
5
  import styles from './Checkbox.module.css';
6
6
  import { InputContainer } from '../input-container/InputContainer';
7
- export function Checkbox({ checked: controlled, onChange, variant = 'outlined', disabled, label, size = 'md', modified, containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
7
+ export function Checkbox({ checked: controlled, onChange, variant = 'default', disabled, label, size = 'md', modified, containerLabel, error, helpText, orientation = 'horizontal', labelWidth = '160px', fullWidth = false, required = false, noContainer = false, id, 'data-cy': dataCy, }) {
8
8
  const [internal, setInternal] = useState(false);
9
9
  const isChecked = controlled !== null && controlled !== void 0 ? controlled : internal;
10
10
  const generatedId = useId();
@@ -50,10 +50,11 @@
50
50
  pointer-events: none;
51
51
  }
52
52
 
53
- .outlined.checked {
54
- background-color: var(--opac-bg-default);
53
+ .default {
54
+ background-color: var(--color-bg-toolbar);
55
+ border-color: var(--color-bg-toolbar);
55
56
  &:not(:hover) {
56
- border-color: transparent;
57
+ border-color: var(--color-bg-toolbar);
57
58
  }
58
59
 
59
60
  .icon {
@@ -61,6 +62,18 @@
61
62
  }
62
63
  }
63
64
 
65
+ .outlined.checked {
66
+ background-color: transparent;
67
+ border-color: var(--color-border-default);
68
+ &:not(:hover) {
69
+ border-color: var(--color-brand);
70
+ }
71
+
72
+ .icon {
73
+ color: var(--color-brand);
74
+ }
75
+ }
76
+
64
77
  .success.checked {
65
78
  background-color: var(--color-status-success);
66
79
  &:not(:hover) {
@@ -112,7 +112,7 @@ export function Select({ label, error, helpText, orientation = 'vertical', label
112
112
  returnFocus: true, trigger: (toggle, icon, isOpen) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
113
113
  resetActiveToSelected();
114
114
  toggle(e);
115
- }, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
115
+ }, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : _jsx("span", { className: "dbc-muted-text", children: placeholder }) }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
116
116
  const isSelected = typeof opt.value === 'object' && typeof selectedValue === 'object' && datakey
117
117
  ? (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue[datakey]) === opt.value[datakey]
118
118
  : opt.value === selectedValue;
@@ -57,7 +57,7 @@ const MenuItem = React.forwardRef(({ children, active, disabled, className, item
57
57
  });
58
58
  MenuItem.displayName = 'Menu.Item';
59
59
  const MenuCheckItem = React.forwardRef(({ label, checked, disabled, onCheckedChange, className, ...liProps }, ref) => {
60
- return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { noContainer: true, checked: checked, disabled: disabled, label: label, onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
60
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { variant: "default", size: "md", noContainer: true, checked: checked, disabled: disabled, label: label, onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
61
61
  });
62
62
  MenuCheckItem.displayName = 'Menu.CheckItem';
63
63
  const MenuRadioItem = React.forwardRef(({ name, value, checked, disabled, label, onValueChange, className, ...liProps }, ref) => {
@@ -9,7 +9,6 @@
9
9
  inline-size: var(--sidebar-width);
10
10
  box-sizing: border-box;
11
11
  border-inline-end: var(--border-width-thin) solid var(--color-border-default);
12
-
13
12
  transition:
14
13
  width var(--transition-fast) var(--ease-standard),
15
14
  inline-size var(--transition-fast) var(--ease-standard);
@@ -11,6 +11,7 @@
11
11
  cursor: pointer;
12
12
  position: relative;
13
13
  border-radius: var(--border-radius-default);
14
+ user-select: none;
14
15
  transition:
15
16
  background-color var(--transition-fast) var(--ease-standard),
16
17
  color var(--transition-fast) var(--ease-standard);
@@ -59,7 +59,6 @@ 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;
63
62
  };
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;
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;
65
64
  export {};
@@ -7,7 +7,6 @@ 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';
11
10
  import styles from './Table.module.css';
12
11
  import { getAriaSort, getCellDisplayValue, getHeaderLabel, getNextSortDirection, getVisibleColumns, isModifierClick, shouldAllowWrap, shouldToggleOnKey, isActiveSort, } from './table.utils';
13
12
  function buildDefaultGridTemplate(args) {
@@ -19,19 +18,13 @@ function buildDefaultGridTemplate(args) {
19
18
  parts.push('minmax(120px, 1fr)');
20
19
  return parts.join(' ');
21
20
  }
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 }) {
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 }) {
23
22
  void viewportBottomOffset;
24
23
  void viewportMin;
25
24
  void viewportIncludeMarginTop;
26
25
  const filteredColumns = useMemo(() => getVisibleColumns(columns), [columns]);
27
26
  const handlePageChange = useCallback((e) => onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e), [onPageChange]);
28
27
  const hasSelection = Boolean(selectedRows && onRowSelect && dataKey);
29
- const newRowIds = useAnimatedNewRowIds({
30
- data,
31
- dataKey,
32
- enabled: animateNewRows,
33
- animationDurationMs: 1000,
34
- });
35
28
  const template = useMemo(() => {
36
29
  return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({ hasSelection, colCount: filteredColumns.length }));
37
30
  }, [gridTemplateColumns, hasSelection, filteredColumns.length]);
@@ -101,15 +94,13 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
101
94
  })] }, `loading-row-${rowIndex}`))) }));
102
95
  const dataBodyEl = (_jsx("div", { className: `${styles.body} ${striped ? styles.striped : ''}`, role: "rowgroup", children: data.map(row => {
103
96
  const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
104
- const rowId = String(row[dataKey]);
105
- const isNewRow = animateNewRows && newRowIds.has(rowId);
97
+ const rowId = row[dataKey];
106
98
  const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
107
99
  return (_jsxs("div", { className: [
108
100
  styles.row,
109
101
  onRowClick ? styles.clickableRow : '',
110
102
  isSelected ? styles.selectedRow : '',
111
103
  rowSeverity ? styles.severity : '',
112
- isNewRow ? styles.newRow : '',
113
104
  ]
114
105
  .filter(Boolean)
115
106
  .join(' '), style: {
@@ -187,15 +187,16 @@
187
187
  }
188
188
 
189
189
  .selectionCell {
190
- padding-block: inherit !important;
191
- padding-inline: 0 !important;
192
190
  overflow: visible !important;
193
191
  display: flex;
194
- align-items: center;
192
+ align-items: flex-start;
195
193
  justify-content: flex-start;
196
194
  min-width: 0;
197
195
  }
198
196
 
197
+ .headerCell.selectionCell {
198
+ align-items: center;
199
+ }
199
200
  .clickableRow {
200
201
  cursor: pointer;
201
202
  }
@@ -117,7 +117,7 @@ export function Tabs({ header, variant, panelStyle = false, tabs, value, default
117
117
  const selected = index === activeIndex;
118
118
  const tabDomId = `${uid}-tab-${String(tab.id)}`;
119
119
  const panelDomId = `${uid}-panel-${String(tab.id)}`;
120
- return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { severity: "neutral", size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
120
+ return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { type: "subtle", severity: null, size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
121
121
  }) }), _jsx("div", { id: activeTab ? `${uid}-panel-${String(activeTab.id)}` : undefined, role: "tabpanel", "aria-labelledby": activeTab ? `${uid}-tab-${String(activeTab.id)}` : undefined, className: styles.tabContent, children: activeTab === null || activeTab === void 0 ? void 0 : activeTab.content })] })] }));
122
122
  }
123
123
  Tabs.Item = TabsItem;
@@ -1,12 +1,20 @@
1
1
  /**
2
- * Formats a Date as "dd:mm:yyyy" or "dd:mm:yyyy hh:mm:ss".
3
- * All parts are zero-padded numbers.
2
+ * Formats a Date as "dd/mm/yyyy" or "dd/mm/yyyy hh:mm:ss".
3
+ * Optionally returns user-friendly Danish labels like "I dag" or "I går".
4
+ *
5
+ * Examples:
6
+ * - formatDate(new Date(), { userFriendlyLabel: true }) => "I dag"
7
+ * - formatDate(new Date(), { userFriendlyLabel: true, showTime: true }) => "I dag, kl. 14:30"
8
+ * - formatDate(date, { showTime: true }) => "10/03/2026 14:30"
4
9
  *
5
10
  * @param date - The Date or date-parsable value to format
6
- * @param options.showSeconds - If true, append time "hh:mm:ss"
11
+ * @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
12
+ * @param options.showSeconds - If true, append seconds when showTime is enabled
13
+ * @param options.userFriendlyLabel - If true, use "I dag" / "I går" when applicable
7
14
  * @returns A formatted string
8
15
  */
9
16
  export declare function formatDate(date: Date | string | number, options?: {
10
17
  showTime?: boolean;
11
18
  showSeconds?: boolean;
19
+ userFriendlyLabel?: boolean;
12
20
  }): string;
@@ -1,26 +1,51 @@
1
1
  /**
2
- * Formats a Date as "dd:mm:yyyy" or "dd:mm:yyyy hh:mm:ss".
3
- * All parts are zero-padded numbers.
2
+ * Formats a Date as "dd/mm/yyyy" or "dd/mm/yyyy hh:mm:ss".
3
+ * Optionally returns user-friendly Danish labels like "I dag" or "I går".
4
+ *
5
+ * Examples:
6
+ * - formatDate(new Date(), { userFriendlyLabel: true }) => "I dag"
7
+ * - formatDate(new Date(), { userFriendlyLabel: true, showTime: true }) => "I dag, kl. 14:30"
8
+ * - formatDate(date, { showTime: true }) => "10/03/2026 14:30"
4
9
  *
5
10
  * @param date - The Date or date-parsable value to format
6
- * @param options.showSeconds - If true, append time "hh:mm:ss"
11
+ * @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
12
+ * @param options.showSeconds - If true, append seconds when showTime is enabled
13
+ * @param options.userFriendlyLabel - If true, use "I dag" / "I går" when applicable
7
14
  * @returns A formatted string
8
15
  */
9
16
  export function formatDate(date, options = {}) {
10
17
  const d = date instanceof Date ? date : new Date(date);
11
18
  if (isNaN(d.getTime()))
12
19
  return '';
20
+ const { showTime = false, showSeconds = false, userFriendlyLabel = false } = options;
13
21
  const pad = (n) => n.toString().padStart(2, '0');
22
+ const formatTime = () => {
23
+ const hours = pad(d.getHours());
24
+ const minutes = pad(d.getMinutes());
25
+ if (!showSeconds)
26
+ return `${hours}:${minutes}`;
27
+ const seconds = pad(d.getSeconds());
28
+ return `${hours}:${minutes}:${seconds}`;
29
+ };
30
+ const isSameCalendarDay = (a, b) => a.getDate() === b.getDate() &&
31
+ a.getMonth() === b.getMonth() &&
32
+ a.getFullYear() === b.getFullYear();
33
+ if (userFriendlyLabel) {
34
+ const now = new Date();
35
+ const yesterday = new Date(now);
36
+ yesterday.setDate(now.getDate() - 1);
37
+ if (isSameCalendarDay(d, now)) {
38
+ return showTime ? `I dag, kl. ${formatTime()}` : 'I dag';
39
+ }
40
+ if (isSameCalendarDay(d, yesterday)) {
41
+ return showTime ? `I går, kl. ${formatTime()}` : 'I går';
42
+ }
43
+ }
14
44
  const day = pad(d.getDate());
15
45
  const month = pad(d.getMonth() + 1);
16
46
  const year = d.getFullYear();
17
47
  const base = `${day}/${month}/${year}`;
18
- if (!options.showTime)
48
+ if (!showTime)
19
49
  return base;
20
- const hours = pad(d.getHours());
21
- const minutes = pad(d.getMinutes());
22
- if (!options.showSeconds)
23
- return `${base} ${hours}:${minutes}`;
24
- const seconds = pad(d.getSeconds());
25
- return `${base} ${hours}:${minutes}:${seconds}`;
50
+ return `${base} ${formatTime()}`;
26
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -1,9 +0,0 @@
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 {};
@@ -1,76 +0,0 @@
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
- }