From b94abbd6b3c9e980c8cb154ff379acd4e85f7ad0 Mon Sep 17 00:00:00 2001 From: Nathan Vasse Date: Tue, 26 Sep 2023 11:40:40 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(react)=20add=20themes=20switching=20i?= =?UTF-8?q?n=20Storybook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to be able to visualize easily themes directly inside Storybook. This was not a trivial task as there is no centralized logic to handle Doc / Stories / Manager at the same time. --- packages/react/.storybook/manager.ts | 40 ----------- packages/react/.storybook/manager.tsx | 34 +++++++++ packages/react/.storybook/preview-head.html | 12 +++- packages/react/.storybook/preview.ts | 46 ------------- packages/react/.storybook/preview.tsx | 76 +++++++++++++++++++++ packages/react/.storybook/themes.ts | 61 +++++++++++++++++ packages/react/public/logo-cunningham.svg | 10 --- 7 files changed, 182 insertions(+), 97 deletions(-) delete mode 100644 packages/react/.storybook/manager.ts create mode 100644 packages/react/.storybook/manager.tsx delete mode 100644 packages/react/.storybook/preview.ts create mode 100644 packages/react/.storybook/preview.tsx create mode 100644 packages/react/.storybook/themes.ts diff --git a/packages/react/.storybook/manager.ts b/packages/react/.storybook/manager.ts deleted file mode 100644 index 4d7be41..0000000 --- a/packages/react/.storybook/manager.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { addons } from "@storybook/manager-api"; -import { create } from "@storybook/theming"; -import { defaultTokens } from "@openfun/cunningham-tokens"; - -const COLORS = defaultTokens.theme.colors; - -const theme = create({ - base: "light", - brandUrl: "https://github.com/openfun/cunningham", - brandImage: "logo-cunningham.svg", - brandTitle: "Cunningham", - brandTarget: "_self", - - // - colorPrimary: COLORS["primary-400"], - colorSecondary: COLORS["primary-400"], - - // UI - appBg: COLORS["greyscale-100"], - appContentBg: COLORS["greyscale-000"], - appBorderColor: COLORS["greyscale-300"], - appBorderRadius: 4, - - // Text colors - textColor: COLORS["greyscale-900"], - textInverseColor: COLORS["greyscale-000"], - - // Toolbar default and active colors - barTextColor: COLORS["greyscale-500"], - barSelectedColor: COLORS["greyscale-900"], - barBg: COLORS["greyscale-000"], - - // Form colors - inputBg: COLORS["greyscale-000"], - inputBorder: COLORS["greyscale-300"], - inputTextColor: COLORS["greyscale-800"], - inputBorderRadius: 2, -}); - -addons.setConfig({ theme }); diff --git a/packages/react/.storybook/manager.tsx b/packages/react/.storybook/manager.tsx new file mode 100644 index 0000000..31a915f --- /dev/null +++ b/packages/react/.storybook/manager.tsx @@ -0,0 +1,34 @@ +import { addons, types, useStorybookApi } from '@storybook/manager-api'; +import { getThemeFromGlobals, themes } from './themes'; +import React, { useEffect } from 'react'; +import { useGlobals } from '@storybook/api'; + +addons.setConfig({ theme: themes.default }); + +/** + * This add-on is just here to apply the theme to the Storybook manager ( the top-most frame + * containing sidebar, toolbar, etc ) when the theme is switched. + * + * The reason why we needed to add this add-on is that add-ons are the only place from where you can + * dynamically change the current theme of the manager. + */ +addons.register('theme-synchronizer', () => { + addons.add('theme-synchronizer/main', { + title: 'Theme synchronizer', + //👇 Sets the type of UI element in Storybook + type: types.TOOL, + //👇 Shows the Toolbar UI element if either the Canvas or Docs tab is active + match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)), + render: ({ active }) => { + const api = useStorybookApi(); + const [globals, updateGlobals] = useGlobals(); + const theme = getThemeFromGlobals(globals); + useEffect(() => { + api.setOptions({ + theme: themes[theme] + }) + }, [theme]); + return null; + }, + }); +}); diff --git a/packages/react/.storybook/preview-head.html b/packages/react/.storybook/preview-head.html index 037204a..5d705c6 100644 --- a/packages/react/.storybook/preview-head.html +++ b/packages/react/.storybook/preview-head.html @@ -12,7 +12,17 @@ pre * { font-family: ui-monospace,Menlo,Monaco,"Roboto Mono","Oxygen Mono","Ubuntu Monospace","Source Code Pro","Droid Sans Mono","Courier New",monospace; } + + .cunningham-theme--dark { + .docblock-source { + background-color: var(--c--theme--colors--greyscale-100); + } + + .prismjs { + background-color: var(--c--theme--colors--greyscale-100) !important; + } + } \ No newline at end of file + diff --git a/packages/react/.storybook/preview.ts b/packages/react/.storybook/preview.ts deleted file mode 100644 index adcef59..0000000 --- a/packages/react/.storybook/preview.ts +++ /dev/null @@ -1,46 +0,0 @@ -import "../src/icons.scss"; -import "../src/index.scss"; -import "../src/fonts.scss"; -import { Preview } from "@storybook/react"; - -const preview: Preview = { - parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - options: { - storySort: (a, b) => { - const roots = ["Getting Started", "Components"]; - const gettingStartedOrder = [ - "Installation", - "Customization", - "Colors", - "Spacings", - "Typography", - ]; - - const aParts = a.title.split("/"); - const bParts = b.title.split("/"); - if (aParts[0] !== bParts[0]) { - return roots.indexOf(aParts[0]) - roots.indexOf(bParts[0]); - } - if (aParts[1] !== bParts[1]) { - if (aParts[0] === "Getting Started") { - return ( - gettingStartedOrder.indexOf(aParts[1]) - - gettingStartedOrder.indexOf(bParts[1]) - ); - } - return aParts[1].localeCompare(bParts[1]); - } - return 0; - }, - }, - }, -}; - -export default preview; diff --git a/packages/react/.storybook/preview.tsx b/packages/react/.storybook/preview.tsx new file mode 100644 index 0000000..9bc2352 --- /dev/null +++ b/packages/react/.storybook/preview.tsx @@ -0,0 +1,76 @@ +import '../src/icons.scss'; +import '../src/index.scss'; +import '../src/fonts.scss'; +import { Preview } from '@storybook/react'; +import { DocsContainer } from '@storybook/blocks'; + +import { CunninghamProvider } from ':/components/Provider'; +import { BACKGROUND_COLOR_TO_THEME, getThemeFromGlobals, themes } from './themes'; + +export const DocsWithTheme = (props, context) => { + const theme = getThemeFromGlobals(props.context.store.globals.globals); + return + + ; +}; + +const preview: Preview = { + decorators: [ + (Story, context) => ( + + + + ), + ], + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { + default: null, + values: Object.entries(BACKGROUND_COLOR_TO_THEME).map(value => ({ + name: value[1], + value: value[0], + })), + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + docs: { + container: DocsWithTheme, + }, + options: { + storySort: (a, b) => { + const roots = ['Getting Started', 'Components']; + const gettingStartedOrder = [ + 'Installation', + 'First steps', + 'Customization', + 'Theming', + 'Colors', + 'Spacings', + 'Typography', + ]; + + const aParts = a.title.split('/'); + const bParts = b.title.split('/'); + if (aParts[0] !== bParts[0]) { + return roots.indexOf(aParts[0]) - roots.indexOf(bParts[0]); + } + if (aParts[1] !== bParts[1]) { + if (aParts[0] === 'Getting Started') { + return ( + gettingStartedOrder.indexOf(aParts[1]) - + gettingStartedOrder.indexOf(bParts[1]) + ); + } + return aParts[1].localeCompare(bParts[1]); + } + return 0; + }, + }, + }, +}; + +export default preview; diff --git a/packages/react/.storybook/themes.ts b/packages/react/.storybook/themes.ts new file mode 100644 index 0000000..361466b --- /dev/null +++ b/packages/react/.storybook/themes.ts @@ -0,0 +1,61 @@ +import { create } from '@storybook/theming'; +import { tokens } from '../src/cunningham-tokens'; + +const buildTheme = (colors: typeof tokens.themes.default.theme.colors & any) => { + return { + brandUrl: 'https://github.com/openfun/cunningham', + brandImage: 'logo-cunningham.svg', + brandTitle: 'Cunningham', + brandTarget: '_self', + + // + colorPrimary: colors['primary-400'], + colorSecondary: colors['primary-400'], + + // UI + appBg: colors['greyscale-100'], + appContentBg: colors['greyscale-000'], + appBorderColor: colors['greyscale-300'], + appBorderRadius: 4, + + // Text colors + textColor: colors['greyscale-900'], + textInverseColor: colors['greyscale-000'], + + // Toolbar default and active colors + barTextColor: colors['greyscale-500'], + barSelectedColor: colors['greyscale-900'], + barBg: colors['greyscale-000'], + + // Form colors + inputBg: colors['greyscale-000'], + inputBorder: colors['greyscale-300'], + inputTextColor: colors['greyscale-800'], + inputBorderRadius: 2, + }; +}; + +export const themes = { + default: create({ + base: 'light', + ...buildTheme(tokens.themes.default.theme.colors), + }), + dark: create({ + base: 'dark', + ...buildTheme(tokens.themes.dark.theme.colors), + }), +}; + +export enum Themes { + dark = 'dark', + default = 'default' +} + +export const BACKGROUND_COLOR_TO_THEME = { + '#0C1A2B': Themes.dark, +}; + +export const getThemeFromGlobals = (globals: any): string => { + const color = BACKGROUND_COLOR_TO_THEME[globals.backgrounds?.value]; + return color ?? Themes.default; +}; diff --git a/packages/react/public/logo-cunningham.svg b/packages/react/public/logo-cunningham.svg index b5a5153..e0493d3 100644 --- a/packages/react/public/logo-cunningham.svg +++ b/packages/react/public/logo-cunningham.svg @@ -7,16 +7,6 @@ - - - - - - - - - -