UNPKG

@storybook/addon-docs

Version:

Document component usage and properties in Markdown

154 lines (127 loc) 5.95 kB
import React, { useContext } from 'react'; import { Source as PureSource, SourceError } from '@storybook/components'; import { DocsContext } from './DocsContext'; import { SourceContext } from './SourceContainer'; import { CURRENT_SELECTION } from './types'; import { SourceType } from '../shared'; import { enhanceSource } from './enhanceSource'; import { useStories } from './useStory'; export let SourceState; (function (SourceState) { SourceState["OPEN"] = "open"; SourceState["CLOSED"] = "closed"; SourceState["NONE"] = "none"; })(SourceState || (SourceState = {})); const getSourceState = stories => { const states = stories.map(story => { var _story$parameters$doc, _story$parameters$doc2; return (_story$parameters$doc = story.parameters.docs) === null || _story$parameters$doc === void 0 ? void 0 : (_story$parameters$doc2 = _story$parameters$doc.source) === null || _story$parameters$doc2 === void 0 ? void 0 : _story$parameters$doc2.state; }).filter(Boolean); if (states.length === 0) return SourceState.CLOSED; // FIXME: handling multiple stories is a pain return states[0]; }; const getStorySource = (storyId, sourceContext) => { const { sources } = sourceContext; // source rendering is async so source is unavailable at the start of the render cycle, // so we fail gracefully here without warning return (sources === null || sources === void 0 ? void 0 : sources[storyId]) || { code: '', format: false }; }; const getSnippet = (snippet, story) => { var _parameters$docs, _parameters$docs$sour, _parameters$docs2, _parameters$docs2$sou, _enhanced$docs, _enhanced$docs$source; if (!story) { return snippet; } const { parameters } = story; // eslint-disable-next-line no-underscore-dangle const isArgsStory = parameters.__isArgsStory; const type = ((_parameters$docs = parameters.docs) === null || _parameters$docs === void 0 ? void 0 : (_parameters$docs$sour = _parameters$docs.source) === null || _parameters$docs$sour === void 0 ? void 0 : _parameters$docs$sour.type) || SourceType.AUTO; // if user has hard-coded the snippet, that takes precedence const userCode = (_parameters$docs2 = parameters.docs) === null || _parameters$docs2 === void 0 ? void 0 : (_parameters$docs2$sou = _parameters$docs2.source) === null || _parameters$docs2$sou === void 0 ? void 0 : _parameters$docs2$sou.code; if (userCode !== undefined) { return userCode; } // if user has explicitly set this as dynamic, use snippet if (type === SourceType.DYNAMIC) { var _parameters$docs3, _parameters$docs3$tra; return ((_parameters$docs3 = parameters.docs) === null || _parameters$docs3 === void 0 ? void 0 : (_parameters$docs3$tra = _parameters$docs3.transformSource) === null || _parameters$docs3$tra === void 0 ? void 0 : _parameters$docs3$tra.call(_parameters$docs3, snippet, story)) || snippet; } // if this is an args story and there's a snippet if (type === SourceType.AUTO && snippet && isArgsStory) { var _parameters$docs4, _parameters$docs4$tra; return ((_parameters$docs4 = parameters.docs) === null || _parameters$docs4 === void 0 ? void 0 : (_parameters$docs4$tra = _parameters$docs4.transformSource) === null || _parameters$docs4$tra === void 0 ? void 0 : _parameters$docs4$tra.call(_parameters$docs4, snippet, story)) || snippet; } // otherwise, use the source code logic const enhanced = enhanceSource(story) || parameters; return (enhanced === null || enhanced === void 0 ? void 0 : (_enhanced$docs = enhanced.docs) === null || _enhanced$docs === void 0 ? void 0 : (_enhanced$docs$source = _enhanced$docs.source) === null || _enhanced$docs$source === void 0 ? void 0 : _enhanced$docs$source.code) || ''; }; export const getSourceProps = (props, docsContext, sourceContext) => { const { id: currentId, storyById } = docsContext; const { parameters } = storyById(currentId); const codeProps = props; const singleProps = props; const multiProps = props; let source = codeProps.code; // prefer user-specified code let { format } = codeProps; // prefer user-specified code const targetIds = multiProps.ids || [singleProps.id || currentId]; const storyIds = targetIds.map(targetId => targetId === CURRENT_SELECTION ? currentId : targetId); const stories = useStories(storyIds, docsContext); if (!stories.every(Boolean)) { return { error: SourceError.SOURCE_UNAVAILABLE, state: SourceState.NONE }; } if (!source) { // just take the format from the first story, given how they're all concatinated together... // TODO: we should consider sending an event with all the sources separately, instead of concatenating them here ({ format } = getStorySource(storyIds[0], sourceContext)); source = storyIds.map((storyId, idx) => { const { code: storySource } = getStorySource(storyId, sourceContext); const storyObj = stories[idx]; return getSnippet(storySource, storyObj); }).join('\n\n'); } const state = getSourceState(stories); const { docs: docsParameters = {} } = parameters; const { source: sourceParameters = {} } = docsParameters; const { language: docsLanguage = null } = sourceParameters; return source ? { code: source, state, format, language: props.language || docsLanguage || 'jsx', dark: props.dark || false } : { error: SourceError.SOURCE_UNAVAILABLE, state }; }; /** * Story source doc block renders source code if provided, * or the source for a story if `storyId` is provided, or * the source for the current story if nothing is provided. */ export const Source = props => { const sourceContext = useContext(SourceContext); const docsContext = useContext(DocsContext); const sourceProps = getSourceProps(props, docsContext, sourceContext); return /*#__PURE__*/React.createElement(PureSource, sourceProps); };