@dbcdk/react-components 0.0.26 → 0.0.28

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 (41) 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/headline/Headline.module.css +15 -44
  6. package/dist/components/nav-bar/NavBar.module.css +1 -1
  7. package/dist/components/panel/Panel.module.css +2 -2
  8. package/dist/components/table/Table.d.ts +3 -64
  9. package/dist/components/table/Table.js +20 -150
  10. package/dist/components/table/Table.module.css +38 -6
  11. package/dist/components/table/Table.types.d.ts +58 -0
  12. package/dist/components/table/Table.types.js +1 -0
  13. package/dist/components/table/TanstackTable.d.ts +1 -1
  14. package/dist/components/table/TanstackTable.js +0 -1
  15. package/dist/components/table/components/TableBody.d.ts +19 -0
  16. package/dist/components/table/components/TableBody.js +10 -0
  17. package/dist/components/table/components/TableCell.d.ts +9 -0
  18. package/dist/components/table/components/TableCell.js +7 -0
  19. package/dist/components/table/components/TableHeader.d.ts +16 -0
  20. package/dist/components/table/components/TableHeader.js +7 -0
  21. package/dist/components/table/components/TableHeaderCell.d.ts +12 -0
  22. package/dist/components/table/components/TableHeaderCell.js +24 -0
  23. package/dist/components/table/components/TableLoadingBody.d.ts +10 -0
  24. package/dist/components/table/components/TableLoadingBody.js +10 -0
  25. package/dist/components/table/components/TablePagination.d.ts +0 -0
  26. package/dist/components/table/components/TablePagination.js +1 -0
  27. package/dist/components/table/components/TableRow.d.ts +19 -0
  28. package/dist/components/table/components/TableRow.js +38 -0
  29. package/dist/components/table/components/TableSelectionCell.d.ts +9 -0
  30. package/dist/components/table/components/TableSelectionCell.js +15 -0
  31. package/dist/components/table/hooks/useTableRowInteractions.d.ts +15 -0
  32. package/dist/components/table/hooks/useTableRowInteractions.js +25 -0
  33. package/dist/components/table/table.classes.d.ts +10 -0
  34. package/dist/components/table/table.classes.js +23 -0
  35. package/dist/components/table/table.utils.d.ts +7 -4
  36. package/dist/components/table/table.utils.js +14 -20
  37. package/dist/components/table/tanstackTable.utils.d.ts +1 -2
  38. package/dist/components/table/tanstackTable.utils.js +3 -2
  39. package/dist/utils/date/formatDate.d.ts +1 -1
  40. package/dist/utils/date/formatDate.js +2 -2
  41. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import type { JSX } from 'react';
2
- import { ColumnItem } from '../../../components/table/Table';
2
+ import { ColumnItem } from '../../../components/table/Table.types';
3
3
  type HarvestRunRow = {
4
4
  runId: string;
5
5
  source: string;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Chip } from '../../../components/chip/Chip';
2
+ import { Circle } from '../../../components/circle/Circle';
3
3
  import { Table } from '../../../components/table/Table';
4
4
  const sources = ['onix.vendor-x', 'onix.vendor-y', 'cover.provider-z'];
5
5
  const statuses = ['Completed', 'Running', 'Failed'];
@@ -40,10 +40,16 @@ export const STORY_TABLE_COLUMNS = [
40
40
  header: 'Status',
41
41
  accessor: 'status',
42
42
  sortable: true,
43
- render: (row) => (_jsxs("span", { className: "dbc-flex dbc-flex-row dbc-flex-wrap dbc-gap-xs", children: [row.status === 'Completed' && (_jsx(Chip, { size: "sm", severity: "success", children: "Fuldf\u00F8rt" })), row.status === 'Running' && (_jsx(Chip, { size: "sm", severity: "info", children: "K\u00F8rer" })), row.status === 'Failed' && (_jsx(Chip, { size: "sm", severity: "error", children: "Fejlet" }))] })),
43
+ render: (row) => (_jsxs("span", { className: "dbc-flex dbc-flex-row dbc-flex-wrap dbc-gap-xs", children: [row.status === 'Completed' && (_jsx(Circle, { size: "xs", severity: "success", children: "Fuldf\u00F8rt" })), row.status === 'Running' && (_jsx(Circle, { pulse: true, size: "xs", severity: "info", children: "K\u00F8rer" })), row.status === 'Failed' && (_jsx(Circle, { size: "xs", severity: "error", children: "Fejlet" }))] })),
44
44
  },
