diff --git a/jest.config.js b/jest.config.js index b759006a4eb6..d684084971b7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -84,7 +84,6 @@ module.exports = { 'typings.d.ts$', ], globals: { - DOCS_MODE: false, PREVIEW_URL: undefined, SNAPSHOT_OS: os.platform() === 'win32' ? 'windows' : 'posix', }, diff --git a/lib/api/src/index.tsx b/lib/api/src/index.tsx index 8c08cd28b0c4..26641a0b2f12 100644 --- a/lib/api/src/index.tsx +++ b/lib/api/src/index.tsx @@ -21,6 +21,7 @@ import { } from '@storybook/core-events'; import type { RouterData } from '@storybook/router'; import type { Listener } from '@storybook/channels'; +import type { DocsOptions } from '@storybook/core-common'; import { createContext } from './context'; import Store, { Options } from './store'; @@ -57,7 +58,7 @@ export { default as merge } from './lib/merge'; export type { Options as StoreOptions, Listener as ChannelListener }; export { ActiveTabs }; -const ManagerContext = createContext({ api: undefined, state: getInitialState({}) }); +export const ManagerContext = createContext({ api: undefined, state: getInitialState({}) }); export type ModuleArgs = RouterData & ProviderData & { @@ -67,6 +68,10 @@ export type ModuleArgs = RouterData & store: Store; }; +type OptionsData = { + docsOptions: DocsOptions; +}; + export type State = layout.SubState & stories.SubState & refs.SubState & @@ -78,6 +83,7 @@ export type State = layout.SubState & settings.SubState & globals.SubState & RouterData & + OptionsData & Other; export type API = addons.SubAPI & @@ -106,7 +112,7 @@ export interface Combo { interface ProviderData { provider: provider.Provider; - docsMode: boolean; + docsOptions: DocsOptions; } export type ManagerProviderProps = RouterData & @@ -177,10 +183,10 @@ class ManagerProvider extends Component { location, path, refId, - viewMode = props.docsMode ? 'docs' : 'story', + viewMode = props.docsOptions.docsMode ? 'docs' : 'story', singleStory, storyId, - docsMode, + docsOptions, navigate, } = props; @@ -189,9 +195,10 @@ class ManagerProvider extends Component { setState: (stateChange: Partial, callback) => this.setState(stateChange, callback), }); - const routeData = { location, path, viewMode, singleStory, storyId, refId, docsMode }; + const routeData = { location, path, viewMode, singleStory, storyId, refId }; + const optionsData: OptionsData = { docsOptions }; - this.state = store.getInitialState(getInitialState(routeData)); + this.state = store.getInitialState(getInitialState({ ...routeData, ...optionsData })); const apiData = { navigate, @@ -213,7 +220,9 @@ class ManagerProvider extends Component { globals, url, version, - ].map((m) => m.init({ ...routeData, ...apiData, state: this.state, fullAPI: this.api })); + ].map((m) => + m.init({ ...routeData, ...optionsData, ...apiData, state: this.state, fullAPI: this.api }) + ); // Create our initial state by combining the initial state of all modules, then overlaying any saved state const state = getInitialState(this.state, ...this.modules.map((m) => m.state)); diff --git a/lib/api/src/modules/refs.ts b/lib/api/src/modules/refs.ts index 41369b466c1f..1a0d0ff316d9 100644 --- a/lib/api/src/modules/refs.ts +++ b/lib/api/src/modules/refs.ts @@ -133,7 +133,7 @@ const map = ( }; export const init: ModuleFn = ( - { store, provider, singleStory, docsMode }, + { store, provider, singleStory, docsOptions: { docsMode } = {} }, { runCheck = true } = {} ) => { const api: SubAPI = { diff --git a/lib/api/src/modules/stories.ts b/lib/api/src/modules/stories.ts index 9b9de197822c..e280a22fd67f 100644 --- a/lib/api/src/modules/stories.ts +++ b/lib/api/src/modules/stories.ts @@ -122,7 +122,7 @@ export const init: ModuleFn = ({ provider, storyId: initialStoryId, viewMode: initialViewMode, - docsMode, + docsOptions: { docsMode } = {}, }) => { const api: SubAPI = { storyId: toId, diff --git a/lib/api/src/tests/stories.test.ts b/lib/api/src/tests/stories.test.ts index e80f6ba3cb6a..9005c86d2e63 100644 --- a/lib/api/src/tests/stories.test.ts +++ b/lib/api/src/tests/stories.test.ts @@ -1223,7 +1223,7 @@ describe('stories API', () => { navigate, provider, fullAPI, - docsMode: true, + docsOptions: { docsMode: true }, } as any); Object.assign(fullAPI, api); @@ -1431,7 +1431,7 @@ describe('stories API', () => { navigate, provider, fullAPI, - docsMode: true, + docsOptions: { docsMode: true }, } as any); Object.assign(fullAPI, api); diff --git a/lib/api/src/typings.d.ts b/lib/api/src/typings.d.ts index 167732f17ac5..120e82d8e42f 100644 --- a/lib/api/src/typings.d.ts +++ b/lib/api/src/typings.d.ts @@ -1,5 +1,2 @@ declare module 'global'; declare module 'preval.macro'; - -// provided by the webpack define plugin -declare var DOCS_MODE: string | undefined; diff --git a/lib/builder-manager/src/index.ts b/lib/builder-manager/src/index.ts index 39e4a3248306..83d6a8825cce 100644 --- a/lib/builder-manager/src/index.ts +++ b/lib/builder-manager/src/index.ts @@ -85,9 +85,8 @@ const starter: StarterFunction = async function* starterGeneratorFn({ }) { logger.info('=> Starting manager..'); - const { config, customHead, features, instance, refs, template, title, logLevel } = await getData( - options - ); + const { config, customHead, features, instance, refs, template, title, logLevel, docsOptions } = + await getData(options); yield; @@ -117,6 +116,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ features, refs, logLevel, + docsOptions, options ); @@ -150,9 +150,8 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, throw new Error('outputDir is required'); } logger.info('=> Building manager..'); - const { config, customHead, features, instance, refs, template, title, logLevel } = await getData( - options - ); + const { config, customHead, features, instance, refs, template, title, logLevel, docsOptions } = + await getData(options); yield; const addonsDir = config.outdir; @@ -179,6 +178,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, features, refs, logLevel, + docsOptions, options ); diff --git a/lib/builder-manager/src/utils/data.ts b/lib/builder-manager/src/utils/data.ts index 02c2d74e902b..9775112bd468 100644 --- a/lib/builder-manager/src/utils/data.ts +++ b/lib/builder-manager/src/utils/data.ts @@ -1,5 +1,5 @@ import { join } from 'path'; -import type { Options } from '@storybook/core-common'; +import type { DocsOptions, Options } from '@storybook/core-common'; import { getRefs } from '@storybook/core-common'; import { readTemplate } from './template'; @@ -11,6 +11,7 @@ export const getData = async (options: Options) => { const features = options.presets.apply>('features'); const logLevel = options.presets.apply('logLevel'); const title = options.presets.apply('title'); + const docsOptions = options.presets.apply('docs', {}); const template = readTemplate('template.ejs'); const customHead = safeResolve(join(options.configDir, 'manager-head.html')); @@ -25,6 +26,7 @@ export const getData = async (options: Options) => { refs, features, title, + docsOptions, template, customHead, instance, diff --git a/lib/builder-manager/src/utils/template.ts b/lib/builder-manager/src/utils/template.ts index de06262b2314..dca1eab9023b 100644 --- a/lib/builder-manager/src/utils/template.ts +++ b/lib/builder-manager/src/utils/template.ts @@ -3,7 +3,7 @@ import { readFile, pathExists } from 'fs-extra'; import { render } from 'ejs'; -import type { Options, Ref } from '@storybook/core-common'; +import type { DocsOptions, Options, Ref } from '@storybook/core-common'; import { readDeep } from './directory'; @@ -56,6 +56,7 @@ export const renderHTML = async ( features: Promise>, refs: Promise>, logLevel: Promise, + docsOptions: Promise, { versionCheck, releaseNotesData, docsMode, previewUrl, serverChannelUrl }: Options ) => { const customHeadRef = await customHead; @@ -73,10 +74,10 @@ export const renderHTML = async ( FEATURES: JSON.stringify(await features, null, 2), REFS: JSON.stringify(await refs, null, 2), LOGLEVEL: JSON.stringify(await logLevel, null, 2), + DOCS_OPTIONS: JSON.stringify(await docsOptions, null, 2), // These two need to be double stringified because the UI expects a string VERSIONCHECK: JSON.stringify(JSON.stringify(versionCheck), null, 2), RELEASE_NOTES_DATA: JSON.stringify(JSON.stringify(releaseNotesData), null, 2), - DOCS_MODE: JSON.stringify(docsMode, null, 2), // global docs mode PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL SERVER_CHANNEL_URL: JSON.stringify(serverChannelUrl, null, 2), }, diff --git a/lib/core-common/src/types.ts b/lib/core-common/src/types.ts index a5553a50cbc7..7f8bbed06afe 100644 --- a/lib/core-common/src/types.ts +++ b/lib/core-common/src/types.ts @@ -300,6 +300,10 @@ export type DocsOptions = { * Should we generate a docs entry per CSF file? */ docsPage?: boolean; + /** + * Only show doc entries in the side bar (usually set with the `--docs` CLI flag) + */ + docsMode?: boolean; }; /** diff --git a/lib/core-server/src/presets/common-preset.ts b/lib/core-server/src/presets/common-preset.ts index 46056d3bf42f..fd59437baf61 100644 --- a/lib/core-server/src/presets/common-preset.ts +++ b/lib/core-server/src/presets/common-preset.ts @@ -1,5 +1,6 @@ import fs from 'fs-extra'; import { + CLIOptions, getPreviewBodyTemplate, getPreviewHeadTemplate, getPreviewMainTemplate, @@ -120,3 +121,11 @@ export const storyIndexers = async (indexers?: StoryIndexer[]) => { ...(indexers || []), ]; }; + +export const docs = ( + docsOptions: StorybookConfig['docs'], + { docs: docsMode }: CLIOptions +): StorybookConfig['docs'] => ({ + ...docsOptions, + docsMode, +}); diff --git a/lib/ui/src/app.stories.tsx b/lib/ui/src/app.stories.tsx index ef7df77c6ac6..522e6bb7f841 100644 --- a/lib/ui/src/app.stories.tsx +++ b/lib/ui/src/app.stories.tsx @@ -31,7 +31,7 @@ export const Default = () => ( storyId="ui-app--loading-state" location={{ search: '' }} navigate={() => {}} - docsMode={false} + docsOptions={{ docsMode: false }} > ( storyId="ui-app--loading-state" location={{ search: '' }} navigate={() => {}} - docsMode={false} + docsOptions={{ docsMode: false }} > {}} diff --git a/lib/ui/src/components/sidebar/Explorer.stories.tsx b/lib/ui/src/components/sidebar/Explorer.stories.tsx index 4937b6ec8e05..2f013c174ef7 100644 --- a/lib/ui/src/components/sidebar/Explorer.stories.tsx +++ b/lib/ui/src/components/sidebar/Explorer.stories.tsx @@ -3,12 +3,14 @@ import React from 'react'; import { Explorer } from './Explorer'; import { mockDataset } from './mockdata'; import { RefType } from './types'; +import * as RefStories from './Refs.stories'; export default { component: Explorer, title: 'UI/Sidebar/Explorer', parameters: { layout: 'fullscreen' }, decorators: [ + RefStories.default.decorators[0], (storyFn: any) =>
{storyFn()}
, ], }; diff --git a/lib/ui/src/components/sidebar/Refs.stories.tsx b/lib/ui/src/components/sidebar/Refs.stories.tsx index 61eceb006f39..61541990ebca 100644 --- a/lib/ui/src/components/sidebar/Refs.stories.tsx +++ b/lib/ui/src/components/sidebar/Refs.stories.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { ManagerContext } from '@storybook/api'; import { Ref } from './Refs'; import { standardData as standardHeaderData } from './Heading.stories'; @@ -11,6 +12,11 @@ export default { excludeStories: /.*Data$/, parameters: { layout: 'fullscreen' }, decorators: [ + (storyFn: any) => ( + + {storyFn()} + + ), (storyFn: any) =>
{storyFn()}
, ], }; diff --git a/lib/ui/src/components/sidebar/Refs.tsx b/lib/ui/src/components/sidebar/Refs.tsx index 9109f39b86c6..3070e67ea0c7 100644 --- a/lib/ui/src/components/sidebar/Refs.tsx +++ b/lib/ui/src/components/sidebar/Refs.tsx @@ -1,5 +1,5 @@ import React, { FC, useMemo, useState, useRef, useCallback, MutableRefObject } from 'react'; -import { useStorybookApi } from '@storybook/api'; +import { useStorybookApi, useStorybookState } from '@storybook/api'; import { styled } from '@storybook/theming'; import { transparentize } from 'polished'; @@ -91,6 +91,7 @@ const CollapseButton = styled.button(({ theme }) => ({ })); export const Ref: FC = React.memo((props) => { + const { docsOptions } = useStorybookState(); const api = useStorybookApi(); const { stories, @@ -156,6 +157,7 @@ export const Ref: FC = React.memo((props) => { isMain={isMain} refId={refId} data={stories} + docsMode={docsOptions.docsMode} selectedStoryId={selectedStoryId} onSelectStoryId={onSelectStoryId} highlightedRef={highlightedRef} diff --git a/lib/ui/src/components/sidebar/Sidebar.stories.tsx b/lib/ui/src/components/sidebar/Sidebar.stories.tsx index 029a7352ed47..9b905afda014 100644 --- a/lib/ui/src/components/sidebar/Sidebar.stories.tsx +++ b/lib/ui/src/components/sidebar/Sidebar.stories.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Sidebar, DEFAULT_REF_ID } from './Sidebar'; import { standardData as standardHeaderData } from './Heading.stories'; +import * as ExplorerStories from './Explorer.stories'; import { mockDataset } from './mockdata'; import { RefType } from './types'; @@ -11,6 +12,7 @@ export default { excludeStories: /.*Data$/, parameters: { layout: 'fullscreen' }, decorators: [ + ExplorerStories.default.decorators[0], (storyFn: any) =>
{storyFn()}
, ], }; diff --git a/lib/ui/src/components/sidebar/Tree.tsx b/lib/ui/src/components/sidebar/Tree.tsx index c782e089df0a..e99a572008cc 100644 --- a/lib/ui/src/components/sidebar/Tree.tsx +++ b/lib/ui/src/components/sidebar/Tree.tsx @@ -122,6 +122,7 @@ const SkipToContentLink = styled(Button)(({ theme }) => ({ interface NodeProps { item: Item; refId: string; + docsMode: boolean; isOrphan: boolean; isDisplayed: boolean; isSelected: boolean; @@ -136,6 +137,7 @@ const Node = React.memo( ({ item, refId, + docsMode, isOrphan, isDisplayed, isSelected, @@ -168,6 +170,7 @@ const Node = React.memo( event.preventDefault(); onSelectStoryId(item.id); }} + {...(item.type === 'docs' && { docsMode })} > {(item.renderLabel as (i: typeof item) => React.ReactNode)?.(item) || item.name} @@ -277,6 +280,7 @@ export const Tree = React.memo<{ isMain: boolean; refId: string; data: StoriesHash; + docsMode: boolean; highlightedRef: MutableRefObject; setHighlightedItemId: (itemId: string) => void; selectedStoryId: string | null; @@ -287,6 +291,7 @@ export const Tree = React.memo<{ isMain, refId, data, + docsMode, highlightedRef, setHighlightedItemId, selectedStoryId, @@ -425,6 +430,7 @@ export const Tree = React.memo<{ key={id} item={item} refId={refId} + docsMode={docsMode} isOrphan={orphanIds.some((oid) => itemId === oid || itemId.startsWith(`${oid}-`))} isDisplayed={isDisplayed} isSelected={selectedStoryId === itemId} diff --git a/lib/ui/src/components/sidebar/TreeNode.tsx b/lib/ui/src/components/sidebar/TreeNode.tsx index ef2342e5a931..29af6f0c6a92 100644 --- a/lib/ui/src/components/sidebar/TreeNode.tsx +++ b/lib/ui/src/components/sidebar/TreeNode.tsx @@ -3,7 +3,6 @@ import type { Color, Theme } from '@storybook/theming'; import { Icons } from '@storybook/components'; import { transparentize } from 'polished'; import React, { FunctionComponent, ComponentProps } from 'react'; -import { Combo, Consumer } from '@storybook/api'; export const CollapseIcon = styled.span<{ isExpanded: boolean }>(({ theme, isExpanded }) => ({ display: 'inline-block', @@ -37,7 +36,7 @@ const iconColors = { }, }; const isColor = (theme: Theme, color: string): color is keyof Color => color in theme.color; -const TypeIcon = styled(Icons)( +const TypeIcon = styled(Icons)<{ docsMode?: boolean }>( { width: 12, height: 12, @@ -48,9 +47,8 @@ const TypeIcon = styled(Icons)( }, ({ theme, icon, symbol = icon, docsMode }) => { const colors = theme.base === 'dark' ? iconColors.dark : iconColors.light; - const colorKey: keyof typeof colors = - docsMode && symbol === 'document' ? 'docsModeDocument' : symbol; - const color = colors[colorKey]; + const colorKey = docsMode && symbol === 'document' ? 'docsModeDocument' : symbol; + const color = colors[colorKey as keyof typeof colors]; return { color: isColor(theme, color) ? theme.color[color] : color }; } ); @@ -164,18 +162,14 @@ export const ComponentNode: FunctionComponent> ) ); -export const DocumentNode: FunctionComponent> = React.memo( - ({ theme, children, ...props }) => ( - ({ docsMode: state.docsMode })}> - {({ docsMode }) => ( - - - {children} - - )} - - ) -); +export const DocumentNode: FunctionComponent< + ComponentProps & { docsMode: boolean } +> = React.memo(({ theme, children, docsMode, ...props }) => ( + + + {children} + +)); export const StoryNode: FunctionComponent> = React.memo( ({ theme, children, ...props }) => ( diff --git a/lib/ui/src/globals/exports.ts b/lib/ui/src/globals/exports.ts index 2f55fc90ee7f..041f408c1191 100644 --- a/lib/ui/src/globals/exports.ts +++ b/lib/ui/src/globals/exports.ts @@ -194,6 +194,7 @@ export default { '@storybook/api': [ 'ActiveTabs', 'Consumer', + 'ManagerContext', 'Provider', 'combineParameters', 'merge', diff --git a/lib/ui/src/index.tsx b/lib/ui/src/index.tsx index b6437eadbae4..1eaef8b297c6 100644 --- a/lib/ui/src/index.tsx +++ b/lib/ui/src/index.tsx @@ -6,7 +6,8 @@ import React, { FC } from 'react'; import ReactDOM from 'react-dom'; import { Location, LocationProvider, useNavigate } from '@storybook/router'; -import { Provider as ManagerProvider, Combo } from '@storybook/api'; +import { Provider as ManagerProvider } from '@storybook/api'; +import type { Combo } from '@storybook/api'; import { ThemeProvider, ensure as ensureTheme, @@ -22,21 +23,11 @@ import Provider from './provider'; const emotionCache = createCache({ key: 'sto' }); emotionCache.compat = true; -const { DOCS_MODE } = global; - // @ts-ignore ThemeProvider.displayName = 'ThemeProvider'; // @ts-ignore HelmetProvider.displayName = 'HelmetProvider'; -const getDocsMode = () => { - try { - return !!DOCS_MODE; - } catch (e) { - return false; - } -}; - // @ts-ignore const Container = process.env.XSTORYBOOK_EXAMPLE_APP ? React.StrictMode : React.Fragment; @@ -65,7 +56,7 @@ const Main: FC<{ provider: Provider }> = ({ provider }) => { provider={provider} {...locationData} navigate={navigate} - docsMode={getDocsMode()} + docsOptions={global?.DOCS_OPTIONS || {}} > {({ state, api }: Combo) => { const panelCount = Object.keys(api.getPanels()).length;