@dbcdk/react-components 0.0.174 → 0.0.176

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.
Files changed (40) hide show
  1. package/README.md +17 -18
  2. package/dist/components/filter-field/FilterField.module.css +2 -2
  3. package/dist/components/forms/file-upload/FileUpload.module.css +3 -3
  4. package/dist/components/forms/form-select/FormSelect.module.css +1 -1
  5. package/dist/components/forms/input/Input.module.css +8 -8
  6. package/dist/components/forms/text-area/Textarea.module.css +2 -1
  7. package/dist/components/nav-bar/NavBar.module.css +1 -1
  8. package/dist/components/theme-button/ThemeButton.cjs +4 -15
  9. package/dist/components/theme-button/ThemeButton.d.ts +3 -4
  10. package/dist/components/theme-button/ThemeButton.js +4 -15
  11. package/dist/components/theme-script/ThemeScript.cjs +23 -16
  12. package/dist/components/theme-script/ThemeScript.d.ts +2 -6
  13. package/dist/components/theme-script/ThemeScript.js +24 -17
  14. package/dist/hooks/usePersistentState.cjs +6 -1
  15. package/dist/hooks/usePersistentState.d.ts +1 -1
  16. package/dist/hooks/usePersistentState.js +6 -1
  17. package/dist/hooks/useTheme.cjs +33 -96
  18. package/dist/hooks/useTheme.d.ts +3 -10
  19. package/dist/hooks/useTheme.js +34 -97
  20. package/dist/styles/themes/apply-product-theme.cjs +20 -0
  21. package/dist/styles/themes/apply-product-theme.d.ts +3 -0
  22. package/dist/styles/themes/apply-product-theme.js +18 -0
  23. package/dist/styles/themes/dbc/colors.css +2 -1
  24. package/dist/styles/themes/product-theme-tokens.cjs +39 -0
  25. package/dist/styles/themes/product-theme-tokens.d.ts +3 -0
  26. package/dist/styles/themes/product-theme-tokens.js +37 -0
  27. package/dist/styles/themes/product-themes.cjs +143 -0
  28. package/dist/styles/themes/product-themes.d.ts +11 -0
  29. package/dist/styles/themes/product-themes.js +140 -0
  30. package/dist/styles/themes/types.cjs +3 -2
  31. package/dist/styles/themes/types.d.ts +1 -13
  32. package/dist/styles/themes/types.js +3 -2
  33. package/dist/utils/sessionStorage.utils.cjs +53 -0
  34. package/dist/utils/sessionStorage.utils.d.ts +19 -0
  35. package/dist/utils/sessionStorage.utils.js +49 -0
  36. package/package.json +1 -1
  37. package/dist/styles/themes/filmstriben/theme.css +0 -55
  38. package/dist/themes/filmstriben.css +0 -4
  39. package/dist/types/assets.d.cjs +0 -2
  40. package/dist/types/assets.d.js +0 -1
@@ -1,15 +1,8 @@
1
- import type { ThemeHrefs, ThemeId } from '../styles/themes/types';
2
- export type { ThemeHrefs, ThemeId };
1
+ import type { ThemeId } from '../styles/themes/types';
2
+ export type { ThemeId };
3
3
  export interface UseThemeOptions {
4
- /** The theme the app already loads via a normal static import never
5
- * fetched/linked dynamically, just applied via the attribute. */
4
+ /** The theme the app should fall back to when nothing is stored yet. */
6
5
  defaultTheme: ThemeId;
7
- /** Per-theme CSS URL, for any theme not already loaded some other way. */
8
- hrefs?: ThemeHrefs;
9
- /** DOM id for the `<link>` element used for the currently active non-default theme. */
10
- linkId?: string;
11
- /** Warm the browser cache for every other theme at idle time. Default true. */
12
- preloadOthers?: boolean;
13
6
  storageKey?: string;
14
7
  }
