@dbcdk/react-components 0.0.25 → 0.0.27

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.
Files changed (50) hide show
  1. package/dist/components/__stories__/_data/table.d.ts +1 -1
  2. package/dist/components/__stories__/_data/table.js +11 -5
  3. package/dist/components/accordion/Accordion.module.css +3 -3
  4. package/dist/components/accordion/components/AccordionRow.module.css +5 -1
  5. package/dist/components/forms/checkbox/Checkbox.js +1 -1
  6. package/dist/components/forms/checkbox/Checkbox.module.css +16 -3
  7. package/dist/components/forms/select/Select.js +1 -1
  8. package/dist/components/headline/Headline.module.css +15 -44
  9. package/dist/components/menu/Menu.js +1 -1
  10. package/dist/components/nav-bar/NavBar.module.css +1 -1
  11. package/dist/components/panel/Panel.module.css +2 -2
  12. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +0 -1
  13. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +1 -0
  14. package/dist/components/table/Table.d.ts +3 -65
  15. package/dist/components/table/Table.js +20 -159
  16. package/dist/components/table/Table.module.css +39 -6
  17. package/dist/components/table/Table.types.d.ts +58 -0
  18. package/dist/components/table/Table.types.js +1 -0
  19. package/dist/components/table/TanstackTable.d.ts +1 -1
  20. package/dist/components/table/TanstackTable.js +0 -1
  21. package/dist/components/table/components/TableBody.d.ts +19 -0
  22. package/dist/components/table/components/TableBody.js +10 -0
  23. package/dist/components/table/components/TableCell.d.ts +9 -0
  24. package/dist/components/table/components/TableCell.js +7 -0
  25. package/dist/components/table/components/TableHeader.d.ts +16 -0
  26. package/dist/components/table/components/TableHeader.js +7 -0
  27. package/dist/components/table/components/TableHeaderCell.d.ts +12 -0
  28. package/dist/components/table/components/TableHeaderCell.js +24 -0
  29. package/dist/components/table/components/TableLoadingBody.d.ts +10 -0
  30. package/dist/components/table/components/TableLoadingBody.js +10 -0
  31. package/dist/components/table/components/TablePagination.d.ts +0 -0
  32. package/dist/components/table/components/TablePagination.js +1 -0
  33. package/dist/components/table/components/TableRow.d.ts +19 -0
  34. package/dist/components/table/components/TableRow.js +38 -0
  35. package/dist/components/table/components/TableSelectionCell.d.ts +9 -0
  36. package/dist/components/table/components/TableSelectionCell.js +15 -0
  37. package/dist/components/table/hooks/useTableRowInteractions.d.ts +15 -0
  38. package/dist/components/table/hooks/useTableRowInteractions.js +25 -0
  39. package/dist/components/table/table.classes.d.ts +10 -0
  40. package/dist/components/table/table.classes.js +23 -0
  41. package/dist/components/table/table.utils.d.ts +7 -4
  42. package/dist/components/table/table.utils.js +14 -20
  43. package/dist/components/table/tanstackTable.utils.d.ts +1 -2
  44. package/dist/components/table/tanstackTable.utils.js +3 -2
  45. package/dist/components/tabs/Tabs.js +1 -1
  46. package/dist/utils/date/formatDate.d.ts +11 -3
  47. package/dist/utils/date/formatDate.js +35 -10
  48. package/package.json +1 -1
  49. package/dist/components/table/hooks/useAnimatedRowIds.d.ts +0 -9
  50. package/dist/components/table/hooks/useAnimatedRowIds.js +0 -76
