[storybook] Fix theme switcher (#214306)

## Summary

Both Storybook and the theme switcher addon use Amsterdam by default.
This PR adds Borealis to the theme switcher and defaults it to Borealis.

## NOTE

This PR may conflict with #195148 ... it should likely be merged into
that PR, or into `main` if the conflict is minor. I leave it to @Ikuni17
to determine the best path forward.
This commit is contained in:
Clint Andrew Hall 2025-03-13 10:38:14 -04:00 committed by GitHub
parent c42d763ce4
commit 9cddd5dcdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 73 additions and 21 deletions

View file

@ -17,3 +17,4 @@ export { defaultConfig, defaultConfigWebFinal, mergeWebpackFinal };
export type { StorybookConfig };
export { runStorybookCli } from './src/lib/run_storybook_cli';
export { default as WebpackConfig } from './src/webpack.config';
export { DEFAULT_THEME, THEMES } from './src/lib/themes';

View file

@ -21,7 +21,9 @@ import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
import { i18n } from '@kbn/i18n';
const theme$ = new BehaviorSubject<CoreTheme>({ darkMode: false, name: 'amsterdam' });
import { DEFAULT_THEME, getKibanaTheme } from './themes';
const theme$ = new BehaviorSubject<CoreTheme>(getKibanaTheme(DEFAULT_THEME));
const userProfile = { getUserProfile$: () => of(null) };
const i18nStart: I18nStart = {
@ -41,11 +43,11 @@ const analytics: AnalyticsServiceStart = {
const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => {
// TODO: Add a switcher to see components in other locales or pseudo locale
i18n.init({ locale: 'en', messages: {} });
const colorMode = globals.euiTheme === 'v8.dark' ? 'dark' : 'light';
const { darkMode, name } = getKibanaTheme(globals.euiTheme);
useEffect(() => {
theme$.next({ darkMode: colorMode === 'dark', name: 'amsterdam' });
}, [colorMode]);
theme$.next({ darkMode, name });
}, [darkMode, name, globals.euiTheme]);
return (
<KibanaRootContextProvider {...{ theme: { theme$ }, userProfile, analytics, i18n: i18nStart }}>

View file

@ -11,12 +11,12 @@ import React, { useCallback, useEffect } from 'react';
import { Icons, IconButton, TooltipLinkList, WithTooltip } from '@storybook/components';
import { useGlobals } from '@storybook/api';
import { DEFAULT_THEME, THEMES, THEME_TITLES } from './themes';
type PropsOf<T extends React.FC<any>> = T extends React.FC<infer P> ? P : never;
type ArrayItem<T extends any[]> = T extends Array<infer I> ? I : never;
type Link = ArrayItem<PropsOf<typeof TooltipLinkList>['links']>;
const defaultTheme = 'v8.light';
export function ThemeSwitcher() {
const [{ euiTheme: selectedTheme }, updateGlobals] = useGlobals();
@ -29,7 +29,7 @@ export function ThemeSwitcher() {
useEffect(() => {
if (!selectedTheme) {
selectTheme(defaultTheme);
selectTheme(DEFAULT_THEME);
}
}, [selectTheme, selectedTheme]);
@ -64,25 +64,17 @@ const ThemeSwitcherTooltip = React.memo(
onChangeSelectedTheme: (themeId: string) => void;
selectedTheme: string;
}) => {
const links = [
{
id: 'v8.light',
title: 'Light',
},
{
id: 'v8.dark',
title: 'Dark',
},
].map(
(link): Link => ({
...link,
const links = THEMES.map(
(theme): Link => ({
id: theme,
title: THEME_TITLES[theme],
onClick: (_event, item) => {
if (item.id != null && item.id !== selectedTheme) {
onChangeSelectedTheme(item.id);
}
onHide();
},
active: selectedTheme === link.id,
active: selectedTheme === theme,
})
);

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export const BOREALIS_LIGHT = 'borealis.light';
export const BOREALIS_DARK = 'borealis.dark';
export const AMSTERDAM_LIGHT = 'amsterdam.light';
export const AMSTERDAM_DARK = 'amsterdam.dark';
export const THEMES = [BOREALIS_LIGHT, BOREALIS_DARK, AMSTERDAM_LIGHT, AMSTERDAM_DARK] as const;
export type Theme = (typeof THEMES)[number];
export const DEFAULT_THEME: Theme = 'borealis.light';
export const THEME_TITLES: Record<Theme, string> = {
[BOREALIS_LIGHT]: 'Borealis Light',
[BOREALIS_DARK]: 'Borealis Dark',
[AMSTERDAM_LIGHT]: 'Amsterdam Light',
[AMSTERDAM_DARK]: 'Amsterdam Dark',
};
export const getColorMode = (theme: Theme) => {
if (theme === BOREALIS_DARK || theme === AMSTERDAM_DARK) {
return 'dark';
}
return 'light';
};
export const getEuiThemeName = (theme: Theme) => {
if (theme === AMSTERDAM_LIGHT || theme === AMSTERDAM_DARK) {
return 'amsterdam';
}
return 'borealis';
};
export const getKibanaTheme = (theme: Theme) => {
const colorMode = getColorMode(theme);
const name = getEuiThemeName(theme);
return {
darkMode: colorMode === 'dark',
name,
};
};

View file

@ -8,6 +8,7 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { EuiButton } from '@elastic/eui';
import { AssistantAvatar as Component } from '../avatar';
export default {
@ -31,4 +32,9 @@ export default {
},
} as ComponentMeta<typeof Component>;
export const Avatar: ComponentStory<typeof Component> = (args) => <Component {...args} />;
export const Avatar: ComponentStory<typeof Component> = (args) => (
<>
<Component {...args} />
<EuiButton fill>Test</EuiButton>
</>
);