15
8
  export declare function useTheme(options: UseThemeOptions): {
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import { useEffect, useSyncExternalStore } from 'react';
3
+ import { applyProductTheme } from '../styles/themes/apply-product-theme';
3
4
  import { readCookie, writeCookie } from '../utils/cookie.utils';
4
5
  import { readLocalStorage, writeLocalStorage } from '../utils/localStorage.utils';
5
- import { THEME_STORAGE_KEY, THEME_LINK_ID } from '../utils/theme.constants';
6
+ import { THEME_STORAGE_KEY } from '../utils/theme.constants';
6
7
 
7
- const PRELOAD_ID_PREFIX = "dbc-theme-preload-";
8
8
  function persistTheme(storageKey, id) {
9
9
  writeLocalStorage(storageKey, id);
10
10
  writeCookie(storageKey, id);
@@ -12,47 +12,16 @@ function persistTheme(storageKey, id) {
12
12
  function getDomTheme() {
13
13
  return document.documentElement.dataset.productTheme;
14
14
  }
15
+ function getDomColorScheme() {
16
+ return document.documentElement.dataset.theme;
17
+ }
15
18
  function applyTheme(id) {
16
19
  document.documentElement.dataset.productTheme = id;
17
- }
18
- function findLink(linkId) {
19
- return document.getElementById(linkId);
20
- }
21
- function findPreloadLink(id) {
22
- return document.getElementById(`${PRELOAD_ID_PREFIX}${id}`);
23
- }
24
- function ensureStylesheet(id, href, linkId, onReady) {
25
- var _a;
26
- if (!href) {
27
- onReady();
28
- return;
29
- }
30
- const active = findLink(linkId);
31
- if (active && active.getAttribute("href") === href && active.rel === "stylesheet") {
32
- onReady();
33
- return;
34
- }
35
- const preload = findPreloadLink(id);
36
- const link = (_a = active != null ? active : preload) != null ? _a : document.createElement("link");
37
- const wasConnected = link.isConnected;
38
- link.id = linkId;
39
- link.rel = "stylesheet";
40
- link.removeAttribute("as");
41
- link.href = href;
42
- if (preload && preload !== link) preload.remove();
43
- let settled = false;
44
- const finish = () => {
45
- if (settled) return;
46
- settled = true;
47
- onReady();
48
- };
49
- link.addEventListener("load", finish, { once: true });
50
- link.addEventListener("error", finish, { once: true });
51
- if (!wasConnected) document.head.appendChild(link);
20
+ applyProductTheme(id, getDomColorScheme());
52
21
  }
53
22
  let currentTheme = null;
54
23
  let resolved = false;
55
- let preloadScheduled = false;
24
+ let observerAttached = false;
56
25
  const listeners = /* @__PURE__ */ new Set();
57
26
  function notify() {
58
27
  listeners.forEach((listener) => listener());
@@ -78,75 +47,43 @@ function resolveInitialTheme(storageKey, defaultTheme) {
78
47
  if (fromStorage) return fromStorage;
79
48
  return defaultTheme;
80
49
  }
81
- function ensureResolved(hrefs, defaultTheme, storageKey, linkId) {
50
+ function ensureResolved(defaultTheme, storageKey) {
82
51
  if (resolved) return;
83
52
  resolved = true;
84
53
  const value = resolveInitialTheme(storageKey, defaultTheme);
85
- const finish = () => {
86
- applyTheme(value);
87
- persistTheme(storageKey, value);
88
- currentTheme = value;
89
- notify();
90
- };
91
- if (value === defaultTheme) {
92
- finish();
93
- return;
94
- }
95
- ensureStylesheet(value, hrefs[value], linkId, finish);
54
+ applyTheme(value);
55
+ persistTheme(storageKey, value);
56
+ currentTheme = value;
57
+ notify();
96
58
  }
97
- function schedulePreload(hrefs, defaultTheme) {
98
- if (preloadScheduled) return;
99
- preloadScheduled = true;
100
- const run = () => {
101
- var _a;
102
- for (const [id, href] of Object.entries(hrefs)) {
103
- if (id === defaultTheme || !href) continue;
104
- if (((_a = findLink(THEME_LINK_ID)) == null ? void 0 : _a.getAttribute("href")) === href) continue;
105
- if (findPreloadLink(id)) continue;
106
- const link = document.createElement("link");
107
- link.id = `${PRELOAD_ID_PREFIX}${id}`;
108
- link.rel = "preload";
109
- link.as = "style";
110
- link.href = href;
111
- document.head.appendChild(link);
112
- }
113
- };
114
- const requestIdle = window.requestIdleCallback;
115
- if (typeof requestIdle === "function") {
116
- requestIdle(run);
117
- } else {
118
- setTimeout(run, 1);
119
- }
59
+ function ensureColorSchemeObserver() {
60
+ if (observerAttached) return;
61
+ observerAttached = true;
62
+ const observer = new MutationObserver(() => {
63
+ if (!currentTheme) return;
64
+ applyProductTheme(currentTheme, getDomColorScheme());
65
+ });
66
+ observer.observe(document.documentElement, {
67
+ attributes: true,
68
+ attributeFilter: ["data-theme"]
69
+ });
120
70
  }
121
- function switchThemeInternal(hrefs, defaultTheme, storageKey, linkId, id) {
122
- const finish = () => {
123
- applyTheme(id);
124
- persistTheme(storageKey, id);
125
- currentTheme = id;
126
- resolved = true;
127
- notify();
128
- };
129
- if (id === defaultTheme) {
130
- finish();
131
- return;
132
- }
133
- ensureStylesheet(id, hrefs[id], linkId, finish);
71
+ function switchThemeInternal(storageKey, id) {
72
+ applyTheme(id);
73
+ persistTheme(storageKey, id);
74
+ currentTheme = id;
75
+ resolved = true;
76
+ notify();
134
77
  }
135
78
  function useTheme(options) {
136
- const {
137
- defaultTheme,
138
- hrefs = {},
139
- linkId = THEME_LINK_ID,
140
- preloadOthers = true,
141
- storageKey = THEME_STORAGE_KEY
142
- } = options;
79
+ const { defaultTheme, storageKey = THEME_STORAGE_KEY } = options;
143
80
  useEffect(() => {
144
- ensureResolved(hrefs, defaultTheme, storageKey, linkId);
145
- if (preloadOthers) schedulePreload(hrefs, defaultTheme);
146
- }, []);
81
+ ensureColorSchemeObserver();
82
+ ensureResolved(defaultTheme, storageKey);
83
+ }, [defaultTheme, storageKey]);
147
84
  const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
148
85
  function switchTheme(id) {
149
- switchThemeInternal(hrefs, defaultTheme, storageKey, linkId, id);
86
+ switchThemeInternal(storageKey, id);
150
87
  }
151
88
  return { theme, switchTheme };
152
89
  }
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ var productThemeTokens = require('./product-theme-tokens');
4
+ var productThemes = require('./product-themes');
5
+
6
+ function clearProductThemeTokens(target) {
7
+ for (const token of productThemeTokens.PRODUCT_THEME_TOKEN_NAMES) {
8
+ target.style.removeProperty(token);
9
+ }
10
+ }
11
+ function applyProductTheme(themeId, colorScheme, target = document.documentElement) {
12
+ clearProductThemeTokens(target);
13
+ const tokens = productThemes.getProductThemeTokens(themeId, colorScheme);
14
+ for (const [token, value] of Object.entries(tokens)) {
15
+ if (!value) continue;
16
+ target.style.setProperty(token, value);
17
+ }
18
+ }
19
+
20
+ exports.applyProductTheme = applyProductTheme;
@@ -0,0 +1,3 @@
1
+ import type { ColorScheme } from '../../utils/color-scheme.constants';
2
+ import type { ThemeId } from './types';
3
+ export declare function applyProductTheme(themeId: ThemeId, colorScheme: ColorScheme | undefined, target?: HTMLElement): void;
@@ -0,0 +1,18 @@
1
+ import { PRODUCT_THEME_TOKEN_NAMES } from './product-theme-tokens';
2
+ import { getProductThemeTokens } from './product-themes';
3
+
4
+ function clearProductThemeTokens(target) {
5
+ for (const token of PRODUCT_THEME_TOKEN_NAMES) {
6
+ target.style.removeProperty(token);
7
+ }
8
+ }
9
+ function applyProductTheme(themeId, colorScheme, target = document.documentElement) {
10
+ clearProductThemeTokens(target);
11
+ const tokens = getProductThemeTokens(themeId, colorScheme);
12
+ for (const [token, value] of Object.entries(tokens)) {
13
+ if (!value) continue;
14
+ target.style.setProperty(token, value);
15
+ }
16
+ }
17
+
18
+ export { applyProductTheme };
@@ -215,6 +215,7 @@ html {
215
215
  /* Focus */
216
216
  --color-focus-ring: var(--dbc-blue-300);
217
217
  --focus-ring: 0 0 0 3px light-dark(rgba(12, 62, 227, 0.25), rgba(59, 130, 246, 0.35));
218
+ --color-field-focus: var(--color-brand);
218
219
 
219
220
  /* Syntax (JSON viewer) */
220
221
  --color-syntax-accent: var(--dbc-blue-300);
@@ -240,7 +241,7 @@ html {
240
241
  --button-fg-primary: var(--color-fg-on-brand);
241
242
 
242
243
  /* Product / logo lockup — generic defaults; apps with a custom identity
243
- override these tokens only (see e.g. filmstriben/theme.css) */
244
+ override these tokens only via the product-theme token registry. */
244
245
  --color-logo: var(--color-fg-default);
245
246
  --color-logo-bg: color-mix(in srgb, var(--color-logo) 10%, var(--color-bg-surface));
246
247
  --color-logo-fg: var(--color-fg-default);
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const PRODUCT_THEME_TOKEN_NAMES = [
4
+ "--color-brand",
5
+ "--color-brand-hover",
6
+ "--color-brand-strong",
7
+ "--color-fg-on-brand",
8
+ "--color-border-selected",
9
+ "--color-link",
10
+ "--color-link-hover",
11
+ "--color-bg-selected",
12
+ "--color-bg-selected-hover",
13
+ "--color-chart-primary",
14
+ "--color-chart-brand",
15
+ "--color-navbar-bg",
16
+ "--color-navbar-fg",
17
+ "--color-navbar-fg-muted",
18
+ "--color-navbar-border",
19
+ "--color-navbar-brand",
20
+ "--color-logo",
21
+ "--color-logo-bg",
22
+ "--color-logo-fg",
23
+ "--color-field-focus",
24
+ "--color-focus-ring",
25
+ "--focus-ring",
26
+ "--color-bg-page",
27
+ "--color-bg-surface",
28
+ "--color-bg-surface-subtle",
29
+ "--color-bg-surface-strong",
30
+ "--color-bg-inverse",
31
+ "--color-bg-toolbar",
32
+ "--color-bg-toolbar-hover",
33
+ "--table-header-bg",
34
+ "--table-row-bg",
35
+ "--table-row-bg-alt",
36
+ "--table-row-bg-hover"
37
+ ];
38
+
39
+ exports.PRODUCT_THEME_TOKEN_NAMES = PRODUCT_THEME_TOKEN_NAMES;
@@ -0,0 +1,3 @@
1
+ export declare const PRODUCT_THEME_TOKEN_NAMES: readonly ["--color-brand", "--color-brand-hover", "--color-brand-strong", "--color-fg-on-brand", "--color-border-selected", "--color-link", "--color-link-hover", "--color-bg-selected", "--color-bg-selected-hover", "--color-chart-primary", "--color-chart-brand", "--color-navbar-bg", "--color-navbar-fg", "--color-navbar-fg-muted", "--color-navbar-border", "--color-navbar-brand", "--color-logo", "--color-logo-bg", "--color-logo-fg", "--color-field-focus", "--color-focus-ring", "--focus-ring", "--color-bg-page", "--color-bg-surface", "--color-bg-surface-subtle", "--color-bg-surface-strong", "--color-bg-inverse", "--color-bg-toolbar", "--color-bg-toolbar-hover", "--table-header-bg", "--table-row-bg", "--table-row-bg-alt", "--table-row-bg-hover"];
2
+ export type ProductThemeTokenName = (typeof PRODUCT_THEME_TOKEN_NAMES)[number];
3
+ export type ProductThemeTokens = Partial<Record<ProductThemeTokenName, string>>;
@@ -0,0 +1,37 @@
1
+ const PRODUCT_THEME_TOKEN_NAMES = [
2
+ "--color-brand",
3
+ "--color-brand-hover",
4
+ "--color-brand-strong",
5
+ "--color-fg-on-brand",
6
+ "--color-border-selected",
7
+ "--color-link",
8
+ "--color-link-hover",
9
+ "--color-bg-selected",
10
+ "--color-bg-selected-hover",
11
+ "--color-chart-primary",
12
+ "--color-chart-brand",
13
+ "--color-navbar-bg",
14
+ "--color-navbar-fg",
15
+ "--color-navbar-fg-muted",
16
+ "--color-navbar-border",
17
+ "--color-navbar-brand",
18
+ "--color-logo",
19
+ "--color-logo-bg",
20
+ "--color-logo-fg",
21
+ "--color-field-focus",
22
+ "--color-focus-ring",
23
+ "--focus-ring",
24
+ "--color-bg-page",
25
+ "--color-bg-surface",
26
+ "--color-bg-surface-subtle",
27
+ "--color-bg-surface-strong",
28
+ "--color-bg-inverse",
29
+ "--color-bg-toolbar",
30
+ "--color-bg-toolbar-hover",
31
+ "--table-header-bg",
32
+ "--table-row-bg",
33
+ "--table-row-bg-alt",
34
+ "--table-row-bg-hover"
35
+ ];
36
+
37
+ export { PRODUCT_THEME_TOKEN_NAMES };
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+
3
+ const PRODUCT_THEMES = {
4
+ dbc: {
5
+ label: "DBC"
6
+ },
7
+ filmstriben: {
8
+ label: "Filmstriben",
9
+ base: {
10
+ "--color-navbar-bg": "#212529",
11
+ "--color-navbar-fg": "#ffffff",
12
+ "--color-navbar-fg-muted": "rgba(255, 255, 255, 0.9)",
13
+ "--color-navbar-border": "color-mix(in srgb, var(--color-navbar-fg) 25%, transparent)",
14
+ "--color-navbar-brand": "#ffc107",
15
+ "--color-logo": "#ffc107",
16
+ "--color-logo-bg": "color-mix(in srgb, var(--color-logo) 10%, var(--color-bg-surface))",
17
+ "--color-logo-fg": "#212529"
18
+ },
19
+ light: {
20
+ "--color-brand": "var(--dbc-blue-500)",
21
+ "--color-brand-hover": "var(--dbc-blue-600)",
22
+ "--color-brand-strong": "var(--dbc-blue-600)",
23
+ "--color-fg-on-brand": "#ffffff",
24
+ "--color-border-selected": "var(--color-brand)",
25
+ "--color-field-focus": "var(--dbc-blue-500)",
26
+ "--color-focus-ring": "var(--dbc-blue-300)",
27
+ "--focus-ring": "0 0 0 3px rgba(12, 62, 227, 0.25)",
28
+ "--color-link": "var(--dbc-blue-500)",
29
+ "--color-link-hover": "var(--dbc-blue-600)",
30
+ "--color-bg-selected": "var(--dbc-blue-100)",
31
+ "--color-bg-selected-hover": "var(--dbc-blue-150)",
32
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 75%, rgb(255 255 255 / 0))"
33
+ },
34
+ dark: {
35
+ "--color-bg-page": "#212529",
36
+ "--color-bg-surface": "#212529",
37
+ "--color-bg-surface-subtle": "#2a2f34",
38
+ "--color-bg-surface-strong": "#161a1d",
39
+ "--color-bg-inverse": "#34393f",
40
+ "--color-bg-toolbar": "#2a2f34",
41
+ "--color-bg-toolbar-hover": "#343a40",
42
+ "--table-header-bg": "#2a2f34",
43
+ "--table-row-bg": "#212529",
44
+ "--table-row-bg-alt": "#1c2024",
45
+ "--table-row-bg-hover": "#2a2f34",
46
+ "--color-brand": "#ffc107",
47
+ "--color-brand-hover": "#ffca2c",
48
+ "--color-brand-strong": "#ffcd39",
49
+ "--color-fg-on-brand": "#212529",
50
+ "--color-border-selected": "var(--color-brand)",
51
+ "--color-field-focus": "var(--dbc-blue-300)",
52
+ "--color-focus-ring": "#ffc107",
53
+ "--focus-ring": "0 0 0 3px color-mix(in srgb, #ffc107 35%, transparent)",
54
+ "--color-bg-selected": "#4a3a06",
55
+ "--color-bg-selected-hover": "#5b4707",
56
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 75%, rgb(255 255 255 / 0))"
57
+ }
58
+ },
59
+ valentines: {
60
+ label: "Valentines",
61
+ light: {
62
+ "--color-navbar-bg": "linear-gradient(90deg, #fff1f5 0%, #ffe4ec 52%, #f5ecff 100%)",
63
+ "--color-navbar-fg": "#3f1d2e",
64
+ "--color-navbar-fg-muted": "color-mix(in srgb, #3f1d2e 72%, white)",
65
+ "--color-navbar-border": "color-mix(in srgb, #e11d48 18%, transparent)",
66
+ "--color-navbar-brand": "#e11d48",
67
+ "--color-logo": "#e11d48",
68
+ "--color-logo-bg": "color-mix(in srgb, #e11d48 16%, white)",
69
+ "--color-logo-fg": "#ffffff",
70
+ "--color-bg-page": "#fff5f8",
71
+ "--color-bg-surface": "#fff0f5",
72
+ "--color-bg-surface-subtle": "#fff8fb",
73
+ "--color-bg-surface-strong": "#ffe2eb",
74
+ "--color-bg-inverse": "#3f1d2e",
75
+ "--color-bg-toolbar": "#ffdce8",
76
+ "--color-bg-toolbar-hover": "#ffcddd",
77
+ "--table-header-bg": "#ffe2eb",
78
+ "--table-row-bg": "#fff0f5",
79
+ "--table-row-bg-alt": "#fff7fa",
80
+ "--table-row-bg-hover": "#ffe8f0",
81
+ "--color-brand": "#e11d48",
82
+ "--color-brand-hover": "#be123c",
83
+ "--color-brand-strong": "#9f1239",
84
+ "--color-fg-on-brand": "#ffffff",
85
+ "--color-border-selected": "var(--color-brand)",
86
+ "--color-field-focus": "#4c1d95",
87
+ "--color-focus-ring": "#4c1d95",
88
+ "--focus-ring": "0 0 0 3px color-mix(in srgb, #4c1d95 28%, transparent)",
89
+ "--color-link": "#4c1d95",
90
+ "--color-link-hover": "color-mix(in srgb, #4c1d95 86%, black)",
91
+ "--color-bg-selected": "#ffe4ec",
92
+ "--color-bg-selected-hover": "#ffd3e0",
93
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 78%, rgb(255 255 255 / 0))",
94
+ "--color-chart-brand": "color-mix(in srgb, #4c1d95 76%, rgb(255 255 255 / 0))"
95
+ },
96
+ dark: {
97
+ "--color-navbar-bg": "linear-gradient(90deg, #4c1d95 0%, #7e22ce 35%, #be185d 100%)",
98
+ "--color-navbar-fg": "#fff7fb",
99
+ "--color-navbar-fg-muted": "rgba(255, 247, 251, 0.82)",
100
+ "--color-navbar-border": "rgba(255, 247, 251, 0.18)",
101
+ "--color-navbar-brand": "#ffd6e7",
102
+ "--color-logo": "#ffd6e7",
103
+ "--color-logo-bg": "color-mix(in srgb, #ffd6e7 18%, var(--color-bg-surface))",
104
+ "--color-logo-fg": "#4c1d95",
105
+ "--color-bg-page": "#241326",
106
+ "--color-bg-surface": "#241326",
107
+ "--color-bg-surface-subtle": "#301836",
108
+ "--color-bg-surface-strong": "#1a0d1d",
109
+ "--color-bg-inverse": "#fdf2f8",
110
+ "--color-bg-toolbar": "#301836",
111
+ "--color-bg-toolbar-hover": "#43204b",
112
+ "--table-header-bg": "#301836",
113
+ "--table-row-bg": "#241326",
114
+ "--table-row-bg-alt": "#2a142f",
115
+ "--table-row-bg-hover": "#301836",
116
+ "--color-brand": "#fb7185",
117
+ "--color-brand-hover": "#f43f5e",
118
+ "--color-brand-strong": "#e11d48",
119
+ "--color-fg-on-brand": "#2c1022",
120
+ "--color-border-selected": "var(--color-brand)",
121
+ "--color-field-focus": "#c4b5fd",
122
+ "--color-focus-ring": "#f9a8d4",
123
+ "--focus-ring": "0 0 0 3px color-mix(in srgb, #f9a8d4 36%, transparent)",
124
+ "--color-link": "#f9a8d4",
125
+ "--color-link-hover": "#fbcfe8",
126
+ "--color-bg-selected": "#4a1730",
127
+ "--color-bg-selected-hover": "#5a1c3a",
128
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 78%, rgb(255 255 255 / 0))",
129
+ "--color-chart-brand": "color-mix(in srgb, #c4b5fd 76%, rgb(255 255 255 / 0))"
130
+ }
131
+ }
132
+ };
133
+ function getProductThemeTokens(themeId, colorScheme) {
134
+ const definition = PRODUCT_THEMES[themeId];
135
+ if (!definition) return {};
136
+ return {
137
+ ...definition.base,
138
+ ...colorScheme === "dark" ? definition.dark : definition.light
139
+ };
140
+ }
141
+
142
+ exports.PRODUCT_THEMES = PRODUCT_THEMES;
143
+ exports.getProductThemeTokens = getProductThemeTokens;
@@ -0,0 +1,11 @@
1
+ import type { ColorScheme } from '../../utils/color-scheme.constants';
2
+ import type { ProductThemeTokens } from './product-theme-tokens';
3
+ import type { ThemeId } from './types';
4
+ export interface ProductThemeDefinition {
5
+ label: string;
6
+ base?: ProductThemeTokens;
7
+ light?: ProductThemeTokens;
8
+ dark?: ProductThemeTokens;
9
+ }
10
+ export declare const PRODUCT_THEMES: Record<ThemeId, ProductThemeDefinition>;
11
+ export declare function getProductThemeTokens(themeId: ThemeId, colorScheme: ColorScheme | undefined): ProductThemeTokens;
@@ -0,0 +1,140 @@
1
+ const PRODUCT_THEMES = {
2
+ dbc: {
3
+ label: "DBC"
4
+ },
5
+ filmstriben: {
6
+ label: "Filmstriben",
7
+ base: {
8
+ "--color-navbar-bg": "#212529",
9
+ "--color-navbar-fg": "#ffffff",
10
+ "--color-navbar-fg-muted": "rgba(255, 255, 255, 0.9)",
11
+ "--color-navbar-border": "color-mix(in srgb, var(--color-navbar-fg) 25%, transparent)",
12
+ "--color-navbar-brand": "#ffc107",
13
+ "--color-logo": "#ffc107",
14
+ "--color-logo-bg": "color-mix(in srgb, var(--color-logo) 10%, var(--color-bg-surface))",
15
+ "--color-logo-fg": "#212529"
16
+ },
17
+ light: {
18
+ "--color-brand": "var(--dbc-blue-500)",
19
+ "--color-brand-hover": "var(--dbc-blue-600)",
20
+ "--color-brand-strong": "var(--dbc-blue-600)",
21
+ "--color-fg-on-brand": "#ffffff",
22
+ "--color-border-selected": "var(--color-brand)",
23
+ "--color-field-focus": "var(--dbc-blue-500)",
24
+ "--color-focus-ring": "var(--dbc-blue-300)",
25
+ "--focus-ring": "0 0 0 3px rgba(12, 62, 227, 0.25)",
26
+ "--color-link": "var(--dbc-blue-500)",
27
+ "--color-link-hover": "var(--dbc-blue-600)",
28
+ "--color-bg-selected": "var(--dbc-blue-100)",
29
+ "--color-bg-selected-hover": "var(--dbc-blue-150)",
30
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 75%, rgb(255 255 255 / 0))"
31
+ },
32
+ dark: {
33
+ "--color-bg-page": "#212529",
34
+ "--color-bg-surface": "#212529",
35
+ "--color-bg-surface-subtle": "#2a2f34",
36
+ "--color-bg-surface-strong": "#161a1d",
37
+ "--color-bg-inverse": "#34393f",
38
+ "--color-bg-toolbar": "#2a2f34",
39
+ "--color-bg-toolbar-hover": "#343a40",
40
+ "--table-header-bg": "#2a2f34",
41
+ "--table-row-bg": "#212529",
42
+ "--table-row-bg-alt": "#1c2024",
43
+ "--table-row-bg-hover": "#2a2f34",
44
+ "--color-brand": "#ffc107",
45
+ "--color-brand-hover": "#ffca2c",
46
+ "--color-brand-strong": "#ffcd39",
47
+ "--color-fg-on-brand": "#212529",
48
+ "--color-border-selected": "var(--color-brand)",
49
+ "--color-field-focus": "var(--dbc-blue-300)",
50
+ "--color-focus-ring": "#ffc107",
51
+ "--focus-ring": "0 0 0 3px color-mix(in srgb, #ffc107 35%, transparent)",
52
+ "--color-bg-selected": "#4a3a06",
53
+ "--color-bg-selected-hover": "#5b4707",
54
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 75%, rgb(255 255 255 / 0))"
55
+ }
56
+ },
57
+ valentines: {
58
+ label: "Valentines",
59
+ light: {
60
+ "--color-navbar-bg": "linear-gradient(90deg, #fff1f5 0%, #ffe4ec 52%, #f5ecff 100%)",
61
+ "--color-navbar-fg": "#3f1d2e",
62
+ "--color-navbar-fg-muted": "color-mix(in srgb, #3f1d2e 72%, white)",
63
+ "--color-navbar-border": "color-mix(in srgb, #e11d48 18%, transparent)",
64
+ "--color-navbar-brand": "#e11d48",
65
+ "--color-logo": "#e11d48",
66
+ "--color-logo-bg": "color-mix(in srgb, #e11d48 16%, white)",
67
+ "--color-logo-fg": "#ffffff",
68
+ "--color-bg-page": "#fff5f8",
69
+ "--color-bg-surface": "#fff0f5",
70
+ "--color-bg-surface-subtle": "#fff8fb",
71
+ "--color-bg-surface-strong": "#ffe2eb",
72
+ "--color-bg-inverse": "#3f1d2e",
73
+ "--color-bg-toolbar": "#ffdce8",
74
+ "--color-bg-toolbar-hover": "#ffcddd",
75
+ "--table-header-bg": "#ffe2eb",
76
+ "--table-row-bg": "#fff0f5",
77
+ "--table-row-bg-alt": "#fff7fa",
78
+ "--table-row-bg-hover": "#ffe8f0",
79
+ "--color-brand": "#e11d48",
80
+ "--color-brand-hover": "#be123c",
81
+ "--color-brand-strong": "#9f1239",
82
+ "--color-fg-on-brand": "#ffffff",
83
+ "--color-border-selected": "var(--color-brand)",
84
+ "--color-field-focus": "#4c1d95",
85
+ "--color-focus-ring": "#4c1d95",
86
+ "--focus-ring": "0 0 0 3px color-mix(in srgb, #4c1d95 28%, transparent)",
87
+ "--color-link": "#4c1d95",
88
+ "--color-link-hover": "color-mix(in srgb, #4c1d95 86%, black)",
89
+ "--color-bg-selected": "#ffe4ec",
90
+ "--color-bg-selected-hover": "#ffd3e0",
91
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 78%, rgb(255 255 255 / 0))",
92
+ "--color-chart-brand": "color-mix(in srgb, #4c1d95 76%, rgb(255 255 255 / 0))"
93
+ },
94
+ dark: {
95
+ "--color-navbar-bg": "linear-gradient(90deg, #4c1d95 0%, #7e22ce 35%, #be185d 100%)",
96
+ "--color-navbar-fg": "#fff7fb",
97
+ "--color-navbar-fg-muted": "rgba(255, 247, 251, 0.82)",
98
+ "--color-navbar-border": "rgba(255, 247, 251, 0.18)",
99
+ "--color-navbar-brand": "#ffd6e7",
100
+ "--color-logo": "#ffd6e7",
101
+ "--color-logo-bg": "color-mix(in srgb, #ffd6e7 18%, var(--color-bg-surface))",
102
+ "--color-logo-fg": "#4c1d95",
103
+ "--color-bg-page": "#241326",
104
+ "--color-bg-surface": "#241326",
105
+ "--color-bg-surface-subtle": "#301836",
106
+ "--color-bg-surface-strong": "#1a0d1d",
107
+ "--color-bg-inverse": "#fdf2f8",
108
+ "--color-bg-toolbar": "#301836",
109
+ "--color-bg-toolbar-hover": "#43204b",
110
+ "--table-header-bg": "#301836",
111
+ "--table-row-bg": "#241326",
112
+ "--table-row-bg-alt": "#2a142f",
113
+ "--table-row-bg-hover": "#301836",
114
+ "--color-brand": "#fb7185",
115
+ "--color-brand-hover": "#f43f5e",
116
+ "--color-brand-strong": "#e11d48",
117
+ "--color-fg-on-brand": "#2c1022",
118
+ "--color-border-selected": "var(--color-brand)",
119
+ "--color-field-focus": "#c4b5fd",
120
+ "--color-focus-ring": "#f9a8d4",
121
+ "--focus-ring": "0 0 0 3px color-mix(in srgb, #f9a8d4 36%, transparent)",
122
+ "--color-link": "#f9a8d4",
123
+ "--color-link-hover": "#fbcfe8",
124
+ "--color-bg-selected": "#4a1730",
125
+ "--color-bg-selected-hover": "#5a1c3a",
126
+ "--color-chart-primary": "color-mix(in srgb, var(--color-brand) 78%, rgb(255 255 255 / 0))",
127
+ "--color-chart-brand": "color-mix(in srgb, #c4b5fd 76%, rgb(255 255 255 / 0))"
128
+ }
129
+ }
130
+ };
131
+ function getProductThemeTokens(themeId, colorScheme) {
132
+ const definition = PRODUCT_THEMES[themeId];
133
+ if (!definition) return {};
134
+ return {
135
+ ...definition.base,
136
+ ...colorScheme === "dark" ? definition.dark : definition.light
137
+ };
138
+ }
139
+
140
+ export { PRODUCT_THEMES, getProductThemeTokens };
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const THEME_IDS = ["dbc", "filmstriben"];
3
+ const THEME_IDS = ["dbc", "filmstriben", "valentines"];
4
4
  const THEME_LABELS = {
5
5
  dbc: "DBC",
6
- filmstriben: "Filmstriben"
6
+ filmstriben: "Filmstriben",
7
+ valentines: "Valentines"
7
8
  };
