Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

web: upgrade Storybook #36437

Merged
merged 12 commits into from
Jun 7, 2022
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ const config = {
message:
'Please use components from the Wildcard component library instead. We work on removing `reactstrap` dependency.',
},
{
name: 'chromatic/isChromatic',
message: 'Please use `isChromatic` from the `@sourcegraph/storybook` package.',
},
],
patterns: [
{
Expand Down
2 changes: 2 additions & 0 deletions client/build-config/src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ import path from 'path'
export const ROOT_PATH = path.resolve(__dirname, '../../../')
export const NODE_MODULES_PATH = path.resolve(ROOT_PATH, 'node_modules')
export const MONACO_EDITOR_PATH = path.resolve(NODE_MODULES_PATH, 'monaco-editor')
export const STATIC_ASSETS_PATH = path.join(ROOT_PATH, 'ui/assets')
export const STATIC_INDEX_PATH = path.resolve(STATIC_ASSETS_PATH, 'index.html')
5 changes: 2 additions & 3 deletions client/shared/src/testing/integration/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as prettier from 'prettier'
import { Subject, Subscription, throwError } from 'rxjs'
import { first, timeoutWith } from 'rxjs/operators'

import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
import { asError, keyExistsIn } from '@sourcegraph/common'
import { ErrorGraphQLResult, SuccessGraphQLResult } from '@sourcegraph/http-client'
// eslint-disable-next-line no-restricted-imports
Expand All @@ -31,8 +32,6 @@ util.inspect.defaultOptions.maxStringLength = 80
Polly.register(CdpAdapter as any)
Polly.register(FSPersister)

const ASSETS_DIRECTORY = path.resolve(__dirname, '../../../../../ui/assets')

const checkPollyMode = (mode: string): MODE => {
if (mode === 'record' || mode === 'replay' || mode === 'passthrough' || mode === 'stopped') {
return mode
Expand Down Expand Up @@ -179,7 +178,7 @@ export const createSharedIntegrationTestContext = async <
// Cache all responses for the entire lifetime of the test run
response.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
try {
const content = await readFile(path.join(ASSETS_DIRECTORY, asset), {
const content = await readFile(path.join(STATIC_ASSETS_PATH, asset), {
// Polly doesn't support Buffers or streams at the moment
encoding: 'utf-8',
})
Expand Down
9 changes: 5 additions & 4 deletions client/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
"main": "./src/index.ts",
"scripts": {
"lint:js": "eslint --cache 'src/**/*.[jt]s?(x)'",
"start": "TS_NODE_TRANSPILE_ONLY=true start-storybook -p 9001 -c ./src -s ./assets,../../ui/assets",
"build": "TS_NODE_TRANSPILE_ONLY=true build-storybook -c ./src -s ./assets,../../ui/assets",
"build:webpack-stats": "TS_NODE_TRANSPILE_ONLY=true WEBPACK_DLL_PLUGIN=false start-storybook -c ./src -s ./assets --smoke-test --webpack-stats-json ./storybook-static --loglevel warn",
"start": "TS_NODE_TRANSPILE_ONLY=true start-storybook -p 9001 -c ./src",
"start:chromatic": "CHROMATIC=true TS_NODE_TRANSPILE_ONLY=true start-storybook -p 9001 -c ./src",
"build": "TS_NODE_TRANSPILE_ONLY=true build-storybook -c ./src",
"build:webpack-stats": "TS_NODE_TRANSPILE_ONLY=true WEBPACK_DLL_PLUGIN=false start-storybook -c ./src --smoke-test --webpack-stats-json ./storybook-static --loglevel warn",
"build:dll-bundle": "TS_NODE_TRANSPILE_ONLY=true webpack --config ./src/webpack.config.dll.ts --no-stats",
"start:dll": "TS_NODE_TRANSPILE_ONLY=true WEBPACK_DLL_PLUGIN=true start-storybook -p 9001 -c ./src -s ./assets",
"start:dll": "TS_NODE_TRANSPILE_ONLY=true WEBPACK_DLL_PLUGIN=true start-storybook -p 9001 -c ./src",
"clean:dll": "rm -rf assets/dll-bundle storybook-static/*-stats.json",
"test": "jest"
}
Expand Down
30 changes: 0 additions & 30 deletions client/storybook/src/chromatic-story/Chromatic.story.tsx

This file was deleted.

46 changes: 0 additions & 46 deletions client/storybook/src/chromatic-story/add-story.ts

This file was deleted.

41 changes: 0 additions & 41 deletions client/storybook/src/chromatic-story/create-chromatic-story.tsx

This file was deleted.

13 changes: 0 additions & 13 deletions client/storybook/src/chromatic-story/story-store.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.theme-wrapper {
padding: 1rem;
color: var(--body-color);
background-color: var(--body-bg);
position: relative;
min-height: 50vh;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { FunctionComponent, PropsWithChildren, useState } from 'react'

import classNames from 'classnames'

import { PopoverRoot } from '@sourcegraph/wildcard'

import { ChromaticThemeContext, ChromaticTheme } from '../../../hooks/useChromaticTheme'

import styles from './ChromaticRoot.module.scss'

interface ChromaticRootProps extends ChromaticTheme {}

export const ChromaticRoot: FunctionComponent<PropsWithChildren<ChromaticRootProps>> = props => {
const { theme, children } = props

const [rootReference, setElement] = useState<HTMLDivElement | null>(null)
const themeClass = theme === 'light' ? 'theme-light' : 'theme-dark'

return (
<ChromaticThemeContext.Provider value={{ theme }}>
{/* Required to render `Popover` inside of the `ChromaticRoot` component. */}
<PopoverRoot.Provider value={{ renderRoot: rootReference }}>
<div className={classNames(themeClass, styles.themeWrapper)}>
{children}

<div ref={setElement} />
</div>
</PopoverRoot.Provider>
</ChromaticThemeContext.Provider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ChromaticRoot'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './withChromaticThemes'
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ReactElement } from 'react'

import { DecoratorFunction } from '@storybook/addons'

import { ChromaticRoot } from './ChromaticRoot'

/**
* The global Storybook decorator used to snapshot stories with multiple themes in Chromatic.
*
* It's a recommended way of achieving this goal:
* https://www.chromatic.com/docs/faq#do-you-support-taking-snapshots-of-a-component-with-multiple-the
*
* If the `chromatic.enableDarkMode` story parameter is set to `true`, the story will
* be rendered twice in Chromatic — in light and dark modes.
*/
export const withChromaticThemes: DecoratorFunction<ReactElement> = (StoryFunc, { parameters }) => {
if (parameters?.chromatic?.enableDarkMode) {
return (
<>
<ChromaticRoot theme="light">
<StoryFunc />
</ChromaticRoot>

<ChromaticRoot theme="dark">
<StoryFunc />
</ChromaticRoot>
</>
)
}

return <StoryFunc />
}
1 change: 1 addition & 0 deletions client/storybook/src/environment-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const ENVIRONMENT_CONFIG = {
WEBPACK_BUNDLE_ANALYZER: getEnvironmentBoolean('WEBPACK_BUNDLE_ANALYZER'),
WEBPACK_SPEED_ANALYZER: getEnvironmentBoolean('WEBPACK_SPEED_ANALYZER'),
MINIFY: getEnvironmentBoolean('MINIFY'),
CHROMATIC: getEnvironmentBoolean('CHROMATIC'),
}
13 changes: 13 additions & 0 deletions client/storybook/src/hooks/useChromaticTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext, useContext } from 'react'

export interface ChromaticTheme {
theme: 'light' | 'dark'
}

export const ChromaticThemeContext = createContext<ChromaticTheme>({
theme: 'light',
})

export function useChromaticDarkMode(): boolean {
return useContext(ChromaticThemeContext).theme === 'dark'
}
19 changes: 7 additions & 12 deletions client/storybook/src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { useLayoutEffect, useState } from 'react'

import { useDarkMode } from 'storybook-dark-mode'
import { useDarkMode as useRegularDarkMode } from 'storybook-dark-mode'

import { isChromatic } from '../utils/isChromatic'

import { useChromaticDarkMode } from './useChromaticTheme'

const useDarkMode = isChromatic() ? useChromaticDarkMode : useRegularDarkMode

/**
* Gets current theme and updates value when theme changes
Expand All @@ -17,16 +23,5 @@ export const useTheme = (): boolean => {
setIsLightTheme(!isDarkMode)
}, [isDarkMode])

// This is required for Chromatic to react to theme changes when
// taking screenshots. See `create-chromatic-story.tsx` where
// this event is dispatched.
useLayoutEffect(() => {
const listener = ((event: CustomEvent<boolean>): void => {
setIsLightTheme(event.detail)
}) as EventListener
document.body.addEventListener('chromatic-light-theme-toggled', listener)
return () => document.body.removeEventListener('chromatic-light-theme-toggled', listener)
}, [])

return isLightTheme
}
1 change: 1 addition & 0 deletions client/storybook/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './apollo/MockedStoryProvider'
export * from './hooks/usePrependStyles'
export * from './hooks/useTheme'
export * from './utils/isChromatic'
11 changes: 7 additions & 4 deletions client/storybook/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getBabelLoader,
getBasicCSSLoader,
getStatoscopePlugin,
STATIC_ASSETS_PATH,
} from '@sourcegraph/build-config'

import { ensureDllBundleIsReady } from './dllPlugin'
Expand All @@ -38,9 +39,6 @@ const getStoriesGlob = (): string[] => {
return [path.resolve(ROOT_PATH, ENVIRONMENT_CONFIG.STORIES_GLOB)]
}

// Stories in `Chromatic.story.tsx` are guarded by the `isChromatic()` check. It will result in noop in all other environments.
const chromaticStoriesGlob = path.resolve(ROOT_PATH, 'client/storybook/src/chromatic-story/Chromatic.story.tsx')

// Due to an issue with constant recompiling (https://github.com/storybookjs/storybook/issues/14342)
// we need to make the globs more specific (`(web|shared..)` also doesn't work). Once the above issue
// is fixed, this can be removed and watched for `client/**/*.story.tsx` again.
Expand All @@ -49,7 +47,7 @@ const getStoriesGlob = (): string[] => {
path.resolve(ROOT_PATH, `client/${packageDirectory}/src/**/*.story.tsx`)
)

return [...storiesGlobs, chromaticStoriesGlob]
return [...storiesGlobs]
}

const getDllScriptTag = (): string => {
Expand All @@ -65,6 +63,7 @@ const getDllScriptTag = (): string => {
}

const config = {
staticDirs: [path.resolve(__dirname, '../assets'), STATIC_ASSETS_PATH],
stories: getStoriesGlob(),
addons: [
'@storybook/addon-knobs',
Expand All @@ -77,6 +76,9 @@ const config = {

core: {
builder: 'webpack5',
options: {
fsCache: true,
},
},

features: {
Expand Down Expand Up @@ -111,6 +113,7 @@ const config = {
new DefinePlugin({
NODE_ENV: JSON.stringify(config.mode),
'process.env.NODE_ENV': JSON.stringify(config.mode),
'process.env.CHROMATIC': JSON.stringify(ENVIRONMENT_CONFIG.CHROMATIC),
}),
getProvidePlugin()
)
Expand Down
Loading