@dbcdk/react-components 0.0.161 → 0.0.163

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.
@@ -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);
@@ -137,7 +137,7 @@
137
137
  .navItems {
138
138
  display: flex;
139
139
  align-items: center;
140
- gap: var(--spacing-lg);
140
+ gap: var(--spacing-sm);
141
141
  min-inline-size: 0;
142
142
  list-style: none;
143
143
  margin: 0;
@@ -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-sm);
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);
@@ -219,6 +219,10 @@
219
219
  overflow: visible;
220
220
  }
221
221
 
222
+ .horizontal .content {
223
+ padding-top: var(--spacing-lg);
224
+ }
225
+
222
226
  .footer {
223
227
  min-width: 0;
224
228
  background: var(--color-bg-surface-subtle);
@@ -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 [didHydrate, setDidHydrate] = react.useState(() => !shouldHydrate);
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 = localStorage_utils.readLocalStorage(storageKey);
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
- localStorage_utils.writeLocalStorage(storageKey, value);
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 { useState, useRef, useEffect } from 'react';
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 [didHydrate, setDidHydrate] = useState(() => !shouldHydrate);
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 = readLocalStorage(storageKey);
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
- writeLocalStorage(storageKey, value);
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.161",
3
+ "version": "0.0.163",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",