8
9
 
9
10
  exports.THEME_IDS = THEME_IDS;
@@ -7,18 +7,6 @@
7
7
  * TypeScript-only enforcement; add an id here (and to `THEME_LABELS`)
8
8
  * whenever a new theme folder is added.
9
9
  */
10
- export declare const THEME_IDS: readonly ["dbc", "filmstriben"];
10
+ export declare const THEME_IDS: readonly ["dbc", "filmstriben", "valentines"];
11
11
  export type ThemeId = (typeof THEME_IDS)[number];
12
12
  export declare const THEME_LABELS: Record<ThemeId, string>;
13
- /**
14
- * Per-theme override for where to fetch its CSS from at runtime. Only
15
- * needed for a theme whose CSS isn't already loaded some other way by the
16
- * app — omit an id (or leave its value `''`/unset) for a theme that's
17
- * already bundled statically alongside whatever the app already imports.
18
- *
19
- * This is the only piece of theme data `ThemeScript`/`useTheme` actually
20
- * need at runtime (which id to inject a `<link>` for, and its URL) — they
21
- * don't need the full enumerated id list or labels, only whichever UI
22
- * renders the switcher (`ThemeButton`/`ThemeMenuSection`) does.
23
- */
24
- export type ThemeHrefs = Partial<Record<ThemeId, string>>;