Merge branch 'next' into 7101-source-loader

This commit is contained in:
Michael Shilman 2019-06-25 17:53:16 +08:00
commit ca2dc93f54
23 changed files with 291 additions and 72 deletions

View File

@ -29,7 +29,7 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
const componentMeta = {};
const componentMeta = { includeStories: [] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
@ -105,6 +105,7 @@ const componentMeta = {
</div>
),
],
includeStories: ['one'],
};
const mdxKind = componentMeta.title || componentMeta.displayName;
@ -118,6 +119,65 @@ export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin supports non-story exports 1`] = `
"/* @jsx mdx */
import { DocsContainer } from '@storybook/addon-docs/blocks';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
export const two = 2;
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const layoutProps = {
two,
};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" mdxType=\\"Meta\\" />
<h1>{\`Story definition\`}</h1>
<Story name=\\"one\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">One</Button>
</Story>
<Story name=\\"hello story\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Hello button</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.parameters = { mdxSource: \`<Button>One</Button>\` };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.title = 'hello story';
helloStory.parameters = { mdxSource: \`<Button>Hello button</Button>\` };
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
<DocsContainer context={{ ...context, mdxKind }} content={MDXContent} />
);
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = WrappedMDXContent;
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin supports object-style story definitions 1`] = `
"/* @jsx mdx */
import { DocsContainer } from '@storybook/addon-docs/blocks';
@ -182,7 +242,7 @@ toStorybook.parameters = {
}\`,
};
const componentMeta = { title: 'MDX|Welcome' };
const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
@ -262,6 +322,7 @@ const componentMeta = {
component: Button,
notes: 'component notes',
},
includeStories: ['componentNotes', 'storyNotes'],
};
const mdxKind = componentMeta.title || componentMeta.displayName;
@ -336,6 +397,7 @@ const componentMeta = {
component: Button,
notes: 'component notes',
},
includeStories: ['helloButton', 'two'],
};
const mdxKind = componentMeta.title || componentMeta.displayName;
@ -392,7 +454,7 @@ export const helloStory = () => <Button>Hello button</Button>;
helloStory.title = 'hello story';
helloStory.parameters = { mdxSource: \`<Button>Hello button</Button>\` };
const componentMeta = { title: 'Button' };
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
@ -434,7 +496,7 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
const componentMeta = {};
const componentMeta = { includeStories: [] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
@ -482,7 +544,7 @@ MDXContent.isMDXComponent = true;
export const text = () => 'Plain text';
text.parameters = { mdxSource: \`'Plain text'\` };
const componentMeta = { title: 'Text' };
const componentMeta = { title: 'Text', includeStories: ['text'] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
@ -525,7 +587,7 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
const componentMeta = {};
const componentMeta = { includeStories: [] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (

View File

@ -0,0 +1,16 @@
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
<Meta title="Button" />
# Story definition
<Story name="one">
<Button>One</Button>
</Story>
export const two = 2;
<Story name="hello story">
<Button>Hello button</Button>
</Story>

View File

@ -44,7 +44,7 @@ function genStoryExport(ast, counter) {
// console.log('genStoryExport', JSON.stringify(ast, null, 2));
const statements = [];
const storyFn = getStoryFn(storyName, counter);
const storyKey = getStoryFn(storyName, counter);
let body = ast.children.find(n => n.type !== 'JSXText');
let storyCode = null;
@ -61,13 +61,13 @@ function genStoryExport(ast, counter) {
storyCode = code;
}
statements.push(
`export const ${storyFn} = () => (
`export const ${storyKey} = () => (
${storyCode}
);`
);
if (storyName !== storyFn) {
statements.push(`${storyFn}.title = '${storyName}';`);
if (storyName !== storyKey) {
statements.push(`${storyKey}.title = '${storyName}';`);
}
let parameters = getAttr(ast.openingElement, 'parameters');
@ -76,27 +76,29 @@ function genStoryExport(ast, counter) {
if (parameters) {
const { code: params } = generate(parameters, {});
// FIXME: hack in the story's source as a parameter
statements.push(`${storyFn}.parameters = { mdxSource: ${source}, ...${params} };`);
statements.push(`${storyKey}.parameters = { mdxSource: ${source}, ...${params} };`);
} else {
statements.push(`${storyFn}.parameters = { mdxSource: ${source} };`);
statements.push(`${storyKey}.parameters = { mdxSource: ${source} };`);
}
// console.log(statements);
return [statements.join('\n')];
return {
[storyKey]: statements.join('\n'),
};
}
function genPreviewExports(ast, counter) {
// console.log('genPreviewExports', JSON.stringify(ast, null, 2));
let localCounter = counter;
const previewExports = [];
const previewExports = {};
for (let i = 0; i < ast.children.length; i += 1) {
const child = ast.children[i];
if (child.type === 'JSXElement' && child.openingElement.name.name === 'Story') {
const storyExport = genStoryExport(child, localCounter);
if (storyExport) {
previewExports.push(storyExport);
Object.assign(previewExports, storyExport);
localCounter += 1;
}
}
@ -104,20 +106,24 @@ function genPreviewExports(ast, counter) {
return previewExports;
}
function genMetaExport(ast) {
function genMeta(ast) {
let title = getAttr(ast.openingElement, 'title');
let parameters = getAttr(ast.openingElement, 'parameters');
let decorators = getAttr(ast.openingElement, 'decorators');
title = title && `title: '${title.value}',`;
title = title && `'${title.value}'`;
if (parameters && parameters.expression) {
const { code: params } = generate(parameters.expression, {});
parameters = `parameters: ${params},`;
parameters = params;
}
if (decorators && decorators.expression) {
const { code: decos } = generate(decorators.expression, {});
decorators = `decorators: ${decos},`;
decorators = decos;
}
return `const componentMeta = { ${title || ''} ${parameters || ''} ${decorators || ''} };`;
return {
title,
parameters,
decorators,
};
}
function getExports(node, counter) {
@ -127,7 +133,7 @@ function getExports(node, counter) {
// Single story
const ast = parser.parseExpression(value, { plugins: ['jsx'] });
const storyExport = genStoryExport(ast, counter);
return storyExport && { stories: [storyExport] };
return storyExport && { stories: storyExport };
}
if (PREVIEW_REGEX.exec(value)) {
// Preview, possibly containing multiple stories
@ -137,7 +143,7 @@ function getExports(node, counter) {
if (META_REGEX.exec(value)) {
// Preview, possibly containing multiple stories
const ast = parser.parseExpression(value, { plugins: ['jsx'] });
return { meta: genMetaExport(ast) };
return { meta: genMeta(ast) };
}
}
return null;
@ -152,10 +158,22 @@ componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = WrappedMDXContent;
`.trim();
function stringifyMeta(meta) {
let result = '{ ';
Object.entries(meta).forEach(([key, val]) => {
if (val) {
result += `${key}: ${val}, `;
}
});
result += ' }';
return result;
}
function extractExports(node, options) {
// we're overriding default export
const defaultJsx = mdxToJsx.toJSX(node, {}, { ...options, skipExport: true });
const storyExports = [];
const includeStories = [];
let metaExport = null;
let counter = 0;
node.children.forEach(n => {
@ -163,7 +181,8 @@ function extractExports(node, options) {
if (exports) {
const { stories, meta } = exports;
if (stories) {
stories.forEach(story => {
Object.entries(stories).forEach(([key, story]) => {
includeStories.push(key);
storyExports.push(story);
counter += 1;
});
@ -177,14 +196,15 @@ function extractExports(node, options) {
}
});
if (!metaExport) {
metaExport = 'const componentMeta = { };';
metaExport = {};
}
metaExport.includeStories = JSON.stringify(includeStories);
const fullJsx = [
'import { DocsContainer } from "@storybook/addon-docs/blocks";',
defaultJsx,
...storyExports,
metaExport,
`const componentMeta = ${stringifyMeta(metaExport)};`,
wrapperJs,
'export default componentMeta;',
].join('\n\n');

View File

@ -63,6 +63,10 @@ describe('docs-mdx-compiler-plugin', () => {
const code = await generate(path.resolve(__dirname, './fixtures/parameters.mdx'));
expect(code).toMatchSnapshot();
});
it('supports non-story exports', async () => {
const code = await generate(path.resolve(__dirname, './fixtures/non-story-exports.mdx'));
expect(code).toMatchSnapshot();
});
it('errors on missing story props', async () => {
await expect(
generate(path.resolve(__dirname, './fixtures/story-missing-props.mdx'))

View File

@ -51,6 +51,10 @@ import { Button } from '@storybook/react/demo';
<Button>just a button, not a story</Button>
export const nonStory1 = 'foo'; // a non-story export
export const nonStory2 = () => <Button>Not a story</Button>; // another one
<Preview>
<Story name="hello story">
<Button onClick={action('clicked')}>hello world</Button>
@ -67,6 +71,10 @@ import { Button } from '@storybook/react/demo';
<Story name="plaintext">Plain text</Story>
</Preview>
<Story name="solo story">
<Button onClick={action('clicked')}>solo</Button>
</Story>
<Source name="hello story" />
## Configurable height

View File

@ -69,6 +69,9 @@ module.exports = {
'/dll/',
'/__mocks__ /',
],
globals: {
DOCS_MODE: false,
},
snapshotSerializers: ['jest-emotion', 'enzyme-to-json/serializer'],
coverageDirectory: 'coverage',
setupFilesAfterEnv: ['./scripts/jest.init.js'],

View File

@ -43,7 +43,10 @@ const { STORY_CHANGED, SET_STORIES, SELECT_STORY } = Events;
export type Module = StoreData &
RouterData &
ProviderData & { mode?: 'production' | 'development' };
ProviderData & {
mode?: 'production' | 'development';
state: State;
};
export type State = Other &
LayoutSubState &
@ -82,6 +85,10 @@ interface ProviderData {
provider: Provider;
}
interface DocsModeData {
docsMode: boolean;
}
interface StoreData {
store: Store;
}
@ -92,7 +99,7 @@ interface Children {
type StatePartial = Partial<State>;
export type Props = Children & RouterData & ProviderData;
export type Props = Children & RouterData & ProviderData & DocsModeData;
class ManagerProvider extends Component<Props, State> {
static displayName = 'Manager';
@ -103,26 +110,41 @@ class ManagerProvider extends Component<Props, State> {
constructor(props: Props) {
super(props);
const { provider, location, path, viewMode, storyId, navigate } = props;
const {
provider,
location,
path,
viewMode = props.docsMode ? 'docs' : 'story',
storyId,
docsMode,
navigate,
} = props;
const store = new Store({
getState: () => this.state,
setState: (stateChange: StatePartial, callback) => this.setState(stateChange, callback),
});
const routeData = { location, path, viewMode, storyId };
// Initialize the state to be the initial (persisted) state of the store.
// This gives the modules the chance to read the persisted state, apply their defaults
// and override if necessary
this.state = store.getInitialState(getInitialState({}));
const docsModeState = {
layout: { isToolshown: false, showPanel: false },
ui: { docsMode: true },
};
this.state = store.getInitialState(
getInitialState({
...routeData,
...(docsMode ? docsModeState : null),
})
);
const apiData = {
navigate,
store,
provider,
location,
path,
viewMode,
storyId,
};
this.modules = [
@ -134,7 +156,7 @@ class ManagerProvider extends Component<Props, State> {
initStories,
initURL,
initVersions,
].map(initModule => initModule(apiData));
].map(initModule => initModule({ ...routeData, ...apiData, state: this.state }));
// Create our initial state by combining the initial state of all modules, then overlaying any saved state
const state = getInitialState(...this.modules.map(m => m.state));

View File

@ -24,6 +24,7 @@ export interface UI {
url?: string;
enableShortcuts: boolean;
sidebarAnimations: boolean;
docsMode: boolean;
}
export interface SubState {
@ -132,6 +133,7 @@ const initial: SubState = {
ui: {
enableShortcuts: true,
sidebarAnimations: true,
docsMode: false,
},
layout: {
isToolshown: true,

View File

@ -10,6 +10,7 @@ interface Additions {
panelPosition?: PanelPositions;
showNav?: boolean;
selectedPanel?: string;
viewMode?: string;
}
// Initialize the state based on the URL.
@ -21,7 +22,7 @@ interface Additions {
// - nav: 0/1 -- show or hide the story list
//
// We also support legacy URLs from storybook <5
const initialUrlSupport = ({ navigate, location, path }: Module) => {
const initialUrlSupport = ({ navigate, state: { location, path, viewMode, storyId } }: Module) => {
const addition: Additions = {};
const query = queryFromLocation(location);
let selectedPanel;
@ -70,20 +71,20 @@ const initialUrlSupport = ({ navigate, location, path }: Module) => {
}
if (selectedKind && selectedStory) {
const storyId = toId(selectedKind, selectedStory);
setTimeout(() => navigate(`/story/${storyId}`, { replace: true }), 1);
const id = toId(selectedKind, selectedStory);
setTimeout(() => navigate(`/${viewMode}/${id}`, { replace: true }), 1);
} else if (selectedKind) {
// Create a "storyId" of the form `kind-sanitized--*`
const standInId = toId(selectedKind, 'star').replace(/star$/, '*');
setTimeout(() => navigate(`/story/${standInId}`, { replace: true }), 1);
setTimeout(() => navigate(`/${viewMode}/${standInId}`, { replace: true }), 1);
} else if (!queryPath || queryPath === '/') {
setTimeout(() => navigate(`/story/*`, { replace: true }), 1);
setTimeout(() => navigate(`/${viewMode}/*`, { replace: true }), 1);
} else if (Object.keys(query).length > 1) {
// remove other queries
setTimeout(() => navigate(`${queryPath}`, { replace: true }), 1);
}
return { layout: addition, selectedPanel, location, path, customQueryParams };
return { viewMode, layout: addition, selectedPanel, location, path, customQueryParams, storyId };
};
export interface QueryParams {
@ -102,7 +103,7 @@ export interface SubAPI {
setQueryParams: (input: QueryParams) => void;
}
export default function({ store, navigate, location, path: initialPath, ...rest }: Module) {
export default function({ store, navigate, state, provider, ...rest }: Module) {
const api: SubAPI = {
getQueryParam: key => {
const { customQueryParams } = store.getState();
@ -142,6 +143,6 @@ export default function({ store, navigate, location, path: initialPath, ...rest
return {
api,
state: initialUrlSupport({ store, navigate, location, path: initialPath, ...rest }),
state: initialUrlSupport({ store, navigate, state, provider, ...rest }),
};
}

View File

@ -17,6 +17,7 @@ describe('layout API', () => {
ui: {
enableShortcuts: true,
sidebarAnimations: true,
docsMode: false,
},
layout: {
isToolshown: true,

View File

@ -5,12 +5,14 @@ import initURL from '../modules/url';
jest.useFakeTimers();
describe('initial state', () => {
const viewMode = 'story';
it('redirects to /story/* if path is blank', () => {
const navigate = jest.fn();
const location = { search: null };
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location, viewMode } });
// Nothing unexpected in layout
expect(layout).toEqual({});
@ -25,7 +27,7 @@ describe('initial state', () => {
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ isFullscreen: true });
});
@ -36,7 +38,7 @@ describe('initial state', () => {
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ showNav: false });
});
@ -47,7 +49,7 @@ describe('initial state', () => {
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ panelPosition: 'bottom' });
});
@ -58,7 +60,7 @@ describe('initial state', () => {
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ panelPosition: 'right' });
});
@ -69,7 +71,7 @@ describe('initial state', () => {
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ showPanel: false });
});
@ -91,7 +93,7 @@ describe('initial state', () => {
const location = { search: qs.stringify(defaultLegacyParameters) };
const {
state: { layout, selectedPanel },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location, viewMode } });
// Nothing unexpected in layout
expect(layout).toEqual({});
@ -111,7 +113,7 @@ describe('initial state', () => {
};
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ isFullscreen: true });
});
@ -127,7 +129,7 @@ describe('initial state', () => {
};
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ showNav: false, showPanel: false });
});
@ -142,7 +144,7 @@ describe('initial state', () => {
};
const {
state: { layout },
} = initURL({ navigate, location });
} = initURL({ navigate, state: { location } });
expect(layout).toEqual({ panelPosition: 'right' });
});
@ -158,7 +160,7 @@ describe('queryParams', () => {
},
getState: () => state,
};
const { api } = initURL({ location: { search: '' }, navigate: jest.fn(), store });
const { api } = initURL({ state: { location: { search: '' } }, navigate: jest.fn(), store });
api.setQueryParams({ foo: 'bar' });

View File

@ -1,2 +1,5 @@
declare module 'global';
declare module 'telejson';
// provided by the webpack define plugin
declare var DOCS_MODE: string | undefined;

View File

@ -7,6 +7,7 @@ import { DocsPageWrapper } from '../DocsPage';
export default {
Component: PropRow,
title: 'Docs|PropRow',
excludeStories: /.*Def$/,
decorators: [
getStory => (
<DocsPageWrapper>
@ -18,7 +19,7 @@ export default {
],
};
const stringDef = {
export const stringDef = {
name: 'someString',
type: { name: 'string' },
required: true,
@ -26,17 +27,17 @@ const stringDef = {
defaultValue: 'fixme',
};
const longNameDef = {
export const longNameDef = {
...stringDef,
name: 'reallyLongStringThatTakesUpSpace',
};
const longDescDef = {
export const longDescDef = {
...stringDef,
description: 'really long description that takes up a lot of space. sometimes this happens.',
};
const numberDef = {
export const numberDef = {
name: 'someNumber',
type: { name: 'number' },
required: false,
@ -44,7 +45,7 @@ const numberDef = {
defaultValue: 0,
};
const objectDef = {
export const objectDef = {
name: 'someObject',
type: { name: 'objectOf', value: { name: 'number' } },
required: false,
@ -52,7 +53,7 @@ const objectDef = {
defaultValue: { value: '{ key: 1 }', computed: false },
};
const arrayDef = {
export const arrayDef = {
name: 'someOArray',
type: { name: 'arrayOf', value: { name: 'number' } },
required: false,
@ -60,7 +61,7 @@ const arrayDef = {
defaultValue: { value: '[1, 2, 3]', computed: false },
};
const complexDef = {
export const complexDef = {
name: 'someComplex',
type: {
name: 'objectOf',

View File

@ -1,7 +1,7 @@
import React from 'react';
import { PropsTable, PropsTableError } from './PropsTable';
import { DocsPageWrapper } from '../DocsPage';
import * as rowStories from './PropRow.stories';
import { stringDef, numberDef } from './PropRow.stories';
export default {
Component: PropsTable,
@ -13,6 +13,4 @@ export const error = () => <PropsTable error={PropsTableError.NO_COMPONENT} />;
export const empty = () => <PropsTable rows={[]} />;
const { row: stringRow } = rowStories.string().props;
const { row: numberRow } = rowStories.number().props;
export const normal = () => <PropsTable rows={[stringRow, numberRow]} />;
export const normal = () => <PropsTable rows={[stringDef, numberDef]} />;

View File

@ -21,6 +21,20 @@ const classes = {
ERROR: 'sb-show-errordisplay',
};
function matches(storyKey, arrayOrRegex) {
if (Array.isArray(arrayOrRegex)) {
return arrayOrRegex.includes(storyKey);
}
return storyKey.match(arrayOrRegex);
}
export function isExportStory(key, { includeStories, excludeStories }) {
return (
(!includeStories || matches(key, includeStories)) &&
(!excludeStories || !matches(key, excludeStories))
);
}
function showMain() {
document.body.classList.remove(classes.NOPREVIEW);
document.body.classList.remove(classes.ERROR);
@ -283,7 +297,7 @@ export default function start(render, { decorateStory } = {}) {
);
}
const { default: meta, ...examples } = fileExports;
const { default: meta, ...exports } = fileExports;
const kindName = meta.title;
if (previousExports[filename]) {
@ -307,10 +321,12 @@ export default function start(render, { decorateStory } = {}) {
kind.addParameters(meta.parameters);
}
Object.keys(examples).forEach(key => {
const example = examples[key];
const { title = example.title || key, parameters } = example;
kind.add(title, example, parameters);
Object.keys(exports).forEach(key => {
if (isExportStory(key, meta)) {
const story = exports[key];
const { title = story.title || key, parameters } = story;
kind.add(title, story, parameters);
}
});
previousExports[filename] = fileExports;

View File

@ -2,7 +2,7 @@
import { history, document, window } from 'global';
import Events from '@storybook/core-events';
import start from './start';
import start, { isExportStory } from './start';
jest.mock('@storybook/client-logger');
jest.mock('global', () => ({
@ -142,3 +142,37 @@ describe('STORY_INIT', () => {
expect(store.setSelection).toHaveBeenCalledWith({ storyId: 'kind--story' });
});
});
describe('story filters for module exports', () => {
it('should include all stories when there are no filters', () => {
expect(isExportStory('a', {})).toBeTruthy();
});
it('should filter stories by arrays', () => {
expect(isExportStory('a', { includeStories: ['a'] })).toBeTruthy();
expect(isExportStory('a', { includeStories: [] })).toBeFalsy();
expect(isExportStory('a', { includeStories: ['b'] })).toBeFalsy();
expect(isExportStory('a', { excludeStories: ['a'] })).toBeFalsy();
expect(isExportStory('a', { excludeStories: [] })).toBeTruthy();
expect(isExportStory('a', { excludeStories: ['b'] })).toBeTruthy();
expect(isExportStory('a', { includeStories: ['a'], excludeStories: ['a'] })).toBeFalsy();
expect(isExportStory('a', { includeStories: [], excludeStories: [] })).toBeFalsy();
expect(isExportStory('a', { includeStories: ['a'], excludeStories: ['b'] })).toBeTruthy();
});
it('should filter stories by regex', () => {
expect(isExportStory('a', { includeStories: /a/ })).toBeTruthy();
expect(isExportStory('a', { includeStories: /.*/ })).toBeTruthy();
expect(isExportStory('a', { includeStories: /b/ })).toBeFalsy();
expect(isExportStory('a', { excludeStories: /a/ })).toBeFalsy();
expect(isExportStory('a', { excludeStories: /.*/ })).toBeFalsy();
expect(isExportStory('a', { excludeStories: /b/ })).toBeTruthy();
expect(isExportStory('a', { includeStories: /a/, excludeStories: ['a'] })).toBeFalsy();
expect(isExportStory('a', { includeStories: /.*/, excludeStories: /.*/ })).toBeFalsy();
expect(isExportStory('a', { includeStories: /a/, excludeStories: /b/ })).toBeTruthy();
});
});

