@dbcdk/react-components 0.0.10 → 0.0.13
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.
- package/dist/components/accordion/Accordion.d.ts +2 -2
- package/dist/components/accordion/Accordion.js +34 -41
- package/dist/components/accordion/Accordion.module.css +13 -72
- package/dist/components/accordion/components/AccordionRow.d.ts +10 -0
- package/dist/components/accordion/components/AccordionRow.js +51 -0
- package/dist/components/accordion/components/AccordionRow.module.css +82 -0
- package/dist/components/breadcrumbs/Breadcrumbs.module.css +0 -1
- package/dist/components/button/Button.module.css +7 -7
- package/dist/components/card/Card.d.ts +15 -6
- package/dist/components/card/Card.js +39 -13
- package/dist/components/card/Card.module.css +22 -28
- package/dist/components/card/components/CardMeta.d.ts +15 -0
- package/dist/components/card/components/CardMeta.js +20 -0
- package/dist/components/card/components/CardMeta.module.css +51 -0
- package/dist/components/card-container/CardContainer.js +1 -1
- package/dist/components/card-container/CardContainer.module.css +3 -1
- package/dist/components/chip/Chip.module.css +7 -2
- package/dist/components/circle/Circle.d.ts +2 -1
- package/dist/components/circle/Circle.js +2 -2
- package/dist/components/circle/Circle.module.css +6 -2
- package/dist/components/code-block/CodeBlock.js +1 -1
- package/dist/components/code-block/CodeBlock.module.css +30 -17
- package/dist/components/copy-button/CopyButton.d.ts +1 -0
- package/dist/components/copy-button/CopyButton.js +10 -2
- package/dist/components/datetime-picker/DateTimePicker.d.ts +33 -8
- package/dist/components/datetime-picker/DateTimePicker.js +119 -78
- package/dist/components/datetime-picker/DateTimePicker.module.css +2 -0
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +15 -3
- package/dist/components/datetime-picker/dateTimeHelpers.js +137 -23
- package/dist/components/filter-field/FilterField.js +16 -11
- package/dist/components/filter-field/FilterField.module.css +137 -16
- package/dist/components/forms/checkbox/Checkbox.d.ts +2 -2
- package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
- package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
- package/dist/components/forms/form-select/FormSelect.d.ts +35 -0
- package/dist/components/forms/form-select/FormSelect.js +86 -0
- package/dist/components/forms/form-select/FormSelect.module.css +236 -0
- package/dist/components/forms/input/Input.d.ts +0 -3
- package/dist/components/forms/input/Input.js +1 -4
- package/dist/components/forms/input/Input.module.css +8 -7
- package/dist/components/forms/input-container/InputContainer.module.css +1 -1
- package/dist/components/forms/radio-buttons/RadioButtons.module.css +1 -0
- package/dist/components/forms/select/Select.js +55 -16
- package/dist/components/hyperlink/Hyperlink.d.ts +19 -7
- package/dist/components/hyperlink/Hyperlink.js +35 -11
- package/dist/components/hyperlink/Hyperlink.module.css +50 -2
- package/dist/components/interval-select/IntervalSelect.d.ts +9 -2
- package/dist/components/interval-select/IntervalSelect.js +21 -6
- package/dist/components/menu/Menu.d.ts +29 -0
- package/dist/components/menu/Menu.js +61 -16
- package/dist/components/menu/Menu.module.css +73 -5
- package/dist/components/overlay/modal/Modal.module.css +4 -3
- package/dist/components/overlay/modal/provider/ModalProvider.js +1 -3
- package/dist/components/overlay/side-panel/SidePanel.js +18 -1
- package/dist/components/overlay/side-panel/SidePanel.module.css +1 -3
- package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
- package/dist/components/page-layout/PageLayout.d.ts +16 -4
- package/dist/components/page-layout/PageLayout.js +57 -28
- package/dist/components/page-layout/PageLayout.module.css +153 -33
- package/dist/components/popover/Popover.d.ts +17 -4
- package/dist/components/popover/Popover.js +147 -65
- package/dist/components/popover/Popover.module.css +5 -0
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +22 -18
- package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
- package/dist/components/sidebar/providers/SidebarProvider.js +66 -18
- package/dist/components/split-button/SplitButton.d.ts +1 -1
- package/dist/components/split-button/SplitButton.js +3 -1
- package/dist/components/split-button/SplitButton.module.css +4 -4
- package/dist/components/split-pane/SplitPane.d.ts +10 -24
- package/dist/components/split-pane/SplitPane.js +83 -54
- package/dist/components/split-pane/SplitPane.module.css +11 -6
- package/dist/components/split-pane/provider/SplitPaneContext.js +5 -11
- package/dist/components/state-page/StatePage.module.css +1 -1
- package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +3 -8
- package/dist/components/sticky-footer-layout/StickyFooterLayout.js +57 -20
- package/dist/components/table/Table.d.ts +8 -8
- package/dist/components/table/Table.js +37 -79
- package/dist/components/table/Table.module.css +62 -46
- package/dist/components/table/{tanstack.d.ts → TanstackTable.d.ts} +7 -3
- package/dist/components/table/TanstackTable.js +84 -0
- package/dist/components/table/components/column-resizer/ColumnResizer.js +1 -1
- package/dist/components/table/components/column-resizer/ColumnResizer.module.css +17 -7
- package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
- package/dist/components/table/components/table-settings/TableSettings.js +55 -4
- package/dist/components/table/table.utils.d.ts +17 -0
- package/dist/components/table/table.utils.js +61 -0
- package/dist/components/table/tanstackTable.utils.d.ts +22 -0
- package/dist/components/table/tanstackTable.utils.js +104 -0
- package/dist/components/tabs/Tabs.d.ts +35 -12
- package/dist/components/tabs/Tabs.js +114 -26
- package/dist/components/tabs/Tabs.module.css +158 -71
- package/dist/hooks/useTableSettings.d.ts +23 -4
- package/dist/hooks/useTableSettings.js +64 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/src/styles/styles.css +38 -23
- package/dist/styles/animation.d.ts +5 -0
- package/dist/styles/animation.js +5 -0
- package/dist/styles/styles.css +38 -23
- package/dist/styles/themes/dbc/base.css +136 -0
- package/dist/styles/themes/dbc/dark.css +39 -202
- package/dist/styles/themes/dbc/light.css +17 -174
- package/dist/utils/localStorage.utils.d.ts +19 -0
- package/dist/utils/localStorage.utils.js +78 -0
- package/package.json +4 -4
- package/dist/components/table/tanstack.js +0 -162
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* Table.module.css (updated) */
|
|
2
|
+
|
|
1
3
|
/* =========================
|
|
2
4
|
Base table
|
|
3
5
|
========================= */
|
|
@@ -11,6 +13,7 @@
|
|
|
11
13
|
font-size: var(--font-size-sm);
|
|
12
14
|
color: var(--color-fg-default);
|
|
13
15
|
background: var(--color-bg-surface);
|
|
16
|
+
table-layout: auto;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
.tableScroll {
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
|
|
27
30
|
.table thead {
|
|
28
31
|
position: sticky;
|
|
29
|
-
|
|
32
|
+
top: 0;
|
|
30
33
|
z-index: 10;
|
|
31
34
|
background-color: var(--color-bg-surface);
|
|
32
35
|
}
|
|
@@ -37,19 +40,31 @@
|
|
|
37
40
|
|
|
38
41
|
.table .th {
|
|
39
42
|
position: relative;
|
|
43
|
+
|
|
40
44
|
padding-block: var(--spacing-xs);
|
|
41
45
|
padding-inline: var(--spacing-md);
|
|
42
|
-
|
|
46
|
+
padding-right: var(--spacing-lg);
|
|
47
|
+
|
|
48
|
+
text-align: left;
|
|
43
49
|
vertical-align: middle;
|
|
50
|
+
|
|
44
51
|
background: inherit;
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
/* Typography */
|
|
47
54
|
font-size: var(--font-size-xs);
|
|
48
|
-
font-weight: var(--font-weight-
|
|
55
|
+
font-weight: var(--font-weight-normal);
|
|
56
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
57
|
+
text-transform: uppercase;
|
|
49
58
|
|
|
59
|
+
color: var(--color-fg-subtle);
|
|
60
|
+
|
|
61
|
+
/* Truncation */
|
|
50
62
|
white-space: nowrap;
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
text-overflow: ellipsis;
|
|
65
|
+
|
|
66
|
+
/* Width control */
|
|
67
|
+
min-width: 0;
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
/* Small variant: header padding */
|
|
@@ -57,28 +72,56 @@
|
|
|
57
72
|
padding-inline: var(--spacing-md);
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
/*
|
|
61
|
-
.th.
|
|
75
|
+
/* Header layout */
|
|
76
|
+
.th > .thInner {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: var(--spacing-xxs);
|
|
80
|
+
inline-size: 100%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Actual interactive control for sorting */
|
|
84
|
+
.thButton {
|
|
85
|
+
all: unset;
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: var(--spacing-xxs);
|
|
89
|
+
inline-size: 100%;
|
|
90
|
+
|
|
62
91
|
cursor: pointer;
|
|
63
92
|
user-select: none;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
|
|
94
|
+
/* make it feel like a button */
|
|
95
|
+
border-radius: var(--border-radius-default);
|
|
96
|
+
padding-block: 2px;
|
|
97
|
+
padding-inline: 2px;
|
|
67
98
|
}
|
|
68
99
|
|
|
69
|
-
.
|
|
70
|
-
th.sortable:hover {
|
|
100
|
+
.thButton:hover {
|
|
71
101
|
background-color: var(--color-bg-contextual);
|
|
72
102
|
}
|
|
73
103
|
|
|
74
|
-
.
|
|
75
|
-
th.sortable:focus-visible {
|
|
104
|
+
.thButton:focus-visible {
|
|
76
105
|
outline: none;
|
|
77
106
|
box-shadow: var(--focus-ring);
|
|
78
107
|
}
|
|
79
108
|
|
|
109
|
+
.thLabel {
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
text-overflow: ellipsis;
|
|
112
|
+
white-space: nowrap;
|
|
113
|
+
flex-grow: 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.thExtras {
|
|
117
|
+
margin-inline-start: auto;
|
|
118
|
+
display: inline-flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
}
|
|
121
|
+
|
|
80
122
|
.inActiveSort {
|
|
81
123
|
color: var(--color-fg-subtle);
|
|
124
|
+
opacity: 0.4;
|
|
82
125
|
}
|
|
83
126
|
|
|
84
127
|
.sortIndicator {
|
|
@@ -90,17 +133,6 @@ th.sortable:focus-visible {
|
|
|
90
133
|
block-size: var(--icon-size-sm);
|
|
91
134
|
}
|
|
92
135
|
|
|
93
|
-
.th > .thInner {
|
|
94
|
-
display: inline-block;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.th > .thInner > span {
|
|
98
|
-
display: inline-flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
gap: var(--spacing-xxs);
|
|
101
|
-
inline-size: 100%;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
136
|
/* =========================
|
|
105
137
|
Body + cells
|
|
106
138
|
========================= */
|
|
@@ -137,37 +169,24 @@ th.sortable:focus-visible {
|
|
|
137
169
|
Selection column
|
|
138
170
|
========================= */
|
|
139
171
|
|
|
140
|
-
/*
|
|
141
|
-
Default (no severity rails):
|
|
142
|
-
Remove ALL inline padding for selection/checkbox cells in both header + body.
|
|
143
|
-
|
|
144
|
-
We deliberately use padding-inline (shorthand) so it reliably beats other
|
|
145
|
-
padding-inline shorthands (like .table.sm .tBody tr td).
|
|
146
|
-
*/
|
|
147
|
-
|
|
148
|
-
/* Body selection cells (covers td with .selectionCell even if markup differs) */
|
|
149
172
|
.tBody tr td.selectionCell,
|
|
150
173
|
.table .tBody tr td.selectionCell,
|
|
151
174
|
.table td.selectionCell {
|
|
152
175
|
padding-inline: var(--spacing-xxs);
|
|
153
176
|
}
|
|
154
177
|
|
|
155
|
-
/* Header selection cells:
|
|
156
|
-
- covers your .th class
|
|
157
|
-
- AND real <th> elements that might not have the .th class */
|
|
158
178
|
.table .th.selectionCell,
|
|
159
179
|
.table th.selectionCell,
|
|
160
180
|
th.selectionCell {
|
|
161
|
-
|
|
181
|
+
width: 34px !important;
|
|
162
182
|
}
|
|
163
183
|
|
|
164
|
-
/* Override the .table.sm .tBody tr td padding-inline
|
|
184
|
+
/* Override the .table.sm .tBody tr td padding-inline */
|
|
165
185
|
.table.sm .tBody tr td.selectionCell,
|
|
166
186
|
.table.table.sm .tBody tr td.selectionCell {
|
|
167
187
|
padding-inline: var(--spacing-xxs);
|
|
168
188
|
}
|
|
169
189
|
|
|
170
|
-
/* If severity rails are enabled, reserve a left gutter (still no right padding) */
|
|
171
190
|
.table.severityTable .tBody tr td.selectionCell,
|
|
172
191
|
.table.severityTable td.selectionCell,
|
|
173
192
|
.table.severityTable .th.selectionCell,
|
|
@@ -176,7 +195,6 @@ th.selectionCell {
|
|
|
176
195
|
padding-inline-start: 14px;
|
|
177
196
|
}
|
|
178
197
|
|
|
179
|
-
/* Ensure severityTable also wins in sm */
|
|
180
198
|
.table.sm.severityTable .tBody tr td.selectionCell,
|
|
181
199
|
.table.table.sm.severityTable .tBody tr td.selectionCell,
|
|
182
200
|
.table.sm.severityTable .th.selectionCell,
|
|
@@ -214,8 +232,8 @@ th.selectionCell {
|
|
|
214
232
|
background-color: var(--color-bg-surface-subtle);
|
|
215
233
|
}
|
|
216
234
|
|
|
217
|
-
/* Focus ring */
|
|
218
|
-
.table .
|
|
235
|
+
/* Focus ring (fix selector: .tBody, not .tbody) */
|
|
236
|
+
.table .tBody tr:focus-within {
|
|
219
237
|
outline: none;
|
|
220
238
|
box-shadow:
|
|
221
239
|
inset 2px 0 0 var(--color-brand),
|
|
@@ -259,7 +277,6 @@ th.selectionCell {
|
|
|
259
277
|
position: relative;
|
|
260
278
|
}
|
|
261
279
|
|
|
262
|
-
/* Only render the rail when the table actually uses severity rails */
|
|
263
280
|
.table.severityTable .tBody tr.severity td:first-child::before {
|
|
264
281
|
content: '';
|
|
265
282
|
position: absolute;
|
|
@@ -275,7 +292,6 @@ th.selectionCell {
|
|
|
275
292
|
z-index: 0;
|
|
276
293
|
}
|
|
277
294
|
|
|
278
|
-
/* keep checkbox/content above the rail */
|
|
279
295
|
.table.severityTable .tBody tr.severity td:first-child > * {
|
|
280
296
|
position: relative;
|
|
281
297
|
z-index: 1;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { type ColumnDef } from '@tanstack/react-table';
|
|
1
|
+
import { type ColumnDef, type SortingState } from '@tanstack/react-table';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { type TableProps, type TableVariant } from './Table';
|
|
4
4
|
import { ViewMode } from '../../hooks/useTableSettings';
|
|
5
5
|
type Filterable<T> = Array<keyof T>;
|
|
6
|
-
export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerBelowRow' | 'headerExtras' | 'columnStyles'> & {
|
|
6
|
+
export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerBelowRow' | 'headerExtras' | 'columnStyles' | 'toolbar'> & {
|
|
7
7
|
columns: ReadonlyArray<ColumnDef<T, any>>;
|
|
8
8
|
filterable?: Filterable<T>;
|
|
9
|
-
|
|
9
|
+
sorting?: SortingState;
|
|
10
|
+
manualSorting?: boolean;
|
|
11
|
+
onSortingChange?: (next: SortingState) => void;
|
|
12
|
+
optimisticSortingUi?: boolean;
|
|
10
13
|
variant?: TableVariant;
|
|
11
14
|
viewMode?: ViewMode;
|
|
15
|
+
visibleColumnIds?: string[];
|
|
12
16
|
};
|
|
13
17
|
export declare function TanstackTable<T extends Record<string, any>>(props: TanstackTableProps<T>): React.ReactNode;
|
|
14
18
|
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, } from '@tanstack/react-table';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import ColumnResizer from './components/column-resizer/ColumnResizer';
|
|
6
|
+
import { Table } from './Table';
|
|
7
|
+
import { buildColumnVisibilityFromVisibleIds, mapDefsToColumnItems, sortingEqual, getSortPropsFromSorting, buildColumnStyles, columnItemsToIdSet, } from './tanstackTable.utils';
|
|
8
|
+
export function TanstackTable(props) {
|
|
9
|
+
const { data, dataKey, columns, filterable = [], sorting: controlledSorting, onSortingChange, optimisticSortingUi = true, visibleColumnIds, manualSorting, ...tableProps } = props;
|
|
10
|
+
const isControlledSorting = controlledSorting != null;
|
|
11
|
+
const [uiSorting, setUiSorting] = React.useState(controlledSorting !== null && controlledSorting !== void 0 ? controlledSorting : []);
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
if (!isControlledSorting)
|
|
14
|
+
return;
|
|
15
|
+
if (sortingEqual(uiSorting, controlledSorting))
|
|
16
|
+
return;
|
|
17
|
+
setUiSorting(controlledSorting);
|
|
18
|
+
}, [isControlledSorting, controlledSorting, uiSorting]);
|
|
19
|
+
const columnVisibility = React.useMemo(() => buildColumnVisibilityFromVisibleIds(columns, visibleColumnIds), [columns, visibleColumnIds]);
|
|
20
|
+
const [columnFilters, setColumnFilters] = React.useState([]);
|
|
21
|
+
const [columnSizing, setColumnSizing] = React.useState({});
|
|
22
|
+
const table = useReactTable({
|
|
23
|
+
data,
|
|
24
|
+
columns: columns,
|
|
25
|
+
state: {
|
|
26
|
+
sorting: uiSorting,
|
|
27
|
+
columnFilters,
|
|
28
|
+
columnSizing,
|
|
29
|
+
columnVisibility,
|
|
30
|
+
},
|
|
31
|
+
onSortingChange: updater => {
|
|
32
|
+
const next = typeof updater === 'function' ? updater(uiSorting) : updater;
|
|
33
|
+
if (optimisticSortingUi)
|
|
34
|
+
setUiSorting(next);
|
|
35
|
+
onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(next);
|
|
36
|
+
},
|
|
37
|
+
onColumnFiltersChange: setColumnFilters,
|
|
38
|
+
onColumnSizingChange: setColumnSizing,
|
|
39
|
+
getCoreRowModel: getCoreRowModel(),
|
|
40
|
+
getSortedRowModel: getSortedRowModel(),
|
|
41
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
42
|
+
manualSorting: manualSorting !== null && manualSorting !== void 0 ? manualSorting : false,
|
|
43
|
+
enableColumnResizing: true,
|
|
44
|
+
columnResizeMode: 'onChange',
|
|
45
|
+
defaultColumn: {
|
|
46
|
+
enableResizing: true,
|
|
47
|
+
minSize: 80,
|
|
48
|
+
size: 180,
|
|
49
|
+
maxSize: 300,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const columnItems = React.useMemo(() => mapDefsToColumnItems(columns, columnVisibility), [columns, columnVisibility]);
|
|
53
|
+
const allowedIds = React.useMemo(() => columnItemsToIdSet(columnItems), [columnItems]);
|
|
54
|
+
const visibleData = table.getRowModel().rows.map(r => r.original);
|
|
55
|
+
const { sortById, sortDirection } = getSortPropsFromSorting(uiSorting);
|
|
56
|
+
const columnStyles = React.useMemo(() => {
|
|
57
|
+
const leafCols = table.getAllLeafColumns();
|
|
58
|
+
return buildColumnStyles(leafCols, allowedIds, { minWidth: 80, maxWidth: 800 });
|
|
59
|
+
}, [table, columnSizing, allowedIds]);
|
|
60
|
+
const handleSortChange = React.useCallback((column, direction) => {
|
|
61
|
+
// Translate Table's direction -> TanStack SortingState
|
|
62
|
+
const next = direction == null ? [] : [{ id: column.id, desc: direction === 'desc' }];
|
|
63
|
+
// Mirror TanStack's onSortingChange behavior
|
|
64
|
+
if (optimisticSortingUi)
|
|
65
|
+
setUiSorting(next);
|
|
66
|
+
onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(next);
|
|
67
|
+
// If you are doing server-side sorting, you likely also want:
|
|
68
|
+
// table.resetPageIndex?.() or external pagination reset (depends on your setup)
|
|
69
|
+
}, [optimisticSortingUi, onSortingChange]);
|
|
70
|
+
const headerExtras = React.useCallback(({ index }) => {
|
|
71
|
+
var _a, _b, _c, _d;
|
|
72
|
+
const headerGroups = table.getHeaderGroups();
|
|
73
|
+
const leafHeaders = headerGroups.length > 0 ? headerGroups[headerGroups.length - 1].headers : [];
|
|
74
|
+
const header = leafHeaders[index];
|
|
75
|
+
if (!header)
|
|
76
|
+
return null;
|
|
77
|
+
const canResize = (_c = (_b = (_a = header.column).getCanResize) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : false;
|
|
78
|
+
const handler = (_d = header.getResizeHandler) === null || _d === void 0 ? void 0 : _d.call(header);
|
|
79
|
+
if (!canResize || !handler)
|
|
80
|
+
return null;
|
|
81
|
+
return _jsx(ColumnResizer, { id: header.column.id, handler: handler });
|
|
82
|
+
}, [table]);
|
|
83
|
+
return (_jsx(Table, { ...tableProps, onSortChange: handleSortChange, dataKey: dataKey, data: visibleData, columns: columnItems, sortById: sortById, sortDirection: sortDirection, columnStyles: columnStyles, headerExtras: headerExtras }));
|
|
84
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import styles from './ColumnResizer.module.css';
|
|
4
|
-
const ColumnResizer = ({ id, handler }) => (_jsx("span", { className: styles.resizer, onMouseDown: handler, onTouchStart: handler, role: "separator", "aria-label": `Resize ${id}` }));
|
|
4
|
+
const ColumnResizer = ({ id, handler }) => (_jsx("span", { className: styles.resizer, "data-resizer": "true", onMouseDown: handler, onTouchStart: handler, role: "separator", "aria-orientation": "vertical", "aria-label": `Resize ${id}` }));
|
|
5
5
|
export default ColumnResizer;
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
.resizer {
|
|
2
|
-
display: inline-block;
|
|
3
|
-
width: 1px;
|
|
4
|
-
background-color: var(--opac-bg-default);
|
|
5
|
-
height: 60%;
|
|
6
2
|
position: absolute;
|
|
7
|
-
right:
|
|
8
|
-
top:
|
|
9
|
-
|
|
3
|
+
right: -6px; /* extend hit area into both columns */
|
|
4
|
+
top: 0;
|
|
5
|
+
height: 100%;
|
|
6
|
+
width: 12px; /* big invisible hit area */
|
|
10
7
|
cursor: col-resize;
|
|
11
8
|
user-select: none;
|
|
12
9
|
touch-action: none;
|
|
10
|
+
z-index: 20;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.resizer::after {
|
|
14
|
+
content: '';
|
|
15
|
+
position: absolute;
|
|
16
|
+
left: 50%;
|
|
17
|
+
transform: translateX(-50%);
|
|
18
|
+
width: 1px; /* visual thickness */
|
|
19
|
+
height: 60%;
|
|
20
|
+
top: 50%;
|
|
21
|
+
transform: translate(-50%, -50%);
|
|
22
|
+
background-color: var(--opac-bg-default);
|
|
13
23
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
2
|
+
import type { JSX, ReactNode } from 'react';
|
|
2
3
|
import { ViewMode } from '../../../../hooks/useTableSettings';
|
|
3
|
-
|
|
4
|
+
import { ButtonSize } from '../../../button/Button';
|
|
5
|
+
interface TableSettingsProps<T extends Record<string, any>> {
|
|
4
6
|
handleChangeViewMode: (mode: ViewMode) => void;
|
|
5
7
|
viewMode: ViewMode;
|
|
8
|
+
columns?: ColumnDef<T>[];
|
|
9
|
+
visibleColumnIds?: string[];
|
|
10
|
+
onVisibleColumnIdsChange?: (nextVisibleIds: string[]) => void;
|
|
11
|
+
columnsLabel?: string;
|
|
12
|
+
allPresetLabel?: string;
|
|
13
|
+
standardPresetLabel?: string;
|
|
14
|
+
buttonSize?: ButtonSize;
|
|
15
|
+
additionalSettings?: (close?: () => void) => ReactNode;
|
|
6
16
|
}
|
|
7
|
-
export declare function TableSettings({ viewMode, handleChangeViewMode }: TableSettingsProps): JSX.Element;
|
|
17
|
+
export declare function TableSettings<T extends Record<string, any>>({ viewMode, handleChangeViewMode, columns, visibleColumnIds, onVisibleColumnIdsChange, columnsLabel, allPresetLabel, standardPresetLabel, buttonSize, additionalSettings, }: TableSettingsProps<T>): JSX.Element;
|
|
8
18
|
export {};
|
|
@@ -1,12 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
3
|
import { ListChevronsDownUp, Settings } from 'lucide-react';
|
|
4
|
+
import { useMemo } from 'react';
|
|
3
5
|
import { Button } from '../../../button/Button';
|
|
4
6
|
import { Menu } from '../../../menu/Menu';
|
|
5
7
|
import { Popover } from '../../../popover/Popover';
|
|
6
|
-
export function TableSettings({ viewMode, handleChangeViewMode }) {
|
|
8
|
+
export function TableSettings({ viewMode, handleChangeViewMode, columns = [], visibleColumnIds = [], onVisibleColumnIdsChange, columnsLabel = 'Kolonner', allPresetLabel = 'Alle', standardPresetLabel = 'Standard', buttonSize = 'sm', additionalSettings, }) {
|
|
7
9
|
const handleViewModeChange = (mode, close) => {
|
|
8
|
-
handleChangeViewMode(mode === '
|
|
10
|
+
handleChangeViewMode(mode === 'wrapped' ? 'compact' : 'wrapped');
|
|
9
11
|
close === null || close === void 0 ? void 0 : close();
|
|
10
12
|
};
|
|
11
|
-
|
|
13
|
+
const hideableColumns = useMemo(() => columns.filter(c => c.enableHiding !== false), [columns]);
|
|
14
|
+
const allPresetIds = useMemo(() => hideableColumns.map(c => c.id), [hideableColumns]);
|
|
15
|
+
const standardPresetIds = useMemo(() => hideableColumns.filter(c => { var _a; return ((_a = c.meta) === null || _a === void 0 ? void 0 : _a.hidden) !== true; }).map(c => c.id), [hideableColumns]);
|
|
16
|
+
const visibleSet = useMemo(() => new Set(visibleColumnIds), [visibleColumnIds]);
|
|
17
|
+
const visibleCount = useMemo(() => {
|
|
18
|
+
return hideableColumns.reduce((acc, c) => { var _a; return acc + (visibleSet.has((_a = c.id) !== null && _a !== void 0 ? _a : '') ? 1 : 0); }, 0);
|
|
19
|
+
}, [hideableColumns, visibleSet]);
|
|
20
|
+
const setVisibleIds = (nextIds) => {
|
|
21
|
+
if (!onVisibleColumnIdsChange)
|
|
22
|
+
return;
|
|
23
|
+
const safe = nextIds.length > 0 ? nextIds : standardPresetIds.length > 0 ? standardPresetIds : allPresetIds;
|
|
24
|
+
onVisibleColumnIdsChange(safe.filter((id) => typeof id === 'string' && Boolean(id)));
|
|
25
|
+
};
|
|
26
|
+
const toggleColumn = (id, nextVisible) => {
|
|
27
|
+
const next = new Set(visibleColumnIds);
|
|
28
|
+
if (nextVisible)
|
|
29
|
+
next.add(id);
|
|
30
|
+
else
|
|
31
|
+
next.delete(id);
|
|
32
|
+
setVisibleIds(Array.from(next));
|
|
33
|
+
};
|
|
34
|
+
const isAllActive = useMemo(() => {
|
|
35
|
+
if (!hideableColumns.length)
|
|
36
|
+
return false;
|
|
37
|
+
return hideableColumns.every(c => c.id && visibleSet.has(c.id));
|
|
38
|
+
}, [hideableColumns, visibleSet]);
|
|
39
|
+
const isStandardActive = useMemo(() => {
|
|
40
|
+
if (!hideableColumns.length)
|
|
41
|
+
return false;
|
|
42
|
+
const std = new Set(standardPresetIds);
|
|
43
|
+
return hideableColumns.every(c => c.id && visibleSet.has(c.id) === std.has(c.id));
|
|
44
|
+
}, [hideableColumns, visibleSet, standardPresetIds]);
|
|
45
|
+
// Required by your RadioButton component
|
|
46
|
+
const presetRadioName = 'table-columns-preset';
|
|
47
|
+
return (_jsx(Popover, { trigger: (onClick, icon) => (_jsxs(Button, { size: buttonSize, onClick: onClick, type: "button", children: [_jsx(Settings, {}), icon] })), children: close => (_jsxs(Menu, { children: [additionalSettings === null || additionalSettings === void 0 ? void 0 : additionalSettings(close), _jsx(Menu.Item, { active: viewMode === 'wrapped', children: _jsxs("button", { type: "button", onClick: () => handleViewModeChange(viewMode, close), children: [_jsx(ListChevronsDownUp, {}), "Ombryd tekst"] }) }), hideableColumns.length > 0 && onVisibleColumnIdsChange ? (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), _jsx("div", { style: { padding: '6px 10px', fontSize: 12, opacity: 0.7 }, children: columnsLabel }), _jsx(Menu.RadioItem, { name: presetRadioName, value: "all", checked: isAllActive, label: allPresetLabel, onValueChange: () => {
|
|
48
|
+
setVisibleIds(allPresetIds.filter((id) => typeof id === 'string' && Boolean(id)));
|
|
49
|
+
close === null || close === void 0 ? void 0 : close();
|
|
50
|
+
} }), _jsx(Menu.RadioItem, { name: presetRadioName, value: "standard", checked: isStandardActive, label: standardPresetLabel, onValueChange: () => {
|
|
51
|
+
setVisibleIds(standardPresetIds.filter((id) => typeof id === 'string' && Boolean(id)));
|
|
52
|
+
close === null || close === void 0 ? void 0 : close();
|
|
53
|
+
} }), _jsx(Menu.Separator, {}), hideableColumns.map(col => {
|
|
54
|
+
const isVisible = col.id ? visibleSet.has(col.id) : false;
|
|
55
|
+
const disableUncheckingLast = isVisible && visibleCount <= 1;
|
|
56
|
+
const label = col.header;
|
|
57
|
+
return (_jsx(Menu.CheckItem, { checked: isVisible, disabled: disableUncheckingLast, label: label, onCheckedChange: nextChecked => {
|
|
58
|
+
if (disableUncheckingLast)
|
|
59
|
+
return;
|
|
60
|
+
toggleColumn(col.id, nextChecked);
|
|
61
|
+
} }, col.id));
|
|
62
|
+
})] })) : null] })) }));
|
|
12
63
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import type { ColumnItem } from './Table';
|
|
3
|
+
export type SortDirection = 'asc' | 'desc' | null;
|
|
4
|
+
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'): CSSProperties;
|
|
6
|
+
export declare function getHeaderLabel(header: string | (() => ReactNode)): ReactNode;
|
|
7
|
+
export declare function isActiveSort(sortById: string | undefined, columnId: string): boolean;
|
|
8
|
+
export declare function getAriaSort(sortable: boolean | undefined, active: boolean, direction: SortDirection): 'ascending' | 'descending' | 'none';
|
|
9
|
+
export declare function getNextSortDirection(sortable: boolean | undefined, active: boolean, currentDirection: SortDirection): SortDirection;
|
|
10
|
+
export declare function shouldToggleOnKey(key: string): boolean;
|
|
11
|
+
export declare function isModifierClick(e: {
|
|
12
|
+
metaKey?: boolean;
|
|
13
|
+
ctrlKey?: boolean;
|
|
14
|
+
}): boolean;
|
|
15
|
+
export declare function shouldAllowWrap(columnAllowWrap: boolean | undefined, isRowSelected: boolean, viewMode?: 'wrapped' | string): boolean;
|
|
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;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function getVisibleColumns(columns) {
|
|
2
|
+
return columns.filter(c => !c.hidden);
|
|
3
|
+
}
|
|
4
|
+
export function getColumnStyle(columnId, columnStyles, alignment, verticalAlignment) {
|
|
5
|
+
var _a;
|
|
6
|
+
const baseStyle = columnStyles === null || columnStyles === void 0 ? void 0 : columnStyles[columnId];
|
|
7
|
+
return {
|
|
8
|
+
...(baseStyle !== null && baseStyle !== void 0 ? baseStyle : {}),
|
|
9
|
+
...(alignment === 'right' ? { fontVariantNumeric: 'tabular-nums' } : null),
|
|
10
|
+
verticalAlign: verticalAlignment !== null && verticalAlignment !== void 0 ? verticalAlignment : 'top',
|
|
11
|
+
textAlign: alignment !== null && alignment !== void 0 ? alignment : 'left',
|
|
12
|
+
minWidth: (_a = baseStyle === null || baseStyle === void 0 ? void 0 : baseStyle.minWidth) !== null && _a !== void 0 ? _a : 0,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function getHeaderLabel(header) {
|
|
16
|
+
return typeof header === 'function' ? header() : header;
|
|
17
|
+
}
|
|
18
|
+
export function isActiveSort(sortById, columnId) {
|
|
19
|
+
return sortById === columnId;
|
|
20
|
+
}
|
|
21
|
+
export function getAriaSort(sortable, active, direction) {
|
|
22
|
+
if (!sortable)
|
|
23
|
+
return 'none';
|
|
24
|
+
if (!active)
|
|
25
|
+
return 'none';
|
|
26
|
+
return direction === 'asc' ? 'ascending' : 'descending';
|
|
27
|
+
}
|
|
28
|
+
export function getNextSortDirection(sortable, active, currentDirection) {
|
|
29
|
+
if (!sortable)
|
|
30
|
+
return null;
|
|
31
|
+
if (!active)
|
|
32
|
+
return 'asc';
|
|
33
|
+
if (currentDirection === 'asc')
|
|
34
|
+
return 'desc';
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
export function shouldToggleOnKey(key) {
|
|
38
|
+
return key === 'Enter' || key === ' ';
|
|
39
|
+
}
|
|
40
|
+
export function isModifierClick(e) {
|
|
41
|
+
return Boolean(e.metaKey || e.ctrlKey);
|
|
42
|
+
}
|
|
43
|
+
export function shouldAllowWrap(columnAllowWrap, isRowSelected, viewMode) {
|
|
44
|
+
return Boolean(columnAllowWrap || isRowSelected || viewMode === 'wrapped');
|
|
45
|
+
}
|
|
46
|
+
export function getCellDisplayValue(row, column) {
|
|
47
|
+
var _a;
|
|
48
|
+
const empty = (_a = column.emptyPlaceholder) !== null && _a !== void 0 ? _a : '';
|
|
49
|
+
if (column.render) {
|
|
50
|
+
const rendered = column.render(row);
|
|
51
|
+
return rendered || empty;
|
|
52
|
+
}
|
|
53
|
+
if (column.accessor) {
|
|
54
|
+
const value = row[column.accessor];
|
|
55
|
+
return value || empty;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
export function getRowKey(rowId) {
|
|
60
|
+
return `tableRow-${String(rowId)}`;
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ColumnDef, SortingState, VisibilityState, Column } from '@tanstack/react-table';
|
|
2
|
+
import type { CSSProperties } from 'react';
|
|
3
|
+
import type { ColumnItem } from './Table';
|
|
4
|
+
type AnyRecord = Record<string, any>;
|
|
5
|
+
export declare function getColumnId<T>(def: ColumnDef<T, any>, index: number): string;
|
|
6
|
+
export declare function buildColumnVisibilityFromVisibleIds<T>(defs: ReadonlyArray<ColumnDef<T, any>>, visibleColumnIds?: string[]): VisibilityState;
|
|
7
|
+
export declare function mapDefsToColumnItems<T extends AnyRecord>(defs: ReadonlyArray<ColumnDef<T, any>>, columnVisibility: VisibilityState): ColumnItem<T>[];
|
|
8
|
+
export declare function sortingEqual(a: SortingState, b: SortingState): boolean;
|
|
9
|
+
export declare function getSortPropsFromSorting(sorting: SortingState): {
|
|
10
|
+
sortById?: string;
|
|
11
|
+
sortDirection: 'asc' | 'desc' | null;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Builds `columnStyles` where TanStack is source of truth.
|
|
15
|
+
* Keep this util pure by passing in the leaf columns and the set of allowed IDs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildColumnStyles(leafColumns: Array<Pick<Column<any, any>, 'id' | 'getSize' | 'columnDef'>>, allowedIds: Set<string>, defaults?: {
|
|
18
|
+
minWidth: number;
|
|
19
|
+
maxWidth: number;
|
|
20
|
+
}): Record<string, CSSProperties>;
|
|
21
|
+
export declare function columnItemsToIdSet<T>(items: Array<ColumnItem<T>>): Set<string>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export function getColumnId(def, index) {
|
|
2
|
+
const d = def;
|
|
3
|
+
if (d.id != null && String(d.id).length)
|
|
4
|
+
return String(d.id);
|
|
5
|
+
if (d.accessorKey != null && String(d.accessorKey).length)
|
|
6
|
+
return String(d.accessorKey);
|
|
7
|
+
return `col_${index}`;
|
|
8
|
+
}
|
|
9
|
+
export function buildColumnVisibilityFromVisibleIds(defs, visibleColumnIds) {
|
|
10
|
+
if (!(visibleColumnIds === null || visibleColumnIds === void 0 ? void 0 : visibleColumnIds.length))
|
|
11
|
+
return {};
|
|
12
|
+
const visible = new Set(visibleColumnIds);
|
|
13
|
+
const next = {};
|
|
14
|
+
defs.forEach((def, index) => {
|
|
15
|
+
const id = getColumnId(def, index);
|
|
16
|
+
next[id] = visible.has(id);
|
|
17
|
+
});
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
export function mapDefsToColumnItems(defs, columnVisibility) {
|
|
21
|
+
return defs.map((def, index) => {
|
|
22
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
23
|
+
const id = getColumnId(def, index);
|
|
24
|
+
const accessorKey = def.accessorKey;
|
|
25
|
+
const accessorFn = def.accessorFn;
|
|
26
|
+
const cell = def.cell;
|
|
27
|
+
let render;
|
|
28
|
+
if (typeof cell === 'function') {
|
|
29
|
+
render = (row) => cell({
|
|
30
|
+
row: { original: row },
|
|
31
|
+
getValue: () => accessorKey != null
|
|
32
|
+
? row[accessorKey]
|
|
33
|
+
: accessorFn
|
|
34
|
+
? accessorFn(row)
|
|
35
|
+
: undefined,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else if (accessorFn) {
|
|
39
|
+
render = (row) => accessorFn(row);
|
|
40
|
+
}
|
|
41
|
+
else if (accessorKey != null) {
|
|
42
|
+
render = (row) => row[accessorKey];
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
render = () => null;
|
|
46
|
+
}
|
|
47
|
+
const isVisible = (_a = columnVisibility[id]) !== null && _a !== void 0 ? _a : true;
|
|
48
|
+
return {
|
|
49
|
+
id,
|
|
50
|
+
header: def.header,
|
|
51
|
+
accessor: accessorKey,
|
|
52
|
+
sortable: (_b = def.enableSorting) !== null && _b !== void 0 ? _b : !!accessorKey,
|
|
53
|
+
render,
|
|
54
|
+
hidden: !isVisible,
|
|
55
|
+
align: (_d = (_c = def.meta) === null || _c === void 0 ? void 0 : _c.align) !== null && _d !== void 0 ? _d : undefined,
|
|
56
|
+
verticalAlign: (_f = (_e = def.meta) === null || _e === void 0 ? void 0 : _e.verticalAlign) !== null && _f !== void 0 ? _f : undefined,
|
|
57
|
+
emptyPlaceholder: (_h = (_g = def.meta) === null || _g === void 0 ? void 0 : _g.emptyPlaceholder) !== null && _h !== void 0 ? _h : '-',
|
|
58
|
+
allowWrap: (_k = (_j = def.meta) === null || _j === void 0 ? void 0 : _j.allowWrap) !== null && _k !== void 0 ? _k : false,
|
|
59
|
+
severity: (_m = (_l = def.meta) === null || _l === void 0 ? void 0 : _l.severity) !== null && _m !== void 0 ? _m : undefined,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function sortingEqual(a, b) {
|
|
64
|
+
const A = a === null || a === void 0 ? void 0 : a[0];
|
|
65
|
+
const B = b === null || b === void 0 ? void 0 : b[0];
|
|
66
|
+
if (!A && !B)
|
|
67
|
+
return true;
|
|
68
|
+
if (!A || !B)
|
|
69
|
+
return false;
|
|
70
|
+
return A.id === B.id && A.desc === B.desc;
|
|
71
|
+
}
|
|
72
|
+
export function getSortPropsFromSorting(sorting) {
|
|
73
|
+
const s = sorting === null || sorting === void 0 ? void 0 : sorting[0];
|
|
74
|
+
return {
|
|
75
|
+
sortById: s === null || s === void 0 ? void 0 : s.id,
|
|
76
|
+
sortDirection: s ? (s.desc ? 'desc' : 'asc') : null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Builds `columnStyles` where TanStack is source of truth.
|
|
81
|
+
* Keep this util pure by passing in the leaf columns and the set of allowed IDs.
|
|
82
|
+
*/
|
|
83
|
+
export function buildColumnStyles(leafColumns, allowedIds, defaults = { minWidth: 80, maxWidth: 800 }) {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
const styles = {};
|
|
86
|
+
for (const c of leafColumns) {
|
|
87
|
+
const id = c.id;
|
|
88
|
+
if (!allowedIds.has(id))
|
|
89
|
+
continue;
|
|
90
|
+
const anyDef = c.columnDef;
|
|
91
|
+
styles[id] = {
|
|
92
|
+
width: c.getSize(),
|
|
93
|
+
minWidth: (_a = anyDef.minSize) !== null && _a !== void 0 ? _a : defaults.minWidth,
|
|
94
|
+
maxWidth: (_b = anyDef.maxSize) !== null && _b !== void 0 ? _b : defaults.maxWidth,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return styles;
|
|
98
|
+
}
|
|
99
|
+
export function columnItemsToIdSet(items) {
|
|
100
|
+
const s = new Set();
|
|
101
|
+
for (const c of items)
|
|
102
|
+
s.add(c.id);
|
|
103
|
+
return s;
|
|
104
|
+
}
|