@@ -0,0 +1,58 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ import type { PageChangeEvent } from '../../components/pagination/Pagination';
3
+ import type { Severity } from '../../constants/severity.types';
4
+ import type { ViewMode } from '../../hooks/useTableSettings';
5
+ export interface ColumnItem<T> {
6
+ id: string;
7
+ header: string | (() => ReactNode);
8
+ accessor?: keyof T;
9
+ sortable?: boolean;
10
+ sortFunction?: (a: T, b: T) => -1 | 0 | 1;
11
+ render?: (item: T) => ReactNode;
12
+ hidden?: boolean;
13
+ align?: 'left' | 'right' | 'center';
14
+ verticalAlign?: 'top' | 'middle' | 'bottom';
15
+ divider?: 'right' | 'left';
16
+ allowWrap?: boolean;
17
+ emptyPlaceholder?: ReactNode;
18
+ canHide?: boolean;
19
+ severity?: any;
20
+ }
21
+ export type HeaderExtrasArgs<T> = {
22
+ column: ColumnItem<T>;
23
+ index: number;
24
+ };
25
+ export type TableVariant = 'primary' | 'embedded';
26
+ export type SortDirection = 'asc' | 'desc' | null;
27
+ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTMLDivElement>, 'onClick' | 'onMouseEnter'> & {
28
+ data: T[];
29
+ dataKey: keyof T;
30
+ columns: ColumnItem<T>[];
31
+ selectedRows?: Set<number | string>;
32
+ selectionMode?: 'single' | 'multiple';
33
+ allRowsSelected?: boolean;
34
+ sortById?: string;
35
+ sortDirection?: SortDirection;
36
+ loading?: boolean;
37
+ emptyConfig?: any;
38
+ variant?: TableVariant;
39
+ size?: 'sm' | 'md';
40
+ viewMode?: ViewMode;
41
+ striped?: boolean;
42
+ fillViewport?: boolean;
43
+ gridTemplateColumns?: string;
44
+ toolbar?: ReactNode;
45
+ headerExtras?: (args: HeaderExtrasArgs<T>) => ReactNode;
46
+ take?: number;
47
+ skip?: number;
48
+ totalItemsCount?: number;
49
+ paginationPlacement?: 'top' | 'bottom';
50
+ showFirstLast?: boolean;
51
+ getRowSeverity?: (row: T) => Severity | undefined;
52
+ onRowClick?: (row: T) => void;
53
+ onRowMouseEnter?: (row: T) => void;
54
+ onRowSelect?: (rowId: number | string, isSelected: boolean) => void;
55
+ onSelectAllRows?: (isSelected: boolean) => void;
56
+ onSortChange?: (column: ColumnItem<T>, direction: SortDirection) => void;
57
+ onPageChange?: (e: PageChangeEvent) => void;
58
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { type ColumnDef, type SortingState } from '@tanstack/react-table';
2
2
  import * as React from 'react';
3
- import { type TableProps, type TableVariant } from './Table';
3
+ import { TableProps, TableVariant } from './Table.types';
4
4
  import { ViewMode } from '../../hooks/useTableSettings';
5
5
  type Filterable<T> = Array<keyof T>;
6
6
  export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerExtras' | 'gridTemplateColumns' | 'toolbar'> & {
@@ -77,7 +77,6 @@ export function TanstackTable(props) {
77
77
  table,
78
78
  allowedIds,
79
79
  hasSelection,
80
- selectionPx: 36,
81
80
  defaultMinPx: 80,
82
81
  columnSizing,
83
82
  });
