Skip to content

Commit a254cea

Browse files
committed
Create Storybook add-on for switching VSCode themes
This adds a Storybook add-on that allows you to switch between VSCode theme. It follows the pattern of the [outline](https://github.com/storybookjs/storybook/tree/v6.5.12/addons/outline/src) and [backgrounds](https://github.com/storybookjs/storybook/tree/v6.5.12/addons/backgrounds) add-ons. Unfortunately, it doesn't apply the CSS to just the elements it should be applied to, but globally to the complete preview. This is a limitation of using CSS files rather than setting inline styles on the elements. We might be able to resolve this in the future by extracting the CSS variables from the CSS files, but this is somewhat more involved.
1 parent 157a5d6 commit a254cea

File tree

12 files changed

+162
-60
lines changed

12 files changed

+162
-60
lines changed

extensions/ql-vscode/.storybook/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const config: StorybookConfig = {
88
addons: [
99
'@storybook/addon-links',
1010
'@storybook/addon-essentials',
11-
'@storybook/addon-interactions'
11+
'@storybook/addon-interactions',
12+
'./vscode-theme-addon/preset.ts',
1213
],
1314
framework: '@storybook/react',
1415
core: {

extensions/ql-vscode/.storybook/preview.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { action } from '@storybook/addon-actions';
44
// Allow all stories/components to use Codicons
55
import '@vscode/codicons/dist/codicon.css';
66

7-
import '../src/stories/vscode-theme-dark.css';
8-
97
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
108
export const parameters = {
119
// All props starting with `on` will automatically receive an action as a prop
@@ -22,13 +20,8 @@ export const parameters = {
2220
theme: themes.dark,
2321
},
2422
backgrounds: {
25-
default: 'dark',
26-
values: [
27-
{
28-
name: 'dark',
29-
value: '#1e1e1e',
30-
},
31-
],
23+
// The background is injected by our theme CSS files
24+
disable: true,
3225
}
3326
};
3427

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"compilerOptions": {
3+
"module": "esnext",
4+
"moduleResolution": "node",
5+
"target": "es6",
6+
"outDir": "out",
7+
"lib": ["ES2021", "dom"],
8+
"jsx": "react",
9+
"sourceMap": true,
10+
"rootDir": "..",
11+
"strict": true,
12+
"noUnusedLocals": true,
13+
"noImplicitReturns": true,
14+
"noFallthroughCasesInSwitch": true,
15+
"experimentalDecorators": true,
16+
"skipLibCheck": true
17+
},
18+
"exclude": ["node_modules"]
19+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from 'react';
2+
import { FunctionComponent, useCallback } from 'react';
3+
4+
import { useGlobals } from '@storybook/api';
5+
import { IconButton, Icons, WithTooltip, TooltipLinkList, Link, WithHideFn } from '@storybook/components';
6+
7+
import { themeNames, VSCodeTheme } from './theme';
8+
9+
export const ThemeSelector: FunctionComponent = () => {
10+
const [{ vscodeTheme }, updateGlobals] = useGlobals();
11+
12+
const changeTheme = useCallback((theme: VSCodeTheme) => {
13+
updateGlobals({
14+
vscodeTheme: theme,
15+
});
16+
}, [updateGlobals]);
17+
18+
const createLinks = useCallback((onHide: () => void): Link[] => Object.values(VSCodeTheme).map((theme) => ({
19+
id: theme,
20+
onClick() {
21+
changeTheme(theme);
22+
onHide();
23+
},
24+
title: themeNames[theme],
25+
value: theme,
26+
active: vscodeTheme === theme,
27+
})), [vscodeTheme, changeTheme]);
28+
29+
return (
30+
<WithTooltip
31+
placement="top"
32+
trigger="click"
33+
closeOnClick
34+
tooltip={({ onHide }: WithHideFn) => (
35+
<TooltipLinkList
36+
links={createLinks(onHide)}
37+
/>
38+
)}
39+
>
40+
<IconButton
41+
key="theme"
42+
title="Change the theme of the preview"
43+
active={vscodeTheme !== VSCodeTheme.Dark}
44+
>
45+
<Icons icon="dashboard" />
46+
</IconButton>
47+
</WithTooltip>
48+
);
49+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from 'react';
2+
import { addons, types } from '@storybook/addons';
3+
import { ThemeSelector } from './ThemeSelector';
4+
5+
const ADDON_ID = 'vscode-theme-addon';
6+
7+
addons.register(ADDON_ID, () => {
8+
addons.add(ADDON_ID, {
9+
title: 'VSCode Themes',
10+
type: types.TOOL,
11+
match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
12+
render: () => <ThemeSelector />,
13+
});
14+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function config(entry = []) {
2+
return [...entry, require.resolve("./preview.ts")];
3+
}
4+
5+
export function managerEntries(entry = []) {
6+
return [...entry, require.resolve("./manager.tsx")];
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { withTheme } from './withTheme';
2+
import { VSCodeTheme } from './theme';
3+
4+
export const decorators = [withTheme];
5+
6+
export const globals = {
7+
vscodeTheme: VSCodeTheme.Dark,
8+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export enum VSCodeTheme {
2+
Dark = 'dark',
3+
Light = 'light',
4+
}
5+
6+
export const themeNames: { [key in VSCodeTheme]: string } = {
7+
[VSCodeTheme.Dark]: 'Dark+',
8+
[VSCodeTheme.Light]: 'Light+',
9+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useEffect, useGlobals } from '@storybook/addons';
2+
import type { AnyFramework, PartialStoryFn as StoryFunction, StoryContext } from '@storybook/csf';
3+
4+
import { VSCodeTheme } from './theme';
5+
6+
const themeFiles: { [key in VSCodeTheme]: string } = {
7+
[VSCodeTheme.Dark]: require('!file-loader?modules!../../src/stories/vscode-theme-dark.css').default,
8+
[VSCodeTheme.Light]: require('!file-loader?modules!../../src/stories/vscode-theme-light.css').default,
9+
}
10+
11+
export const withTheme = (
12+
StoryFn: StoryFunction<AnyFramework>,
13+
context: StoryContext<AnyFramework>
14+
) => {
15+
const [{ vscodeTheme }] = useGlobals();
16+
17+
useEffect(() => {
18+
const styleSelectorId =
19+
context.viewMode === 'docs'
20+
? `addon-vscode-theme-docs-${context.id}`
21+
: `addon-vscode-theme-theme`;
22+
23+
const theme = Object.values(VSCodeTheme).includes(vscodeTheme) ? vscodeTheme as VSCodeTheme : VSCodeTheme.Dark;
24+
25+
document.getElementById(styleSelectorId)?.remove();
26+
27+
const link = document.createElement('link');
28+
link.id = styleSelectorId;
29+
link.href = themeFiles[theme];
30+
link.rel = 'stylesheet';
31+
32+
document.head.appendChild(link);
33+
}, [vscodeTheme]);
34+
35+
return StoryFn();
36+
};

extensions/ql-vscode/src/stories/Overview.stories.mdx

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,56 +12,8 @@ Welcome to the Storybook for **CodeQL for Visual Studio Code**! This Storybook c
1212

1313
### Switching themes
1414

15-
To switch between VSCode Dark+ and Light+ themes, you can make the following changes:
16-
17-
```diff
18-
diff --git a/extensions/ql-vscode/.storybook/manager.ts b/extensions/ql-vscode/.storybook/manager.ts
19-
--- a/extensions/ql-vscode/.storybook/manager.ts
20-
+++ b/extensions/ql-vscode/.storybook/manager.ts
21-
@@ -2,6 +2,6 @@ import { addons } from '@storybook/addons';
22-
import { themes } from '@storybook/theming';
23-
24-
addons.setConfig({
25-
- theme: themes.dark,
26-
+ theme: themes.light,
27-
enableShortcuts: false,
28-
});
29-
diff --git a/extensions/ql-vscode/.storybook/preview.ts b/extensions/ql-vscode/.storybook/preview.ts
30-
--- a/extensions/ql-vscode/.storybook/preview.ts
31-
+++ b/extensions/ql-vscode/.storybook/preview.ts
32-
@@ -4,7 +4,7 @@ import { action } from '@storybook/addon-actions';
33-
// Allow all stories/components to use Codicons
34-
import '@vscode/codicons/dist/codicon.css';
35-
36-
-import '../src/stories/vscode-theme-dark.css';
37-
+import '../src/stories/vscode-theme-light.css';
38-
39-
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
40-
export const parameters = {
41-
@@ -19,14 +19,14 @@ export const parameters = {
42-
},
43-
- // Use a dark theme to be aligned with VSCode
44-
+ // Use a light theme to be aligned with VSCode
45-
docs: {
46-
- theme: themes.dark,
47-
+ theme: themes.light,
48-
},
49-
backgrounds: {
50-
- default: 'dark',
51-
+ default: 'light',
52-
values: [
53-
{
54-
- name: 'dark',
55-
- value: '#1e1e1e',
56-
+ name: 'light',
57-
+ value: '#ffffff',
58-
},
59-
],
60-
}
61-
```
62-
63-
You will need to restart Storybook to apply the theme change to the Storybook UI. The preview frame should update
64-
automatically.
15+
To switch between VSCode Dark+ and Light+ themes, use the button in the toolbar. This will not work on this document, so you'll only see
16+
the changes applied to a different story.
6517

6618
### Writing stories
6719

0 commit comments

Comments
 (0)