mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:31:48 +00:00
web: upgrade Storybook (#36437)
This commit is contained in:
parent
3fefbdf487
commit
4a09ea0f27
@ -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: [
|
||||
{
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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',
|
||||
})
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
import { PublishedStoreItem } from '@storybook/client-api'
|
||||
import { raw } from '@storybook/react'
|
||||
import isChromatic from 'chromatic/isChromatic'
|
||||
|
||||
import { addStory } from './add-story'
|
||||
import { storyStore } from './story-store'
|
||||
|
||||
// Execute logic below only in the environment where Chromatic snapshots are captured.
|
||||
if (isChromatic()) {
|
||||
// CSF stories need to be evaluated before they are added to the `StoryStore` and thus are not immediately available.
|
||||
// We setTimeout to delay this logic until all stories have been added.
|
||||
setTimeout(() => {
|
||||
// Get an array of all stories which are already added to the `StoryStore`.
|
||||
// Use `raw()` because we don't want to apply any filtering and sorting on the array of stories.
|
||||
const storeItems = raw() as PublishedStoreItem[]
|
||||
|
||||
// `StoryStore` is immutable outside of a configure() call.
|
||||
// As we delay this logic to support CSF stories, we need to set this to ensure changes are still applied.
|
||||
storyStore.startConfiguring()
|
||||
|
||||
// Add three more versions of each story to test visual regressions with Chromatic snapshots.
|
||||
// In other environments, these themes can be explored by a user via toolbar toggles.
|
||||
for (const storeItem of storeItems) {
|
||||
// Default theme + Dark mode.
|
||||
addStory({ storeItem })
|
||||
}
|
||||
|
||||
storyStore.finishConfiguring()
|
||||
}, 0)
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import { PublishedStoreItem } from '@storybook/client-api'
|
||||
import { toId } from '@storybook/csf'
|
||||
|
||||
import { createChromaticStory } from './create-chromatic-story'
|
||||
import { storyStore } from './story-store'
|
||||
|
||||
interface AddStoryOptions {
|
||||
storeItem: PublishedStoreItem
|
||||
}
|
||||
|
||||
export const addStory = (options: AddStoryOptions): void => {
|
||||
const {
|
||||
storeItem: { name, kind, storyFn, parameters },
|
||||
} = options
|
||||
|
||||
const isDarkModeEnabled = Boolean(parameters?.chromatic?.enableDarkMode)
|
||||
|
||||
// Add suffix to the story name based on theme options:
|
||||
// 1. Default + Dark: "Text" -> "Text 🌚"
|
||||
const storyName = [name, isDarkModeEnabled && '🌚'].filter(Boolean).join(' ')
|
||||
|
||||
/**
|
||||
* Use `storyStore.addStory()` to avoid applying decorators to stories, because `PublishedStoreItem.storyFn` already has decorators applied.
|
||||
* `storiesOf().add()` usage API would result in decorators duplication. It's possible to avoid this issue using `PublishedStoreItem.getOriginal()`,
|
||||
* which returns only story function without any decorators and story context. It means that we should apply them manually and
|
||||
* keep this logic in sync with Storybook internals to have consistent behavior. `storyStore.addStory()` allows to avoid it.
|
||||
*/
|
||||
storyStore.addStory(
|
||||
{
|
||||
id: toId(kind, storyName),
|
||||
kind,
|
||||
name: storyName,
|
||||
parameters,
|
||||
loaders: [],
|
||||
storyFn: createChromaticStory({
|
||||
storyFn,
|
||||
isDarkModeEnabled,
|
||||
}),
|
||||
},
|
||||
{
|
||||
// The default `applyDecorators` implementation accepts `decorators` as a second arg and applies them to the `storyFn`.
|
||||
// Our `storyFn` already has all the decorators applied, so we just return it.
|
||||
applyDecorators: storyFunc => storyFunc,
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
|
||||
import { StoryFn } from '@storybook/addons'
|
||||
import { useDarkMode } from 'storybook-dark-mode'
|
||||
|
||||
import { THEME_DARK_CLASS, THEME_LIGHT_CLASS } from '../themes'
|
||||
|
||||
export interface CreateChromaticStoryOptions {
|
||||
storyFn: StoryFn<ReactElement>
|
||||
isDarkModeEnabled: boolean
|
||||
}
|
||||
|
||||
// Wrap `storyFn` into a decorator which takes care of CSS classes toggling based on received theme options.
|
||||
export const createChromaticStory = (options: CreateChromaticStoryOptions): StoryFn => () => {
|
||||
const { storyFn, isDarkModeEnabled } = options
|
||||
// The `storyFn` is retrieved from the `StoryStore`, so it already has a `StoryContext`.
|
||||
// We can safely change its type to remove required props `StoryContext` props check.
|
||||
const Story = storyFn as React.ComponentType<React.PropsWithChildren<unknown>>
|
||||
|
||||
const isDarkModeEnabledInitially = useDarkMode()
|
||||
|
||||
useEffect(() => {
|
||||
// 'storybook-dark-mode' doesn't expose any API to toggle dark/light theme programmatically, so we do it manually.
|
||||
document.body.classList.toggle(THEME_DARK_CLASS, isDarkModeEnabled)
|
||||
document.body.classList.toggle(THEME_LIGHT_CLASS, !isDarkModeEnabled)
|
||||
document.body.dispatchEvent(new CustomEvent('chromatic-light-theme-toggled', { detail: !isDarkModeEnabled }))
|
||||
|
||||
return () => {
|
||||
// Always toggle dark mode back to the previous value because otherwise, it might be out of sync with the toolbar toggle.
|
||||
document.body.classList.toggle(THEME_DARK_CLASS, isDarkModeEnabledInitially)
|
||||
document.body.classList.toggle(THEME_LIGHT_CLASS, !isDarkModeEnabledInitially)
|
||||
document.body.dispatchEvent(
|
||||
new CustomEvent('chromatic-light-theme-toggled', { detail: !isDarkModeEnabledInitially })
|
||||
)
|
||||
}
|
||||
// We need to execute `useEffect` callback once to take snapshot in Chromatic, so we can omit dependencies here.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return <Story />
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import { StoryStore } from '@storybook/client-api'
|
||||
|
||||
// This global reference is used internally by Storybook:
|
||||
// https://github.com/storybookjs/storybook/blob/3ec358f71c6111838092397d13fbe35b627a9a9d/lib/core-client/src/preview/start.ts#L43
|
||||
declare global {
|
||||
interface Window {
|
||||
__STORYBOOK_STORY_STORE__: StoryStore
|
||||
}
|
||||
}
|
||||
|
||||
// See the discussion about `StoryStore` usage in stories:
|
||||
// https://github.com/storybookjs/storybook/discussions/12050#discussioncomment-125658
|
||||
export const storyStore = window.__STORYBOOK_STORY_STORE__
|
||||
@ -0,0 +1,7 @@
|
||||
.theme-wrapper {
|
||||
padding: 1rem;
|
||||
color: var(--body-color);
|
||||
background-color: var(--body-bg);
|
||||
position: relative;
|
||||
min-height: 50vh;
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './ChromaticRoot'
|
||||
@ -0,0 +1 @@
|
||||
export * from './withChromaticThemes'
|
||||
@ -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 />
|
||||
}
|
||||
@ -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
client/storybook/src/hooks/useChromaticTheme.ts
Normal file
13
client/storybook/src/hooks/useChromaticTheme.ts
Normal file
@ -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'
|
||||
}
|
||||
@ -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
|
||||
@ -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,3 +1,4 @@
|
||||
export * from './apollo/MockedStoryProvider'
|
||||
export * from './hooks/usePrependStyles'
|
||||
export * from './hooks/useTheme'
|
||||
export * from './utils/isChromatic'
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
getBabelLoader,
|
||||
getBasicCSSLoader,
|
||||
getStatoscopePlugin,
|
||||
STATIC_ASSETS_PATH,
|
||||
} from '@sourcegraph/build-config'
|
||||
|
||||
import { ensureDllBundleIsReady } from './dllPlugin'
|
||||
@ -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.
|
||||
@ -49,7 +47,7 @@ const getStoriesGlob = (): string[] => {
|
||||
path.resolve(ROOT_PATH, `client/${packageDirectory}/src/**/*.story.tsx`)
|
||||
)
|
||||
|
||||
return [...storiesGlobs, chromaticStoriesGlob]
|
||||
return [...storiesGlobs]
|
||||
}
|
||||
|
||||
const getDllScriptTag = (): string => {
|
||||
@ -65,6 +63,7 @@ const getDllScriptTag = (): string => {
|
||||
}
|
||||
|
||||
const config = {
|
||||
staticDirs: [path.resolve(__dirname, '../assets'), STATIC_ASSETS_PATH],
|
||||
stories: getStoriesGlob(),
|
||||
addons: [
|
||||
'@storybook/addon-knobs',
|
||||
@ -77,6 +76,9 @@ const config = {
|
||||
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
options: {
|
||||
fsCache: true,
|
||||
},
|
||||
},
|
||||
|
||||
features: {
|
||||
@ -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()
|
||||
)
|
||||
|
||||
@ -3,20 +3,22 @@ import { ReactElement } from 'react'
|
||||
|
||||
import { configureActions } from '@storybook/addon-actions'
|
||||
import { withConsole } from '@storybook/addon-console'
|
||||
import { DecoratorFunction } from '@storybook/addons'
|
||||
import isChromatic from 'chromatic/isChromatic'
|
||||
import { DecoratorFunction, Parameters } from '@storybook/addons'
|
||||
import { withDesign } from 'storybook-addon-designs'
|
||||
|
||||
import { setLinkComponent, AnchorLink } from '@sourcegraph/wildcard'
|
||||
|
||||
import { withChromaticThemes } from './decorators/withChromaticThemes'
|
||||
import { themeDark, themeLight, THEME_DARK_CLASS, THEME_LIGHT_CLASS } from './themes'
|
||||
import { isChromatic } from './utils/isChromatic'
|
||||
|
||||
const withConsoleDecorator: DecoratorFunction<ReactElement> = (storyFunc, context): ReactElement =>
|
||||
withConsole()(storyFunc)(context)
|
||||
|
||||
export const decorators = [withDesign, withConsoleDecorator]
|
||||
export const decorators = [withDesign, withConsoleDecorator, isChromatic() && withChromaticThemes].filter(Boolean)
|
||||
|
||||
export const parameters = {
|
||||
export const parameters: Parameters = {
|
||||
layout: 'fullscreen',
|
||||
options: {
|
||||
storySort: {
|
||||
order: ['wildcard', 'shared', 'branded', '*'],
|
||||
|
||||
7
client/storybook/src/utils/isChromatic.ts
Normal file
7
client/storybook/src/utils/isChromatic.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import isChromaticDefault from 'chromatic/isChromatic'
|
||||
|
||||
/**
|
||||
* `chromatic/isChromatic` wrapper that takes into account `process.env.CHROMATIC` for local testing.
|
||||
*/
|
||||
export const isChromatic = (): boolean => isChromaticDefault() || Boolean(process.env.CHROMATIC)
|
||||
@ -3,9 +3,9 @@ import path from 'path'
|
||||
import * as esbuild from 'esbuild'
|
||||
import signale from 'signale'
|
||||
|
||||
import { MONACO_LANGUAGES_AND_FEATURES } from '@sourcegraph/build-config'
|
||||
import { MONACO_LANGUAGES_AND_FEATURES, ROOT_PATH, STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { ENVIRONMENT_CONFIG, ROOT_PATH, STATIC_ASSETS_PATH } from '../utils'
|
||||
import { ENVIRONMENT_CONFIG } from '../utils'
|
||||
|
||||
import { manifestPlugin } from './manifestPlugin'
|
||||
import { monacoPlugin } from './monacoPlugin'
|
||||
|
||||
@ -3,7 +3,8 @@ import path from 'path'
|
||||
|
||||
import * as esbuild from 'esbuild'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '../utils'
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { WebpackManifest } from '../webpack/get-html-webpack-plugins'
|
||||
|
||||
export const assetPathPrefix = '/.assets'
|
||||
|
||||
@ -4,9 +4,7 @@ import * as esbuild from 'esbuild'
|
||||
import { EditorFeature, featuresArr } from 'monaco-editor-webpack-plugin/out/features'
|
||||
import { EditorLanguage, languagesArr } from 'monaco-editor-webpack-plugin/out/languages'
|
||||
|
||||
import { MONACO_LANGUAGES_AND_FEATURES } from '@sourcegraph/build-config'
|
||||
|
||||
import { ROOT_PATH } from '../utils'
|
||||
import { MONACO_LANGUAGES_AND_FEATURES, ROOT_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
const monacoModulePath = (modulePath: string): string =>
|
||||
require.resolve(path.join('monaco-editor/esm', modulePath), {
|
||||
|
||||
@ -5,7 +5,7 @@ import express from 'express'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import signale from 'signale'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '../utils'
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { buildMonaco, BUILD_OPTIONS } from './build'
|
||||
import { assetPathPrefix } from './manifestPlugin'
|
||||
|
||||
@ -6,10 +6,11 @@ import postcss from 'postcss'
|
||||
import postcssModules from 'postcss-modules'
|
||||
import sass from 'sass'
|
||||
|
||||
import { ROOT_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import postcssConfig from '../../../../postcss.config'
|
||||
import { ROOT_PATH } from '../utils'
|
||||
|
||||
/**
|
||||
* An esbuild plugin that builds .css and .scss stylesheets (including support for CSS modules).
|
||||
|
||||
@ -5,13 +5,14 @@ import signale from 'signale'
|
||||
import createWebpackCompiler, { Configuration } from 'webpack'
|
||||
import WebpackDevServer, { ProxyConfigArrayItem } from 'webpack-dev-server'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { getManifest } from '../esbuild/manifestPlugin'
|
||||
import { esbuildDevelopmentServer } from '../esbuild/server'
|
||||
import {
|
||||
ENVIRONMENT_CONFIG,
|
||||
getAPIProxySettings,
|
||||
shouldCompressResponse,
|
||||
STATIC_ASSETS_PATH,
|
||||
STATIC_ASSETS_URL,
|
||||
HTTPS_WEB_SERVER_URL,
|
||||
HTTP_WEB_SERVER_URL,
|
||||
|
||||
@ -5,12 +5,12 @@ import expressStaticGzip from 'express-static-gzip'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import signale from 'signale'
|
||||
|
||||
import { STATIC_ASSETS_PATH, STATIC_INDEX_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import {
|
||||
PROXY_ROUTES,
|
||||
getAPIProxySettings,
|
||||
ENVIRONMENT_CONFIG,
|
||||
STATIC_ASSETS_PATH,
|
||||
STATIC_INDEX_PATH,
|
||||
HTTP_WEB_SERVER_URL,
|
||||
HTTPS_WEB_SERVER_URL,
|
||||
} from '../utils'
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import path from 'path'
|
||||
|
||||
export const ROOT_PATH = path.resolve(__dirname, '../../../../')
|
||||
export const STATIC_ASSETS_PATH = path.resolve(ROOT_PATH, 'ui/assets')
|
||||
export const STATIC_INDEX_PATH = path.resolve(STATIC_ASSETS_PATH, 'index.html')
|
||||
import { ROOT_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
export const STATIC_ASSETS_URL = '/.assets/'
|
||||
export const DEV_SERVER_LISTEN_ADDR = { host: 'localhost', port: 3080 } as const
|
||||
export const DEV_SERVER_PROXY_TARGET_ADDR = { host: 'localhost', port: 3081 } as const
|
||||
|
||||
@ -5,7 +5,9 @@ import HtmlWebpackPlugin, { TemplateParameter, Options } from 'html-webpack-plug
|
||||
import signale from 'signale'
|
||||
import { WebpackPluginInstance } from 'webpack'
|
||||
|
||||
import { createJsContext, ENVIRONMENT_CONFIG, STATIC_ASSETS_PATH } from '../utils'
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { createJsContext, ENVIRONMENT_CONFIG } from '../utils'
|
||||
|
||||
const { SOURCEGRAPH_HTTPS_PORT, NODE_ENV } = ENVIRONMENT_CONFIG
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
.container {
|
||||
padding: 2rem;
|
||||
min-height: 60vh;
|
||||
}
|
||||
@ -22,6 +22,8 @@ import {
|
||||
FIXTURE_WARNING_MARKDOWN_ALERT,
|
||||
} from './WebHoverOverlay.fixtures'
|
||||
|
||||
import styles from './WebHoverOverlay.story.module.scss'
|
||||
|
||||
registerHighlightContributions()
|
||||
|
||||
const { add } = storiesOf('web/WebHoverOverlay', module)
|
||||
@ -210,14 +212,16 @@ add('With long markdown text and dismissible alert with icon.', () => (
|
||||
))
|
||||
|
||||
add('Multiple MarkupContents with badges and alerts', () => (
|
||||
<WebHoverOverlay
|
||||
{...commonProps()}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT, FIXTURE_CONTENT, FIXTURE_CONTENT],
|
||||
aggregatedBadges: [FIXTURE_SEMANTIC_BADGE],
|
||||
alerts: [FIXTURE_SMALL_TEXT_MARKDOWN_ALERT, FIXTURE_WARNING_MARKDOWN_ALERT],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
onAlertDismissed={action('onAlertDismissed')}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<WebHoverOverlay
|
||||
{...commonProps()}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT, FIXTURE_CONTENT, FIXTURE_CONTENT],
|
||||
aggregatedBadges: [FIXTURE_SEMANTIC_BADGE],
|
||||
alerts: [FIXTURE_SMALL_TEXT_MARKDOWN_ALERT, FIXTURE_WARNING_MARKDOWN_ALERT],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
onAlertDismissed={action('onAlertDismissed')}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { boolean } from '@storybook/addon-knobs'
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import isChromatic from 'chromatic/isChromatic'
|
||||
import classNames from 'classnames'
|
||||
import { subDays } from 'date-fns'
|
||||
|
||||
import { isChromatic } from '@sourcegraph/storybook'
|
||||
|
||||
import { WebStory } from '../../../components/WebStory'
|
||||
|
||||
import { BatchChangeNode } from './BatchChangeNode'
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import isChromatic from 'chromatic/isChromatic'
|
||||
import { isChromatic } from '@sourcegraph/storybook'
|
||||
|
||||
const incrementedLocalStorageKeys = new Map<string, number>()
|
||||
|
||||
/**
|
||||
|
||||
@ -29,7 +29,6 @@ export const Default: Story = () => (
|
||||
Default.parameters = {
|
||||
component: Modal,
|
||||
chromatic: {
|
||||
enableDarkMode: true,
|
||||
disableSnapshot: false,
|
||||
},
|
||||
design: [
|
||||
|
||||
@ -2,7 +2,7 @@ import { FunctionComponent, MutableRefObject, PropsWithChildren, useCallback, us
|
||||
|
||||
import { noop } from 'lodash'
|
||||
|
||||
import { PopoverContext } from './context'
|
||||
import { PopoverContext } from './contexts/internal-context'
|
||||
|
||||
export enum PopoverOpenEventReason {
|
||||
TriggerClick = 'TriggerClick',
|
||||
|
||||
@ -4,7 +4,7 @@ import { noop } from 'lodash'
|
||||
import { useCallbackRef, useMergeRefs } from 'use-callback-ref'
|
||||
|
||||
import { ForwardReferenceComponent } from '../../../types'
|
||||
import { PopoverContext } from '../context'
|
||||
import { PopoverContext } from '../contexts/internal-context'
|
||||
import { PopoverOpenEventReason } from '../Popover'
|
||||
|
||||
interface PopoverTriggerProps {}
|
||||
|
||||
@ -15,6 +15,13 @@ export interface FloatingPanelProps extends Omit<Tether, 'target' | 'element'>,
|
||||
* Renders nothing if target isn't specified.
|
||||
*/
|
||||
target: HTMLElement | null
|
||||
|
||||
/**
|
||||
* The root element where Popover renders popover content element.
|
||||
* This element is used when we render popover with fixed strategy -
|
||||
* outside the dom tree.
|
||||
*/
|
||||
rootRender?: HTMLElement | null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,6 +44,7 @@ export const FloatingPanel = forwardRef((props, reference) => {
|
||||
constraintPadding,
|
||||
targetPadding,
|
||||
constraint,
|
||||
rootRender,
|
||||
...otherProps
|
||||
} = props
|
||||
|
||||
@ -100,6 +108,6 @@ export const FloatingPanel = forwardRef((props, reference) => {
|
||||
<Component {...otherProps} ref={references} className={classNames(styles.floatingPanel, otherProps.className)}>
|
||||
{props.children}
|
||||
</Component>,
|
||||
document.body
|
||||
rootRender ?? document.body
|
||||
)
|
||||
}) as ForwardReferenceComponent<'div', PropsWithChildren<FloatingPanelProps>>
|
||||
|
||||
@ -6,7 +6,8 @@ import { useCallbackRef, useMergeRefs } from 'use-callback-ref'
|
||||
|
||||
import { useKeyboard, useOnClickOutside } from '../../../../hooks'
|
||||
import { ForwardReferenceComponent } from '../../../../types'
|
||||
import { PopoverContext } from '../../context'
|
||||
import { PopoverContext } from '../../contexts/internal-context'
|
||||
import { PopoverRoot } from '../../contexts/public-context'
|
||||
import { PopoverOpenEventReason } from '../../Popover'
|
||||
import { FloatingPanel, FloatingPanelProps } from '../floating-panel/FloatingPanel'
|
||||
|
||||
@ -31,6 +32,8 @@ export const PopoverContent = forwardRef((props, reference) => {
|
||||
} = props
|
||||
|
||||
const { isOpen: isOpenContext, targetElement, tailElement, anchor, setOpen } = useContext(PopoverContext)
|
||||
const { renderRoot } = useContext(PopoverRoot)
|
||||
|
||||
const [focusLock, setFocusLock] = useState(false)
|
||||
|
||||
const [tooltipElement, setTooltipElement] = useState<HTMLDivElement | null>(null)
|
||||
@ -78,6 +81,7 @@ export const PopoverContent = forwardRef((props, reference) => {
|
||||
marker={tailElement}
|
||||
role={role}
|
||||
aria-modal={ariaModel}
|
||||
rootRender={renderRoot}
|
||||
className={classNames(styles.popover, otherProps.className)}
|
||||
>
|
||||
{focusLocked ? (
|
||||
|
||||
@ -2,7 +2,8 @@ import { FunctionComponent, HTMLAttributes, useContext } from 'react'
|
||||
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { PopoverContext } from '../../context'
|
||||
import { PopoverContext } from '../../contexts/internal-context'
|
||||
import { PopoverRoot } from '../../contexts/public-context'
|
||||
|
||||
import style from './PopoverTail.module.scss'
|
||||
|
||||
@ -10,6 +11,7 @@ interface PopoverTailProps extends HTMLAttributes<SVGElement> {}
|
||||
|
||||
export const PopoverTail: FunctionComponent<PopoverTailProps> = props => {
|
||||
const { setTailElement, isOpen } = useContext(PopoverContext)
|
||||
const { renderRoot } = useContext(PopoverRoot)
|
||||
|
||||
if (!isOpen) {
|
||||
return null
|
||||
@ -19,6 +21,6 @@ export const PopoverTail: FunctionComponent<PopoverTailProps> = props => {
|
||||
<svg {...props} width="17.2" height="11" viewBox="0 0 200 130" className={style.tail} ref={setTailElement}>
|
||||
<path d="M0,0 L100,130 200,0" className={style.tailTrianglePath} />
|
||||
</svg>,
|
||||
document.body
|
||||
renderRoot ?? document.body
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@ import { createContext, MutableRefObject } from 'react'
|
||||
|
||||
import { noop } from 'lodash'
|
||||
|
||||
import { PopoverOpenEvent } from './Popover'
|
||||
import { PopoverOpenEvent } from '../Popover'
|
||||
|
||||
export interface PopoverContextData {
|
||||
export interface PopoverInternalContextData {
|
||||
isOpen: boolean
|
||||
targetElement: HTMLElement | null
|
||||
tailElement: SVGGElement | null
|
||||
@ -14,7 +14,7 @@ export interface PopoverContextData {
|
||||
setTailElement: (element: SVGGElement | null) => void
|
||||
}
|
||||
|
||||
const DEFAULT_CONTEXT_VALUE: PopoverContextData = {
|
||||
const DEFAULT_CONTEXT_VALUE: PopoverInternalContextData = {
|
||||
isOpen: false,
|
||||
targetElement: null,
|
||||
tailElement: null,
|
||||
@ -23,4 +23,4 @@ const DEFAULT_CONTEXT_VALUE: PopoverContextData = {
|
||||
setTailElement: noop,
|
||||
}
|
||||
|
||||
export const PopoverContext = createContext<PopoverContextData>(DEFAULT_CONTEXT_VALUE)
|
||||
export const PopoverContext = createContext<PopoverInternalContextData>(DEFAULT_CONTEXT_VALUE)
|
||||
@ -0,0 +1,11 @@
|
||||
import { createContext } from 'react'
|
||||
|
||||
interface PopoverRootData {
|
||||
renderRoot: HTMLElement | null
|
||||
}
|
||||
|
||||
const DEFAULT_POPOVER_PROVIDER_INFO: PopoverRootData = {
|
||||
renderRoot: null,
|
||||
}
|
||||
|
||||
export const PopoverRoot = createContext<PopoverRootData>(DEFAULT_POPOVER_PROVIDER_INFO)
|
||||
@ -3,6 +3,7 @@ export * from './Popover'
|
||||
export * from './components/PopoverTrigger'
|
||||
export * from './components/popover-content'
|
||||
export * from './components/popover-tail'
|
||||
export * from './contexts/public-context'
|
||||
|
||||
// Tether (popover, tooltip) position calculation engine
|
||||
export * from './tether'
|
||||
|
||||
@ -162,7 +162,6 @@ export const PositionSettingsGallery: Story = () => {
|
||||
|
||||
PositionSettingsGallery.parameters = {
|
||||
chromatic: {
|
||||
enableDarkMode: true,
|
||||
disableSnapshot: false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ export {
|
||||
PopoverContent,
|
||||
Position,
|
||||
PopoverTail,
|
||||
PopoverRoot,
|
||||
PopoverOpenEventReason,
|
||||
EMPTY_RECTANGLE,
|
||||
createRectangle,
|
||||
|
||||
43
package.json
43
package.json
@ -138,24 +138,24 @@
|
||||
"@sourcegraph/stylelint-plugin-sourcegraph": "^1.0.1",
|
||||
"@sourcegraph/tsconfig": "^4.0.1",
|
||||
"@statoscope/webpack-plugin": "^5.20.1",
|
||||
"@storybook/addon-a11y": "^6.3.12",
|
||||
"@storybook/addon-actions": "^6.3.12",
|
||||
"@storybook/addon-a11y": "^6.5.7",
|
||||
"@storybook/addon-actions": "^6.5.7",
|
||||
"@storybook/addon-console": "^1.2.3",
|
||||
"@storybook/addon-knobs": "^6.3.1",
|
||||
"@storybook/addon-links": "^6.3.12",
|
||||
"@storybook/addon-storyshots": "^6.3.12",
|
||||
"@storybook/addon-storyshots-puppeteer": "^6.3.12",
|
||||
"@storybook/addon-toolbars": "^6.3.12",
|
||||
"@storybook/addons": "^6.3.12",
|
||||
"@storybook/api": "^6.3.12",
|
||||
"@storybook/builder-webpack5": "^6.3.12",
|
||||
"@storybook/client-api": "^6.3.12",
|
||||
"@storybook/components": "^6.3.12",
|
||||
"@storybook/core": "^6.3.12",
|
||||
"@storybook/core-events": "^6.3.12",
|
||||
"@storybook/manager-webpack5": "^6.3.12",
|
||||
"@storybook/react": "^6.3.12",
|
||||
"@storybook/theming": "^6.3.12",
|
||||
"@storybook/addon-knobs": "^6.4.0",
|
||||
"@storybook/addon-links": "^6.5.7",
|
||||
"@storybook/addon-storyshots": "^6.5.7",
|
||||
"@storybook/addon-storyshots-puppeteer": "^6.5.7",
|
||||
"@storybook/addon-toolbars": "^6.5.7",
|
||||
"@storybook/addons": "^6.5.7",
|
||||
"@storybook/api": "^6.5.7",
|
||||
"@storybook/builder-webpack5": "^6.5.7",
|
||||
"@storybook/client-api": "^6.5.7",
|
||||
"@storybook/components": "^6.5.7",
|
||||
"@storybook/core": "^6.5.7",
|
||||
"@storybook/core-events": "^6.5.7",
|
||||
"@storybook/manager-webpack5": "^6.5.7",
|
||||
"@storybook/react": "^6.5.7",
|
||||
"@storybook/theming": "^6.5.7",
|
||||
"@terminus-term/to-string-loader": "^1.1.7-beta.1",
|
||||
"@testing-library/dom": "^8.13.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
@ -307,7 +307,7 @@
|
||||
"postcss-focus-visible": "^5.0.0",
|
||||
"postcss-loader": "^6.1.1",
|
||||
"postcss-modules": "^4.2.2",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier": "2.2.1",
|
||||
"process": "^0.11.10",
|
||||
"protoc-gen-ts": "0.8.1",
|
||||
"puppeteer": "^13.5.1",
|
||||
@ -323,8 +323,8 @@
|
||||
"socket.io": "^2.3.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"speed-measure-webpack-plugin": "^1.5.0",
|
||||
"storybook-addon-designs": "^6.2.0",
|
||||
"storybook-dark-mode": "^1.0.8",
|
||||
"storybook-addon-designs": "^6.2.1",
|
||||
"storybook-dark-mode": "^1.1.0",
|
||||
"string-width": "^4.2.0",
|
||||
"style-loader": "^3.1.0",
|
||||
"stylelint": "^14.3.0",
|
||||
@ -478,6 +478,7 @@
|
||||
"history": "4.5.1",
|
||||
"cssnano": "4.1.10",
|
||||
"webpack": "5",
|
||||
"tslib": "2.1.0"
|
||||
"tslib": "2.1.0",
|
||||
"prettier": "2.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user