@@ -0,0 +1,19 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+ import type { ViewMode } from '../../../hooks/useTableSettings';
3
+ import type { ColumnItem } from '../Table.types';
4
+ type Props<T extends Record<string, any>> = {
5
+ data: T[];
6
+ dataKey: keyof T;
7
+ columns: ColumnItem<T>[];
8
+ gridStyle: CSSProperties;
9
+ striped?: boolean;
10
+ selectedRows?: Set<number | string>;
11
+ hasSelection: boolean;
12
+ viewMode?: ViewMode;
13
+ getRowSeverity?: (row: T) => any;
14
+ onRowClick?: (row: T) => void;
15
+ onRowMouseEnter?: (row: T) => void;
16
+ onRowSelect?: (rowId: number | string, isSelected: boolean) => void;
17
+ };
18
+ export declare function TableBody<T extends Record<string, any>>({ data, dataKey, columns, gridStyle, striped, selectedRows, hasSelection, viewMode, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, }: Props<T>): ReactNode;
19
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../table.classes';
3
+ import styles from '../Table.module.css';
4
+ import { TableRow } from './TableRow';
5
+ export function TableBody({ data, dataKey, columns, gridStyle, striped, selectedRows, hasSelection, viewMode, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, }) {
6
+ return (_jsx("div", { className: cx(styles.body, striped && styles.striped), role: "rowgroup", children: data.map(row => {
7
+ const rowId = row[dataKey];
8
+ return (_jsx(TableRow, { row: row, rowId: rowId, columns: columns, gridStyle: gridStyle, selectedRows: selectedRows, hasSelection: hasSelection, viewMode: viewMode, getRowSeverity: getRowSeverity, onRowClick: onRowClick, onRowMouseEnter: onRowMouseEnter, onRowSelect: onRowSelect }, `gridRow-${rowId}`));
9
+ }) }));
10
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ type Props = {
3
+ align?: 'left' | 'right' | 'center';
4
+ divider?: 'left' | 'right';
5
+ allowWrap?: boolean;
6
+ children: ReactNode;
7
+ };
8
+ export declare function TableCell({ align, divider, allowWrap, children }: Props): ReactNode;
9
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../table.classes';
3
+ import styles from '../Table.module.css';
4
+ export function TableCell({ align = 'left', divider, allowWrap, children }) {
5
+ const dividerClass = divider === 'left' ? styles.dividerLeft : divider === 'right' ? styles.dividerRight : '';
6
+ return (_jsx("div", { className: cx(styles.cell, allowWrap ? styles.allowWrap : styles.nowrap, dividerClass), role: "cell", "data-align": align, "data-divider": divider, children: _jsx("div", { className: styles.cellContent, children: allowWrap ? children : _jsx("div", { className: styles.cellValueEllipsis, children: children }) }) }));
7
+ }
@@ -0,0 +1,16 @@
1
+ import React, { type CSSProperties, type ReactNode } from 'react';
2
+ import type { ColumnItem, HeaderExtrasArgs, SortDirection } from '../Table.types';
3
+ type Props<T> = {
4
+ columns: ColumnItem<T>[];
5
+ gridStyle: CSSProperties;
6
+ hasSelection: boolean;
7
+ selectionMode: 'single' | 'multiple';
8
+ allRowsSelected?: boolean;
9
+ onSelectAllRows?: (isSelected: boolean) => void;
10
+ sortById?: string;
11
+ sortDirection?: SortDirection;
12
+ onSortChange?: (column: ColumnItem<T>, direction: SortDirection) => void;
13
+ headerExtras?: (args: HeaderExtrasArgs<T>) => ReactNode;
14
+ };
15
+ export declare function TableHeader<T>({ columns, gridStyle, hasSelection, selectionMode, allRowsSelected, onSelectAllRows, sortById, sortDirection, onSortChange, headerExtras, }: Props<T>): React.ReactNode;
16
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TableHeaderCell } from './TableHeaderCell';
3
+ import { TableSelectionCell } from './TableSelectionCell';
4
+ import styles from '../Table.module.css';
5
+ export function TableHeader({ columns, gridStyle, hasSelection, selectionMode, allRowsSelected, onSelectAllRows, sortById, sortDirection, onSortChange, headerExtras, }) {
6
+ return (_jsxs("div", { className: styles.headerRow, style: gridStyle, role: "row", children: [hasSelection ? (_jsx(TableSelectionCell, { isHeader: true, multiple: selectionMode === 'multiple', checked: allRowsSelected, onToggle: checked => onSelectAllRows === null || onSelectAllRows === void 0 ? void 0 : onSelectAllRows(checked) })) : null, columns.map((column, index) => (_jsx(TableHeaderCell, { column: column, index: index, sortById: sortById, sortDirection: sortDirection, onSortChange: onSortChange, extraContent: headerExtras === null || headerExtras === void 0 ? void 0 : headerExtras({ column, index }) }, column.id)))] }));
7
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ColumnItem, SortDirection } from '../Table.types';
3
+ type Props<T> = {
4
+ column: ColumnItem<T>;
5
+ index: number;
6
+ sortById?: string;
7
+ sortDirection?: SortDirection;
8
+ onSortChange?: (column: ColumnItem<T>, direction: SortDirection) => void;
9
+ extraContent?: ReactNode;
10
+ };
11
+ export declare function TableHeaderCell<T>({ column, index, sortById, sortDirection, onSortChange, extraContent, }: Props<T>): ReactNode;
12
+ export {};
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowDown, ArrowUp } from 'lucide-react';
3
+ import { cx, getAlignValue, getDividerClass, getHeaderAlignClasses } from '../table.classes';
4
+ import styles from '../Table.module.css';
5
+ import { getAriaSort, getHeaderLabel, getNextSortDirection, isActiveSort, shouldToggleOnKey, } from '../table.utils';
6
+ export function TableHeaderCell({ column, index, sortById, sortDirection, onSortChange, extraContent, }) {
7
+ const active = isActiveSort(sortById, column.id);
8
+ const ariaSort = getAriaSort(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
9
+ const align = getAlignValue(column);
10
+ const dividerClass = getDividerClass(column);
11
+ const alignClasses = getHeaderAlignClasses(align);
12
+ const handleToggleSort = () => {
13
+ if (!column.sortable || !onSortChange)
14
+ return;
15
+ const nextDirection = getNextSortDirection(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
16
+ onSortChange(column, nextDirection);
17
+ };
18
+ return (_jsxs("div", { className: cx(styles.headerCell, dividerClass), role: "columnheader", "aria-sort": ariaSort, "data-align": align, "data-divider": column.divider, "data-column-index": index, children: [_jsx("div", { className: alignClasses.inner, children: _jsx("div", { className: alignClasses.main, children: column.sortable ? (_jsxs("button", { type: "button", className: alignClasses.button, onClick: handleToggleSort, onKeyDown: e => {
19
+ if (!shouldToggleOnKey(e.key))
20
+ return;
21
+ e.preventDefault();
22
+ handleToggleSort();
23
+ }, children: [_jsx("span", { className: alignClasses.label, 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: alignClasses.label, children: getHeaderLabel(column.header) })) }) }), extraContent != null ? _jsx("div", { className: styles.thOverlayExtras, children: extraContent }) : null] }, column.id));
24
+ }
@@ -0,0 +1,10 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+ import type { ColumnItem } from '../Table.types';
3
+ type Props<T> = {
4
+ rows: number;
5
+ columns: ColumnItem<T>[];
6
+ hasSelection: boolean;
7
+ gridStyle: CSSProperties;
8
+ };
9
+ export declare function TableLoadingBody<T>({ rows, columns, hasSelection, gridStyle, }: Props<T>): ReactNode;
10
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SkeletonLoaderItem } from '../../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
3
+ import { cx } from '../table.classes';
4
+ import styles from '../Table.module.css';
5
+ export function TableLoadingBody({ rows, columns, hasSelection, gridStyle, }) {
6
+ return (_jsx("div", { className: styles.body, role: "rowgroup", children: Array.from({ length: rows }).map((_, rowIndex) => (_jsxs("div", { className: styles.row, style: gridStyle, role: "row", children: [hasSelection ? (_jsx("div", { className: cx(styles.cell, styles.selectionCell), role: "cell" })) : null, columns.map(column => {
7
+ var _a;
8
+ return (_jsx("div", { className: cx(styles.cell, column.divider === 'left' && styles.dividerLeft, column.divider === 'right' && styles.dividerRight), role: "cell", "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', "data-divider": column.divider, children: _jsx("div", { className: styles.cellContent, children: _jsx("div", { className: styles.cellValueEllipsis, children: _jsx(SkeletonLoaderItem, { height: 20, width: "100%" }) }) }) }, column.id));
9
+ })] }, `loading-row-${rowIndex}`))) }));
10
+ }
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,19 @@
1
+ import type { CSSProperties } from 'react';
2
+ import React from 'react';
3
+ import type { ViewMode } from '../../../hooks/useTableSettings';
4
+ import type { ColumnItem } from '../Table.types';
5
+ type Props<T extends Record<string, any>> = {
6
+ row: T;
7
+ rowId: string | number;
8
+ columns: ColumnItem<T>[];
9
+ gridStyle: CSSProperties;
10
+ selectedRows?: Set<number | string>;
11
+ hasSelection: boolean;
12
+ viewMode?: ViewMode;
13
+ getRowSeverity?: (row: T) => any;
14
+ onRowClick?: (row: T) => void;
15
+ onRowMouseEnter?: (row: T) => void;
16
+ onRowSelect?: (rowId: number | string, isSelected: boolean) => void;
17
+ };
18
+ export declare function TableRow<T extends Record<string, any>>({ row, rowId, columns, gridStyle, selectedRows, hasSelection, viewMode, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, }: Props<T>): React.ReactNode;
19
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Checkbox } from '../../../components/forms/checkbox/Checkbox';
3
+ import { SeverityBgColor } from '../../../constants/severity';
4
+ import { useTableRowInteractions } from '../hooks/useTableRowInteractions';
5
+ import { cx } from '../table.classes';
6
+ import styles from '../Table.module.css';
7
+ import { getCellDisplayValue, shouldAllowWrap } from '../table.utils';
8
+ export function TableRow({ row, rowId, columns, gridStyle, selectedRows, hasSelection, viewMode, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, }) {
9
+ var _a;
10
+ const isSelected = (_a = selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId)) !== null && _a !== void 0 ? _a : false;
11
+ const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
12
+ const canSelect = Boolean(selectedRows && onRowSelect);
13
+ const { handleRowClick, handleRowKeyDown } = useTableRowInteractions({
14
+ row,
15
+ rowId,
16
+ isSelected,
17
+ canSelect,
18
+ onRowClick,
19
+ onRowSelect,
20
+ });
21
+ return (_jsxs("div", { className: cx(styles.row, onRowClick && styles.clickableRow, isSelected && styles.selectedRow, rowSeverity && styles.severity), style: {
22
+ ...gridStyle,
23
+ ['--row-severity-color']: rowSeverity
24
+ ? SeverityBgColor[rowSeverity]
25
+ : undefined,
26
+ }, role: "row", tabIndex: onRowClick ? 0 : -1, onKeyDown: handleRowKeyDown, onMouseEnter: () => onRowMouseEnter === null || onRowMouseEnter === void 0 ? void 0 : onRowMouseEnter(row), onClick: handleRowClick, children: [hasSelection ? (_jsx("div", { className: cx(styles.cell, styles.selectionCell), role: "cell", onClick: e => {
27
+ e.stopPropagation();
28
+ onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, !isSelected);
29
+ }, children: _jsx("div", { className: styles.selectionHitArea, children: _jsx(Checkbox, { variant: "primary", checked: isSelected, size: "sm", onChange: (checked, e) => {
30
+ e.stopPropagation();
31
+ onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, checked);
32
+ } }) }) })) : null, columns.map(column => {
33
+ var _a;
34
+ const allowWrap = shouldAllowWrap(column.allowWrap, isSelected, viewMode);
35
+ const cellValue = getCellDisplayValue(row, column);
36
+ return (_jsx("div", { className: cx(styles.cell, allowWrap ? styles.allowWrap : styles.nowrap, column.divider === 'left' && styles.dividerLeft, column.divider === 'right' && styles.dividerRight), 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));
37
+ })] }));
38
+ }
@@ -0,0 +1,9 @@
1
+ import React, { type JSX } from 'react';
2
+ type Props = {
3
+ checked?: boolean;
4
+ isHeader?: boolean;
5
+ multiple?: boolean;
6
+ onToggle: (checked: boolean, e: React.MouseEvent | React.KeyboardEvent | any) => void;
7
+ };
8
+ export declare function TableSelectionCell({ checked, isHeader, multiple, onToggle }: Props): JSX.Element;
9
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Checkbox } from '../../../components/forms/checkbox/Checkbox';
3
+ import { cx } from '../table.classes';
4
+ import styles from '../Table.module.css';
5
+ export function TableSelectionCell({ checked, isHeader, multiple, onToggle }) {
6
+ return (_jsx("div", { className: cx(styles.headerCell, styles.selectionCell), role: isHeader ? 'columnheader' : 'cell', onClick: e => {
7
+ if (isHeader && !multiple)
8
+ return;
9
+ e.stopPropagation();
10
+ onToggle(!checked, e);
11
+ }, children: isHeader && !multiple ? null : (_jsx("div", { className: styles.selectionHitArea, children: _jsx(Checkbox, { size: "sm", variant: "primary", checked: checked, onChange: (nextChecked, e) => {
12
+ e.stopPropagation();
13
+ onToggle(nextChecked, e);
14
+ } }) })) }));
15
+ }
@@ -0,0 +1,15 @@
1
+ import type { KeyboardEvent, MouseEvent } from 'react';
2
+ type UseTableRowInteractionsArgs<T> = {
3
+ row: T;
4
+ rowId: string | number;
5
+ isSelected: boolean;
6
+ canSelect: boolean;
7
+ onRowClick?: (row: T) => void;
8
+ onRowSelect?: (rowId: string | number, isSelected: boolean) => void;
9
+ };
10
+ type UseTableRowInteractionsReturn = {
11
+ handleRowClick: (e: MouseEvent<HTMLDivElement>) => void;
12
+ handleRowKeyDown: (e: KeyboardEvent<HTMLDivElement>) => void;
13
+ };
14
+ export declare function useTableRowInteractions<T>({ row, rowId, isSelected, canSelect, onRowClick, onRowSelect, }: UseTableRowInteractionsArgs<T>): UseTableRowInteractionsReturn;
15
+ export {};
@@ -0,0 +1,25 @@
1
+ import { useCallback } from 'react';
2
+ import { isModifierClick, shouldToggleOnKey } from '../table.utils';
3
+ export function useTableRowInteractions({ row, rowId, isSelected, canSelect, onRowClick, onRowSelect, }) {
4
+ const handleRowClick = useCallback((e) => {
5
+ if (isModifierClick(e) && canSelect) {
6
+ e.preventDefault();
7
+ e.stopPropagation();
8
+ onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, !isSelected);
9
+ return;
10
+ }
11
+ onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row);
12
+ }, [canSelect, isSelected, onRowClick, onRowSelect, row, rowId]);
13
+ const handleRowKeyDown = useCallback((e) => {
14
+ if (!onRowClick)
15
+ return;
16
+ if (!shouldToggleOnKey(e.key))
17
+ return;
18
+ e.preventDefault();
19
+ onRowClick(row);
20
+ }, [onRowClick, row]);
21
+ return {
22
+ handleRowClick,
23
+ handleRowKeyDown,
24
+ };
25
+ }
@@ -0,0 +1,10 @@
1
+ import type { ColumnItem } from './Table.types';
2
+ export declare function cx(...values: Array<string | false | null | undefined>): string;
3
+ export declare function getDividerClass<T>(column: ColumnItem<T>): string;
4
+ export declare function getAlignValue<T>(column: ColumnItem<T>): 'left' | 'right' | 'center';
5
+ export declare function getHeaderAlignClasses(align: 'left' | 'right' | 'center'): {
6
+ inner: string;
7
+ main: string;
8
+ button: string;
9
+ label: string;
10
+ };
@@ -0,0 +1,23 @@
1
+ import styles from './Table.module.css';
2
+ export function cx(...values) {
3
+ return values.filter(Boolean).join(' ');
4
+ }
5
+ export function getDividerClass(column) {
6
+ if (column.divider === 'left')
7
+ return styles.dividerLeft;
8
+ if (column.divider === 'right')
9
+ return styles.dividerRight;
10
+ return '';
11
+ }
12
+ export function getAlignValue(column) {
13
+ var _a;
14
+ return (_a = column.align) !== null && _a !== void 0 ? _a : 'left';
15
+ }
16
+ export function getHeaderAlignClasses(align) {
17
+ return {
18
+ inner: cx(styles.thInner, align === 'right' && styles.thInnerRight, align === 'center' && styles.thInnerCenter),
19
+ main: cx(styles.thMain, align === 'right' && styles.thMainRight, align === 'center' && styles.thMainCenter),
20
+ button: cx(styles.thButton, align === 'right' && styles.thButtonRight, align === 'center' && styles.thButtonCenter),
21
+ label: cx(styles.thLabel, align === 'right' && styles.thLabelRight, align === 'center' && styles.thLabelCenter),
22
+ };
23
+ }
@@ -1,8 +1,8 @@
1
- import type { CSSProperties, ReactNode } from 'react';
2
- import type { ColumnItem } from './Table';
1
+ import type { ReactNode } from 'react';
2
+ import type { ColumnItem } from './Table.types';
3
+ export declare const SELECTION_COLUMN_PX: 40;
3
4
  export type SortDirection = 'asc' | 'desc' | null;
4
5
  export declare function getVisibleColumns<T>(columns: Array<ColumnItem<T>>): Array<ColumnItem<T>>;
5
- export declare function getColumnStyle(columnId: string, columnStyles: Partial<Record<string, CSSProperties>> | undefined, alignment?: 'left' | 'right' | 'center', verticalAlignment?: 'top' | 'middle' | 'bottom', divider?: boolean): CSSProperties;
6
6
  export declare function getHeaderLabel(header: string | (() => ReactNode)): ReactNode;
7
7
  export declare function isActiveSort(sortById: string | undefined, columnId: string): boolean;
8
8
  export declare function getAriaSort(sortable: boolean | undefined, active: boolean, direction: SortDirection): 'ascending' | 'descending' | 'none';
@@ -14,4 +14,7 @@ export declare function isModifierClick(e: {
14
14
  }): boolean;
15
15
  export declare function shouldAllowWrap(columnAllowWrap: boolean | undefined, isRowSelected: boolean, viewMode?: 'wrapped' | string): boolean;
16
16
  export declare function getCellDisplayValue<T extends Record<string, any>>(row: T, column: ColumnItem<T>): ReactNode;
17
- export declare function getRowKey(rowId: string | number): string;
17
+ export declare function buildDefaultGridTemplate(args: {
18
+ hasSelection: boolean;
19
+ colCount: number;
20
+ }): string;
@@ -1,15 +1,6 @@
1
+ export const SELECTION_COLUMN_PX = 40;
1
2
  export function getVisibleColumns(columns) {
2
- return columns.filter(c => !c.hidden);
3
- }
4
- export function getColumnStyle(columnId, columnStyles, alignment, verticalAlignment, divider) {
5
- const baseStyle = columnStyles === null || columnStyles === void 0 ? void 0 : columnStyles[columnId];
6
- return {
7
- ...(baseStyle !== null && baseStyle !== void 0 ? baseStyle : {}),
8
- ...(alignment === 'right' ? { fontVariantNumeric: 'tabular-nums' } : null),
9
- verticalAlign: verticalAlignment !== null && verticalAlignment !== void 0 ? verticalAlignment : 'top',
10
- textAlign: alignment !== null && alignment !== void 0 ? alignment : 'left',
11
- ...(divider ? { borderLeft: '1px solid var(--color-border-subtle)' } : null),
12
- };
3
+ return columns.filter(column => !column.hidden);
13
4
  }
14
5
  export function getHeaderLabel(header) {
15
6
  return typeof header === 'function' ? header() : header;
@@ -18,9 +9,7 @@ export function isActiveSort(sortById, columnId) {
18
9
  return sortById === columnId;
19
10
  }
20
11
  export function getAriaSort(sortable, active, direction) {
21
- if (!sortable)
22
- return 'none';
23
- if (!active)
12
+ if (!sortable || !active || !direction)
24
13
  return 'none';
25
14
  return direction === 'asc' ? 'ascending' : 'descending';
26
15
  }
@@ -47,14 +36,19 @@ export function getCellDisplayValue(row, column) {
47
36
  const empty = (_a = column.emptyPlaceholder) !== null && _a !== void 0 ? _a : '';
48
37
  if (column.render) {
49
38
  const rendered = column.render(row);
50
- return rendered || empty;
39
+ return rendered !== null && rendered !== void 0 ? rendered : empty;
51
40
  }
52
41
  if (column.accessor) {
53
42
  const value = row[column.accessor];
54
- return value || empty;
43
+ return value !== null && value !== void 0 ? value : empty;
55
44
  }
56
- return null;
57
- }
58
- export function getRowKey(rowId) {
59
- return `tableRow-${String(rowId)}`;
45
+ return empty;
46
+ }
47
+ export function buildDefaultGridTemplate(args) {
48
+ const parts = [];
49
+ if (args.hasSelection)
50
+ parts.push(`${SELECTION_COLUMN_PX}px`);
51
+ for (let i = 0; i < args.colCount; i++)
52
+ parts.push('minmax(120px, 1fr)');
53
+ return parts.join(' ');
60
54
  }
@@ -1,5 +1,5 @@
1
1
  import type { ColumnDef, ColumnSizingState, SortingState, VisibilityState } from '@tanstack/react-table';
2
- import type { ColumnItem } from './Table';
2
+ import { ColumnItem } from './Table.types';
3
3
  type AnyRecord = Record<string, any>;
4
4
  export declare function getColumnId<T>(def: ColumnDef<T, any>, index: number): string;
5
5
  export declare function buildColumnVisibilityFromVisibleIds<T>(defs: ReadonlyArray<ColumnDef<T, any>>, visibleColumnIds?: string[]): VisibilityState;
@@ -14,7 +14,6 @@ export declare function buildDistributedGridTemplateColumns(args: {
14
14
  table: any;
15
15
  allowedIds: Set<string>;
16
16
  hasSelection: boolean;
17
- selectionPx: number;
18
17
  defaultMinPx: number;
19
18
  columnSizing: ColumnSizingState;
20
19
  }): string;
@@ -1,3 +1,4 @@
1
+ import { SELECTION_COLUMN_PX } from './table.utils';
1
2
  export function getColumnId(def, index) {
2
3
  const d = def;
3
4
  if (d.id != null && String(d.id).length > 0)
@@ -92,11 +93,11 @@ function clamp(value, min, max) {
92
93
  }
93
94
  export function buildDistributedGridTemplateColumns(args) {
94
95
  var _a, _b, _c, _d;
95
- const { table, allowedIds, hasSelection, selectionPx, defaultMinPx, columnSizing } = args;
96
+ const { table, allowedIds, hasSelection, defaultMinPx, columnSizing } = args;
96
97
  const parts = [];
97
98
  const leaf = table.getVisibleLeafColumns().filter((c) => allowedIds.has(c.id));
98
99
  if (hasSelection)
99
- parts.push(`${selectionPx}px`);
100
+ parts.push(`${SELECTION_COLUMN_PX}px`);
100
101
  for (const c of leaf) {
101
102
  const def = c.columnDef;
102
103
  const meta = ((_b = ((_a = def.meta) !== null && _a !== void 0 ? _a : {})) !== null && _b !== void 0 ? _b : {});
@@ -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.27",
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 {};