45
- { id: 'updated', header: 'Opdateret', accessor: 'updated', sortable: true },
46
- { id: 'warnings', header: 'Advarsler', accessor: 'warnings', sortable: true },
47
- { id: 'failed', header: 'Fejlet', accessor: 'failed', sortable: true },
45
+ {
46
+ id: 'updated',
47
+ header: 'Opdateret',
48
+ accessor: 'updated',
49
+ sortable: true,
50
+ align: 'right',
51
+ },
52
+ { id: 'warnings', header: 'Advarsler', accessor: 'warnings', sortable: true, align: 'right' },
53
+ { id: 'failed', header: 'Fejlet', accessor: 'failed', sortable: true, align: 'right' },
48
54
  ];
49
55
  export const SampleTable = () => (_jsx(Table, { fillViewport: true, data: STORY_TABLE_DATA(), columns: STORY_TABLE_COLUMNS, dataKey: "runId", variant: "embedded" }));
@@ -10,9 +10,9 @@
10
10
 
11
11
  /* Size variables (consumed by AccordionRow) */
12
12
  .sm {
13
- --acc-trigger-py: var(--spacing-sm);
14
- --acc-trigger-px: var(--spacing-md);
15
- --acc-content-py: var(--spacing-sm);
13
+ --acc-trigger-py: var(--spacing-xs);
14
+ --acc-trigger-px: var(--spacing-sm);
15
+ --acc-content-py: var(--spacing-xs);
16
16
  }
17
17
 
18
18
  .md {
@@ -34,7 +34,6 @@
34
34
  min-width: 0;
35
35
  flex: 1 1 auto;
36
36
  overflow: hidden;
37
- justify-content: space-between;
38
37
  }
39
38
 
40
39
  .icon {
@@ -43,6 +42,11 @@
43
42
  align-items: center;
44
43
  }
45
44
 
45
+ .icon svg {
46
+ height: var(--icon-size-sm);
47
+ width: var(--icon-size-sm);
48
+ }
49
+
46
50
  .chevron {
47
51
  width: var(--icon-size-md);
48
52
  height: var(--icon-size-md);
@@ -6,19 +6,25 @@
6
6
  max-width: 100%;
7
7
  }
8
8
 
9
- /* Base headline: inherit colour from parent surface */
9
+ /* Base headline */
10
10
  .headline {
11
11
  position: relative;
12
12
  display: inline-flex;
13
13
  align-items: center;
14
14
  gap: var(--spacing-xs);
15
- font-weight: var(--font-weight, var(--font-weight-bold));
15
+
16
+ /* Typography */
17
+ font-weight: var(--font-weight-medium, 500);
16
18
  letter-spacing: var(--letter-spacing-tight);
17
- color: inherit;
18
19
  line-height: var(--line-height-tight);
20
+ color: inherit;
21
+
22
+ /* Behaviour */
19
23
  transition: color var(--transition-fast) var(--ease-standard);
24
+ min-width: 0; /* required for truncation inside flex */
20
25
  }
21
26
 
27
+ /* Tone overrides */
22
28
  .tone-dark .headline {
23
29
  color: var(--color-fg-default);
24
30
  }
@@ -31,6 +37,7 @@
31
37
  margin: 0;
32
38
  }
33
39
 
40
+ /* Marker variant */
34
41
  .headline.marker {
35
42
  padding-inline-start: calc(var(--border-width-thick) + var(--spacing-sm));
36
43
  }
