@dbcdk/react-components 0.0.134 → 0.0.136
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 +7 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +1 -0
- package/dist/components/forms/file-upload/FileUpload.cjs +13 -3
- package/dist/components/forms/file-upload/FileUpload.d.ts +4 -1
- package/dist/components/forms/file-upload/FileUpload.js +14 -4
- package/dist/components/headline/CollapsibleHeadline.cjs +8 -16
- package/dist/components/headline/CollapsibleHeadline.js +9 -17
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.cjs +32 -19
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +32 -19
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +14 -0
- package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +0 -1
- package/dist/components/tabs/Tabs.cjs +16 -3
- package/dist/components/tabs/Tabs.d.ts +6 -1
- package/dist/components/tabs/Tabs.js +17 -4
- package/dist/hooks/usePersistentState.cjs +41 -0
- package/dist/hooks/usePersistentState.d.ts +13 -0
- package/dist/hooks/usePersistentState.js +39 -0
- package/package.json +1 -1
package/dist/client.cjs
CHANGED
|
@@ -19,6 +19,7 @@ var Footer = require('./components/page-layout/components/footer/Footer');
|
|
|
19
19
|
var Input = require('./components/forms/input/Input');
|
|
20
20
|
var SearchBox = require('./components/search-box/SearchBox');
|
|
21
21
|
var useTheme = require('./hooks/useTheme');
|
|
22
|
+
var usePersistentState = require('./hooks/usePersistentState');
|
|
22
23
|
var Chip = require('./components/chip/Chip');
|
|
23
24
|
var Panel = require('./components/panel/Panel');
|
|
24
25
|
var Card = require('./components/card/Card');
|
|
@@ -199,6 +200,12 @@ Object.keys(useTheme).forEach(function (k) {
|
|
|
199
200
|
get: function () { return useTheme[k]; }
|
|
200
201
|
});
|
|
201
202
|
});
|
|
203
|
+
Object.keys(usePersistentState).forEach(function (k) {
|
|
204
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
205
|
+
enumerable: true,
|
|
206
|
+
get: function () { return usePersistentState[k]; }
|
|
207
|
+
});
|
|
208
|
+
});
|
|
202
209
|
Object.keys(Chip).forEach(function (k) {
|
|
203
210
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
204
211
|
enumerable: true,
|
package/dist/client.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from './components/page-layout/components/footer/Footer';
|
|
|
16
16
|
export * from './components/forms/input/Input';
|
|
17
17
|
export * from './components/search-box/SearchBox';
|
|
18
18
|
export * from './hooks/useTheme';
|
|
19
|
+
export * from './hooks/usePersistentState';
|
|
19
20
|
export * from './components/chip/Chip';
|
|
20
21
|
export * from './components/panel/Panel';
|
|
21
22
|
export * from './components/card/Card';
|
package/dist/client.js
CHANGED
|
@@ -17,6 +17,7 @@ export * from './components/page-layout/components/footer/Footer';
|
|
|
17
17
|
export * from './components/forms/input/Input';
|
|
18
18
|
export * from './components/search-box/SearchBox';
|
|
19
19
|
export * from './hooks/useTheme';
|
|
20
|
+
export * from './hooks/usePersistentState';
|
|
20
21
|
export * from './components/chip/Chip';
|
|
21
22
|
export * from './components/panel/Panel';
|
|
22
23
|
export * from './components/card/Card';
|
|
@@ -11,7 +11,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
11
11
|
|
|
12
12
|
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const FileUpload = react.forwardRef(function FileUpload2({
|
|
15
15
|
label,
|
|
16
16
|
labelAction,
|
|
17
17
|
error,
|
|
@@ -33,11 +33,20 @@ function FileUpload({
|
|
|
33
33
|
disabled,
|
|
34
34
|
className,
|
|
35
35
|
...inputProps
|
|
36
|
-
}) {
|
|
36
|
+
}, ref) {
|
|
37
37
|
const reactId = react.useId();
|
|
38
38
|
const inputId = id != null ? id : `file-upload-${reactId}`;
|
|
39
39
|
const inputRef = react.useRef(null);
|
|
40
40
|
const [dragging, setDragging] = react.useState(false);
|
|
41
|
+
react.useImperativeHandle(
|
|
42
|
+
ref,
|
|
43
|
+
() => ({
|
|
44
|
+
clear() {
|
|
45
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
[]
|
|
49
|
+
);
|
|
41
50
|
function handleDragOver(e) {
|
|
42
51
|
e.preventDefault();
|
|
43
52
|
if (!disabled) setDragging(true);
|
|
@@ -112,6 +121,7 @@ function FileUpload({
|
|
|
112
121
|
)
|
|
113
122
|
}
|
|
114
123
|
);
|
|
115
|
-
}
|
|
124
|
+
});
|
|
125
|
+
FileUpload.displayName = "FileUpload";
|
|
116
126
|
|
|
117
127
|
exports.FileUpload = FileUpload;
|
|
@@ -12,4 +12,7 @@ export interface FileUploadProps extends Omit<InputHTMLAttributes<HTMLInputEleme
|
|
|
12
12
|
width?: string | number;
|
|
13
13
|
maxWidth?: string | number;
|
|
14
14
|
}
|
|
15
|
-
export
|
|
15
|
+
export type FileUploadHandle = {
|
|
16
|
+
clear(): void;
|
|
17
|
+
};
|
|
18
|
+
export declare const FileUpload: import("react").ForwardRefExoticComponent<FileUploadProps & import("react").RefAttributes<FileUploadHandle>>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { UploadCloud } from 'lucide-react';
|
|
4
|
-
import { useId, useRef, useState } from 'react';
|
|
4
|
+
import { forwardRef, useId, useRef, useState, useImperativeHandle } from 'react';
|
|
5
5
|
import styles from './FileUpload.module.css';
|
|
6
6
|
import { InputContainer } from '../input-container/InputContainer';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const FileUpload = forwardRef(function FileUpload2({
|
|
9
9
|
label,
|
|
10
10
|
labelAction,
|
|
11
11
|
error,
|
|
@@ -27,11 +27,20 @@ function FileUpload({
|
|
|
27
27
|
disabled,
|
|
28
28
|
className,
|
|
29
29
|
...inputProps
|
|
30
|
-
}) {
|
|
30
|
+
}, ref) {
|
|
31
31
|
const reactId = useId();
|
|
32
32
|
const inputId = id != null ? id : `file-upload-${reactId}`;
|
|
33
33
|
const inputRef = useRef(null);
|
|
34
34
|
const [dragging, setDragging] = useState(false);
|
|
35
|
+
useImperativeHandle(
|
|
36
|
+
ref,
|
|
37
|
+
() => ({
|
|
38
|
+
clear() {
|
|
39
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
[]
|
|
43
|
+
);
|
|
35
44
|
function handleDragOver(e) {
|
|
36
45
|
e.preventDefault();
|
|
37
46
|
if (!disabled) setDragging(true);
|
|
@@ -106,6 +115,7 @@ function FileUpload({
|
|
|
106
115
|
)
|
|
107
116
|
}
|
|
108
117
|
);
|
|
109
|
-
}
|
|
118
|
+
});
|
|
119
|
+
FileUpload.displayName = "FileUpload";
|
|
110
120
|
|
|
111
121
|
export { FileUpload };
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var lucideReact = require('lucide-react');
|
|
6
6
|
var react = require('react');
|
|
7
|
+
var usePersistentState = require('../../hooks/usePersistentState');
|
|
7
8
|
var Headline = require('./Headline');
|
|
8
9
|
var styles = require('./Headline.module.css');
|
|
9
10
|
var Button = require('../button/Button');
|
|
@@ -29,26 +30,17 @@ function CollapsibleHeadline({
|
|
|
29
30
|
const generatedId = react.useId();
|
|
30
31
|
const panelId = controls != null ? controls : generatedId;
|
|
31
32
|
const isControlled = expanded !== void 0;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
() =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return stored !== null ? stored === "true" : defaultExpanded;
|
|
40
|
-
},
|
|
41
|
-
() => defaultExpanded
|
|
42
|
-
);
|
|
43
|
-
const isExpanded = isControlled ? expanded : hasToggled ? internalExpanded : persistedExpanded;
|
|
33
|
+
const { value: internalExpanded, setValue: setInternalExpanded } = usePersistentState.usePersistentState({
|
|
34
|
+
initialValue: defaultExpanded,
|
|
35
|
+
storageKey,
|
|
36
|
+
isControlled,
|
|
37
|
+
validate: (stored) => typeof stored === "boolean"
|
|
38
|
+
});
|
|
39
|
+
const isExpanded = isControlled ? expanded : internalExpanded;
|
|
44
40
|
const handleToggle = () => {
|
|
45
41
|
const next = !isExpanded;
|
|
46
42
|
if (!isControlled) {
|
|
47
|
-
setHasToggled(true);
|
|
48
43
|
setInternalExpanded(next);
|
|
49
|
-
if (storageKey) {
|
|
50
|
-
localStorage.setItem(storageKey, String(next));
|
|
51
|
-
}
|
|
52
44
|
}
|
|
53
45
|
onToggle == null ? void 0 : onToggle();
|
|
54
46
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { ChevronDown } from 'lucide-react';
|
|
4
|
-
import { useId
|
|
4
|
+
import { useId } from 'react';
|
|
5
|
+
import { usePersistentState } from '../../hooks/usePersistentState';
|
|
5
6
|
import { Headline } from './Headline';
|
|
6
7
|
import styles from './Headline.module.css';
|
|
7
8
|
import { Button } from '../button/Button';
|
|
@@ -23,26 +24,17 @@ function CollapsibleHeadline({
|
|
|
23
24
|
const generatedId = useId();
|
|
24
25
|
const panelId = controls != null ? controls : generatedId;
|
|
25
26
|
const isControlled = expanded !== void 0;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
() =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return stored !== null ? stored === "true" : defaultExpanded;
|
|
34
|
-
},
|
|
35
|
-
() => defaultExpanded
|
|
36
|
-
);
|
|
37
|
-
const isExpanded = isControlled ? expanded : hasToggled ? internalExpanded : persistedExpanded;
|
|
27
|
+
const { value: internalExpanded, setValue: setInternalExpanded } = usePersistentState({
|
|
28
|
+
initialValue: defaultExpanded,
|
|
29
|
+
storageKey,
|
|
30
|
+
isControlled,
|
|
31
|
+
validate: (stored) => typeof stored === "boolean"
|
|
32
|
+
});
|
|
33
|
+
const isExpanded = isControlled ? expanded : internalExpanded;
|
|
38
34
|
const handleToggle = () => {
|
|
39
35
|
const next = !isExpanded;
|
|
40
36
|
if (!isControlled) {
|
|
41
|
-
setHasToggled(true);
|
|
42
37
|
setInternalExpanded(next);
|
|
43
|
-
if (storageKey) {
|
|
44
|
-
localStorage.setItem(storageKey, String(next));
|
|
45
|
-
}
|
|
46
38
|
}
|
|
47
39
|
onToggle == null ? void 0 : onToggle();
|
|
48
40
|
};
|
package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.cjs
CHANGED
|
@@ -106,29 +106,42 @@ function ExpandableSidebarItem({
|
|
|
106
106
|
);
|
|
107
107
|
};
|
|
108
108
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles__default.default.container}`, children: [
|
|
109
|
-
/* @__PURE__ */ jsxRuntime.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.itemRow, children: [
|
|
110
|
+
/* @__PURE__ */ jsxRuntime.jsx(Component, { onClick: () => toggleAccordion(void 0, true), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
111
|
+
SidebarItemContent.SidebarItemContent,
|
|
112
|
+
{
|
|
113
|
+
headerStyle: expanded,
|
|
114
|
+
icon,
|
|
115
|
+
iconWidth,
|
|
116
|
+
label,
|
|
117
|
+
href,
|
|
118
|
+
disableActiveStyles: expanded,
|
|
119
|
+
truncateLabel
|
|
120
|
+
}
|
|
121
|
+
) }),
|
|
122
|
+
!isSidebarCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.toggleButton, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
123
|
+
Button.Button,
|
|
124
|
+
{
|
|
125
|
+
variant: "outlined",
|
|
126
|
+
"aria-label": expanded ? `Collapse ${label}` : `Expand ${label}`,
|
|
127
|
+
"aria-expanded": expanded,
|
|
128
|
+
"aria-controls": `${href}-children`,
|
|
129
|
+
onClick: toggleAccordion,
|
|
130
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
131
|
+
lucideReact.ChevronDown,
|
|
132
|
+
{
|
|
133
|
+
className: `${styles__default.default.chevron} ${expanded ? styles__default.default.chevronExpanded : ""}`
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
) })
|
|
138
|
+
] }),
|
|
127
139
|
expanded && !isSidebarCollapsed && /* @__PURE__ */ jsxRuntime.jsx(
|
|
128
140
|
"div",
|
|
129
141
|
{
|
|
142
|
+
id: `${href}-children`,
|
|
130
143
|
onAnimationEnd: handleAnimationEnd,
|
|
131
|
-
className: `${styles__default.default.childrenContainer} ${closing ? "animate--collapse" :
|
|
144
|
+
className: `${styles__default.default.childrenContainer} ${closing ? "animate--collapse" : ""}`,
|
|
132
145
|
children: items.map((item, idx) => renderNavItem(item, `${href}-${idx}`))
|
|
133
146
|
}
|
|
134
147
|
)
|
|
@@ -100,29 +100,42 @@ function ExpandableSidebarItem({
|
|
|
100
100
|
);
|
|
101
101
|
};
|
|
102
102
|
return /* @__PURE__ */ jsxs("div", { className: `${styles.container}`, children: [
|
|
103
|
-
/* @__PURE__ */
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: styles.itemRow, children: [
|
|
104
|
+
/* @__PURE__ */ jsx(Component, { onClick: () => toggleAccordion(void 0, true), children: /* @__PURE__ */ jsx(
|
|
105
|
+
SidebarItemContent,
|
|
106
|
+
{
|
|
107
|
+
headerStyle: expanded,
|
|
108
|
+
icon,
|
|
109
|
+
iconWidth,
|
|
110
|
+
label,
|
|
111
|
+
href,
|
|
112
|
+
disableActiveStyles: expanded,
|
|
113
|
+
truncateLabel
|
|
114
|
+
}
|
|
115
|
+
) }),
|
|
116
|
+
!isSidebarCollapsed && /* @__PURE__ */ jsx("div", { className: styles.toggleButton, children: /* @__PURE__ */ jsx(
|
|
117
|
+
Button,
|
|
118
|
+
{
|
|
119
|
+
variant: "outlined",
|
|
120
|
+
"aria-label": expanded ? `Collapse ${label}` : `Expand ${label}`,
|
|
121
|
+
"aria-expanded": expanded,
|
|
122
|
+
"aria-controls": `${href}-children`,
|
|
123
|
+
onClick: toggleAccordion,
|
|
124
|
+
children: /* @__PURE__ */ jsx(
|
|
125
|
+
ChevronDown,
|
|
126
|
+
{
|
|
127
|
+
className: `${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
) })
|
|
132
|
+
] }),
|
|
121
133
|
expanded && !isSidebarCollapsed && /* @__PURE__ */ jsx(
|
|
122
134
|
"div",
|
|
123
135
|
{
|
|
136
|
+
id: `${href}-children`,
|
|
124
137
|
onAnimationEnd: handleAnimationEnd,
|
|
125
|
-
className: `${styles.childrenContainer} ${closing ? "animate--collapse" :
|
|
138
|
+
className: `${styles.childrenContainer} ${closing ? "animate--collapse" : ""}`,
|
|
126
139
|
children: items.map((item, idx) => renderNavItem(item, `${href}-${idx}`))
|
|
127
140
|
}
|
|
128
141
|
)
|
package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css
CHANGED
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
border-radius: var(--border-radius-default);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
.itemRow {
|
|
9
|
+
display: grid;
|
|
10
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
11
|
+
align-items: center;
|
|
12
|
+
column-gap: var(--spacing-xs);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.toggleButton {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
flex: 0 0 auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
.container button {
|
|
9
23
|
color: var(--color-text);
|
|
10
24
|
min-block-size: 20px !important;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var react = require('react');
|
|
5
|
+
var usePersistentState = require('../../hooks/usePersistentState');
|
|
5
6
|
var styles = require('./Tabs.module.css');
|
|
6
7
|
var Chip = require('../chip/Chip');
|
|
7
8
|
var Headline = require('../headline/Headline');
|
|
@@ -19,6 +20,9 @@ function getFirstEnabledId(items) {
|
|
|
19
20
|
var _a;
|
|
20
21
|
return (_a = items.find((t) => !t.hidden && !t.disabled)) == null ? void 0 : _a.id;
|
|
21
22
|
}
|
|
23
|
+
function isTabId(value) {
|
|
24
|
+
return typeof value === "string" || typeof value === "number";
|
|
25
|
+
}
|
|
22
26
|
function normalizeFromChildren(children) {
|
|
23
27
|
const items = [];
|
|
24
28
|
react.Children.forEach(children, (child) => {
|
|
@@ -46,6 +50,7 @@ function Tabs({
|
|
|
46
50
|
tabs,
|
|
47
51
|
value,
|
|
48
52
|
defaultValue,
|
|
53
|
+
storageKey,
|
|
49
54
|
onValueChange,
|
|
50
55
|
addition,
|
|
51
56
|
disableTopPadding,
|
|
@@ -59,8 +64,16 @@ function Tabs({
|
|
|
59
64
|
}, [tabs, children]);
|
|
60
65
|
const visibleTabs = react.useMemo(() => sourceTabs.filter((t) => !t.hidden), [sourceTabs]);
|
|
61
66
|
const isControlled = value !== void 0;
|
|
62
|
-
const
|
|
63
|
-
|
|
67
|
+
const fallbackValue = defaultValue != null ? defaultValue : getFirstEnabledId(visibleTabs);
|
|
68
|
+
const validateStoredTabId = react.useCallback(
|
|
69
|
+
(stored) => isTabId(stored) && visibleTabs.some((t) => t.id === stored && !t.disabled),
|
|
70
|
+
[visibleTabs]
|
|
71
|
+
);
|
|
72
|
+
const { value: internalValue, setValue: setInternalValue } = usePersistentState.usePersistentState({
|
|
73
|
+
initialValue: fallbackValue,
|
|
74
|
+
storageKey,
|
|
75
|
+
isControlled,
|
|
76
|
+
validate: validateStoredTabId
|
|
64
77
|
});
|
|
65
78
|
const currentValue = isControlled ? value : internalValue;
|
|
66
79
|
const activeIndex = react.useMemo(() => {
|
|
@@ -77,7 +90,7 @@ function Tabs({
|
|
|
77
90
|
if (!isControlled) setInternalValue(nextId);
|
|
78
91
|
onValueChange == null ? void 0 : onValueChange(nextId, tab, idx);
|
|
79
92
|
},
|
|
80
|
-
[visibleTabs, isControlled, onValueChange]
|
|
93
|
+
[visibleTabs, isControlled, onValueChange, setInternalValue]
|
|
81
94
|
);
|
|
82
95
|
react.useEffect(() => {
|
|
83
96
|
if (!visibleTabs.length) return;
|
|
@@ -21,6 +21,11 @@ export interface TabsProps {
|
|
|
21
21
|
value?: TabId;
|
|
22
22
|
/** Uncontrolled initial */
|
|
23
23
|
defaultValue?: TabId;
|
|
24
|
+
/**
|
|
25
|
+
* Persist the active tab id in localStorage when used uncontrolled.
|
|
26
|
+
* Ignored in controlled mode.
|
|
27
|
+
*/
|
|
28
|
+
storageKey?: string;
|
|
24
29
|
onValueChange?: (id: TabId, tab: TabItem, index: number) => void;
|
|
25
30
|
addition?: ReactNode;
|
|
26
31
|
disableTopPadding?: boolean;
|
|
@@ -41,7 +46,7 @@ type SlotName = 'Item';
|
|
|
41
46
|
type TabsItemComponent = ((props: TabsItemProps) => JSX.Element) & {
|
|
42
47
|
__TABS_SLOT__?: SlotName;
|
|
43
48
|
};
|
|
44
|
-
export declare function Tabs({ header, subheader, variant, panelStyle, tabs, value, defaultValue, onValueChange, addition, disableTopPadding, loading, children, }: TabsProps): JSX.Element;
|
|
49
|
+
export declare function Tabs({ header, subheader, variant, panelStyle, tabs, value, defaultValue, storageKey, onValueChange, addition, disableTopPadding, loading, children, }: TabsProps): JSX.Element;
|
|
45
50
|
export declare namespace Tabs {
|
|
46
51
|
var Item: TabsItemComponent;
|
|
47
52
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useId, useMemo,
|
|
2
|
+
import { useId, useMemo, useCallback, useEffect, Children, isValidElement } from 'react';
|
|
3
|
+
import { usePersistentState } from '../../hooks/usePersistentState';
|
|
3
4
|
import styles from './Tabs.module.css';
|
|
4
5
|
import { Chip } from '../chip/Chip';
|
|
5
6
|
import { Headline } from '../headline/Headline';
|
|
@@ -13,6 +14,9 @@ function getFirstEnabledId(items) {
|
|
|
13
14
|
var _a;
|
|
14
15
|
return (_a = items.find((t) => !t.hidden && !t.disabled)) == null ? void 0 : _a.id;
|
|
15
16
|
}
|
|
17
|
+
function isTabId(value) {
|
|
18
|
+
return typeof value === "string" || typeof value === "number";
|
|
19
|
+
}
|
|
16
20
|
function normalizeFromChildren(children) {
|
|
17
21
|
const items = [];
|
|
18
22
|
Children.forEach(children, (child) => {
|
|
@@ -40,6 +44,7 @@ function Tabs({
|
|
|
40
44
|
tabs,
|
|
41
45
|
value,
|
|
42
46
|
defaultValue,
|
|
47
|
+
storageKey,
|
|
43
48
|
onValueChange,
|
|
44
49
|
addition,
|
|
45
50
|
disableTopPadding,
|
|
@@ -53,8 +58,16 @@ function Tabs({
|
|
|
53
58
|
}, [tabs, children]);
|
|
54
59
|
const visibleTabs = useMemo(() => sourceTabs.filter((t) => !t.hidden), [sourceTabs]);
|
|
55
60
|
const isControlled = value !== void 0;
|
|
56
|
-
const
|
|
57
|
-
|
|
61
|
+
const fallbackValue = defaultValue != null ? defaultValue : getFirstEnabledId(visibleTabs);
|
|
62
|
+
const validateStoredTabId = useCallback(
|
|
63
|
+
(stored) => isTabId(stored) && visibleTabs.some((t) => t.id === stored && !t.disabled),
|
|
64
|
+
[visibleTabs]
|
|
65
|
+
);
|
|
66
|
+
const { value: internalValue, setValue: setInternalValue } = usePersistentState({
|
|
67
|
+
initialValue: fallbackValue,
|
|
68
|
+
storageKey,
|
|
69
|
+
isControlled,
|
|
70
|
+
validate: validateStoredTabId
|
|
58
71
|
});
|
|
59
72
|
const currentValue = isControlled ? value : internalValue;
|
|
60
73
|
const activeIndex = useMemo(() => {
|
|
@@ -71,7 +84,7 @@ function Tabs({
|
|
|
71
84
|
if (!isControlled) setInternalValue(nextId);
|
|
72
85
|
onValueChange == null ? void 0 : onValueChange(nextId, tab, idx);
|
|
73
86
|
},
|
|
74
|
-
[visibleTabs, isControlled, onValueChange]
|
|
87
|
+
[visibleTabs, isControlled, onValueChange, setInternalValue]
|
|
75
88
|
);
|
|
76
89
|
useEffect(() => {
|
|
77
90
|
if (!visibleTabs.length) return;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var localStorage_utils = require('../utils/localStorage.utils');
|
|
6
|
+
|
|
7
|
+
function usePersistentState({
|
|
8
|
+
initialValue,
|
|
9
|
+
storageKey,
|
|
10
|
+
isControlled = false,
|
|
11
|
+
validate
|
|
12
|
+
}) {
|
|
13
|
+
const shouldHydrate = !isControlled && Boolean(storageKey);
|
|
14
|
+
const [didHydrate, setDidHydrate] = react.useState(() => !shouldHydrate);
|
|
15
|
+
const [value, setValue] = react.useState(initialValue);
|
|
16
|
+
const hydrated = !shouldHydrate || didHydrate;
|
|
17
|
+
const validateRef = react.useRef(validate);
|
|
18
|
+
react.useEffect(() => {
|
|
19
|
+
validateRef.current = validate;
|
|
20
|
+
}, [validate]);
|
|
21
|
+
react.useEffect(() => {
|
|
22
|
+
if (!storageKey || !shouldHydrate) return;
|
|
23
|
+
const stored = localStorage_utils.readLocalStorage(storageKey);
|
|
24
|
+
const currentValidate = validateRef.current;
|
|
25
|
+
const nextValue = currentValidate == null || currentValidate(stored) ? stored : initialValue;
|
|
26
|
+
setValue(nextValue);
|
|
27
|
+
setDidHydrate(true);
|
|
28
|
+
}, [initialValue, shouldHydrate, storageKey]);
|
|
29
|
+
react.useEffect(() => {
|
|
30
|
+
if (isControlled || !storageKey) return;
|
|
31
|
+
if (!hydrated) return;
|
|
32
|
+
localStorage_utils.writeLocalStorage(storageKey, value);
|
|
33
|
+
}, [hydrated, isControlled, storageKey, value]);
|
|
34
|
+
return {
|
|
35
|
+
value,
|
|
36
|
+
setValue,
|
|
37
|
+
hydrated
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exports.usePersistentState = usePersistentState;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
export interface UsePersistentStateOptions<T> {
|
|
3
|
+
initialValue: T;
|
|
4
|
+
storageKey?: string;
|
|
5
|
+
isControlled?: boolean;
|
|
6
|
+
validate?: (value: unknown) => value is T;
|
|
7
|
+
}
|
|
8
|
+
export interface UsePersistentStateResult<T> {
|
|
9
|
+
value: T;
|
|
10
|
+
setValue: Dispatch<SetStateAction<T>>;
|
|
11
|
+
hydrated: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function usePersistentState<T>({ initialValue, storageKey, isControlled, validate, }: UsePersistentStateOptions<T>): UsePersistentStateResult<T>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { readLocalStorage, writeLocalStorage } from '../utils/localStorage.utils';
|
|
4
|
+
|
|
5
|
+
function usePersistentState({
|
|
6
|
+
initialValue,
|
|
7
|
+
storageKey,
|
|
8
|
+
isControlled = false,
|
|
9
|
+
validate
|
|
10
|
+
}) {
|
|
11
|
+
const shouldHydrate = !isControlled && Boolean(storageKey);
|
|
12
|
+
const [didHydrate, setDidHydrate] = useState(() => !shouldHydrate);
|
|
13
|
+
const [value, setValue] = useState(initialValue);
|
|
14
|
+
const hydrated = !shouldHydrate || didHydrate;
|
|
15
|
+
const validateRef = useRef(validate);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
validateRef.current = validate;
|
|
18
|
+
}, [validate]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!storageKey || !shouldHydrate) return;
|
|
21
|
+
const stored = readLocalStorage(storageKey);
|
|
22
|
+
const currentValidate = validateRef.current;
|
|
23
|
+
const nextValue = currentValidate == null || currentValidate(stored) ? stored : initialValue;
|
|
24
|
+
setValue(nextValue);
|
|
25
|
+
setDidHydrate(true);
|
|
26
|
+
}, [initialValue, shouldHydrate, storageKey]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (isControlled || !storageKey) return;
|
|
29
|
+
if (!hydrated) return;
|
|
30
|
+
writeLocalStorage(storageKey, value);
|
|
31
|
+
}, [hydrated, isControlled, storageKey, value]);
|
|
32
|
+
return {
|
|
33
|
+
value,
|
|
34
|
+
setValue,
|
|
35
|
+
hydrated
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { usePersistentState };
|