@dbcdk/react-components 0.0.162 → 0.0.164
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/headline/Headline.module.css +4 -0
- package/dist/components/menu/Menu.cjs +15 -1
- package/dist/components/menu/Menu.d.ts +4 -0
- package/dist/components/menu/Menu.js +15 -1
- package/dist/components/menu/Menu.module.css +16 -0
- package/dist/components/nav-bar/NavBar.module.css +1 -1
- package/dist/components/page-layout/PageLayout.module.css +4 -0
- package/dist/components/tabs/Tabs.cjs +1 -3
- package/dist/components/tabs/Tabs.d.ts +1 -2
- package/dist/components/tabs/Tabs.js +1 -3
- package/dist/components/tabs/Tabs.module.css +5 -12
- package/dist/hooks/usePersistentState.cjs +29 -9
- package/dist/hooks/usePersistentState.d.ts +21 -1
- package/dist/hooks/usePersistentState.js +30 -10
- package/dist/utils/cookie.utils.cjs +44 -0
- package/dist/utils/cookie.utils.d.ts +19 -0
- package/dist/utils/cookie.utils.js +40 -0
- package/package.json +1 -1
|
@@ -428,6 +428,19 @@ const MenuHeader = React__namespace.forwardRef(
|
|
|
428
428
|
)
|
|
429
429
|
);
|
|
430
430
|
MenuHeader.displayName = "Menu.Header";
|
|
431
|
+
const MenuDescription = React__namespace.forwardRef(
|
|
432
|
+
({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
433
|
+
"li",
|
|
434
|
+
{
|
|
435
|
+
ref,
|
|
436
|
+
role: "presentation",
|
|
437
|
+
className: [styles__default.default.description, className].filter(Boolean).join(" "),
|
|
438
|
+
...props,
|
|
439
|
+
children
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
MenuDescription.displayName = "Menu.Description";
|
|
431
444
|
const Menu = Object.assign(MenuBase, {
|
|
432
445
|
Item: MenuItem,
|
|
433
446
|
CheckItem: MenuCheckItem,
|
|
@@ -435,7 +448,8 @@ const Menu = Object.assign(MenuBase, {
|
|
|
435
448
|
SwitchItem: MenuSwitchItem,
|
|
436
449
|
Label: MenuLabel,
|
|
437
450
|
Separator: MenuSeparator,
|
|
438
|
-
Header: MenuHeader
|
|
451
|
+
Header: MenuHeader,
|
|
452
|
+
Description: MenuDescription
|
|
439
453
|
});
|
|
440
454
|
|
|
441
455
|
exports.Menu = Menu;
|
|
@@ -15,6 +15,9 @@ export type MenuSeparatorProps = React.LiHTMLAttributes<HTMLLIElement>;
|
|
|
15
15
|
export interface MenuHeaderProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
16
16
|
children: React.ReactNode;
|
|
17
17
|
}
|
|
18
|
+
export interface MenuDescriptionProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
}
|
|
18
21
|
export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
19
22
|
children: React.ReactNode;
|
|
20
23
|
active?: boolean;
|
|
@@ -94,5 +97,6 @@ export declare const Menu: React.ForwardRefExoticComponent<MenuProps & React.Ref
|
|
|
94
97
|
SwitchItem: React.ForwardRefExoticComponent<MenuSwitchItemProps & React.RefAttributes<HTMLLIElement>>;
|
|
95
98
|
Separator: React.ForwardRefExoticComponent<MenuSeparatorProps & React.RefAttributes<HTMLLIElement>>;
|
|
96
99
|
Header: React.ForwardRefExoticComponent<MenuHeaderProps & React.RefAttributes<HTMLLIElement>>;
|
|
100
|
+
Description: React.ForwardRefExoticComponent<MenuDescriptionProps & React.RefAttributes<HTMLLIElement>>;
|
|
97
101
|
Label: React.ForwardRefExoticComponent<MenuLabelProps & React.RefAttributes<HTMLSpanElement>>;
|
|
98
102
|
};
|
|
@@ -403,6 +403,19 @@ const MenuHeader = React.forwardRef(
|
|
|
403
403
|
)
|
|
404
404
|
);
|
|
405
405
|
MenuHeader.displayName = "Menu.Header";
|
|
406
|
+
const MenuDescription = React.forwardRef(
|
|
407
|
+
({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
408
|
+
"li",
|
|
409
|
+
{
|
|
410
|
+
ref,
|
|
411
|
+
role: "presentation",
|
|
412
|
+
className: [styles.description, className].filter(Boolean).join(" "),
|
|
413
|
+
...props,
|
|
414
|
+
children
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
);
|
|
418
|
+
MenuDescription.displayName = "Menu.Description";
|
|
406
419
|
const Menu = Object.assign(MenuBase, {
|
|
407
420
|
Item: MenuItem,
|
|
408
421
|
CheckItem: MenuCheckItem,
|
|
@@ -410,7 +423,8 @@ const Menu = Object.assign(MenuBase, {
|
|
|
410
423
|
SwitchItem: MenuSwitchItem,
|
|
411
424
|
Label: MenuLabel,
|
|
412
425
|
Separator: MenuSeparator,
|
|
413
|
-
Header: MenuHeader
|
|
426
|
+
Header: MenuHeader,
|
|
427
|
+
Description: MenuDescription
|
|
414
428
|
});
|
|
415
429
|
|
|
416
430
|
export { Menu };
|
|
@@ -251,6 +251,22 @@
|
|
|
251
251
|
color: inherit;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
.description {
|
|
255
|
+
padding-block: var(--spacing-xxs);
|
|
256
|
+
padding-inline: var(--spacing-md);
|
|
257
|
+
color: var(--color-fg-muted);
|
|
258
|
+
font-size: var(--font-size-xs);
|
|
259
|
+
font-weight: 400;
|
|
260
|
+
line-height: var(--line-height-normal);
|
|
261
|
+
text-transform: none;
|
|
262
|
+
letter-spacing: normal;
|
|
263
|
+
cursor: default;
|
|
264
|
+
user-select: text;
|
|
265
|
+
white-space: normal;
|
|
266
|
+
overflow-wrap: anywhere;
|
|
267
|
+
word-break: break-word;
|
|
268
|
+
}
|
|
269
|
+
|
|
254
270
|
/* Gap between items */
|
|
255
271
|
.gap {
|
|
256
272
|
gap: var(--spacing-xs);
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
gap: var(--spacing-xs);
|
|
173
173
|
color: var(--color-fg-muted);
|
|
174
174
|
font-family: var(--font-family);
|
|
175
|
-
font-size: var(--font-size-
|
|
175
|
+
font-size: var(--font-size-md);
|
|
176
176
|
text-decoration: none;
|
|
177
177
|
border-radius: var(--border-radius-default);
|
|
178
178
|
padding-block: var(--spacing-xs);
|
|
@@ -53,7 +53,6 @@ function Tabs({
|
|
|
53
53
|
storageKey,
|
|
54
54
|
onValueChange,
|
|
55
55
|
addition,
|
|
56
|
-
disableTopPadding,
|
|
57
56
|
loading = false,
|
|
58
57
|
children
|
|
59
58
|
}) {
|
|
@@ -139,8 +138,7 @@ function Tabs({
|
|
|
139
138
|
{
|
|
140
139
|
className: [
|
|
141
140
|
styles__default.default.headerContainer,
|
|
142
|
-
variant === "outlined" ? styles__default.default.headerContainerOutlined : ""
|
|
143
|
-
disableTopPadding ? styles__default.default.disableTopPadding : ""
|
|
141
|
+
variant === "outlined" ? styles__default.default.headerContainerOutlined : ""
|
|
144
142
|
].filter(Boolean).join(" "),
|
|
145
143
|
children: /* @__PURE__ */ jsxRuntime.jsx(Headline.Headline, { disableMargin: true, size: 2, subheader, addition, children: header })
|
|
146
144
|
}
|
|
@@ -28,7 +28,6 @@ export interface TabsProps {
|
|
|
28
28
|
storageKey?: string;
|
|
29
29
|
onValueChange?: (id: TabId, tab: TabItem, index: number) => void;
|
|
30
30
|
addition?: ReactNode;
|
|
31
|
-
disableTopPadding?: boolean;
|
|
32
31
|
loading?: boolean;
|
|
33
32
|
/** Composition API */
|
|
34
33
|
children?: ReactNode;
|
|
@@ -46,7 +45,7 @@ type SlotName = 'Item';
|
|
|
46
45
|
type TabsItemComponent = ((props: TabsItemProps) => JSX.Element) & {
|
|
47
46
|
__TABS_SLOT__?: SlotName;
|
|
48
47
|
};
|
|
49
|
-
export declare function Tabs({ header, subheader, variant, panelStyle, tabs, value, defaultValue, storageKey, onValueChange, addition,
|
|
48
|
+
export declare function Tabs({ header, subheader, variant, panelStyle, tabs, value, defaultValue, storageKey, onValueChange, addition, loading, children, }: TabsProps): JSX.Element;
|
|
50
49
|
export declare namespace Tabs {
|
|
51
50
|
var Item: TabsItemComponent;
|
|
52
51
|
}
|
|
@@ -47,7 +47,6 @@ function Tabs({
|
|
|
47
47
|
storageKey,
|
|
48
48
|
onValueChange,
|
|
49
49
|
addition,
|
|
50
|
-
disableTopPadding,
|
|
51
50
|
loading = false,
|
|
52
51
|
children
|
|
53
52
|
}) {
|
|
@@ -133,8 +132,7 @@ function Tabs({
|
|
|
133
132
|
{
|
|
134
133
|
className: [
|
|
135
134
|
styles.headerContainer,
|
|
136
|
-
variant === "outlined" ? styles.headerContainerOutlined : ""
|
|
137
|
-
disableTopPadding ? styles.disableTopPadding : ""
|
|
135
|
+
variant === "outlined" ? styles.headerContainerOutlined : ""
|
|
138
136
|
].filter(Boolean).join(" "),
|
|
139
137
|
children: /* @__PURE__ */ jsx(Headline, { disableMargin: true, size: 2, subheader, addition, children: header })
|
|
140
138
|
}
|
|
@@ -10,12 +10,9 @@
|
|
|
10
10
|
display: flex;
|
|
11
11
|
justify-content: space-between;
|
|
12
12
|
align-items: center;
|
|
13
|
-
padding-block: var(--spacing-lg);
|
|
14
|
-
gap: var(--spacing-md);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.headerContainer.disableTopPadding {
|
|
18
13
|
padding-block-start: 0;
|
|
14
|
+
padding-block-end: var(--spacing-lg);
|
|
15
|
+
gap: var(--spacing-md);
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
/* Outer wrapper for tablist + content */
|
|
@@ -188,12 +185,7 @@
|
|
|
188
185
|
}
|
|
189
186
|
|
|
190
187
|
.headerContainerOutlined {
|
|
191
|
-
padding-block-
|
|
192
|
-
padding-block-end: 0;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.headerContainerOutlined.disableTopPadding {
|
|
196
|
-
padding-block-start: 0;
|
|
188
|
+
padding-block-end: var(--spacing-xs);
|
|
197
189
|
}
|
|
198
190
|
|
|
199
191
|
.outlined .tab {
|
|
@@ -240,7 +232,8 @@
|
|
|
240
232
|
|
|
241
233
|
@media (max-width: 767px) {
|
|
242
234
|
.headerContainer {
|
|
243
|
-
padding-block:
|
|
235
|
+
padding-block-start: 0;
|
|
236
|
+
padding-block-end: var(--spacing-md);
|
|
244
237
|
}
|
|
245
238
|
|
|
246
239
|
.tabButton {
|
|
@@ -2,35 +2,55 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var react = require('react');
|
|
5
|
+
var cookie_utils = require('../utils/cookie.utils');
|
|
5
6
|
var localStorage_utils = require('../utils/localStorage.utils');
|
|
6
7
|
|
|
8
|
+
function readStorage(storage, key) {
|
|
9
|
+
return storage === "cookie" ? cookie_utils.readCookie(key) : localStorage_utils.readLocalStorage(key);
|
|
10
|
+
}
|
|
11
|
+
function writeStorage(storage, key, value, maxAgeSeconds) {
|
|
12
|
+
if (storage === "cookie") {
|
|
13
|
+
cookie_utils.writeCookie(key, value, maxAgeSeconds);
|
|
14
|
+
} else {
|
|
15
|
+
localStorage_utils.writeLocalStorage(key, value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
7
18
|
function usePersistentState({
|
|
8
19
|
initialValue,
|
|
9
20
|
storageKey,
|
|
10
21
|
isControlled = false,
|
|
11
|
-
validate
|
|
22
|
+
validate,
|
|
23
|
+
storage = "localStorage",
|
|
24
|
+
hydration = "effect",
|
|
25
|
+
maxAgeSeconds
|
|
12
26
|
}) {
|
|
13
27
|
const shouldHydrate = !isControlled && Boolean(storageKey);
|
|
14
|
-
const
|
|
15
|
-
const [value, setValue] = react.useState(initialValue);
|
|
16
|
-
const hydrated = !shouldHydrate || didHydrate;
|
|
28
|
+
const sync = hydration === "sync";
|
|
17
29
|
const validateRef = react.useRef(validate);
|
|
30
|
+
const [didHydrate, setDidHydrate] = react.useState(() => !shouldHydrate || sync);
|
|
31
|
+
const [value, setValue] = react.useState(() => {
|
|
32
|
+
if (!shouldHydrate || !sync) return initialValue;
|
|
33
|
+
const stored = readStorage(storage, storageKey);
|
|
34
|
+
const currentValidate = validateRef.current;
|
|
35
|
+
return currentValidate == null || currentValidate(stored) ? stored : initialValue;
|
|
36
|
+
});
|
|
37
|
+
const hydrated = !shouldHydrate || didHydrate;
|
|
18
38
|
react.useEffect(() => {
|
|
19
39
|
validateRef.current = validate;
|
|
20
40
|
}, [validate]);
|
|
21
41
|
react.useEffect(() => {
|
|
22
|
-
if (!storageKey || !shouldHydrate) return;
|
|
23
|
-
const stored =
|
|
42
|
+
if (!storageKey || !shouldHydrate || sync) return;
|
|
43
|
+
const stored = readStorage(storage, storageKey);
|
|
24
44
|
const currentValidate = validateRef.current;
|
|
25
45
|
const nextValue = currentValidate == null || currentValidate(stored) ? stored : initialValue;
|
|
26
46
|
setValue(nextValue);
|
|
27
47
|
setDidHydrate(true);
|
|
28
|
-
}, [initialValue, shouldHydrate, storageKey]);
|
|
48
|
+
}, [initialValue, shouldHydrate, storageKey, storage, sync]);
|
|
29
49
|
react.useEffect(() => {
|
|
30
50
|
if (isControlled || !storageKey) return;
|
|
31
51
|
if (!hydrated) return;
|
|
32
|
-
|
|
33
|
-
}, [hydrated, isControlled, storageKey, value]);
|
|
52
|
+
writeStorage(storage, storageKey, value, maxAgeSeconds);
|
|
53
|
+
}, [hydrated, isControlled, storageKey, storage, maxAgeSeconds, value]);
|
|
34
54
|
return {
|
|
35
55
|
value,
|
|
36
56
|
setValue,
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction } from 'react';
|
|
2
|
+
export type PersistentStateStorage = 'localStorage' | 'cookie';
|
|
3
|
+
/**
|
|
4
|
+
* 'effect' (default): the stored value is read in a useEffect after mount,
|
|
5
|
+
* so `value` starts as `initialValue` on every render — including the
|
|
6
|
+
* first client render of a component that was server-rendered — and only
|
|
7
|
+
* updates once the effect runs. This is what makes it hydration-safe.
|
|
8
|
+
*
|
|
9
|
+
* 'sync': the stored value is read synchronously during the initial
|
|
10
|
+
* render, so there's no post-mount update and no flash of `initialValue`.
|
|
11
|
+
* Only use this for components that are never part of an SSR/hydration
|
|
12
|
+
* pass (e.g. mounted purely client-side, behind a data-loading gate) —
|
|
13
|
+
* otherwise the server and the first client render will disagree and
|
|
14
|
+
* React will emit a hydration mismatch and force a visible patch-up,
|
|
15
|
+
* which is the exact flash this mode is meant to avoid.
|
|
16
|
+
*/
|
|
17
|
+
export type PersistentStateHydration = 'effect' | 'sync';
|
|
2
18
|
export interface UsePersistentStateOptions<T> {
|
|
3
19
|
initialValue: T;
|
|
4
20
|
storageKey?: string;
|
|
5
21
|
isControlled?: boolean;
|
|
6
22
|
validate?: (value: unknown) => value is T;
|
|
23
|
+
storage?: PersistentStateStorage;
|
|
24
|
+
hydration?: PersistentStateHydration;
|
|
25
|
+
/** Only used when storage is 'cookie'. Defaults to one year. */
|
|
26
|
+
maxAgeSeconds?: number;
|
|
7
27
|
}
|
|
8
28
|
export interface UsePersistentStateResult<T> {
|
|
9
29
|
value: T;
|
|
10
30
|
setValue: Dispatch<SetStateAction<T>>;
|
|
11
31
|
hydrated: boolean;
|
|
12
32
|
}
|
|
13
|
-
export declare function usePersistentState<T>({ initialValue, storageKey, isControlled, validate, }: UsePersistentStateOptions<T>): UsePersistentStateResult<T>;
|
|
33
|
+
export declare function usePersistentState<T>({ initialValue, storageKey, isControlled, validate, storage, hydration, maxAgeSeconds, }: UsePersistentStateOptions<T>): UsePersistentStateResult<T>;
|
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { useRef, useState, useEffect } from 'react';
|
|
3
|
+
import { readCookie, writeCookie } from '../utils/cookie.utils';
|
|
3
4
|
import { readLocalStorage, writeLocalStorage } from '../utils/localStorage.utils';
|
|
4
5
|
|
|
6
|
+
function readStorage(storage, key) {
|
|
7
|
+
return storage === "cookie" ? readCookie(key) : readLocalStorage(key);
|
|
8
|
+
}
|
|
9
|
+
function writeStorage(storage, key, value, maxAgeSeconds) {
|
|
10
|
+
if (storage === "cookie") {
|
|
11
|
+
writeCookie(key, value, maxAgeSeconds);
|
|
12
|
+
} else {
|
|
13
|
+
writeLocalStorage(key, value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
5
16
|
function usePersistentState({
|
|
6
17
|
initialValue,
|
|
7
18
|
storageKey,
|
|
8
19
|
isControlled = false,
|
|
9
|
-
validate
|
|
20
|
+
validate,
|
|
21
|
+
storage = "localStorage",
|
|
22
|
+
hydration = "effect",
|
|
23
|
+
maxAgeSeconds
|
|
10
24
|
}) {
|
|
11
25
|
const shouldHydrate = !isControlled && Boolean(storageKey);
|
|
12
|
-
const
|
|
13
|
-
const [value, setValue] = useState(initialValue);
|
|
14
|
-
const hydrated = !shouldHydrate || didHydrate;
|
|
26
|
+
const sync = hydration === "sync";
|
|
15
27
|
const validateRef = useRef(validate);
|
|
28
|
+
const [didHydrate, setDidHydrate] = useState(() => !shouldHydrate || sync);
|
|
29
|
+
const [value, setValue] = useState(() => {
|
|
30
|
+
if (!shouldHydrate || !sync) return initialValue;
|
|
31
|
+
const stored = readStorage(storage, storageKey);
|
|
32
|
+
const currentValidate = validateRef.current;
|
|
33
|
+
return currentValidate == null || currentValidate(stored) ? stored : initialValue;
|
|
34
|
+
});
|
|
35
|
+
const hydrated = !shouldHydrate || didHydrate;
|
|
16
36
|
useEffect(() => {
|
|
17
37
|
validateRef.current = validate;
|
|
18
38
|
}, [validate]);
|
|
19
39
|
useEffect(() => {
|
|
20
|
-
if (!storageKey || !shouldHydrate) return;
|
|
21
|
-
const stored =
|
|
40
|
+
if (!storageKey || !shouldHydrate || sync) return;
|
|
41
|
+
const stored = readStorage(storage, storageKey);
|
|
22
42
|
const currentValidate = validateRef.current;
|
|
23
43
|
const nextValue = currentValidate == null || currentValidate(stored) ? stored : initialValue;
|
|
24
44
|
setValue(nextValue);
|
|
25
45
|
setDidHydrate(true);
|
|
26
|
-
}, [initialValue, shouldHydrate, storageKey]);
|
|
46
|
+
}, [initialValue, shouldHydrate, storageKey, storage, sync]);
|
|
27
47
|
useEffect(() => {
|
|
28
48
|
if (isControlled || !storageKey) return;
|
|
29
49
|
if (!hydrated) return;
|
|
30
|
-
|
|
31
|
-
}, [hydrated, isControlled, storageKey, value]);
|
|
50
|
+
writeStorage(storage, storageKey, value, maxAgeSeconds);
|
|
51
|
+
}, [hydrated, isControlled, storageKey, storage, maxAgeSeconds, value]);
|
|
32
52
|
return {
|
|
33
53
|
value,
|
|
34
54
|
setValue,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
4
|
+
function isBrowser() {
|
|
5
|
+
return typeof document !== "undefined";
|
|
6
|
+
}
|
|
7
|
+
function readCookie(name) {
|
|
8
|
+
if (!isBrowser()) return void 0;
|
|
9
|
+
try {
|
|
10
|
+
const match = document.cookie.split("; ").find((row) => row.startsWith(`${name}=`));
|
|
11
|
+
if (match == null) return void 0;
|
|
12
|
+
const raw = decodeURIComponent(match.slice(name.length + 1));
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
} catch {
|
|
16
|
+
return raw;
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function writeCookie(name, value, maxAgeSeconds = ONE_YEAR_SECONDS) {
|
|
23
|
+
if (!isBrowser()) return;
|
|
24
|
+
try {
|
|
25
|
+
if (value === void 0) {
|
|
26
|
+
removeCookie(name);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
30
|
+
document.cookie = `${name}=${encodeURIComponent(raw)}; path=/; max-age=${maxAgeSeconds}`;
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function removeCookie(name) {
|
|
35
|
+
if (!isBrowser()) return;
|
|
36
|
+
try {
|
|
37
|
+
document.cookie = `${name}=; path=/; max-age=0`;
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.readCookie = readCookie;
|
|
43
|
+
exports.removeCookie = removeCookie;
|
|
44
|
+
exports.writeCookie = writeCookie;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely read from a cookie.
|
|
3
|
+
* - Returns undefined if not in browser or the cookie is missing
|
|
4
|
+
* - Returns parsed JSON if the value is JSON
|
|
5
|
+
* - Returns plain string if not JSON
|
|
6
|
+
* - Never throws
|
|
7
|
+
*/
|
|
8
|
+
export declare function readCookie<T = unknown>(name: string): T | string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Safely write a cookie.
|
|
11
|
+
* - Automatically JSON.stringifies objects/arrays/booleans/numbers
|
|
12
|
+
* - Stores plain strings as-is
|
|
13
|
+
* - Never throws
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeCookie(name: string, value: unknown, maxAgeSeconds?: number): void;
|
|
16
|
+
/**
|
|
17
|
+
* Remove a cookie safely
|
|
18
|
+
*/
|
|
19
|
+
export declare function removeCookie(name: string): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
2
|
+
function isBrowser() {
|
|
3
|
+
return typeof document !== "undefined";
|
|
4
|
+
}
|
|
5
|
+
function readCookie(name) {
|
|
6
|
+
if (!isBrowser()) return void 0;
|
|
7
|
+
try {
|
|
8
|
+
const match = document.cookie.split("; ").find((row) => row.startsWith(`${name}=`));
|
|
9
|
+
if (match == null) return void 0;
|
|
10
|
+
const raw = decodeURIComponent(match.slice(name.length + 1));
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
} catch {
|
|
14
|
+
return raw;
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function writeCookie(name, value, maxAgeSeconds = ONE_YEAR_SECONDS) {
|
|
21
|
+
if (!isBrowser()) return;
|
|
22
|
+
try {
|
|
23
|
+
if (value === void 0) {
|
|
24
|
+
removeCookie(name);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
28
|
+
document.cookie = `${name}=${encodeURIComponent(raw)}; path=/; max-age=${maxAgeSeconds}`;
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function removeCookie(name) {
|
|
33
|
+
if (!isBrowser()) return;
|
|
34
|
+
try {
|
|
35
|
+
document.cookie = `${name}=; path=/; max-age=0`;
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { readCookie, removeCookie, writeCookie };
|