View File

@ -343,5 +343,6 @@ export async function buildDev({ packageJson, ...loadOptions }) {
...loadOptions,
packageJson,
configDir: loadOptions.configDir || cliOptions.configDir || './.storybook',
docsMode: !!cliOptions.docs,
});
}

View File

@ -123,6 +123,7 @@ async function buildManager(configType, outputDir, configDir, options) {
configDir,
corePresets: [require.resolve('./manager/manager-preset.js')],
frameworkPresets: options.frameworkPresets,
docsMode: options.docsMode,
});
if (options.debugWebpack) {
@ -203,5 +204,6 @@ export function buildStatic({ packageJson, ...loadOptions }) {
packageJson,
configDir: loadOptions.configDir || cliOptions.configDir || './.storybook',
outputDir: loadOptions.outputDir || cliOptions.outputDir || './storybook-static',
docsMode: !!cliOptions.docs,
});
}

View File

@ -36,6 +36,7 @@ async function getCLI(packageJson) {
.option('--quiet', 'Suppress verbose build output')
.option('--no-dll', 'Do not use dll reference')
.option('--debug-webpack', 'Display final webpack configurations for debugging purposes')
.option('--docs', 'Build a documentation-only site using addon-docs')
.parse(process.argv);
// Workaround the `-h` shorthand conflict.

View File

@ -16,6 +16,7 @@ function getCLI(packageJson) {
.option('--loglevel [level]', 'Control level of logging during build')
.option('--no-dll', 'Do not use dll reference')
.option('--debug-webpack', 'Display final webpack configurations for debugging purposes')
.option('--docs', 'Build a documentation-only site using addon-docs')
.parse(process.argv);
logger.setLevel(program.loglevel);

View File

@ -19,7 +19,16 @@ const coreDirName = path.dirname(require.resolve('@storybook/core/package.json')
const context = path.join(coreDirName, '../../node_modules');
const cacheDir = findCacheDir({ name: 'storybook' });
export default ({ configDir, configType, entries, dll, outputDir, cache, babelOptions }) => {
export default ({
configDir,
configType,
docsMode,
entries,
dll,
outputDir,
cache,
babelOptions,
}) => {
const { raw, stringified } = loadEnv();
const isProd = configType === 'PRODUCTION';
@ -63,6 +72,7 @@ export default ({ configDir, configType, entries, dll, outputDir, cache, babelOp
new DefinePlugin({
'process.env': stringified,
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
DOCS_MODE: docsMode, // global docs mode
}),
// See https://github.com/graphql/graphql-language-service/issues/111#issuecomment-306723400
new ContextReplacementPlugin(/graphql-language-service-interface[/\\]dist/, /\.js$/),

View File

@ -486,11 +486,13 @@ class Layout extends Component {
{isDragging ? <HoverBlocker /> : null}
{children({
mainProps: {
viewMode,
animate: !isDragging,
isFullscreen,
position: getMainPosition({ bounds, resizerNav, isNavHidden, isFullscreen, margin }),
},
previewProps: {
viewMode,
animate: !isDragging,
isFullscreen,
isToolshown,
@ -506,6 +508,7 @@ class Layout extends Component {
}),
},
navProps: {
viewMode,
animate: !isDragging,
hidden: isNavHidden,
position: {
@ -516,6 +519,7 @@ class Layout extends Component {
},
},
panelProps: {
viewMode,
animate: !isDragging,
align: options.panelPosition,
hidden: isPanelHidden,

View File

@ -15,6 +15,8 @@ ThemeProvider.displayName = 'ThemeProvider';
HelmetProvider.displayName = 'HelmetProvider';
const Container = process.env.XSTORYBOOK_EXAMPLE_APP ? React.StrictMode : React.Fragment;
// eslint-disable-next-line no-undef
const docsMode = !!DOCS_MODE; // webpack-injected
const Root = ({ provider }) => (
<Container key="container">
@ -22,7 +24,12 @@ const Root = ({ provider }) => (
<LocationProvider key="location.provider">
<Location key="location.consumer">
{locationData => (
<ManagerProvider key="manager" provider={provider} {...locationData}>
<ManagerProvider
key="manager"
provider={provider}
{...locationData}
docsMode={docsMode}
>
{({ state }) => (
<ThemeProvider key="theme.provider" theme={ensureTheme(state.theme)}>
<App key="app" viewMode={state.viewMode} layout={state.layout} />