@@ -48,8 +55,7 @@
48
55
  pointer-events: none;
49
56
  }
50
57
 
51
- /* Subheadline inherits colour but is slightly softened,
52
- so it works on both light and dark text surfaces. */
58
+ /* Subheadline */
53
59
  .subHeadline {
54
60
  color: inherit;
55
61
  opacity: 0.85;
@@ -58,55 +64,20 @@
58
64
  line-height: var(--line-height-normal);
59
65
  }
60
66
 
61
- .headline {
62
- position: relative;
63
- display: inline-flex;
64
- align-items: center;
65
- gap: var(--spacing-xs);
66
- font-weight: var(--font-weight, var(--font-weight-bold));
67
- letter-spacing: var(--letter-spacing-tight);
68
- color: inherit;
69
- line-height: var(--line-height-tight);
70
- transition: color var(--transition-fast) var(--ease-standard);
71
-
72
- /* Needed so truncation works inside flex parents */
73
- min-width: 0;
74
- }
75
-
76
- /* Truncated variant (single line with ellipsis) */
77
- .truncate {
78
- white-space: nowrap;
79
- overflow: hidden;
80
- text-overflow: ellipsis;
81
- }
82
-
83
- .headline {
84
- position: relative;
85
- display: inline-flex;
86
- align-items: center;
87
- gap: var(--spacing-xs);
88
- font-weight: var(--font-weight, var(--font-weight-bold));
89
- letter-spacing: var(--letter-spacing-tight);
90
- color: inherit;
91
- line-height: var(--line-height-tight);
92
- transition: color var(--transition-fast) var(--ease-standard);
93
-
94
- /* helps inside flex parents */
95
- min-width: 0;
96
- }
97
-
67
+ /* Icon */
98
68
  .icon {
99
69
  flex: 0 0 auto;
100
70
  display: inline-flex;
101
71
  align-items: center;
102
72
  }
103
73
 
104
- /* text wrapper must be the shrinkable element */
74
+ /* Text wrapper (allows shrinking inside flex layouts) */
105
75
  .text {
106
- min-width: 0;
107
76
  flex: 1 1 auto;
77
+ min-width: 0;
108
78
  }
109
79
 
