@dbcdk/react-components 0.0.184 → 0.0.186
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/client.cjs +28 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +4 -0
- package/dist/components/accordion/components/AccordionRow.cjs +2 -36
- package/dist/components/accordion/components/AccordionRow.js +2 -36
- package/dist/components/button-select/ButtonSelect.cjs +30 -21
- package/dist/components/button-select/ButtonSelect.d.ts +8 -4
- package/dist/components/button-select/ButtonSelect.js +30 -21
- package/dist/components/page/Page.module.css +1 -0
- package/dist/components/pagination/Pagination.module.css +1 -0
- package/dist/components/panel/Panel.module.css +0 -1
- package/dist/components/popover/Popover.cjs +1 -1
- package/dist/components/popover/Popover.js +1 -1
- package/dist/components/print-friendly/PrintFriendlyCard.cjs +39 -0
- package/dist/components/print-friendly/PrintFriendlyCard.d.ts +23 -0
- package/dist/components/print-friendly/PrintFriendlyCard.js +37 -0
- package/dist/components/print-friendly/PrintFriendlyMetaBar.cjs +25 -0
- package/dist/components/print-friendly/PrintFriendlyMetaBar.d.ts +19 -0
- package/dist/components/print-friendly/PrintFriendlyMetaBar.js +23 -0
- package/dist/components/print-friendly/PrintFriendlyTable.cjs +51 -0
- package/dist/components/print-friendly/PrintFriendlyTable.d.ts +34 -0
- package/dist/components/print-friendly/PrintFriendlyTable.js +49 -0
- package/dist/components/print-friendly/components/AccumulationFooter.cjs +42 -0
- package/dist/components/print-friendly/components/AccumulationFooter.d.ts +9 -0
- package/dist/components/print-friendly/components/AccumulationFooter.js +40 -0
- package/dist/components/print-friendly/emptyValue.cjs +9 -0
- package/dist/components/print-friendly/emptyValue.d.ts +2 -0
- package/dist/components/print-friendly/emptyValue.js +6 -0
- package/dist/components/print-friendly/printSelection.cjs +14 -0
- package/dist/components/print-friendly/printSelection.d.ts +19 -0
- package/dist/components/print-friendly/printSelection.js +10 -0
- package/dist/components/stack/Stack.cjs +6 -5
- package/dist/components/stack/Stack.d.ts +2 -1
- package/dist/components/stack/Stack.js +6 -5
- package/dist/components/table/Table.cjs +20 -4
- package/dist/components/table/Table.js +21 -5
- package/dist/components/table/Table.module.css +6 -0
- package/dist/components/table/Table.types.d.ts +1 -0
- package/dist/components/table/components/TableHeader.cjs +1 -0
- package/dist/components/table/components/TableHeader.js +1 -0
- package/dist/components/tabs/Tabs.cjs +49 -39
- package/dist/components/tabs/Tabs.d.ts +3 -1
- package/dist/components/tabs/Tabs.js +49 -39
- package/dist/components/tabs/Tabs.module.css +43 -25
- package/dist/components/timeline/Timeline.cjs +102 -0
- package/dist/components/timeline/Timeline.d.ts +60 -0
- package/dist/components/timeline/Timeline.js +96 -0
- package/dist/components/timeline/Timeline.module.css +22 -0
- package/dist/components/timeline/components/TimelineRow.cjs +147 -0
- package/dist/components/timeline/components/TimelineRow.d.ts +15 -0
- package/dist/components/timeline/components/TimelineRow.js +141 -0
- package/dist/components/timeline/components/TimelineRow.module.css +195 -0
- package/dist/hooks/useCollapsibleHeight.cjs +41 -0
- package/dist/hooks/useCollapsibleHeight.d.ts +6 -0
- package/dist/hooks/useCollapsibleHeight.js +39 -0
- package/dist/hooks/useViewportFill.cjs +11 -3
- package/dist/hooks/useViewportFill.js +11 -3
- package/dist/index.cjs +7 -0
- package/dist/index.css +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles/styles.css +74 -2
- package/dist/styles.css +74 -2
- package/dist/utils/date/formatDate.d.ts +3 -2
- package/package.json +1 -1
|
@@ -51,6 +51,7 @@ function Tabs({
|
|
|
51
51
|
onRemoveTab,
|
|
52
52
|
onAddTab,
|
|
53
53
|
addition,
|
|
54
|
+
tabListAddition,
|
|
54
55
|
loading = false,
|
|
55
56
|
children
|
|
56
57
|
}) {
|
|
@@ -143,44 +144,52 @@ function Tabs({
|
|
|
143
144
|
) : null,
|
|
144
145
|
/* @__PURE__ */ jsxs("div", { className: `${styles.tabs} ${styles[variant]} ${panelStyle ? styles.panelStyle : ""}`, children: [
|
|
145
146
|
/* @__PURE__ */ jsxs("div", { className: styles.tabList, children: [
|
|
146
|
-
/* @__PURE__ */ jsx(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
"div",
|
|
149
|
+
{
|
|
150
|
+
className: onAddTab ? styles.tabButtons : `${styles.tabButtons} ${styles.tabButtonsEnd}`,
|
|
151
|
+
role: "tablist",
|
|
152
|
+
"aria-label": header != null ? header : "Tabs",
|
|
153
|
+
children: visibleTabs.map((tab, index) => {
|
|
154
|
+
const selected = index === activeIndex;
|
|
155
|
+
const tabDomId = `${uid}-tab-${String(tab.id)}`;
|
|
156
|
+
const panelDomId = `${uid}-panel-${String(tab.id)}`;
|
|
157
|
+
return /* @__PURE__ */ jsxs("div", { className: `${styles.tab} ${selected ? styles.active : ""}`, children: [
|
|
158
|
+
/* @__PURE__ */ jsxs(
|
|
159
|
+
"button",
|
|
160
|
+
{
|
|
161
|
+
id: tabDomId,
|
|
162
|
+
type: "button",
|
|
163
|
+
className: styles.tabButton,
|
|
164
|
+
role: "tab",
|
|
165
|
+
"aria-selected": selected,
|
|
166
|
+
"aria-controls": panelDomId,
|
|
167
|
+
tabIndex: selected ? 0 : -1,
|
|
168
|
+
disabled: tab.disabled,
|
|
169
|
+
onClick: () => setValue(tab.id),
|
|
170
|
+
onKeyDown: (e) => onKeyDownTab(e, index),
|
|
171
|
+
children: [
|
|
172
|
+
tab.headerIcon ? /* @__PURE__ */ jsx("span", { className: styles.icon, children: tab.headerIcon }) : null,
|
|
173
|
+
/* @__PURE__ */ jsx("span", { className: styles.label, children: tab.header }),
|
|
174
|
+
tab.badge ? /* @__PURE__ */ jsx("span", { className: styles.badge, children: /* @__PURE__ */ jsx(Chip, { size: "sm", children: tab.badge.toLocaleString("da-DK") }) }) : null
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
tab.removable && onRemoveTab ? /* @__PURE__ */ jsx(
|
|
179
|
+
"button",
|
|
180
|
+
{
|
|
181
|
+
type: "button",
|
|
182
|
+
className: styles.removeTabButton,
|
|
183
|
+
"aria-label": `Fjern fane ${tab.header}`,
|
|
184
|
+
disabled: tab.disabled,
|
|
185
|
+
onClick: () => onRemoveTab(tab.id, tab, index),
|
|
186
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
|
|
187
|
+
}
|
|
188
|
+
) : null
|
|
189
|
+
] }, tab.id);
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
),
|
|
184
193
|
onAddTab ? /* @__PURE__ */ jsx(
|
|
185
194
|
"button",
|
|
186
195
|
{
|
|
@@ -190,7 +199,8 @@ function Tabs({
|
|
|
190
199
|
"aria-label": "Tilf\xF8j fane",
|
|
191
200
|
children: /* @__PURE__ */ jsx(Plus, { "aria-hidden": "true" })
|
|
192
201
|
}
|
|
193
|
-
) : null
|
|
202
|
+
) : null,
|
|
203
|
+
tabListAddition ? /* @__PURE__ */ jsx("div", { className: styles.tabListAddition, children: tabListAddition }) : null
|
|
194
204
|
] }),
|
|
195
205
|
/* @__PURE__ */ jsx(
|
|
196
206
|
"div",
|
|
@@ -36,6 +36,15 @@
|
|
|
36
36
|
min-width: 0;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/* Extra content next to the tab buttons, pushed into the free space on the right */
|
|
40
|
+
.tabListAddition {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: var(--spacing-sm);
|
|
44
|
+
margin-inline-start: auto;
|
|
45
|
+
padding-inline-start: var(--spacing-md);
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
/* Individual tab wrapper */
|
|
40
49
|
.tab {
|
|
41
50
|
display: flex;
|
|
@@ -199,26 +208,35 @@
|
|
|
199
208
|
overflow: hidden;
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
.filled .tabList {
|
|
211
|
+
.filled > .tabList {
|
|
203
212
|
border-block-end: 1px solid var(--color-border-subtle);
|
|
204
|
-
background: var(--color-bg-toolbar);
|
|
205
|
-
inline-size: 100%;
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
|
|
215
|
+
/* One continuous backdrop behind the whole button row, so the curved gap
|
|
216
|
+
next to the active tab's rounded corners shows toolbar gray, not the
|
|
217
|
+
surface color behind the row. */
|
|
218
|
+
.filled > .tabList > .tabButtons {
|
|
209
219
|
display: flex;
|
|
220
|
+
background: var(--color-bg-toolbar);
|
|
221
|
+
border-start-start-radius: var(--border-radius-md);
|
|
210
222
|
}
|
|
211
223
|
|
|
212
|
-
.filled .
|
|
224
|
+
.filled > .tabList > .tabButtons.tabButtonsEnd {
|
|
225
|
+
border-start-end-radius: var(--border-radius-md);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.filled > .tabList > .addTabButton {
|
|
229
|
+
background: var(--color-bg-toolbar);
|
|
213
230
|
border: 1px solid transparent;
|
|
214
231
|
border-block-end: 0;
|
|
232
|
+
border-start-end-radius: var(--border-radius-md);
|
|
215
233
|
}
|
|
216
234
|
|
|
217
|
-
.filled .addTabButton:hover {
|
|
235
|
+
.filled > .tabList > .addTabButton:hover {
|
|
218
236
|
background: var(--color-bg-toolbar-hover);
|
|
219
237
|
}
|
|
220
238
|
|
|
221
|
-
.filled .tab {
|
|
239
|
+
.filled > .tabList > .tabButtons > .tab {
|
|
222
240
|
border: 1px solid transparent;
|
|
223
241
|
border-block-end: 0;
|
|
224
242
|
color: var(--color-fg-muted);
|
|
@@ -229,14 +247,14 @@
|
|
|
229
247
|
border-color var(--transition-fast) var(--ease-standard);
|
|
230
248
|
}
|
|
231
249
|
|
|
232
|
-
.filled .tab:not(.active):hover {
|
|
250
|
+
.filled > .tabList > .tabButtons > .tab:not(.active):hover {
|
|
233
251
|
background: var(--color-bg-toolbar-hover);
|
|
234
252
|
color: var(--color-fg-default);
|
|
235
253
|
}
|
|
236
254
|
|
|
237
255
|
/* Active tab physically overlaps the tabList separator by 1px so its
|
|
238
256
|
erased bottom border seamlessly merges with the panel below. */
|
|
239
|
-
.filled .tab.active {
|
|
257
|
+
.filled > .tabList > .tabButtons > .tab.active {
|
|
240
258
|
background: var(--color-bg-surface);
|
|
241
259
|
border-color: var(--color-border-subtle);
|
|
242
260
|
border-block-end-color: var(--color-bg-surface);
|
|
@@ -247,13 +265,13 @@
|
|
|
247
265
|
color: var(--color-fg-default);
|
|
248
266
|
}
|
|
249
267
|
|
|
250
|
-
.filled .tabContent {
|
|
268
|
+
.filled > .tabContent {
|
|
251
269
|
background: var(--color-bg-surface);
|
|
252
270
|
border: 1px solid var(--color-border-subtle);
|
|
253
271
|
border-block-start: none;
|
|
254
272
|
border-radius: 0 0 var(--border-radius-md) var(--border-radius-md);
|
|
255
273
|
box-shadow: var(--shadow-xs);
|
|
256
|
-
padding: var(--spacing-
|
|
274
|
+
padding: var(--spacing-md);
|
|
257
275
|
}
|
|
258
276
|
|
|
259
277
|
/* =========================
|
|
@@ -268,22 +286,22 @@
|
|
|
268
286
|
padding-block-end: var(--spacing-xs);
|
|
269
287
|
}
|
|
270
288
|
|
|
271
|
-
.outlined .tab {
|
|
289
|
+
.outlined > .tabList > .tabButtons > .tab {
|
|
272
290
|
color: var(--color-fg-muted);
|
|
273
291
|
border-block-end: 2px solid var(--color-border-default);
|
|
274
292
|
transition: color var(--transition-fast) var(--ease-standard);
|
|
275
293
|
}
|
|
276
294
|
|
|
277
|
-
.outlined .tab:not(.active):hover {
|
|
295
|
+
.outlined > .tabList > .tabButtons > .tab:not(.active):hover {
|
|
278
296
|
color: var(--color-brand);
|
|
279
297
|
}
|
|
280
298
|
|
|
281
|
-
.outlined .tab.active {
|
|
299
|
+
.outlined > .tabList > .tabButtons > .tab.active {
|
|
282
300
|
color: var(--color-brand);
|
|
283
301
|
border-block-end-color: var(--color-brand);
|
|
284
302
|
}
|
|
285
303
|
|
|
286
|
-
.outlined .tabContent {
|
|
304
|
+
.outlined > .tabContent {
|
|
287
305
|
background: transparent;
|
|
288
306
|
padding: 0;
|
|
289
307
|
}
|
|
@@ -292,22 +310,22 @@
|
|
|
292
310
|
Panel style modifier
|
|
293
311
|
========================= */
|
|
294
312
|
|
|
295
|
-
.panelStyle .tabList {
|
|
313
|
+
.panelStyle > .tabList {
|
|
296
314
|
border: var(--border-width-thin) solid var(--color-border-default);
|
|
297
315
|
border-block-end: 0;
|
|
298
316
|
}
|
|
299
317
|
|
|
300
318
|
/* When panelStyle is on, tabs look “embedded” (no bottom border indicators) */
|
|
301
|
-
.panelStyle .tab {
|
|
319
|
+
.panelStyle > .tabList > .tabButtons > .tab {
|
|
302
320
|
border-block-end: 0;
|
|
303
321
|
}
|
|
304
322
|
|
|
305
323
|
/* In filled mode, keep the filled content panel visuals (already handled above).
|
|
306
324
|
In outlined mode with panelStyle, give the content a panel container too. */
|
|
307
|
-
.panelStyle.outlined .tabContent {
|
|
325
|
+
.panelStyle.outlined > .tabContent {
|
|
308
326
|
border: var(--border-width-thin) solid var(--color-border-default);
|
|
309
327
|
background: var(--color-bg-surface);
|
|
310
|
-
padding: var(--spacing-
|
|
328
|
+
padding: var(--spacing-md);
|
|
311
329
|
}
|
|
312
330
|
|
|
313
331
|
@media (max-width: 767px) {
|
|
@@ -327,17 +345,17 @@
|
|
|
327
345
|
box-shadow: none;
|
|
328
346
|
}
|
|
329
347
|
|
|
330
|
-
.filled .tabList {
|
|
348
|
+
.filled > .tabList {
|
|
331
349
|
inline-size: 100%;
|
|
332
350
|
}
|
|
333
351
|
|
|
334
|
-
.filled .tabContent,
|
|
335
|
-
.panelStyle.outlined .tabContent {
|
|
336
|
-
padding-block: var(--spacing-
|
|
337
|
-
padding-inline: var(--spacing-
|
|
352
|
+
.filled > .tabContent,
|
|
353
|
+
.panelStyle.outlined > .tabContent {
|
|
354
|
+
padding-block: var(--spacing-sm);
|
|
355
|
+
padding-inline: var(--spacing-sm);
|
|
338
356
|
}
|
|
339
357
|
|
|
340
|
-
.panelStyle .tabList {
|
|
358
|
+
.panelStyle > .tabList {
|
|
341
359
|
border: 0;
|
|
342
360
|
}
|
|
343
361
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var TimelineRow = require('./components/TimelineRow');
|
|
7
|
+
var styles = require('./Timeline.module.css');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
12
|
+
|
|
13
|
+
function uniqSorted(nums) {
|
|
14
|
+
return Array.from(new Set(nums)).sort((a, b) => a - b);
|
|
15
|
+
}
|
|
16
|
+
function collapsibleIndexesOf(items) {
|
|
17
|
+
return items.reduce((acc, item, i) => {
|
|
18
|
+
if (item.collapsible) acc.push(i);
|
|
19
|
+
return acc;
|
|
20
|
+
}, []);
|
|
21
|
+
}
|
|
22
|
+
function normalizeOpenIndexes(indexes, items) {
|
|
23
|
+
const collapsible = collapsibleIndexesOf(items);
|
|
24
|
+
if (indexes === "all") {
|
|
25
|
+
return collapsible;
|
|
26
|
+
}
|
|
27
|
+
const collapsibleSet = new Set(collapsible);
|
|
28
|
+
return uniqSorted((indexes != null ? indexes : []).filter((n) => collapsibleSet.has(n)));
|
|
29
|
+
}
|
|
30
|
+
function Timeline({
|
|
31
|
+
items,
|
|
32
|
+
size = "md",
|
|
33
|
+
variant = "default",
|
|
34
|
+
renderMode = "lazy",
|
|
35
|
+
defaultOpenIndexes = [],
|
|
36
|
+
openIndexes,
|
|
37
|
+
onOpenIndexesChange,
|
|
38
|
+
className,
|
|
39
|
+
...rest
|
|
40
|
+
}) {
|
|
41
|
+
const uid = react.useId();
|
|
42
|
+
const isControlled = openIndexes !== void 0;
|
|
43
|
+
const [hasMounted, setHasMounted] = react.useState(false);
|
|
44
|
+
react.useEffect(() => {
|
|
45
|
+
setHasMounted(true);
|
|
46
|
+
}, []);
|
|
47
|
+
const [internalOpenIndexes, setInternalOpenIndexes] = react.useState(
|
|
48
|
+
() => normalizeOpenIndexes(defaultOpenIndexes, items)
|
|
49
|
+
);
|
|
50
|
+
const currentOpenIndexes = react.useMemo(
|
|
51
|
+
() => normalizeOpenIndexes(isControlled ? openIndexes : internalOpenIndexes, items),
|
|
52
|
+
[isControlled, openIndexes, internalOpenIndexes, items]
|
|
53
|
+
);
|
|
54
|
+
const openSet = react.useMemo(() => new Set(currentOpenIndexes), [currentOpenIndexes]);
|
|
55
|
+
function commit(nextIndexes) {
|
|
56
|
+
const normalized = normalizeOpenIndexes(nextIndexes, items);
|
|
57
|
+
if (isControlled) {
|
|
58
|
+
onOpenIndexesChange == null ? void 0 : onOpenIndexesChange(normalized);
|
|
59
|
+
} else {
|
|
60
|
+
setInternalOpenIndexes(normalized);
|
|
61
|
+
onOpenIndexesChange == null ? void 0 : onOpenIndexesChange(normalized);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function toggle(index) {
|
|
65
|
+
const item = items[index];
|
|
66
|
+
if (!item || !item.collapsible || item.disabled) return;
|
|
67
|
+
const isOpen = openSet.has(index);
|
|
68
|
+
if (isOpen) {
|
|
69
|
+
commit(currentOpenIndexes.filter((i) => i !== index));
|
|
70
|
+
} else {
|
|
71
|
+
commit([...currentOpenIndexes, index]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
...rest,
|
|
78
|
+
className: [styles__default.default.container, styles__default.default[size], className].filter(Boolean).join(" "),
|
|
79
|
+
children: items.map((item, i) => {
|
|
80
|
+
var _a;
|
|
81
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
82
|
+
TimelineRow.TimelineRow,
|
|
83
|
+
{
|
|
84
|
+
uid,
|
|
85
|
+
index: i,
|
|
86
|
+
item,
|
|
87
|
+
isLast: i === items.length - 1,
|
|
88
|
+
isOpen: openSet.has(i),
|
|
89
|
+
onToggle: toggle,
|
|
90
|
+
shouldAnimate: hasMounted,
|
|
91
|
+
size,
|
|
92
|
+
variant,
|
|
93
|
+
renderMode
|
|
94
|
+
},
|
|
95
|
+
(_a = item.id) != null ? _a : i
|
|
96
|
+
);
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
exports.Timeline = Timeline;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { HTMLAttributes, JSX, ReactNode } from 'react';
|
|
2
|
+
import type { Severity } from '../../constants/severity.types';
|
|
3
|
+
import type { FormatDateOptions } from '../../utils/date/formatDate';
|
|
4
|
+
export type TimelineContextMenuActionItem = {
|
|
5
|
+
type?: 'item';
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
active?: boolean;
|
|
10
|
+
selected?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
variant?: 'default' | 'danger';
|
|
13
|
+
};
|
|
14
|
+
export type TimelineContextMenuHeaderItem = {
|
|
15
|
+
type: 'header';
|
|
16
|
+
label: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
export type TimelineContextMenuItem = TimelineContextMenuActionItem | TimelineContextMenuHeaderItem;
|
|
19
|
+
export interface TimelineItem {
|
|
20
|
+
id?: string;
|
|
21
|
+
label: ReactNode;
|
|
22
|
+
content: ReactNode;
|
|
23
|
+
collapsible?: boolean;
|
|
24
|
+
severity?: Severity;
|
|
25
|
+
icon?: ReactNode;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* When set, renders a muted timestamp next to the label using the
|
|
29
|
+
* component's own date formatting (see `dateOptions`) instead of the
|
|
30
|
+
* consumer building one into `label`/`content` by hand.
|
|
31
|
+
*/
|
|
32
|
+
date?: Date | string | number;
|
|
33
|
+
/** Passed through to the internal `formatDate` call for `date`. */
|
|
34
|
+
dateOptions?: FormatDateOptions;
|
|
35
|
+
/**
|
|
36
|
+
* Renders a kebab menu next to the label (same pattern as `Table`'s row
|
|
37
|
+
* actions), instead of the consumer putting action buttons directly in
|
|
38
|
+
* `label` — those inflate the header's height and throw off its
|
|
39
|
+
* alignment with the marker dot.
|
|
40
|
+
*/
|
|
41
|
+
contextMenuItems?: TimelineContextMenuItem[];
|
|
42
|
+
}
|
|
43
|
+
type Size = 'sm' | 'md' | 'lg';
|
|
44
|
+
type Variant = 'default' | 'outlined';
|
|
45
|
+
type OpenIndexes = number[] | 'all';
|
|
46
|
+
type RenderMode = 'lazy' | 'eager';
|
|
47
|
+
export interface TimelineProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
48
|
+
items: TimelineItem[];
|
|
49
|
+
size?: Size;
|
|
50
|
+
variant?: Variant;
|
|
51
|
+
renderMode?: RenderMode;
|
|
52
|
+
/** Uncontrolled default */
|
|
53
|
+
defaultOpenIndexes?: OpenIndexes;
|
|
54
|
+
/** Controlled state */
|
|
55
|
+
openIndexes?: OpenIndexes;
|
|
56
|
+
/** Change callback */
|
|
57
|
+
onOpenIndexesChange?: (indexes: number[]) => void;
|
|
58
|
+
}
|
|
59
|
+
export declare function Timeline({ items, size, variant, renderMode, defaultOpenIndexes, openIndexes, onOpenIndexesChange, className, ...rest }: TimelineProps): JSX.Element;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId, useState, useEffect, useMemo } from 'react';
|
|
4
|
+
import { TimelineRow } from './components/TimelineRow';
|
|
5
|
+
import styles from './Timeline.module.css';
|
|
6
|
+
|
|
7
|
+
function uniqSorted(nums) {
|
|
8
|
+
return Array.from(new Set(nums)).sort((a, b) => a - b);
|
|
9
|
+
}
|
|
10
|
+
function collapsibleIndexesOf(items) {
|
|
11
|
+
return items.reduce((acc, item, i) => {
|
|
12
|
+
if (item.collapsible) acc.push(i);
|
|
13
|
+
return acc;
|
|
14
|
+
}, []);
|
|
15
|
+
}
|
|
16
|
+
function normalizeOpenIndexes(indexes, items) {
|
|
17
|
+
const collapsible = collapsibleIndexesOf(items);
|
|
18
|
+
if (indexes === "all") {
|
|
19
|
+
return collapsible;
|
|
20
|
+
}
|
|
21
|
+
const collapsibleSet = new Set(collapsible);
|
|
22
|
+
return uniqSorted((indexes != null ? indexes : []).filter((n) => collapsibleSet.has(n)));
|
|
23
|
+
}
|
|
24
|
+
function Timeline({
|
|
25
|
+
items,
|
|
26
|
+
size = "md",
|
|
27
|
+
variant = "default",
|
|
28
|
+
renderMode = "lazy",
|
|
29
|
+
defaultOpenIndexes = [],
|
|
30
|
+
openIndexes,
|
|
31
|
+
onOpenIndexesChange,
|
|
32
|
+
className,
|
|
33
|
+
...rest
|
|
34
|
+
}) {
|
|
35
|
+
const uid = useId();
|
|
36
|
+
const isControlled = openIndexes !== void 0;
|
|
37
|
+
const [hasMounted, setHasMounted] = useState(false);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setHasMounted(true);
|
|
40
|
+
}, []);
|
|
41
|
+
const [internalOpenIndexes, setInternalOpenIndexes] = useState(
|
|
42
|
+
() => normalizeOpenIndexes(defaultOpenIndexes, items)
|
|
43
|
+
);
|
|
44
|
+
const currentOpenIndexes = useMemo(
|
|
45
|
+
() => normalizeOpenIndexes(isControlled ? openIndexes : internalOpenIndexes, items),
|
|
46
|
+
[isControlled, openIndexes, internalOpenIndexes, items]
|
|
47
|
+
);
|
|
48
|
+
const openSet = useMemo(() => new Set(currentOpenIndexes), [currentOpenIndexes]);
|
|
49
|
+
function commit(nextIndexes) {
|
|
50
|
+
const normalized = normalizeOpenIndexes(nextIndexes, items);
|
|
51
|
+
if (isControlled) {
|
|
52
|
+
onOpenIndexesChange == null ? void 0 : onOpenIndexesChange(normalized);
|
|
53
|
+
} else {
|
|
54
|
+
setInternalOpenIndexes(normalized);
|
|
55
|
+
onOpenIndexesChange == null ? void 0 : onOpenIndexesChange(normalized);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function toggle(index) {
|
|
59
|
+
const item = items[index];
|
|
60
|
+
if (!item || !item.collapsible || item.disabled) return;
|
|
61
|
+
const isOpen = openSet.has(index);
|
|
62
|
+
if (isOpen) {
|
|
63
|
+
commit(currentOpenIndexes.filter((i) => i !== index));
|
|
64
|
+
} else {
|
|
65
|
+
commit([...currentOpenIndexes, index]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return /* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
...rest,
|
|
72
|
+
className: [styles.container, styles[size], className].filter(Boolean).join(" "),
|
|
73
|
+
children: items.map((item, i) => {
|
|
74
|
+
var _a;
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
TimelineRow,
|
|
77
|
+
{
|
|
78
|
+
uid,
|
|
79
|
+
index: i,
|
|
80
|
+
item,
|
|
81
|
+
isLast: i === items.length - 1,
|
|
82
|
+
isOpen: openSet.has(i),
|
|
83
|
+
onToggle: toggle,
|
|
84
|
+
shouldAnimate: hasMounted,
|
|
85
|
+
size,
|
|
86
|
+
variant,
|
|
87
|
+
renderMode
|
|
88
|
+
},
|
|
89
|
+
(_a = item.id) != null ? _a : i
|
|
90
|
+
);
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { Timeline };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.sm {
|
|
7
|
+
--tl-gap: var(--spacing-xs);
|
|
8
|
+
--tl-row-gap: var(--spacing-sm);
|
|
9
|
+
--tl-font-size: var(--font-size-sm);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.md {
|
|
13
|
+
--tl-gap: var(--spacing-sm);
|
|
14
|
+
--tl-row-gap: var(--spacing-md);
|
|
15
|
+
--tl-font-size: var(--font-size-md);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.lg {
|
|
19
|
+
--tl-gap: var(--spacing-sm);
|
|
20
|
+
--tl-row-gap: var(--spacing-lg);
|
|
21
|
+
--tl-font-size: var(--font-size-md);
|
|
22
|
+
}
|