Skip to content

Commit a4afc5e

Browse files
authored
docs(storybook): support switching between light, dark and high constrast modes (#2072)
1 parent 1f53476 commit a4afc5e

File tree

6 files changed

+197
-40
lines changed

6 files changed

+197
-40
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { addons, useEffect } from "storybook/preview-api";
2+
3+
const getInitialMode = () => {
4+
// If in an iframe, check parent URL first
5+
if (window.parent !== window) {
6+
return getModeFromURL(window.parent.location.href);
7+
}
8+
return getModeFromURL(window.location.href);
9+
};
10+
11+
const getModeFromURL = (url: string) => {
12+
const urlObj = new URL(url);
13+
const globalsParam = urlObj.searchParams.get("globals");
14+
if (globalsParam) {
15+
const globals = globalsParam.split(";").reduce(
16+
(acc, pair) => {
17+
const [key, value] = pair.split(":");
18+
if (key && value) {
19+
acc[key] = value;
20+
}
21+
return acc;
22+
},
23+
{} as Record<string, string>
24+
);
25+
return globals.mode || "light";
26+
}
27+
return "light";
28+
};
29+
30+
const applyMode = (mode: string) => {
31+
document.body.classList.remove("theme-dark", "theme-highcontrast");
32+
33+
if (mode === "dark") {
34+
document.body.classList.add("theme-dark");
35+
} else if (mode === "highcontrast") {
36+
document.body.classList.add("theme-highcontrast");
37+
} else if (mode === "highcontrast-dark") {
38+
document.body.classList.add("theme-dark", "theme-highcontrast");
39+
}
40+
};
41+
42+
const WithMode = (Story) => {
43+
const onGlobalsUpdated = (globals: { mode: string }) => {
44+
applyMode(globals.mode);
45+
};
46+
47+
useEffect(() => {
48+
// handle initial load
49+
const mode = getInitialMode();
50+
applyMode(mode);
51+
52+
// handle updates
53+
const channel = addons.getChannel();
54+
channel.on("globalsUpdated", onGlobalsUpdated);
55+
return () => {
56+
channel.off("globalsUpdated", onGlobalsUpdated);
57+
};
58+
}, []);
59+
60+
return Story();
61+
};
62+
63+
export default WithMode;

packages/stacks-svelte/.storybook/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const config: StorybookConfig = {
1313
"@storybook/addon-docs",
1414
"@storybook/addon-svelte-csf",
1515
],
16+
features: {
17+
outline: false,
18+
},
1619
framework: {
1720
name: "@storybook/svelte-vite",
1821
options: {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script type="module">
2+
const findIframeById = (id, root = document) => {
3+
for (const iframe of root.getElementsByTagName("iframe")) {
4+
if (iframe.id === id) return iframe;
5+
const found = findIframeById(id, iframe.contentDocument);
6+
if (found) return found;
7+
}
8+
9+
return null;
10+
};
11+
12+
window.addEventListener("message", function (event) {
13+
if (event?.data?.type === "resize-iframe") {
14+
const iframe = findIframeById(event.data.frameId);
15+
if (iframe) {
16+
iframe.style.height = event.data.height + "px";
17+
}
18+
}
19+
});
20+
</script>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<style>
2+
/* ensure story preview iframes don't have a border */
3+
.docs-story .innerZoomElementWrapper > div {
4+
border: none !important;
5+
}
6+
</style>
7+
<script type="module">
8+
const sendHeight = () => {
9+
if (window.parent === window) return; // not in iframe
10+
11+
const height = Math.max(
12+
document.documentElement.scrollHeight,
13+
document.documentElement.offsetHeight,
14+
document.body.scrollHeight,
15+
document.body.offsetHeight
16+
);
17+
18+
window.top.postMessage(
19+
{
20+
type: "resize-iframe",
21+
height,
22+
frameId: window.frameElement ? window.frameElement.id : null,
23+
},
24+
"*"
25+
);
26+
};
27+
28+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
29+
30+
await delay(2000); // heuristic delay to ensure the iframe content has been loaded
31+
sendHeight();
32+
</script>

packages/stacks-svelte/.storybook/preview.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Preview } from "@storybook/svelte";
2+
import WithMode from "./decorators/with-mode";
23
import "@stackoverflow/stacks/lib/stacks.less";
34

45
const preview: Preview = {
@@ -18,9 +19,43 @@ const preview: Preview = {
1819
},
1920
docs: {
2021
controls: { sort: "requiredFirst" },
22+
story: {
23+
inline: false,
24+
iframeHeight: "auto",
25+
},
26+
},
27+
backgrounds: {
28+
disable: true,
29+
},
30+
},
31+
globalTypes: {
32+
mode: {
33+
name: "Mode",
34+
description: "Global mode for components",
35+
defaultValue: "light",
36+
toolbar: {
37+
icon: "circlehollow",
38+
items: [
39+
{ value: "light", title: "Light", icon: "circlehollow" },
40+
{ value: "dark", title: "Dark", icon: "circle" },
41+
{
42+
value: "highcontrast",
43+
title: "High Contrast",
44+
icon: "contrast",
45+
},
46+
{
47+
value: "highcontrast-dark",
48+
title: "High Contrast Dark",
49+
icon: "contrast",
50+
},
51+
],
52+
showName: true,
53+
dynamicTitle: true,
54+
},
2155
},
2256
},
2357
tags: ["autodocs"],
58+
decorators: [WithMode],
2459
};
2560

2661
export default preview;

packages/stacks-svelte/src/components/Menu/Menu.stories.svelte

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,16 @@
3333

3434
<Story name="Base">
3535
{#snippet template()}
36-
<Popover id="base-popover" visible>
37-
<PopoverContent class="ps-relative is-visible p8 ws2">
38-
<Menu>
39-
{@render basicChildren()}
40-
</Menu>
41-
</PopoverContent>
42-
</Popover>
36+
<div class="s-card p8 ws2">
37+
<Menu>
38+
{@render basicChildren()}
39+
</Menu>
40+
</div>
4341
{/snippet}
4442
</Story>
4543

4644
<Story name="Basic Examples" asChild>
47-
<div class="d-flex g32 fw-wrap">
45+
<div class="d-flex g32 fw-wrap hmn2">
4846
<div>
4947
<div class="ff-mono mb16">Within a popover</div>
5048
<Popover id="basic-example-popover" visible>
@@ -73,44 +71,50 @@
7371
</Story>
7472

7573
<Story name="Titles and Dividers" asChild>
76-
<Popover id="title-and-divider-popover" visible>
77-
<PopoverContent class="ps-relative is-visible p8 ws2">
78-
<Menu>
79-
<MenuTitle>Layout</MenuTitle>
80-
{@render basicChildren()}
81-
<MenuDivider class="mxn8" />
82-
<MenuItem href="#" danger>Deactivate</MenuItem>
83-
<MenuItem href="#" danger>Delete</MenuItem>
84-
</Menu>
85-
</PopoverContent>
86-
</Popover>
74+
<div class="hmn3">
75+
<Popover id="title-and-divider-popover" visible>
76+
<PopoverContent class="ps-relative is-visible p8 ws2">
77+
<Menu>
78+
<MenuTitle>Layout</MenuTitle>
79+
{@render basicChildren()}
80+
<MenuDivider class="mxn8" />
81+
<MenuItem href="#" danger>Deactivate</MenuItem>
82+
<MenuItem href="#" danger>Delete</MenuItem>
83+
</Menu>
84+
</PopoverContent>
85+
</Popover>
86+
</div>
8787
</Story>
8888

8989
<Story name="Menu Items with Icons" asChild>
90-
<Popover id="menu-items-with-icons-popover" visible>
91-
<PopoverContent class="ps-relative is-visible p8 ws1">
92-
<Menu>
93-
<MenuItem href="#" icon={IconHome}>Home</MenuItem>
94-
<MenuItem href="#" icon={IconInbox}>Inbox</MenuItem>
95-
<MenuItem href="#" icon={IconSettings}>Settings</MenuItem>
96-
</Menu>
97-
</PopoverContent>
98-
</Popover>
90+
<div class="hmn2">
91+
<Popover id="menu-items-with-icons-popover" visible>
92+
<PopoverContent class="ps-relative is-visible p8 ws1">
93+
<Menu>
94+
<MenuItem href="#" icon={IconHome}>Home</MenuItem>
95+
<MenuItem href="#" icon={IconInbox}>Inbox</MenuItem>
96+
<MenuItem href="#" icon={IconSettings}>Settings</MenuItem>
97+
</Menu>
98+
</PopoverContent>
99+
</Popover>
100+
</div>
99101
</Story>
100102

101103
<Story name="Selected States" asChild>
102-
<Popover id="selected-states-popover" visible>
103-
<PopoverContent class="ps-relative is-visible p8 ws2">
104-
<Menu>
105-
<MenuItem href="#">Frequent</MenuItem>
106-
<MenuItem href="#">Votes</MenuItem>
107-
<MenuItem href="#" selected>Unanswered</MenuItem>
108-
<MenuTitle>Custom filters</MenuTitle>
109-
<MenuItem href="#">Frontend questions</MenuItem>
110-
<MenuItem href="#">Design systems</MenuItem>
111-
</Menu>
112-
</PopoverContent>
113-
</Popover>
104+
<div class="hmn3">
105+
<Popover id="selected-states-popover" visible>
106+
<PopoverContent class="ps-relative is-visible p8 ws2">
107+
<Menu>
108+
<MenuItem href="#">Frequent</MenuItem>
109+
<MenuItem href="#">Votes</MenuItem>
110+
<MenuItem href="#" selected>Unanswered</MenuItem>
111+
<MenuTitle>Custom filters</MenuTitle>
112+
<MenuItem href="#">Frontend questions</MenuItem>
113+
<MenuItem href="#">Design systems</MenuItem>
114+
</Menu>
115+
</PopoverContent>
116+
</Popover>
117+
</div>
114118
</Story>
115119

116120
<Story name="In Popover" asChild>

0 commit comments

Comments
 (0)