80
+ /* Text behaviour */
110
81
  .truncate {
111
82
  overflow: hidden;
112
83
  white-space: nowrap;
@@ -46,7 +46,7 @@
46
46
  font-size: var(--font-size-sm);
47
47
  text-decoration: none;
48
48
  border-radius: var(--border-radius-default);
49
- padding-block: calc(var(--spacing-xs) + var(--density));
49
+ padding-block: var(--spacing-xs);
50
50
  padding-inline: var(--spacing-sm);
51
51
  transition:
52
52
  background-color var(--transition-fast) var(--ease-standard),
@@ -1,5 +1,5 @@
1
1
  .container {
2
- border: 1px solid var(--color-border-strong);
2
+ border: var(--border-width-thin) solid var(--color-border-default);
3
3
  border-radius: var(--border-radius-default);
4
4
  background-color: var(--color-bg-surface);
5
5
  box-sizing: border-box;
@@ -8,7 +8,7 @@
8
8
  }
9
9
 
10
10
  .header {
11
- border-bottom: 1px solid var(--color-border-strong);
11
+ border-bottom: var(--border-width-thin) solid var(--color-border-default);
12
12
  }
13
13
 
14
14
  .content {
@@ -1,64 +1,3 @@
1
- import type { HTMLAttributes, JSX, ReactNode } from 'react';
2
- import { PageChangeEvent } from '../../components/pagination/Pagination';
3
- import { Severity } from '../../constants/severity.types';
4
- import { ViewMode } from '../../hooks/useTableSettings';
5
- import { TableEmptyConfig } from './components/empty-state/EmptyState';
6
- import { SortDirection } from './table.utils';
7
- export interface ColumnItem<T> {
8
- id: string;
9
- header: string | (() => ReactNode);
10
- accessor?: keyof T;
11
- sortable?: boolean;
12
- sortFunction?: (a: T, b: T) => -1 | 0 | 1;
13
- render?: (item: T) => ReactNode;
14
- hidden?: boolean;
15
- align?: 'left' | 'right' | 'center';
16
- verticalAlign?: 'top' | 'middle' | 'bottom';
17
- divider?: 'right' | 'left';
18
- allowWrap?: boolean;
19
- emptyPlaceholder?: ReactNode;
20
- canHide?: boolean;
21
- severity?: any;
22
- }
23
- type HeaderExtrasArgs<T> = {
24
- column: ColumnItem<T>;
25
- index: number;
26
- };
27
- export type TableVariant = 'primary' | 'embedded';
28
- export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTMLDivElement>, 'onClick' | 'onMouseEnter'> & {
29
- data: T[];
30
- dataKey: keyof T;
31
- columns: ColumnItem<T>[];
32
- selectedRows?: Set<number | string>;
33
- selectionMode?: 'single' | 'multiple';
34
- allRowsSelected?: boolean;
35
- onRowClick?: (row: T) => void;
36
- onRowMouseEnter?: (row: T) => void;
37
- onRowSelect?: (rowId: number | string, isSelected: boolean) => void;
38
- onSelectAllRows?: (isSelected: boolean) => void;
39
- onSortChange?: (column: ColumnItem<T>, direction: SortDirection) => void;
40
- sortById?: string;
41
- sortDirection?: SortDirection;
42
- loading?: boolean;
43
- headerExtras?: (args: HeaderExtrasArgs<T>) => ReactNode;
44
- gridTemplateColumns?: string;
45
- toolbar?: ReactNode;
46
- striped?: boolean;
47
- fillViewport?: boolean;
48
- viewportBottomOffset?: number;
49
- viewportMin?: number;
50
- viewportIncludeMarginTop?: boolean;
51
- take?: number;
52
- skip?: number;
53
- paginationPlacement?: 'top' | 'bottom';
54
- totalItemsCount?: number;
55
- onPageChange?: (e: PageChangeEvent) => void;
56
- variant?: TableVariant;
57
- size?: 'sm' | 'md';
58
- getRowSeverity?: (row: T) => Severity | undefined;
59
- showFirstLast?: boolean;
60
- viewMode?: ViewMode;
61
- emptyConfig?: TableEmptyConfig;
62
- };
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 {};
1
+ import type { JSX } from 'react';
2
+ import type { TableProps } from './Table.types';
3
+ export declare function Table<T extends Record<string, any>>({ data, dataKey, columns, selectedRows, selectionMode, allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant, size, viewMode, striped, fillViewport, gridTemplateColumns, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement, showFirstLast, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }: TableProps<T>): JSX.Element;
@@ -1,161 +1,31 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { ArrowDown, ArrowUp } from 'lucide-react';
4
3
  import { useCallback, useMemo } from 'react';
5
- import { Checkbox } from '../../components/forms/checkbox/Checkbox';
6
4
  import { Pagination } from '../../components/pagination/Pagination';
7
- import { SkeletonLoaderItem } from '../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
8
- import { SeverityBgColor } from '../../constants/severity';
9
5
  import { TableEmptyState } from './components/empty-state/EmptyState';
6
+ import { TableBody } from './components/TableBody';
7
+ import { TableHeader } from './components/TableHeader';
8
+ import { TableLoadingBody } from './components/TableLoadingBody';
9
+ import { cx } from './table.classes';
10
10
  import styles from './Table.module.css';
11
- import { getAriaSort, getCellDisplayValue, getHeaderLabel, getNextSortDirection, getVisibleColumns, isModifierClick, shouldAllowWrap, shouldToggleOnKey, isActiveSort, } from './table.utils';
12
- function buildDefaultGridTemplate(args) {
13
- const { hasSelection, colCount } = args;
14
- const parts = [];
15
- if (hasSelection)
16
- parts.push('34px');
17
- for (let i = 0; i < colCount; i++)
18
- parts.push('minmax(120px, 1fr)');
19
- return parts.join(' ');
20
- }
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
- void viewportBottomOffset;
23
- void viewportMin;
24
- void viewportIncludeMarginTop;
25
- const filteredColumns = useMemo(() => getVisibleColumns(columns), [columns]);
26
- const handlePageChange = useCallback((e) => onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e), [onPageChange]);
27
- const hasSelection = Boolean(selectedRows && onRowSelect && dataKey);
11
+ import { buildDefaultGridTemplate, getVisibleColumns } from './table.utils';
12
+ export function Table({ data, dataKey, columns, selectedRows, selectionMode = 'single', allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant = 'primary', size = 'md', viewMode, striped, fillViewport = false, gridTemplateColumns, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement = 'bottom', showFirstLast = false, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }) {
13
+ const visibleColumns = useMemo(() => getVisibleColumns(columns), [columns]);
14
+ const hasSelection = Boolean(selectedRows && onRowSelect);
28
15
  const template = useMemo(() => {
29
- return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({ hasSelection, colCount: filteredColumns.length }));
30
- }, [gridTemplateColumns, hasSelection, filteredColumns.length]);
16
+ return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({
17
+ hasSelection,
18
+ colCount: visibleColumns.length,
19
+ }));
20
+ }, [gridTemplateColumns, hasSelection, visibleColumns.length]);
31
21
  const gridStyle = useMemo(() => ({ ['--grid-template']: template }), [template]);
