@dbcdk/react-components 0.0.173 → 0.0.175
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 +14 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +2 -0
- package/dist/components/app-header/AppHeader.module.css +2 -2
- package/dist/components/button/Button.d.ts +3 -1
- package/dist/components/button/Button.module.css +26 -0
- package/dist/components/color-scheme-button/ColorSchemeButton.cjs +78 -0
- package/dist/components/color-scheme-button/ColorSchemeButton.d.ts +8 -0
- package/dist/components/color-scheme-button/ColorSchemeButton.js +75 -0
- package/dist/components/filter-field/FilterField.module.css +2 -2
- package/dist/components/forms/file-upload/FileUpload.module.css +3 -3
- package/dist/components/forms/form-select/FormSelect.d.ts +1 -1
- package/dist/components/forms/form-select/FormSelect.module.css +20 -1
- package/dist/components/forms/input/Input.cjs +1 -1
- package/dist/components/forms/input/Input.d.ts +1 -1
- package/dist/components/forms/input/Input.js +1 -1
- package/dist/components/forms/input/Input.module.css +40 -8
- package/dist/components/forms/text-area/Textarea.module.css +2 -1
- package/dist/components/forms/typeahead/Typeahead.cjs +1 -0
- package/dist/components/forms/typeahead/Typeahead.js +1 -0
- package/dist/components/hyperlink/Hyperlink.cjs +2 -1
- package/dist/components/hyperlink/Hyperlink.d.ts +2 -1
- package/dist/components/hyperlink/Hyperlink.js +2 -1
- package/dist/components/hyperlink/Hyperlink.module.css +16 -0
- package/dist/components/nav-bar/NavBar.cjs +1 -0
- package/dist/components/nav-bar/NavBar.js +1 -0
- package/dist/components/nav-bar/NavBar.module.css +10 -10
- package/dist/components/page-layout/PageLayout.module.css +1 -1
- package/dist/components/popover/Popover.cjs +4 -2
- package/dist/components/popover/Popover.d.ts +2 -0
- package/dist/components/popover/Popover.js +4 -2
- package/dist/components/popover/Popover.module.css +5 -0
- package/dist/components/theme-button/ThemeButton.cjs +30 -23
- package/dist/components/theme-button/ThemeButton.d.ts +9 -3
- package/dist/components/theme-button/ThemeButton.js +31 -24
- package/dist/components/theme-script/ThemeScript.cjs +50 -12
- package/dist/components/theme-script/ThemeScript.d.ts +35 -12
- package/dist/components/theme-script/ThemeScript.js +51 -13
- package/dist/hooks/useColorScheme.cjs +74 -0
- package/dist/hooks/useColorScheme.d.ts +6 -0
- package/dist/hooks/useColorScheme.js +72 -0
- package/dist/hooks/useTheme.cjs +116 -50
- package/dist/hooks/useTheme.d.ts +17 -5
- package/dist/hooks/useTheme.js +117 -51
- package/dist/index.cjs +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/styles/themes/dbc/colors.css +14 -2
- package/dist/styles/themes/filmstriben/theme.css +108 -0
- package/dist/styles/themes/types.cjs +8 -0
- package/dist/styles/themes/types.d.ts +24 -16
- package/dist/styles/themes/types.js +6 -0
- package/dist/themes/filmstriben.css +4 -0
- package/dist/utils/color-scheme.constants.cjs +11 -0
- package/dist/utils/color-scheme.constants.d.ts +4 -0
- package/dist/utils/color-scheme.constants.js +7 -0
- package/dist/utils/theme.constants.cjs +3 -7
- package/dist/utils/theme.constants.d.ts +2 -4
- package/dist/utils/theme.constants.js +3 -6
- package/package.json +1 -1
- package/dist/styles/themes/filmstriben-budget/theme.css +0 -22
- package/dist/themes/filmstriben-budget.css +0 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.container {
|
|
2
2
|
display: flex;
|
|
3
3
|
align-items: center;
|
|
4
|
-
gap: var(--spacing-
|
|
4
|
+
gap: var(--spacing-2xl);
|
|
5
5
|
inline-size: 100%;
|
|
6
6
|
box-sizing: border-box;
|
|
7
7
|
flex-grow: 1;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
block-size: var(--component-size-md);
|
|
41
41
|
border: none;
|
|
42
42
|
background: transparent;
|
|
43
|
-
color: var(--color-fg
|
|
43
|
+
color: var(--color-navbar-fg);
|
|
44
44
|
border-radius: var(--border-radius-default);
|
|
45
45
|
cursor: pointer;
|
|
46
46
|
padding: 0;
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
.burgerButton:hover {
|
|
51
|
-
background-color: var(--color-
|
|
51
|
+
background-color: color-mix(in srgb, var(--color-navbar-fg) 12%, transparent);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
.burgerButton:focus-visible {
|
|
55
55
|
outline: none;
|
|
56
|
-
box-shadow: var(--
|
|
56
|
+
box-shadow: inset 0 0 0 1px var(--color-navbar-fg);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/* Mobile dropdown content */
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
display: inline-flex;
|
|
171
171
|
align-items: center;
|
|
172
172
|
gap: var(--spacing-xs);
|
|
173
|
-
color: var(--color-fg-muted);
|
|
173
|
+
color: var(--color-navbar-fg-muted);
|
|
174
174
|
font-family: var(--font-family);
|
|
175
175
|
font-size: var(--font-size-sm);
|
|
176
176
|
text-decoration: none;
|
|
@@ -185,17 +185,17 @@
|
|
|
185
185
|
|
|
186
186
|
/* Hover + focus */
|
|
187
187
|
.link:hover {
|
|
188
|
-
color: var(--color-fg
|
|
188
|
+
color: var(--color-navbar-fg);
|
|
189
189
|
}
|
|
190
190
|
.link:focus-visible {
|
|
191
191
|
outline: none;
|
|
192
|
-
box-shadow: var(--
|
|
192
|
+
box-shadow: inset 0 0 0 1px var(--color-navbar-fg);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/* Active underline */
|
|
196
196
|
.link.active,
|
|
197
197
|
.link[aria-current='page'] {
|
|
198
|
-
color: var(--color-brand);
|
|
198
|
+
color: var(--color-navbar-brand);
|
|
199
199
|
box-shadow: none;
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
inset-block-end: 0;
|
|
207
207
|
inline-size: 0;
|
|
208
208
|
block-size: 2px;
|
|
209
|
-
background-color: var(--color-
|
|
209
|
+
background-color: var(--color-navbar-brand);
|
|
210
210
|
transition: inline-size var(--transition-fast) var(--ease-standard);
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
.link.active::after,
|
|
218
218
|
.link[aria-current='page']::after {
|
|
219
219
|
inline-size: 100%;
|
|
220
|
-
background-color: var(--color-brand);
|
|
220
|
+
background-color: var(--color-navbar-brand);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/* Icons/images inside links */
|
|
@@ -96,7 +96,8 @@ const Popover = React.forwardRef(function Popover2({
|
|
|
96
96
|
autoFocusContent = false,
|
|
97
97
|
returnFocus = true,
|
|
98
98
|
anchorRef,
|
|
99
|
-
overlayRef
|
|
99
|
+
overlayRef,
|
|
100
|
+
variant = "default"
|
|
100
101
|
}, ref) {
|
|
101
102
|
const internalId = React.useId();
|
|
102
103
|
const resolvedContentId = contentId != null ? contentId : `popover-${internalId}`;
|
|
@@ -266,7 +267,8 @@ const Popover = React.forwardRef(function Popover2({
|
|
|
266
267
|
if (lastCloseReasonRef.current === "outside") return;
|
|
267
268
|
(_b = (_a = triggerElRef.current) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a);
|
|
268
269
|
}, [isOpen, returnFocus]);
|
|
269
|
-
const
|
|
270
|
+
const chevronClassName = variant === "header" ? styles__default.default.chevronHeader : "dbc-muted-text";
|
|
271
|
+
const icon = isOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: chevronClassName, size: 20 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: chevronClassName, size: 20 });
|
|
270
272
|
const setOverlayRef = React__namespace.useCallback(
|
|
271
273
|
(node) => {
|
|
272
274
|
assignRef(overlayRef, node);
|
|
@@ -18,6 +18,8 @@ export interface PopoverProps {
|
|
|
18
18
|
returnFocus?: boolean;
|
|
19
19
|
anchorRef?: React.RefObject<HTMLElement | null>;
|
|
20
20
|
overlayRef?: React.Ref<HTMLDivElement>;
|
|
21
|
+
/** 'header' colors the built-in chevron icon with --color-navbar-fg tokens — only meaningful inside/near AppHeader */
|
|
22
|
+
variant?: 'default' | 'header';
|
|
21
23
|
}
|
|
22
24
|
export interface PopoverHandle {
|
|
23
25
|
close: () => void;
|
|
@@ -72,7 +72,8 @@ const Popover = forwardRef(function Popover2({
|
|
|
72
72
|
autoFocusContent = false,
|
|
73
73
|
returnFocus = true,
|
|
74
74
|
anchorRef,
|
|
75
|
-
overlayRef
|
|
75
|
+
overlayRef,
|
|
76
|
+
variant = "default"
|
|
76
77
|
}, ref) {
|
|
77
78
|
const internalId = useId();
|
|
78
79
|
const resolvedContentId = contentId != null ? contentId : `popover-${internalId}`;
|
|
@@ -242,7 +243,8 @@ const Popover = forwardRef(function Popover2({
|
|
|
242
243
|
if (lastCloseReasonRef.current === "outside") return;
|
|
243
244
|
(_b = (_a = triggerElRef.current) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a);
|
|
244
245
|
}, [isOpen, returnFocus]);
|
|
245
|
-
const
|
|
246
|
+
const chevronClassName = variant === "header" ? styles.chevronHeader : "dbc-muted-text";
|
|
247
|
+
const icon = isOpen ? /* @__PURE__ */ jsx(ChevronUp, { className: chevronClassName, size: 20 }) : /* @__PURE__ */ jsx(ChevronDown, { className: chevronClassName, size: 20 });
|
|
246
248
|
const setOverlayRef = React.useCallback(
|
|
247
249
|
(node) => {
|
|
248
250
|
assignRef(overlayRef, node);
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
position: fixed;
|
|
41
41
|
border: 1px solid var(--color-border-subtle);
|
|
42
42
|
background-color: var(--color-bg-surface);
|
|
43
|
+
color: var(--color-fg-default);
|
|
43
44
|
border-radius: var(--border-radius-lg);
|
|
44
45
|
padding: var(--spacing-xxs);
|
|
45
46
|
z-index: var(--z-popover);
|
|
@@ -50,3 +51,7 @@
|
|
|
50
51
|
.content[hidden] {
|
|
51
52
|
display: none;
|
|
52
53
|
}
|
|
54
|
+
|
|
55
|
+
.chevronHeader {
|
|
56
|
+
color: var(--color-navbar-fg-muted);
|
|
57
|
+
}
|
|
@@ -7,36 +7,43 @@ var Button = require('../../components/button/Button');
|
|
|
7
7
|
var Menu = require('../../components/menu/Menu');
|
|
8
8
|
var Popover = require('../../components/popover/Popover');
|
|
9
9
|
var useTheme = require('../../hooks/useTheme');
|
|
10
|
+
var types = require('../../styles/themes/types');
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function ThemeMenuSection() {
|
|
17
|
-
const { theme, switchTheme } = useTheme.useTheme();
|
|
12
|
+
function ThemeMenuSection({ themes, hrefs }) {
|
|
13
|
+
const { theme, switchTheme } = useTheme.useTheme({
|
|
14
|
+
defaultTheme: themes[0],
|
|
15
|
+
hrefs
|
|
16
|
+
});
|
|
18
17
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
19
|
-
/* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: "
|
|
20
|
-
|
|
18
|
+
/* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: "Tema" }),
|
|
19
|
+
themes.map((id) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
21
20
|
Menu.Menu.RadioItem,
|
|
22
21
|
{
|
|
23
22
|
name: "theme",
|
|
24
|
-
value:
|
|
25
|
-
checked: theme ===
|
|
26
|
-
label:
|
|
27
|
-
onValueChange: (
|
|
23
|
+
value: id,
|
|
24
|
+
checked: theme === id,
|
|
25
|
+
label: types.THEME_LABELS[id],
|
|
26
|
+
onValueChange: () => switchTheme(id)
|
|
28
27
|
},
|
|
29
|
-
|
|
28
|
+
id
|
|
30
29
|
))
|
|
31
30
|
] });
|
|
32
31
|
}
|
|
33
|
-
function ThemeButton({
|
|
34
|
-
|
|
32
|
+
function ThemeButton({
|
|
33
|
+
themes,
|
|
34
|
+
hrefs,
|
|
35
|
+
size,
|
|
36
|
+
variant = "outlined"
|
|
37
|
+
}) {
|
|
38
|
+
const { theme, switchTheme } = useTheme.useTheme({
|
|
39
|
+
defaultTheme: themes[0],
|
|
40
|
+
hrefs
|
|
41
|
+
});
|
|
35
42
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
36
43
|
Popover.Popover,
|
|
37
44
|
{
|
|
38
45
|
matchTriggerWidth: false,
|
|
39
|
-
minWidth: "
|
|
46
|
+
minWidth: "160px",
|
|
40
47
|
trigger: (handleClick, chevron) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
41
48
|
Button.Button,
|
|
42
49
|
{
|
|
@@ -52,20 +59,20 @@ function ThemeButton({ size, variant = "outlined" }) {
|
|
|
52
59
|
}
|
|
53
60
|
),
|
|
54
61
|
children: (close) => /* @__PURE__ */ jsxRuntime.jsxs(Menu.Menu, { children: [
|
|
55
|
-
/* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: "
|
|
56
|
-
|
|
62
|
+
/* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: "Tema" }),
|
|
63
|
+
themes.map((id) => /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Item, { active: theme === id, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
57
64
|
"button",
|
|
58
65
|
{
|
|
59
66
|
onClick: () => {
|
|
60
|
-
switchTheme(
|
|
67
|
+
switchTheme(id);
|
|
61
68
|
close();
|
|
62
69
|
},
|
|
63
70
|
children: [
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
types.THEME_LABELS[id],
|
|
72
|
+
theme === id && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16 })
|
|
66
73
|
]
|
|
67
74
|
}
|
|
68
|
-
) },
|
|
75
|
+
) }, id))
|
|
69
76
|
] })
|
|
70
77
|
}
|
|
71
78
|
);
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
import type { ButtonSize, ButtonVariant } from '../../components/button/Button';
|
|
3
|
-
|
|
3
|
+
import type { ThemeHrefs, ThemeId } from '../../hooks/useTheme';
|
|
4
|
+
export interface ThemeMenuSectionProps {
|
|
5
|
+
/** Every theme to offer as an option. The first entry is treated as the default. */
|
|
6
|
+
themes: readonly ThemeId[];
|
|
7
|
+
hrefs?: ThemeHrefs;
|
|
8
|
+
}
|
|
9
|
+
export interface ThemeButtonProps extends ThemeMenuSectionProps {
|
|
4
10
|
size?: ButtonSize;
|
|
5
11
|
variant?: ButtonVariant;
|
|
6
12
|
}
|
|
7
|
-
export declare function ThemeMenuSection(): JSX.Element;
|
|
8
|
-
export declare function ThemeButton({ size, variant }: ThemeButtonProps): JSX.Element;
|
|
13
|
+
export declare function ThemeMenuSection({ themes, hrefs }: ThemeMenuSectionProps): JSX.Element;
|
|
14
|
+
export declare function ThemeButton({ themes, hrefs, size, variant, }: ThemeButtonProps): JSX.Element;
|
|
@@ -1,40 +1,47 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import {
|
|
3
|
+
import { Check, Palette } from 'lucide-react';
|
|
4
4
|
import { Button } from '../../components/button/Button';
|
|
5
5
|
import { Menu } from '../../components/menu/Menu';
|
|
6
6
|
import { Popover } from '../../components/popover/Popover';
|
|
7
7
|
import { useTheme } from '../../hooks/useTheme';
|
|
8
|
+
import { THEME_LABELS } from '../../styles/themes/types';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function ThemeMenuSection() {
|
|
15
|
-
const { theme, switchTheme } = useTheme();
|
|
10
|
+
function ThemeMenuSection({ themes, hrefs }) {
|
|
11
|
+
const { theme, switchTheme } = useTheme({
|
|
12
|
+
defaultTheme: themes[0],
|
|
13
|
+
hrefs
|
|
14
|
+
});
|
|
16
15
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
-
/* @__PURE__ */ jsx(Menu.Header, { children: "
|
|
18
|
-
|
|
16
|
+
/* @__PURE__ */ jsx(Menu.Header, { children: "Tema" }),
|
|
17
|
+
themes.map((id) => /* @__PURE__ */ jsx(
|
|
19
18
|
Menu.RadioItem,
|
|
20
19
|
{
|
|
21
20
|
name: "theme",
|
|
22
|
-
value:
|
|
23
|
-
checked: theme ===
|
|
24
|
-
label:
|
|
25
|
-
onValueChange: (
|
|
21
|
+
value: id,
|
|
22
|
+
checked: theme === id,
|
|
23
|
+
label: THEME_LABELS[id],
|
|
24
|
+
onValueChange: () => switchTheme(id)
|
|
26
25
|
},
|
|
27
|
-
|
|
26
|
+
id
|
|
28
27
|
))
|
|
29
28
|
] });
|
|
30
29
|
}
|
|
31
|
-
function ThemeButton({
|
|
32
|
-
|
|
30
|
+
function ThemeButton({
|
|
31
|
+
themes,
|
|
32
|
+
hrefs,
|
|
33
|
+
size,
|
|
34
|
+
variant = "outlined"
|
|
35
|
+
}) {
|
|
36
|
+
const { theme, switchTheme } = useTheme({
|
|
37
|
+
defaultTheme: themes[0],
|
|
38
|
+
hrefs
|
|
39
|
+
});
|
|
33
40
|
return /* @__PURE__ */ jsx(
|
|
34
41
|
Popover,
|
|
35
42
|
{
|
|
36
43
|
matchTriggerWidth: false,
|
|
37
|
-
minWidth: "
|
|
44
|
+
minWidth: "160px",
|
|
38
45
|
trigger: (handleClick, chevron) => /* @__PURE__ */ jsxs(
|
|
39
46
|
Button,
|
|
40
47
|
{
|
|
@@ -50,20 +57,20 @@ function ThemeButton({ size, variant = "outlined" }) {
|
|
|
50
57
|
}
|
|
51
58
|
),
|
|
52
59
|
children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
|
|
53
|
-
/* @__PURE__ */ jsx(Menu.Header, { children: "
|
|
54
|
-
|
|
60
|
+
/* @__PURE__ */ jsx(Menu.Header, { children: "Tema" }),
|
|
61
|
+
themes.map((id) => /* @__PURE__ */ jsx(Menu.Item, { active: theme === id, children: /* @__PURE__ */ jsxs(
|
|
55
62
|
"button",
|
|
56
63
|
{
|
|
57
64
|
onClick: () => {
|
|
58
|
-
switchTheme(
|
|
65
|
+
switchTheme(id);
|
|
59
66
|
close();
|
|
60
67
|
},
|
|
61
68
|
children: [
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
THEME_LABELS[id],
|
|
70
|
+
theme === id && /* @__PURE__ */ jsx(Check, { size: 16 })
|
|
64
71
|
]
|
|
65
72
|
}
|
|
66
|
-
) },
|
|
73
|
+
) }, id))
|
|
67
74
|
] })
|
|
68
75
|
}
|
|
69
76
|
);
|
|
@@ -1,22 +1,60 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var colorScheme_constants = require('../../utils/color-scheme.constants');
|
|
4
5
|
var theme_constants = require('../../utils/theme.constants');
|
|
5
6
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function buildInitScript(defaultColorScheme, hrefs, defaultTheme, linkId, storageKey) {
|
|
8
|
+
const hrefById = {};
|
|
9
|
+
for (const [id, href] of Object.entries(hrefs)) {
|
|
10
|
+
if (id !== defaultTheme && href) hrefById[id] = href;
|
|
11
|
+
}
|
|
12
|
+
const themeBlock = defaultTheme === void 0 ? "" : `try{
|
|
13
|
+
var THEME_KEY=${JSON.stringify(storageKey)};
|
|
14
|
+
var THEME_DEFAULT=${JSON.stringify(defaultTheme)};
|
|
15
|
+
var HREFS=${JSON.stringify(hrefById)};
|
|
16
|
+
var LINK_ID=${JSON.stringify(linkId)};
|
|
17
|
+
var resolvedTheme=getCookie(THEME_KEY);
|
|
18
|
+
if(!resolvedTheme){try{resolvedTheme=localStorage.getItem(THEME_KEY)}catch(e){}}
|
|
19
|
+
if(!resolvedTheme)resolvedTheme=THEME_DEFAULT;
|
|
20
|
+
if(resolvedTheme!==THEME_DEFAULT&&HREFS[resolvedTheme]){
|
|
21
|
+
var link=document.createElement('link');
|
|
22
|
+
link.id=LINK_ID;
|
|
23
|
+
link.rel='stylesheet';
|
|
24
|
+
link.href=HREFS[resolvedTheme];
|
|
25
|
+
document.head.appendChild(link);
|
|
26
|
+
}
|
|
27
|
+
document.documentElement.dataset.productTheme=resolvedTheme;
|
|
28
|
+
}catch(e){}`;
|
|
29
|
+
return `(function(){
|
|
11
30
|
function getCookie(name){var m=document.cookie.match(new RegExp('(?:^|; )'+name+'=([^;]*)'));return m?decodeURIComponent(m[1]):null}
|
|
12
|
-
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
31
|
+
try{
|
|
32
|
+
var SCHEME_KEY=${JSON.stringify(colorScheme_constants.COLOR_SCHEME_STORAGE_KEY)};
|
|
33
|
+
var SCHEMES=${JSON.stringify(colorScheme_constants.COLOR_SCHEMES)};
|
|
34
|
+
function isScheme(x){return !!x && SCHEMES.indexOf(x)!==-1}
|
|
35
|
+
var resolvedScheme=${JSON.stringify(defaultColorScheme)};
|
|
36
|
+
var fromCookie=getCookie(SCHEME_KEY);
|
|
37
|
+
if(isScheme(fromCookie)){resolvedScheme=fromCookie}else{try{var fromStorage=localStorage.getItem(SCHEME_KEY);if(isScheme(fromStorage))resolvedScheme=fromStorage}catch(e){}}
|
|
38
|
+
document.documentElement.dataset.theme=resolvedScheme;
|
|
39
|
+
}catch(e){}
|
|
40
|
+
${themeBlock}
|
|
41
|
+
})();`;
|
|
17
42
|
}
|
|
18
|
-
function ThemeScript({
|
|
19
|
-
|
|
43
|
+
function ThemeScript({
|
|
44
|
+
defaultColorScheme = "system",
|
|
45
|
+
defaultTheme,
|
|
46
|
+
hrefs = {},
|
|
47
|
+
linkId = theme_constants.THEME_LINK_ID,
|
|
48
|
+
storageKey = theme_constants.THEME_STORAGE_KEY
|
|
49
|
+
}) {
|
|
50
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
51
|
+
"script",
|
|
52
|
+
{
|
|
53
|
+
dangerouslySetInnerHTML: {
|
|
54
|
+
__html: buildInitScript(defaultColorScheme, hrefs, defaultTheme, linkId, storageKey)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
);
|
|
20
58
|
}
|
|
21
59
|
|
|
22
60
|
exports.ThemeScript = ThemeScript;
|
|
@@ -1,25 +1,48 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ThemeHrefs, ThemeId } from '../../styles/themes/types';
|
|
2
|
+
import type { ColorScheme } from '../../utils/color-scheme.constants';
|
|
2
3
|
export interface ThemeScriptProps {
|
|
3
|
-
/**
|
|
4
|
-
|
|
4
|
+
/** Color scheme applied when nothing is stored yet. Defaults to 'system'. */
|
|
5
|
+
defaultColorScheme?: ColorScheme;
|
|
6
|
+
/**
|
|
7
|
+
* The product theme the app already loads via a normal static import. Omit
|
|
8
|
+
* entirely if the app only ever ships one theme (e.g. just dbc) — no
|
|
9
|
+
* `data-product-theme` handling happens at all in that case.
|
|
10
|
+
*/
|
|
11
|
+
defaultTheme?: ThemeId;
|
|
12
|
+
/** Per-theme CSS URL, for any theme not already loaded some other way. */
|
|
13
|
+
hrefs?: ThemeHrefs;
|
|
14
|
+
/** DOM id for the `<link>` this script (and useTheme) manage. */
|
|
15
|
+
linkId?: string;
|
|
16
|
+
storageKey?: string;
|
|
5
17
|
}
|
|
6
18
|
/**
|
|
7
|
-
* Renders a blocking inline <script> that
|
|
8
|
-
*
|
|
9
|
-
* — before the page paints and before React
|
|
19
|
+
* Renders a blocking inline <script> that restores both the persisted color
|
|
20
|
+
* scheme (light/dark/system) and, if `defaultTheme` is given, the persisted
|
|
21
|
+
* product theme — synchronously, before the page paints and before React
|
|
22
|
+
* hydrates. One tag covers both axes, so most apps only ever need this one
|
|
23
|
+
* component in `<head>`.
|
|
10
24
|
*
|
|
11
|
-
* `useTheme()` alone
|
|
12
|
-
* resolution runs inside a `useEffect`, which
|
|
13
|
-
* the first render has already been committed (and,
|
|
14
|
-
* `document` to read a cookie from at all). Closing
|
|
15
|
-
* that runs outside React's render lifecycle — this
|
|
25
|
+
* Neither `useColorScheme()` nor `useTheme()` alone can prevent a flash of
|
|
26
|
+
* the wrong value on load: their resolution runs inside a `useEffect`, which
|
|
27
|
+
* only fires after the first render has already been committed (and,
|
|
28
|
+
* server-side, there is no `document` to read a cookie from at all). Closing
|
|
29
|
+
* that gap requires code that runs outside React's render lifecycle — this
|
|
30
|
+
* component is that code.
|
|
16
31
|
*
|
|
17
32
|
* Place it as early as possible in <head>, e.g. in Next.js's `_document.js`:
|
|
18
33
|
*
|
|
19
34
|
* ```tsx
|
|
20
35
|
* <Head>
|
|
36
|
+
* <ThemeScript defaultTheme="filmstriben" />
|
|
37
|
+
* </Head>
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Or, for an app with only one product theme (no `defaultTheme` needed):
|
|
41
|
+
*
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <Head>
|
|
21
44
|
* <ThemeScript />
|
|
22
45
|
* </Head>
|
|
23
46
|
* ```
|
|
24
47
|
*/
|
|
25
|
-
export declare function ThemeScript({ defaultTheme }: ThemeScriptProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
export declare function ThemeScript({ defaultColorScheme, defaultTheme, hrefs, linkId, storageKey, }: ThemeScriptProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,20 +1,58 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { COLOR_SCHEME_STORAGE_KEY, COLOR_SCHEMES } from '../../utils/color-scheme.constants';
|
|
3
|
+
import { THEME_LINK_ID, THEME_STORAGE_KEY } from '../../utils/theme.constants';
|
|
3
4
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
function buildInitScript(defaultColorScheme, hrefs, defaultTheme, linkId, storageKey) {
|
|
6
|
+
const hrefById = {};
|
|
7
|
+
for (const [id, href] of Object.entries(hrefs)) {
|
|
8
|
+
if (id !== defaultTheme && href) hrefById[id] = href;
|
|
9
|
+
}
|
|
10
|
+
const themeBlock = defaultTheme === void 0 ? "" : `try{
|
|
11
|
+
var THEME_KEY=${JSON.stringify(storageKey)};
|
|
12
|
+
var THEME_DEFAULT=${JSON.stringify(defaultTheme)};
|
|
13
|
+
var HREFS=${JSON.stringify(hrefById)};
|
|
14
|
+
var LINK_ID=${JSON.stringify(linkId)};
|
|
15
|
+
var resolvedTheme=getCookie(THEME_KEY);
|
|
16
|
+
if(!resolvedTheme){try{resolvedTheme=localStorage.getItem(THEME_KEY)}catch(e){}}
|
|
17
|
+
if(!resolvedTheme)resolvedTheme=THEME_DEFAULT;
|
|
18
|
+
if(resolvedTheme!==THEME_DEFAULT&&HREFS[resolvedTheme]){
|
|
19
|
+
var link=document.createElement('link');
|
|
20
|
+
link.id=LINK_ID;
|
|
21
|
+
link.rel='stylesheet';
|
|
22
|
+
link.href=HREFS[resolvedTheme];
|
|
23
|
+
document.head.appendChild(link);
|
|
24
|
+
}
|
|
25
|
+
document.documentElement.dataset.productTheme=resolvedTheme;
|
|
26
|
+
}catch(e){}`;
|
|
27
|
+
return `(function(){
|
|
9
28
|
function getCookie(name){var m=document.cookie.match(new RegExp('(?:^|; )'+name+'=([^;]*)'));return m?decodeURIComponent(m[1]):null}
|
|
10
|
-
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
try{
|
|
30
|
+
var SCHEME_KEY=${JSON.stringify(COLOR_SCHEME_STORAGE_KEY)};
|
|
31
|
+
var SCHEMES=${JSON.stringify(COLOR_SCHEMES)};
|
|
32
|
+
function isScheme(x){return !!x && SCHEMES.indexOf(x)!==-1}
|
|
33
|
+
var resolvedScheme=${JSON.stringify(defaultColorScheme)};
|
|
34
|
+
var fromCookie=getCookie(SCHEME_KEY);
|
|
35
|
+
if(isScheme(fromCookie)){resolvedScheme=fromCookie}else{try{var fromStorage=localStorage.getItem(SCHEME_KEY);if(isScheme(fromStorage))resolvedScheme=fromStorage}catch(e){}}
|
|
36
|
+
document.documentElement.dataset.theme=resolvedScheme;
|
|
37
|
+
}catch(e){}
|
|
38
|
+
${themeBlock}
|
|
39
|
+
})();`;
|
|
15
40
|
}
|
|
16
|
-
function ThemeScript({
|
|
17
|
-
|
|
41
|
+
function ThemeScript({
|
|
42
|
+
defaultColorScheme = "system",
|
|
43
|
+
defaultTheme,
|
|
44
|
+
hrefs = {},
|
|
45
|
+
linkId = THEME_LINK_ID,
|
|
46
|
+
storageKey = THEME_STORAGE_KEY
|
|
47
|
+
}) {
|
|
48
|
+
return /* @__PURE__ */ jsx(
|
|
49
|
+
"script",
|
|
50
|
+
{
|
|
51
|
+
dangerouslySetInnerHTML: {
|
|
52
|
+
__html: buildInitScript(defaultColorScheme, hrefs, defaultTheme, linkId, storageKey)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
18
56
|
}
|
|
19
57
|
|
|
20
58
|
export { ThemeScript };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var colorScheme_constants = require('../utils/color-scheme.constants');
|
|
6
|
+
var cookie_utils = require('../utils/cookie.utils');
|
|
7
|
+
var localStorage_utils = require('../utils/localStorage.utils');
|
|
8
|
+
|
|
9
|
+
function persistColorScheme(id) {
|
|
10
|
+
localStorage_utils.writeLocalStorage(colorScheme_constants.COLOR_SCHEME_STORAGE_KEY, id);
|
|
11
|
+
cookie_utils.writeCookie(colorScheme_constants.COLOR_SCHEME_STORAGE_KEY, id);
|
|
12
|
+
}
|
|
13
|
+
function getDomColorScheme() {
|
|
14
|
+
return document.documentElement.dataset.theme;
|
|
15
|
+
}
|
|
16
|
+
function applyColorScheme(id) {
|
|
17
|
+
document.documentElement.dataset.theme = id;
|
|
18
|
+
}
|
|
19
|
+
let currentColorScheme = null;
|
|
20
|
+
let resolved = false;
|
|
21
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
22
|
+
function notify() {
|
|
23
|
+
listeners.forEach((listener) => listener());
|
|
24
|
+
}
|
|
25
|
+
function subscribe(listener) {
|
|
26
|
+
listeners.add(listener);
|
|
27
|
+
return () => {
|
|
28
|
+
listeners.delete(listener);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function getSnapshot() {
|
|
32
|
+
return currentColorScheme;
|
|
33
|
+
}
|
|
34
|
+
function getServerSnapshot() {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function resolveInitialColorScheme(initialColorScheme) {
|
|
38
|
+
const fromDomAttribute = getDomColorScheme();
|
|
39
|
+
let resolvedColorScheme = colorScheme_constants.isColorScheme(fromDomAttribute) ? fromDomAttribute : initialColorScheme;
|
|
40
|
+
const fromCookie = cookie_utils.readCookie(colorScheme_constants.COLOR_SCHEME_STORAGE_KEY);
|
|
41
|
+
if (colorScheme_constants.isColorScheme(fromCookie)) {
|
|
42
|
+
resolvedColorScheme = fromCookie;
|
|
43
|
+
} else {
|
|
44
|
+
const fromStorage = localStorage_utils.readLocalStorage(colorScheme_constants.COLOR_SCHEME_STORAGE_KEY);
|
|
45
|
+
if (colorScheme_constants.isColorScheme(fromStorage)) resolvedColorScheme = fromStorage;
|
|
46
|
+
}
|
|
47
|
+
return resolvedColorScheme;
|
|
48
|
+
}
|
|
49
|
+
function ensureResolved(initialColorScheme) {
|
|
50
|
+
if (resolved) return;
|
|
51
|
+
resolved = true;
|
|
52
|
+
const value = resolveInitialColorScheme(initialColorScheme);
|
|
53
|
+
applyColorScheme(value);
|
|
54
|
+
persistColorScheme(value);
|
|
55
|
+
currentColorScheme = value;
|
|
56
|
+
notify();
|
|
57
|
+
}
|
|
58
|
+
function switchColorScheme(id) {
|
|
59
|
+
applyColorScheme(id);
|
|
60
|
+
persistColorScheme(id);
|
|
61
|
+
currentColorScheme = id;
|
|
62
|
+
resolved = true;
|
|
63
|
+
notify();
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
function useColorScheme(initialColorScheme = "system") {
|
|
67
|
+
react.useEffect(() => {
|
|
68
|
+
ensureResolved(initialColorScheme);
|
|
69
|
+
}, [initialColorScheme]);
|
|
70
|
+
const colorScheme = react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
71
|
+
return { colorScheme, switchColorScheme };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exports.useColorScheme = useColorScheme;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ColorScheme } from '../utils/color-scheme.constants';
|
|
2
|
+
export type { ColorScheme };
|
|
3
|
+
export declare function useColorScheme(initialColorScheme?: ColorScheme): {
|
|
4
|
+
colorScheme: ColorScheme | null;
|
|
5
|
+
switchColorScheme: (id: ColorScheme) => ColorScheme;
|
|
6
|
+
};
|