Skip to content

Commit 86647a3

Browse files
muller39Lenajava1
andauthored
feat(VCST-4216): add storybook theme preset selector (#2043)
## Description <img width="1328" height="630" alt="image" src="https://github.com/user-attachments/assets/b9d435c6-623b-4f1f-abd3-51e84353a32d" /> ## References ### Jira-link: https://virtocommerce.atlassian.net/browse/VCST-4216 ### Artifact URL: https://vc3prerelease.blob.core.windows.net/packages/vc-theme-b2b-vue-2.35.0-pr-2043-16a5-16a53718.zip --------- Co-authored-by: Elena Mutykova <[email protected]>
1 parent 48c84f9 commit 86647a3

File tree

1 file changed

+108
-18
lines changed

1 file changed

+108
-18
lines changed

.storybook/preview.ts

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { setup } from "@storybook/vue3-vite";
2+
import { presets } from "../client-app/assets/presets";
23
import { useThemeContext } from "../client-app/core/composables";
34
import { setGlobals } from "../client-app/core/globals";
45
import { createI18n } from "../client-app/i18n";
@@ -74,25 +75,87 @@ setThemeContext({
7475
},
7576
} as StoreResponseType);
7677

77-
async function configureThemeSettings() {
78-
const module = (await import(`@/assets/presets/coffee.json`)) as {
79-
default: IThemeConfigPreset;
80-
};
81-
const preset = module.default;
82-
83-
if (preset) {
84-
const styleElement = document.createElement("style");
85-
styleElement.innerText = ":root {";
86-
Object.entries(preset).forEach(([key, value]) => {
87-
styleElement.innerText += `--${key.replace(/_/g, "-")}: ${value};`;
78+
// List of available theme presets
79+
const PRESETS = Object.keys(presets);
80+
type PresetNameType = keyof typeof presets;
81+
82+
let currentStyleElement: HTMLStyleElement | null = null;
83+
let currentPresetName: PresetNameType | null = null;
84+
let loadingPreset: PresetNameType | null = null;
85+
let i18nConfigured = false;
86+
87+
// Enable for debugging: track DOM changes (only in dev mode)
88+
const ENABLE_DOM_MONITORING = import.meta.env.MODE === "development";
89+
if (ENABLE_DOM_MONITORING && typeof window !== "undefined") {
90+
const observer = new MutationObserver((mutations) => {
91+
mutations.forEach((mutation) => {
92+
if (mutation.type === "childList" && mutation.target === document.head) {
93+
// eslint-disable-next-line no-console
94+
console.log("DOM changed in <head>:", {
95+
addedNodes: mutation.addedNodes.length,
96+
removedNodes: mutation.removedNodes.length,
97+
target: mutation.target,
98+
});
99+
}
88100
});
89-
styleElement.innerText += "}";
90-
document.head.prepend(styleElement);
101+
});
102+
103+
observer.observe(document.head, {
104+
childList: true,
105+
subtree: false,
106+
});
107+
108+
// eslint-disable-next-line no-console
109+
console.log("MutationObserver activated for monitoring <head>");
110+
}
111+
112+
async function configureThemeSettings(presetName: PresetNameType = "default") {
113+
if (currentPresetName === presetName && currentStyleElement) {
114+
return;
115+
}
116+
if (loadingPreset === presetName) {
117+
return;
118+
}
119+
120+
loadingPreset = presetName;
121+
122+
try {
123+
const module = (await import(`@/assets/presets/${presetName}.json`)) as {
124+
default: IThemeConfigPreset;
125+
};
126+
const preset = module.default;
127+
128+
if (preset) {
129+
if (currentStyleElement && currentStyleElement.parentNode) {
130+
currentStyleElement.parentNode.removeChild(currentStyleElement);
131+
}
132+
133+
currentStyleElement = document.createElement("style");
134+
currentStyleElement.innerText = ":root {";
135+
Object.entries(preset).forEach(([key, value]) => {
136+
currentStyleElement!.innerText += `--${key.replace(/_/g, "-")}: ${value};`;
137+
});
138+
currentStyleElement.innerText += "}";
139+
document.head.prepend(currentStyleElement);
140+
currentPresetName = presetName;
141+
}
142+
} catch (error) {
143+
// eslint-disable-next-line no-console
144+
console.error(`Failed to load preset "${presetName}":`, error);
145+
} finally {
146+
if (loadingPreset === presetName) {
147+
loadingPreset = null;
148+
}
91149
}
92150
}
93151

94152
function configureI18N() {
153+
if (i18nConfigured) {
154+
return;
155+
}
156+
95157
i18n.global.setLocaleMessage(DEFAULT_LOCALE, UI_KIT_DEFAULT_MESSAGE);
158+
i18nConfigured = true;
96159
}
97160

98161
setGlobals({ i18n });
@@ -111,15 +174,28 @@ setup((app) => {
111174
console.error("Storybook Vue setup error:", error);
112175
}
113176

114-
configureThemeSettings().catch(() => {
115-
// eslint-disable-next-line no-console
116-
console.error("Storybook theme setup error:");
117-
});
118-
119177
configureI18N();
120178
});
121179

122180
const preview: Preview = {
181+
globalTypes: {
182+
themePreset: {
183+
description: "Select theme preset",
184+
defaultValue: "default",
185+
toolbar: {
186+
title: "Theme preset",
187+
icon: "paintbrush",
188+
items: PRESETS.map((preset) => ({
189+
value: preset,
190+
title: preset.charAt(0).toUpperCase() + preset.slice(1).replace(/-/g, " "),
191+
})),
192+
dynamicTitle: true,
193+
},
194+
},
195+
},
196+
initialGlobals: {
197+
themePreset: "default",
198+
},
123199
parameters: {
124200
controls: {
125201
matchers: {
@@ -135,6 +211,20 @@ const preview: Preview = {
135211
},
136212
a11y: a11yConfig,
137213
},
214+
decorators: [
215+
(story, context) => {
216+
const presetName = context.globals.themePreset || "default";
217+
218+
if (presetName !== currentPresetName && presetName !== loadingPreset) {
219+
configureThemeSettings(presetName).catch(() => {
220+
// eslint-disable-next-line no-console
221+
console.error("Storybook theme setup error");
222+
});
223+
}
224+
225+
return story();
226+
},
227+
],
138228
tags: ["autodocs"],
139229
};
140230

0 commit comments

Comments
 (0)