32
- const headerEl = (_jsxs("div", { className: styles.headerRow, style: gridStyle, role: "row", children: [hasSelection && (_jsx("div", { className: `${styles.headerCell} ${styles.selectionCell}`, role: "columnheader", children: selectionMode === 'multiple' ? (_jsx(Checkbox, { size: "sm", variant: "primary", checked: allRowsSelected, onChange: checked => onSelectAllRows === null || onSelectAllRows === void 0 ? void 0 : onSelectAllRows(checked) })) : null })), filteredColumns.map((column, index) => {
33
- var _a;
34
- const active = isActiveSort(sortById, column.id);
35
- const ariaSort = getAriaSort(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
36
- const align = (_a = column.align) !== null && _a !== void 0 ? _a : 'left';
37
- const extraContent = headerExtras === null || headerExtras === void 0 ? void 0 : headerExtras({ column, index });
38
- const toggleSort = () => {
39
- if (!onSortChange || !column.sortable)
40
- return;
41
- const nextDir = getNextSortDirection(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
42
- onSortChange(column, nextDir);
43
- };
44
- const dividerClass = column.divider === 'left'
45
- ? styles.dividerLeft
46
- : column.divider === 'right'
47
- ? styles.dividerRight
48
- : '';
49
- return (_jsxs("div", { className: [styles.headerCell, dividerClass].filter(Boolean).join(' '), role: "columnheader", "aria-sort": ariaSort, "data-align": align, "data-divider": column.divider, children: [_jsx("div", { className: [
50
- styles.thInner,
51
- align === 'right' ? styles.thInnerRight : '',
52
- align === 'center' ? styles.thInnerCenter : '',
53
- ]
54
- .filter(Boolean)
55
- .join(' '), children: _jsx("div", { className: [
56
- styles.thMain,
57
- align === 'right' ? styles.thMainRight : '',
58
- align === 'center' ? styles.thMainCenter : '',
59
- ]
60
- .filter(Boolean)
61
- .join(' '), children: column.sortable ? (_jsxs("button", { type: "button", className: [
62
- styles.thButton,
63
- align === 'right' ? styles.thButtonRight : '',
64
- align === 'center' ? styles.thButtonCenter : '',
65
- ]
66
- .filter(Boolean)
67
- .join(' '), onClick: toggleSort, onKeyDown: e => {
68
- if (shouldToggleOnKey(e.key)) {
69
- e.preventDefault();
70
- toggleSort();
71
- }
72
- }, children: [_jsx("span", { className: [
73
- styles.thLabel,
74
- align === 'right' ? styles.thLabelRight : '',
75
- align === 'center' ? styles.thLabelCenter : '',
76
- ]
77
- .filter(Boolean)
78
- .join(' '), 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: [
79
- styles.thLabel,
80
- align === 'right' ? styles.thLabelRight : '',
81
- align === 'center' ? styles.thLabelCenter : '',
82
- ]
83
- .filter(Boolean)
84
- .join(' '), children: getHeaderLabel(column.header) })) }) }), extraContent != null ? (_jsx("div", { className: styles.thOverlayExtras, children: extraContent })) : null] }, column.id));
85
- })] }));
86
- const loadingBodyEl = (_jsx("div", { className: styles.body, role: "rowgroup", children: Array.from({ length: take !== null && take !== void 0 ? take : 5 }).map((_, rowIndex) => (_jsxs("div", { className: styles.row, style: gridStyle, role: "row", children: [hasSelection ? (_jsx("div", { className: `${styles.cell} ${styles.selectionCell}`, role: "cell" })) : null, filteredColumns.map(column => {
87
- var _a;
88
- const dividerClass = column.divider === 'left'
89
- ? styles.dividerLeft
90
- : column.divider === 'right'
91
- ? styles.dividerRight
92
- : '';
93
- return (_jsx("div", { className: [styles.cell, dividerClass].filter(Boolean).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: _jsx("div", { className: styles.cellValueEllipsis, children: _jsx(SkeletonLoaderItem, { height: 20, width: "100%" }) }) }) }, column.id));
94
- })] }, `loading-row-${rowIndex}`))) }));
95
- const dataBodyEl = (_jsx("div", { className: `${styles.body} ${striped ? styles.striped : ''}`, role: "rowgroup", children: data.map(row => {
96
- const rowSeverity = getRowSeverity === null || getRowSeverity === void 0 ? void 0 : getRowSeverity(row);
97
- const rowId = row[dataKey];
98
- const isSelected = Boolean(selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(rowId));
99
- return (_jsxs("div", { className: [
100
- styles.row,
101
- onRowClick ? styles.clickableRow : '',
102
- isSelected ? styles.selectedRow : '',
103
- rowSeverity ? styles.severity : '',
104
- ]
105
- .filter(Boolean)
106
- .join(' '), style: {
107
- ...gridStyle,
108
- ['--row-severity-color']: rowSeverity
109
- ? SeverityBgColor[rowSeverity]
110
- : undefined,
111
- }, role: "row", tabIndex: onRowClick ? 0 : -1, onKeyDown: e => {
112
- if (!onRowClick)
113
- return;
114
- if (shouldToggleOnKey(e.key)) {
115
- e.preventDefault();
116
- onRowClick(row);
117
- }
118
- }, onMouseEnter: () => onRowMouseEnter === null || onRowMouseEnter === void 0 ? void 0 : onRowMouseEnter(row), onClick: e => {
119
- const canSelect = Boolean(selectedRows && onRowSelect && dataKey);
120
- if (isModifierClick(e) && canSelect) {
121
- e.preventDefault();
122
- e.stopPropagation();
123
- onRowSelect(rowId, !selectedRows.has(rowId));
124
- return;
125
- }
126
- onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row);
127
- }, children: [hasSelection && (_jsx("div", { className: `${styles.cell} ${styles.selectionCell}`, role: "cell", children: _jsx(Checkbox, { variant: "primary", checked: selectedRows.has(rowId), size: "sm", onChange: (checked, e) => {
128
- e.stopPropagation();
129
- onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, checked);
130
- } }) })), filteredColumns.map(column => {
131
- var _a;
132
- const dividerClass = column.divider === 'left'
133
- ? styles.dividerLeft
134
- : column.divider === 'right'
135
- ? styles.dividerRight
136
- : '';
137
- const allowWrap = shouldAllowWrap(column.allowWrap, isSelected, viewMode);
138
- const cellValue = getCellDisplayValue(row, column);
139
- return (_jsx("div", { className: [
140
- styles.cell,
141
- allowWrap ? styles.allowWrap : styles.nowrap,
142
- dividerClass,
143
- ]
144
- .filter(Boolean)
145
- .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-${rowId}`));
147
- }) }));
148
- const bodyContent = loading && !data.length ? loadingBodyEl : dataBodyEl;
149
- const tableClassName = [
150
- styles.tableRoot,
151
- styles[variant],
152
- styles[size],
153
- getRowSeverity ? styles.severityTable : '',
154
- ]
155
- .filter(Boolean)
156
- .join(' ');
157
- const tableShell = (_jsx("div", { ...rest, className: tableClassName, role: "table", "aria-rowcount": data.length, children: _jsxs("div", { className: styles.tableContent, children: [_jsx("div", { className: styles.header, role: "rowgroup", children: headerEl }), _jsx("div", { className: styles.bodyScroll, children: !data.length && !loading ? (_jsx("div", { className: styles.emptyStateSlot, children: _jsx(TableEmptyState, { config: emptyConfig }) })) : (bodyContent) })] }) }));
158
- const paginationEl = onPageChange && data.length > 0 ? (_jsx("div", { className: `${styles.paginationSlot} ${paginationPlacement === 'top' ? styles.paginationSlotTop : ''}`, children: _jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast }) })) : null;
22
+ const handlePageChange = useCallback((e) => {
23
+ onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e);
24
+ }, [onPageChange]);
25
+ const bodyContent = loading && !data.length ? (_jsx(TableLoadingBody, { rows: take !== null && take !== void 0 ? take : 5, columns: visibleColumns, hasSelection: hasSelection, gridStyle: gridStyle })) : (_jsx(TableBody, { data: data, dataKey: dataKey, columns: visibleColumns, gridStyle: gridStyle, striped: striped, selectedRows: selectedRows, hasSelection: hasSelection, viewMode: viewMode, getRowSeverity: getRowSeverity, onRowClick: onRowClick, onRowMouseEnter: onRowMouseEnter, onRowSelect: onRowSelect }));
26
+ const paginationEl = onPageChange && data.length > 0 ? (_jsx("div", { className: cx(styles.paginationSlot, paginationPlacement === 'top' && styles.paginationSlotTop), children: _jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast }) })) : null;
27
+ const tableClassName = cx(styles.tableRoot, styles[variant], styles[size], getRowSeverity && styles.severityTable);
28
+ const tableShell = (_jsx("div", { ...rest, className: tableClassName, role: "table", "aria-rowcount": data.length, children: _jsxs("div", { className: styles.tableContent, children: [_jsx("div", { className: styles.header, role: "rowgroup", children: _jsx(TableHeader, { columns: visibleColumns, gridStyle: gridStyle, hasSelection: hasSelection, selectionMode: selectionMode, allRowsSelected: allRowsSelected, onSelectAllRows: onSelectAllRows, sortById: sortById, sortDirection: sortDirection, onSortChange: onSortChange, headerExtras: headerExtras }) }), _jsx("div", { className: styles.bodyScroll, children: !data.length && !loading ? (_jsx("div", { className: styles.emptyStateSlot, children: _jsx(TableEmptyState, { config: emptyConfig }) })) : (bodyContent) })] }) }));
159
29
  if (fillViewport) {
160
30
  return (_jsxs("div", { className: styles.fillViewportRoot, style: {
161
31
  flexDirection: paginationPlacement === 'top' ? 'column-reverse' : 'column',
@@ -186,17 +186,42 @@
186
186
  text-align: center;
187
187
  }
188
188
 
189
- .selectionCell {
189
+ .headerCell.selectionCell,
190
+ .cell.selectionCell {
190
191
  overflow: visible !important;
191
192
  display: flex;
192
- align-items: flex-start;
193
- justify-content: flex-start;
193
+ align-items: center;
194
+ justify-content: center;
194
195
  min-width: 0;
196
+ padding-inline: 0;
197
+ padding-block: 0;
198
+ cursor: pointer;
199
+ }
200
+
201
+ .headerCell.selectionCell:hover button,
202
+ .cell.selectionCell:hover button {
203
+ border-color: var(--color-fg-default);
204
+ }
205
+
206
+ .headerCell.selectionCell:focus-within > button,
207
+ .cell.selectionCell:focus-within > button {
208
+ border-color: var(--color-fg-default);
195
209
  }
196
210
 
197
- .headerCell.selectionCell {
211
+ .selectionHitArea {
212
+ display: inline-flex;
198
213
  align-items: center;
214
+ justify-content: center;
215
+ inline-size: 100%;
216
+ block-size: 100%;
217
+ min-block-size: var(--component-size-md);
218
+ padding: 4px;
199
219
  }
220
+
221
+ .sm .selectionHitArea {
222
+ min-block-size: var(--component-size-sm);
223
+ }
224
+
200
225
  .clickableRow {
201
226
  cursor: pointer;
202
227
  }
@@ -362,11 +387,18 @@
362
387
  --row-rail-offset: calc(var(--row-rail-width) + var(--row-rail-gap));
363
388
  --row-rail-inset-block: 1px;
364
389
  --row-rail-radius: 0 2px 2px 0;
390
+ --selection-rail-compensation: calc(var(--row-rail-offset) / 2);
365
391
  }
366
392
 
367
- .severityTable .headerRow .selectionCell,
393
+ .severityTable .headerCell.selectionCell,
368
394
  .severityTable .row .selectionCell {
369
- padding-inline-start: var(--row-rail-offset) !important;
395
+ padding-inline-start: 0 !important;
396
+ }
397
+
398
+ .severityTable .headerCell.selectionCell .selectionHitArea,
399
+ .severityTable .row .selectionCell .selectionHitArea {
400
+ inline-size: calc(100% - var(--selection-rail-compensation));
401
+ margin-inline-start: var(--selection-rail-compensation);
370
402
  }
371
403
 
372
404
  .severityTable .row.severity {
@@ -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 : {});
@@ -5,7 +5,7 @@
5
5
  * Examples:
6
6
  * - formatDate(new Date(), { userFriendlyLabel: true }) => "I dag"
7
7
  * - formatDate(new Date(), { userFriendlyLabel: true, showTime: true }) => "I dag, kl. 14:30"
8
- * - formatDate(date, { showTime: true }) => "10/03/2026 14:30"
8
+ * - formatDate(date, { showTime: true }) => "10.03.2026 14:30"
9
9
  *
10
10
  * @param date - The Date or date-parsable value to format
11
11
  * @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
@@ -5,7 +5,7 @@
5
5
  * Examples:
6
6
  * - formatDate(new Date(), { userFriendlyLabel: true }) => "I dag"
7
7
  * - formatDate(new Date(), { userFriendlyLabel: true, showTime: true }) => "I dag, kl. 14:30"
8
- * - formatDate(date, { showTime: true }) => "10/03/2026 14:30"
8
+ * - formatDate(date, { showTime: true }) => "10.03.2026 14:30"
9
9
  *
10
10
  * @param date - The Date or date-parsable value to format
11
11
  * @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
@@ -44,7 +44,7 @@ export function formatDate(date, options = {}) {
44
44
  const day = pad(d.getDate());
45
45
  const month = pad(d.getMonth() + 1);
46
46
  const year = d.getFullYear();
47
- const base = `${day}/${month}/${year}`;
47
+ const base = `${day}.${month}.${year}`;
48
48
  if (!showTime)
49
49
  return base;
50
50
  return `${base} ${formatTime()}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",