diff --git a/addons/docs/package.json b/addons/docs/package.json index 057532365d3..2ec46af49c7 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -71,11 +71,13 @@ "@storybook/components": "6.4.0-alpha.34", "@storybook/core": "6.4.0-alpha.34", "@storybook/core-events": "6.4.0-alpha.34", - "@storybook/csf": "0.0.1", + "@storybook/csf": "0.0.2--canary.b1d5348.0", "@storybook/csf-tools": "6.4.0-alpha.34", "@storybook/node-logger": "6.4.0-alpha.34", "@storybook/postinstall": "6.4.0-alpha.34", + "@storybook/preview-web": "6.4.0-alpha.34", "@storybook/source-loader": "6.4.0-alpha.34", + "@storybook/store": "6.4.0-alpha.34", "@storybook/theming": "6.4.0-alpha.34", "acorn": "^7.4.1", "acorn-jsx": "^5.3.1", @@ -106,6 +108,7 @@ "@emotion/core": "^10.1.1", "@emotion/styled": "^10.0.27", "@storybook/angular": "6.4.0-alpha.34", + "@storybook/html": "6.4.0-alpha.34", "@storybook/react": "6.4.0-alpha.34", "@storybook/vue": "6.4.0-alpha.34", "@storybook/web-components": "6.4.0-alpha.34", @@ -139,6 +142,8 @@ }, "peerDependencies": { "@storybook/angular": "6.4.0-alpha.34", + "@storybook/html": "6.4.0-alpha.34", + "@storybook/react": "6.4.0-alpha.34", "@storybook/vue": "6.4.0-alpha.34", "@storybook/vue3": "6.4.0-alpha.34", "@storybook/web-components": "6.4.0-alpha.34", @@ -155,6 +160,12 @@ "@storybook/angular": { "optional": true }, + "@storybook/html": { + "optional": true + }, + "@storybook/react": { + "optional": true + }, "@storybook/vue": { "optional": true }, diff --git a/addons/docs/src/blocks/ArgsTable.tsx b/addons/docs/src/blocks/ArgsTable.tsx index fe8702db042..7dac7f5a972 100644 --- a/addons/docs/src/blocks/ArgsTable.tsx +++ b/addons/docs/src/blocks/ArgsTable.tsx @@ -1,24 +1,23 @@ -/* eslint-disable no-underscore-dangle */ import React, { FC, useContext, useEffect, useState, useCallback } from 'react'; import mapValues from 'lodash/mapValues'; import { ArgsTable as PureArgsTable, ArgsTableProps as PureArgsTableProps, ArgsTableError, - ArgTypes, SortType, TabbedArgsTable, } from '@storybook/components'; -import { Args } from '@storybook/addons'; -import { StoryStore, filterArgTypes } from '@storybook/client-api'; -import type { PropDescriptor } from '@storybook/client-api'; +import { addons } from '@storybook/addons'; +import { filterArgTypes, PropDescriptor } from '@storybook/store'; import Events from '@storybook/core-events'; +import { StrictArgTypes, Args, AnyFramework } from '@storybook/csf'; import { DocsContext, DocsContextProps } from './DocsContext'; import { Component, CURRENT_SELECTION, PRIMARY_STORY } from './types'; -import { getComponentName, getDocsStories } from './utils'; +import { getComponentName } from './utils'; import { ArgTypesExtractor } from '../lib/docgen/types'; import { lookupStoryId } from './Story'; +import { useStory } from './useStory'; interface BaseProps { include?: PropDescriptor; @@ -45,29 +44,33 @@ type ArgsTableProps = BaseProps | OfProps | ComponentsProps | StoryProps; const useArgs = ( storyId: string, - storyStore: StoryStore + context: DocsContextProps ): [Args, (args: Args) => void, (argNames?: string[]) => void] => { - const story = storyStore.fromId(storyId); + const channel = addons.getChannel(); + + const story = context.storyById(storyId); if (!story) { throw new Error(`Unknown story: ${storyId}`); } - const { args: initialArgs } = story; - const [args, setArgs] = useState(initialArgs); + const storyContext = context.getStoryContext(story); + + const [args, setArgs] = useState(storyContext.args); useEffect(() => { const cb = (changed: { storyId: string; args: Args }) => { if (changed.storyId === storyId) { setArgs(changed.args); } }; - storyStore._channel.on(Events.STORY_ARGS_UPDATED, cb); - return () => storyStore._channel.off(Events.STORY_ARGS_UPDATED, cb); + channel.on(Events.STORY_ARGS_UPDATED, cb); + return () => channel.off(Events.STORY_ARGS_UPDATED, cb); }, [storyId]); - const updateArgs = useCallback((newArgs) => storyStore.updateStoryArgs(storyId, newArgs), [ - storyId, - ]); + const updateArgs = useCallback( + (updatedArgs) => channel.emit(Events.UPDATE_STORY_ARGS, { storyId, updatedArgs }), + [storyId] + ); const resetArgs = useCallback( - (argNames?: string[]) => storyStore.resetStoryArgs(storyId, argNames), + (argNames?: string[]) => channel.emit(Events.RESET_STORY_ARGS, { storyId, argNames }), [storyId] ); return [args, updateArgs, resetArgs]; @@ -75,12 +78,12 @@ const useArgs = ( export const extractComponentArgTypes = ( component: Component, - { parameters }: DocsContextProps, + { id, storyById }: DocsContextProps, include?: PropDescriptor, exclude?: PropDescriptor -): ArgTypes => { - const params = parameters || {}; - const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = params.docs || {}; +): StrictArgTypes => { + const { parameters } = storyById(id); + const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = parameters.docs || {}; if (!extractArgTypes) { throw new Error(ArgsTableError.ARGS_UNSUPPORTED); } @@ -94,11 +97,13 @@ const isShortcut = (value?: string) => { return value && [CURRENT_SELECTION, PRIMARY_STORY].includes(value); }; -export const getComponent = (props: ArgsTableProps = {}, context: DocsContextProps): Component => { +export const getComponent = ( + props: ArgsTableProps = {}, + { id, storyById }: DocsContextProps +): Component => { const { of } = props as OfProps; const { story } = props as StoryProps; - const { parameters = {} } = context; - const { component } = parameters; + const { component } = storyById(id); if (isShortcut(of) || isShortcut(story)) { return component || null; } @@ -127,47 +132,50 @@ export const StoryTable: FC< StoryProps & { component: Component; subcomponents: Record } > = (props) => { const context = useContext(DocsContext); + const { id: currentId, componentStories } = context; const { - id: currentId, - parameters: { argTypes }, - storyStore, - } = context; - const { story, component, subcomponents, showComponent, include, exclude, sort } = props; - let storyArgTypes; + story: storyName, + component, + subcomponents, + showComponent, + include, + exclude, + sort, + } = props; try { let storyId; - switch (story) { + switch (storyName) { case CURRENT_SELECTION: { storyId = currentId; - storyArgTypes = argTypes; break; } case PRIMARY_STORY: { - const primaryStory = getDocsStories(context)[0]; + const primaryStory = componentStories()[0]; storyId = primaryStory.id; - storyArgTypes = primaryStory.parameters.argTypes; break; } default: { - storyId = lookupStoryId(story, context); - const data = storyStore.fromId(storyId); - storyArgTypes = data.parameters.argTypes; + storyId = lookupStoryId(storyName, context); } } - storyArgTypes = filterArgTypes(storyArgTypes, include, exclude); + const story = useStory(storyId, context); + if (!story) { + return
Loading...
; + } + + const argTypes = filterArgTypes(story.argTypes, include, exclude); const mainLabel = getComponentName(component) || 'Story'; // eslint-disable-next-line prefer-const - let [args, updateArgs, resetArgs] = useArgs(storyId, storyStore); - let tabs = { [mainLabel]: { rows: storyArgTypes, args, updateArgs, resetArgs } } as Record< + let [args, updateArgs, resetArgs] = useArgs(storyId, context); + let tabs = { [mainLabel]: { rows: argTypes, args, updateArgs, resetArgs } } as Record< string, PureArgsTableProps >; // Use the dynamically generated component tabs if there are no controls - const storyHasArgsWithControls = - storyArgTypes && Object.values(storyArgTypes).find((v) => !!v?.control); + const storyHasArgsWithControls = argTypes && Object.values(argTypes).find((v) => !!v?.control); if (!storyHasArgsWithControls) { updateArgs = null; @@ -203,15 +211,19 @@ export const ComponentsTable: FC = (props) => { export const ArgsTable: FC = (props) => { const context = useContext(DocsContext); - const { parameters: { subcomponents, controls } = {} } = context; + const { id, storyById } = context; + const { + parameters: { controls }, + subcomponents, + } = storyById(id); const { include, exclude, components, sort: sortProp } = props as ComponentsProps; - const { story } = props as StoryProps; + const { story: storyName } = props as StoryProps; const sort = sortProp || controls?.sort; const main = getComponent(props, context); - if (story) { + if (storyName) { return ; } diff --git a/addons/docs/src/blocks/Canvas.tsx b/addons/docs/src/blocks/Canvas.tsx index 41deb826d8d..1f42270c907 100644 --- a/addons/docs/src/blocks/Canvas.tsx +++ b/addons/docs/src/blocks/Canvas.tsx @@ -1,6 +1,6 @@ import React, { FC, ReactElement, ReactNode, ReactNodeArray, useContext } from 'react'; import { MDXProvider } from '@mdx-js/react'; -import { toId, storyNameFromExport } from '@storybook/csf'; +import { toId, storyNameFromExport, AnyFramework } from '@storybook/csf'; import { resetComponents, Preview as PurePreview, @@ -19,10 +19,10 @@ type CanvasProps = PurePreviewProps & { const getPreviewProps = ( { withSource, mdxSource, children, ...props }: CanvasProps & { children?: ReactNode }, - docsContext: DocsContextProps, + docsContext: DocsContextProps, sourceContext: SourceContextProps ): PurePreviewProps => { - const { mdxComponentMeta, mdxStoryNameToKey } = docsContext; + const { mdxComponentAnnotations, mdxStoryNameToKey } = docsContext; let sourceState = withSource; if (sourceState === SourceState.NONE) { return props; @@ -41,7 +41,7 @@ const getPreviewProps = ( (s) => s.props.id || toId( - mdxComponentMeta.id || mdxComponentMeta.title, + mdxComponentAnnotations.id || mdxComponentAnnotations.title, storyNameFromExport(mdxStoryNameToKey[s.props.name]) ) ); diff --git a/addons/docs/src/blocks/Description.tsx b/addons/docs/src/blocks/Description.tsx index f8bf8e711db..2f92c105e16 100644 --- a/addons/docs/src/blocks/Description.tsx +++ b/addons/docs/src/blocks/Description.tsx @@ -31,12 +31,13 @@ const noDescription = (component?: Component): string | null => null; export const getDescriptionProps = ( { of, type, markdown, children }: DescriptionProps, - { parameters }: DocsContextProps + { id, storyById }: DocsContextProps ): PureDescriptionProps => { + const { component, parameters } = storyById(id); if (children || markdown) { return { markdown: children || markdown }; } - const { component, notes, info, docs } = parameters; + const { notes, info, docs } = parameters; const { extractComponentDescription = noDescription, description } = docs || {}; const target = of === CURRENT_SELECTION ? component : of; @@ -63,7 +64,7 @@ ${extractComponentDescription(target) || ''} case DescriptionType.DOCGEN: case DescriptionType.AUTO: default: - return { markdown: extractComponentDescription(target, parameters) }; + return { markdown: extractComponentDescription(target, { component, ...parameters }) }; } }; diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index 214090580a4..66d904b1f5d 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -5,6 +5,7 @@ import dedent from 'ts-dedent'; import { MDXProvider } from '@mdx-js/react'; import { ThemeProvider, ensure as ensureTheme } from '@storybook/theming'; import { DocsWrapper, DocsContent, components as htmlComponents } from '@storybook/components'; +import { AnyFramework } from '@storybook/csf'; import { DocsContextProps, DocsContext } from './DocsContext'; import { anchorBlockIdFromId } from './Anchor'; import { storyBlockIdFromId } from './Story'; @@ -14,8 +15,8 @@ import { scrollToElement } from './utils'; const { document, window: globalWindow } = global; -export interface DocsContainerProps { - context: DocsContextProps; +export interface DocsContainerProps { + context: DocsContextProps; } const defaultComponents = { @@ -35,8 +36,10 @@ const warnOptionsTheme = deprecate( ); export const DocsContainer: FunctionComponent = ({ context, children }) => { - const { id: storyId = null, parameters = {} } = context || {}; - const { options = {}, docs = {} } = parameters; + const { id: storyId, storyById } = context; + const { + parameters: { options = {}, docs = {} }, + } = storyById(storyId); let themeVars = docs.theme; if (!themeVars && options.theme) { warnOptionsTheme(); diff --git a/addons/docs/src/blocks/DocsContext.ts b/addons/docs/src/blocks/DocsContext.ts index 9f66a258855..3fada361a32 100644 --- a/addons/docs/src/blocks/DocsContext.ts +++ b/addons/docs/src/blocks/DocsContext.ts @@ -1,22 +1,10 @@ import { Context, createContext } from 'react'; import { window as globalWindow } from 'global'; -export interface DocsContextProps { - id?: string; - kind?: string; - name?: string; +import { DocsContextProps } from '@storybook/preview-web'; +import { AnyFramework } from '@storybook/csf'; - /** - * mdxStoryNameToKey is an MDX-compiler-generated mapping of an MDX story's - * display name to its story key for ID generation. It's used internally by the `` - * and `Preview` doc blocks. - */ - mdxStoryNameToKey?: Record; - mdxComponentMeta?: any; - parameters?: any; - storyStore?: any; - forceRender?: () => void; -} +export type { DocsContextProps }; // We add DocsContext to window. The reason is that in case DocsContext.ts is // imported multiple times (maybe once directly, and another time from a minified bundle) @@ -29,4 +17,4 @@ if (globalWindow.__DOCS_CONTEXT__ === undefined) { globalWindow.__DOCS_CONTEXT__.displayName = 'DocsContext'; } -export const DocsContext: Context = globalWindow.__DOCS_CONTEXT__; +export const DocsContext: Context> = globalWindow.__DOCS_CONTEXT__; diff --git a/addons/docs/src/blocks/DocsPage.test.ts b/addons/docs/src/blocks/DocsPage.test.ts index 34a120d6e90..0e94fcb1d7d 100644 --- a/addons/docs/src/blocks/DocsPage.test.ts +++ b/addons/docs/src/blocks/DocsPage.test.ts @@ -2,9 +2,8 @@ import { extractTitle } from './Title'; describe('defaultTitleSlot', () => { it('splits on last /', () => { - const parameters = {}; - expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); - expect(extractTitle({ kind: 'a|b', parameters })).toBe('a|b'); - expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('c.d'); + expect(extractTitle({ title: 'a/b/c' } as any)).toBe('c'); + expect(extractTitle({ title: 'a|b' } as any)).toBe('a|b'); + expect(extractTitle({ title: 'a/b/c.d' } as any)).toBe('c.d'); }); }); diff --git a/addons/docs/src/blocks/Meta.tsx b/addons/docs/src/blocks/Meta.tsx index 5fd193e5df2..206496266e8 100644 --- a/addons/docs/src/blocks/Meta.tsx +++ b/addons/docs/src/blocks/Meta.tsx @@ -1,17 +1,15 @@ import React, { FC, useContext } from 'react'; import global from 'global'; -import { Args, BaseAnnotations, BaseMeta } from '@storybook/addons'; +import { AnyFramework, BaseAnnotations } from '@storybook/csf'; import { Anchor } from './Anchor'; import { DocsContext, DocsContextProps } from './DocsContext'; -import { getDocsStories } from './utils'; -import { Component } from './types'; const { document } = global; -type MetaProps = BaseMeta & BaseAnnotations; +type MetaProps = BaseAnnotations; function getFirstStoryId(docsContext: DocsContextProps): string { - const stories = getDocsStories(docsContext); + const stories = docsContext.componentStories(); return stories.length > 0 ? stories[0].id : null; } diff --git a/addons/docs/src/blocks/Primary.tsx b/addons/docs/src/blocks/Primary.tsx index 242e1779487..01252656a32 100644 --- a/addons/docs/src/blocks/Primary.tsx +++ b/addons/docs/src/blocks/Primary.tsx @@ -1,18 +1,19 @@ import React, { useContext, FC } from 'react'; +import { Story } from '@storybook/store'; + import { DocsContext } from './DocsContext'; import { DocsStory } from './DocsStory'; -import { getDocsStories } from './utils'; interface PrimaryProps { name?: string; } export const Primary: FC = ({ name }) => { - const context = useContext(DocsContext); - const componentStories = getDocsStories(context); + const { componentStories: getComponentStories } = useContext(DocsContext); + const componentStories = getComponentStories(); let story; if (componentStories) { - story = name ? componentStories.find((s) => s.name === name) : componentStories[0]; + story = name ? componentStories.find((s: Story) => s.name === name) : componentStories[0]; } return story ? : null; }; diff --git a/addons/docs/src/blocks/Source.tsx b/addons/docs/src/blocks/Source.tsx index 4777d11072e..1c521af3426 100644 --- a/addons/docs/src/blocks/Source.tsx +++ b/addons/docs/src/blocks/Source.tsx @@ -5,8 +5,7 @@ import { SourceProps as PureSourceProps, } from '@storybook/components'; import { StoryId } from '@storybook/api'; -import { logger } from '@storybook/client-logger'; -import { StoryContext } from '@storybook/addons'; +import { Story } from '@storybook/store'; import { DocsContext, DocsContextProps } from './DocsContext'; import { SourceContext, SourceContextProps } from './SourceContainer'; @@ -43,25 +42,15 @@ type NoneProps = CommonProps; type SourceProps = SingleSourceProps | MultiSourceProps | CodeProps | NoneProps; -const getStoryContext = (storyId: StoryId, docsContext: DocsContextProps): StoryContext | null => { - const { storyStore } = docsContext; - const storyContext = storyStore?.fromId(storyId); - - if (!storyContext) { - // Fallback if we can't get the story data for this story - logger.warn(`Unable to find information for story ID '${storyId}'`); - return null; - } - - return storyContext; +const getStory = (storyId: StoryId, docsContext: DocsContextProps): Story | null => { + return docsContext.storyById(storyId); }; const getSourceState = (storyIds: string[], docsContext: DocsContextProps) => { const states = storyIds .map((storyId) => { - const storyContext = getStoryContext(storyId, docsContext); - if (!storyContext) return null; - return storyContext.parameters.docs?.source?.state; + const story = getStory(storyId, docsContext); + return story?.parameters.docs?.source?.state; }) .filter(Boolean); @@ -77,12 +66,12 @@ const getStorySource = (storyId: StoryId, sourceContext: SourceContextProps): st return sources?.[storyId] || ''; }; -const getSnippet = (snippet: string, storyContext?: StoryContext): string => { - if (!storyContext) { +const getSnippet = (snippet: string, story?: Story): string => { + if (!story) { return snippet; } - const { parameters } = storyContext; + const { parameters } = story; // eslint-disable-next-line no-underscore-dangle const isArgsStory = parameters.__isArgsStory; const type = parameters.docs?.source?.type || SourceType.AUTO; @@ -95,16 +84,16 @@ const getSnippet = (snippet: string, storyContext?: StoryContext): string => { // if user has explicitly set this as dynamic, use snippet if (type === SourceType.DYNAMIC) { - return parameters.docs?.transformSource?.(snippet, storyContext) || snippet; + return parameters.docs?.transformSource?.(snippet, story) || snippet; } // if this is an args story and there's a snippet if (type === SourceType.AUTO && snippet && isArgsStory) { - return parameters.docs?.transformSource?.(snippet, storyContext) || snippet; + return parameters.docs?.transformSource?.(snippet, story) || snippet; } // otherwise, use the source code logic - const enhanced = enhanceSource(storyContext) || parameters; + const enhanced = enhanceSource(story) || parameters; return enhanced?.docs?.source?.code || ''; }; @@ -112,10 +101,11 @@ type SourceStateProps = { state: SourceState }; export const getSourceProps = ( props: SourceProps, - docsContext: DocsContextProps, + docsContext: DocsContextProps, sourceContext: SourceContextProps ): PureSourceProps & SourceStateProps => { - const { id: currentId, parameters = {} } = docsContext; + const { id: currentId, storyById } = docsContext; + const { parameters } = storyById(currentId); const codeProps = props as CodeProps; const singleProps = props as SingleSourceProps; @@ -131,8 +121,8 @@ export const getSourceProps = ( source = targetIds .map((storyId) => { const storySource = getStorySource(storyId, sourceContext); - const storyContext = getStoryContext(storyId, docsContext); - return getSnippet(storySource, storyContext); + const story = getStory(storyId, docsContext); + return getSnippet(storySource, story); }) .join('\n\n'); } diff --git a/addons/docs/src/blocks/Stories.tsx b/addons/docs/src/blocks/Stories.tsx index afb7de3e8ad..9d460a5cd83 100644 --- a/addons/docs/src/blocks/Stories.tsx +++ b/addons/docs/src/blocks/Stories.tsx @@ -2,7 +2,6 @@ import React, { useContext, FunctionComponent } from 'react'; import { DocsContext } from './DocsContext'; import { DocsStory } from './DocsStory'; import { Heading } from './Heading'; -import { getDocsStories } from './utils'; import { DocsStoryProps } from './types'; interface StoriesProps { @@ -11,10 +10,9 @@ interface StoriesProps { } export const Stories: FunctionComponent = ({ title, includePrimary = false }) => { - const context = useContext(DocsContext); - const componentStories = getDocsStories(context); + const { componentStories } = useContext(DocsContext); - let stories: DocsStoryProps[] = componentStories; + let stories: DocsStoryProps[] = componentStories(); if (!includePrimary) stories = stories.slice(1); if (!stories || stories.length === 0) { diff --git a/addons/docs/src/blocks/Story.tsx b/addons/docs/src/blocks/Story.tsx index 6e239f072ca..7d88e533ef4 100644 --- a/addons/docs/src/blocks/Story.tsx +++ b/addons/docs/src/blocks/Story.tsx @@ -1,17 +1,28 @@ -import React, { FunctionComponent, ReactNode, ElementType, ComponentProps } from 'react'; +import React, { + FunctionComponent, + ReactNode, + ElementType, + ComponentProps, + useContext, +} from 'react'; import { MDXProvider } from '@mdx-js/react'; import { resetComponents, Story as PureStory } from '@storybook/components'; -import { toId, storyNameFromExport } from '@storybook/csf'; -import { Args, BaseAnnotations } from '@storybook/addons'; -import { CURRENT_SELECTION } from './types'; +import { StoryId, toId, storyNameFromExport, StoryAnnotations, AnyFramework } from '@storybook/csf'; +import { Story as StoryType } from '@storybook/store'; +import { CURRENT_SELECTION } from './types'; import { DocsContext, DocsContextProps } from './DocsContext'; +import { useStory } from './useStory'; export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`; type PureStoryProps = ComponentProps; -type CommonProps = BaseAnnotations & { +type Annotations = Pick< + StoryAnnotations, + 'decorators' | 'parameters' | 'args' | 'argTypes' | 'loaders' +>; +type CommonProps = Annotations & { height?: string; inline?: boolean; }; @@ -34,22 +45,26 @@ export type StoryProps = (StoryDefProps | StoryRefProps | StoryImportProps) & Co export const lookupStoryId = ( storyName: string, - { mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps + { mdxStoryNameToKey, mdxComponentAnnotations }: DocsContextProps ) => toId( - mdxComponentMeta.id || mdxComponentMeta.title, + mdxComponentAnnotations.id || mdxComponentAnnotations.title, storyNameFromExport(mdxStoryNameToKey[storyName]) ); -export const getStoryProps = (props: StoryProps, context: DocsContextProps): PureStoryProps => { +export const getStoryId = (props: StoryProps, context: DocsContextProps): StoryId => { const { id } = props as StoryRefProps; const { name } = props as StoryDefProps; const inputId = id === CURRENT_SELECTION ? context.id : id; - const previewId = inputId || lookupStoryId(name, context); - const data = context.storyStore.fromId(previewId) || {}; + return inputId || lookupStoryId(name, context); +}; - const { height, inline } = props; - const { storyFn = undefined, name: storyName = undefined, parameters = {} } = data; +export const getStoryProps = ( + { height, inline }: StoryProps, + story: StoryType, + context: DocsContextProps +): PureStoryProps => { + const { name: storyName, parameters } = story; const { docs = {} } = parameters; if (docs.disable) { @@ -65,33 +80,41 @@ export const getStoryProps = (props: StoryProps, context: DocsContextProps): Pur ); } + const boundStoryFn = () => + story.unboundStoryFn({ + ...context.getStoryContext(story), + loaded: {}, + }); return { parameters, inline: storyIsInline, - id: previewId, - storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn, data) : storyFn, + id: story.id, + storyFn: prepareForInline ? () => prepareForInline(boundStoryFn, story) : boundStoryFn, height: height || (storyIsInline ? undefined : iframeHeight), title: storyName, }; }; -const Story: FunctionComponent = (props) => ( - - {(context) => { - const storyProps = getStoryProps(props, context); - if (!storyProps) { - return null; - } - return ( -
- - - -
- ); - }} -
-); +const Story: FunctionComponent = (props) => { + const context = useContext(DocsContext); + const story = useStory(getStoryId(props, context), context); + + if (!story) { + return
Loading...
; + } + + const storyProps = getStoryProps(props, story, context); + if (!storyProps) { + return null; + } + return ( +
+ + + +
+ ); +}; Story.defaultProps = { children: null, diff --git a/addons/docs/src/blocks/Subtitle.tsx b/addons/docs/src/blocks/Subtitle.tsx index 3fb44a8af5a..64e160176dd 100644 --- a/addons/docs/src/blocks/Subtitle.tsx +++ b/addons/docs/src/blocks/Subtitle.tsx @@ -7,8 +7,8 @@ interface SubtitleProps { } export const Subtitle: FunctionComponent = ({ children }) => { - const context = useContext(DocsContext); - const { parameters } = context; + const { id, storyById } = useContext(DocsContext); + const { parameters } = storyById(id); let text: JSX.Element | string = children; if (!text) { text = parameters?.componentSubtitle; diff --git a/addons/docs/src/blocks/Title.tsx b/addons/docs/src/blocks/Title.tsx index 052f0dbe904..100b8b2926d 100644 --- a/addons/docs/src/blocks/Title.tsx +++ b/addons/docs/src/blocks/Title.tsx @@ -8,9 +8,9 @@ interface TitleProps { const STORY_KIND_PATH_SEPARATOR = /\s*\/\s*/; -export const extractTitle = ({ kind }: DocsContextProps) => { - const groups = kind.trim().split(STORY_KIND_PATH_SEPARATOR); - return (groups && groups[groups.length - 1]) || kind; +export const extractTitle = ({ title }: DocsContextProps) => { + const groups = title.trim().split(STORY_KIND_PATH_SEPARATOR); + return (groups && groups[groups.length - 1]) || title; }; export const Title: FunctionComponent = ({ children }) => { diff --git a/addons/docs/src/blocks/enhanceSource.ts b/addons/docs/src/blocks/enhanceSource.ts index 42a87ca906a..ea969c1d46c 100644 --- a/addons/docs/src/blocks/enhanceSource.ts +++ b/addons/docs/src/blocks/enhanceSource.ts @@ -1,5 +1,6 @@ import { combineParameters } from '@storybook/client-api'; -import { StoryContext, Parameters } from '@storybook/addons'; +import { Parameters } from '@storybook/addons'; +import { Story } from '@storybook/store'; // ============================================================ // START @storybook/source-loader/extract-source @@ -76,8 +77,8 @@ const extract = (targetId: string, { source, locationsMap }: StorySource) => { return extractSource(location, lines); }; -export const enhanceSource = (context: StoryContext): Parameters => { - const { id, parameters } = context; +export const enhanceSource = (story: Story): Parameters => { + const { id, parameters } = story; const { storySource, docs = {} } = parameters; const { transformSource } = docs; @@ -87,7 +88,7 @@ export const enhanceSource = (context: StoryContext): Parameters => { } const input = extract(id, storySource); - const code = transformSource ? transformSource(input, context) : input; + const code = transformSource ? transformSource(input, story) : input; return { docs: combineParameters(docs, { source: { code } }) }; }; diff --git a/addons/docs/src/blocks/useStory.ts b/addons/docs/src/blocks/useStory.ts new file mode 100644 index 00000000000..6c785247fe9 --- /dev/null +++ b/addons/docs/src/blocks/useStory.ts @@ -0,0 +1,18 @@ +import { useState, useEffect } from 'react'; +import { StoryId, AnyFramework } from '@storybook/csf'; +import { Story } from '@storybook/store'; + +import { DocsContextProps } from './DocsContext'; + +export function useStory( + storyId: StoryId, + context: DocsContextProps +): Story | void { + const [story, setStory] = useState(null); + + useEffect(() => { + context.loadStory(storyId).then((s) => setStory(s)); + }); + + return story; +} diff --git a/addons/docs/src/blocks/utils.ts b/addons/docs/src/blocks/utils.ts index 036925af27e..75395913774 100644 --- a/addons/docs/src/blocks/utils.ts +++ b/addons/docs/src/blocks/utils.ts @@ -1,18 +1,5 @@ /* eslint-disable no-underscore-dangle */ -import { DocsContextProps } from './DocsContext'; -import { StoryData, Component } from './types'; - -export const getDocsStories = (context: DocsContextProps): StoryData[] => { - const { storyStore, kind } = context; - - if (!storyStore) { - return []; - } - - return storyStore - .getStoriesForKind(kind) - .filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable)); -}; +import { Component } from './types'; const titleCase = (str: string): string => str diff --git a/addons/docs/src/frameworks/angular/compodoc.ts b/addons/docs/src/frameworks/angular/compodoc.ts index 8a7d68c87af..c6dd02d450c 100644 --- a/addons/docs/src/frameworks/angular/compodoc.ts +++ b/addons/docs/src/frameworks/angular/compodoc.ts @@ -43,7 +43,7 @@ export const checkValidCompodocJson = (compodocJson: CompodocJson) => { const hasDecorator = (item: Property, decoratorName: string) => item.decorators && item.decorators.find((x: any) => x.name === decoratorName); -const mapPropertyToSection = (key: string, item: Property) => { +const mapPropertyToSection = (item: Property) => { if (hasDecorator(item, 'ViewChild')) { return 'view child'; } @@ -73,7 +73,7 @@ const mapItemToSection = (key: string, item: Method | Property): string => { if (isMethod(item)) { throw new Error("Cannot be of type Method if key === 'propertiesClass'"); } - return mapPropertyToSection(key, item); + return mapPropertyToSection(item); default: throw new Error(`Unknown key: ${key}`); } diff --git a/addons/docs/src/frameworks/angular/prepareForInline.ts b/addons/docs/src/frameworks/angular/prepareForInline.ts index fc9e4f0ab26..76be79a3f4a 100644 --- a/addons/docs/src/frameworks/angular/prepareForInline.ts +++ b/addons/docs/src/frameworks/angular/prepareForInline.ts @@ -2,16 +2,19 @@ import React from 'react'; import pLimit from 'p-limit'; import { nanoid } from 'nanoid'; -import { IStory, StoryContext } from '@storybook/angular'; +import { AngularFramework, StoryContext } from '@storybook/angular'; import { rendererFactory } from '@storybook/angular/renderer'; -import { StoryFn } from '@storybook/addons'; +import { PartialStoryFn } from '@storybook/csf'; const limit = pLimit(1); /** * Uses the angular renderer to generate a story. Uses p-limit to run synchronously */ -export const prepareForInline = (storyFn: StoryFn, { id, parameters }: StoryContext) => { +export const prepareForInline = ( + storyFn: PartialStoryFn, + { id, parameters }: StoryContext +) => { return React.createElement('div', { ref: async (node?: HTMLDivElement): Promise => { if (!node) { diff --git a/addons/docs/src/frameworks/angular/sourceDecorator.ts b/addons/docs/src/frameworks/angular/sourceDecorator.ts index d5e3b1bd96d..496a316f55c 100644 --- a/addons/docs/src/frameworks/angular/sourceDecorator.ts +++ b/addons/docs/src/frameworks/angular/sourceDecorator.ts @@ -1,5 +1,6 @@ -import { addons, StoryContext, StoryFn } from '@storybook/addons'; -import { IStory } from '@storybook/angular'; +import { addons } from '@storybook/addons'; +import { PartialStoryFn } from '@storybook/csf'; +import { StoryContext, AngularFramework } from '@storybook/angular'; import { computesTemplateSourceFromComponent } from '@storybook/angular/renderer'; import prettierHtml from 'prettier/parser-html'; import prettier from 'prettier/standalone'; @@ -30,7 +31,10 @@ const prettyUp = (source: string) => { * @param storyFn Fn * @param context StoryContext */ -export const sourceDecorator = (storyFn: StoryFn, context: StoryContext) => { +export const sourceDecorator = ( + storyFn: PartialStoryFn, + context: StoryContext +) => { const story = storyFn(); if (skipSourceRender(context)) { return story; @@ -38,9 +42,7 @@ export const sourceDecorator = (storyFn: StoryFn, context: StoryContext) const channel = addons.getChannel(); const { props, template, userDefinedTemplate } = story; - const { - parameters: { component, argTypes }, - } = context; + const { component, argTypes } = context; if (component && !userDefinedTemplate) { const source = computesTemplateSourceFromComponent(component, props, argTypes); diff --git a/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts b/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts index 252dd5cfe01..43e0da96710 100644 --- a/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts +++ b/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts @@ -1,4 +1,5 @@ -import { ArgType, ArgTypes } from '@storybook/api'; +import { ArgTypes } from '@storybook/api'; +import { StrictInputType } from '@storybook/csf'; import { enhanceArgTypes } from './enhanceArgTypes'; expect.addSnapshotSerializer({ @@ -12,30 +13,28 @@ const enhance = ({ extractedArgTypes, isArgsStory = true, }: { - argType?: ArgType; + argType?: StrictInputType; arg?: any; extractedArgTypes?: ArgTypes; isArgsStory?: boolean; }) => { const context = { - id: 'foo--bar', + componentId: 'foo', + title: 'foo', kind: 'foo', + id: 'foo--bar', name: 'bar', + story: 'bar', + component: 'dummy', parameters: { - component: 'dummy', __isArgsStory: isArgsStory, docs: { extractArgTypes: extractedArgTypes && (() => extractedArgTypes), }, - argTypes: argType && { - input: argType, - }, - args: { - input: arg, - }, }, - args: {}, - argTypes: {}, + argTypes: argType && { input: argType }, + initialArgs: { input: arg }, + args: { input: arg }, globals: {}, }; return enhanceArgTypes(context); @@ -46,7 +45,7 @@ describe('enhanceArgTypes', () => { it('should no-op', () => { expect( enhance({ - argType: { foo: 'unmodified', type: { name: 'number' } }, + argType: { name: 'input', foo: 'unmodified', type: { name: 'number' } }, isArgsStory: false, }).input ).toMatchInlineSnapshot(` @@ -66,7 +65,7 @@ describe('enhanceArgTypes', () => { it('number', () => { expect( enhance({ - argType: { type: { name: 'number' } }, + argType: { name: 'input', type: { name: 'number' } }, }).input ).toMatchInlineSnapshot(` { @@ -99,7 +98,7 @@ describe('enhanceArgTypes', () => { it('range', () => { expect( enhance({ - argType: { control: { type: 'range', min: 0, max: 100 } }, + argType: { name: 'input', control: { type: 'range', min: 0, max: 100 } }, }).input ).toMatchInlineSnapshot(` { @@ -115,7 +114,7 @@ describe('enhanceArgTypes', () => { it('options', () => { expect( enhance({ - argType: { control: { type: 'radio', options: [1, 2] } }, + argType: { name: 'input', control: { type: 'radio', options: [1, 2] } }, }).input ).toMatchInlineSnapshot(` { @@ -137,7 +136,7 @@ describe('enhanceArgTypes', () => { it('user-specified argTypes take precedence over extracted argTypes', () => { expect( enhance({ - argType: { type: { name: 'number' } }, + argType: { name: 'input', type: { name: 'number' } }, extractedArgTypes: { input: { type: { name: 'string' } } }, }).input ).toMatchInlineSnapshot(` @@ -153,7 +152,7 @@ describe('enhanceArgTypes', () => { it('user-specified argTypes take precedence over inferred argTypes', () => { expect( enhance({ - argType: { type: { name: 'number' } }, + argType: { name: 'input', type: { name: 'number' } }, arg: 'hello', }).input ).toMatchInlineSnapshot(` @@ -184,7 +183,7 @@ describe('enhanceArgTypes', () => { it('user-specified controls take precedence over inferred controls', () => { expect( enhance({ - argType: { defaultValue: 5, control: { type: 'range', step: 50 } }, + argType: { name: 'input', defaultValue: 5, control: { type: 'range', step: 50 } }, arg: 3, extractedArgTypes: { input: { name: 'input' } }, }).input @@ -223,7 +222,7 @@ describe('enhanceArgTypes', () => { it('includes extracted argTypes when user-specified argTypes match', () => { expect( enhance({ - argType: { type: { name: 'number' } }, + argType: { name: 'input', type: { name: 'number' } }, extractedArgTypes: { input: { name: 'input' }, foo: { type: { name: 'number' } } }, }) ).toMatchInlineSnapshot(` @@ -246,7 +245,7 @@ describe('enhanceArgTypes', () => { it('excludes extracted argTypes when user-specified argTypes do not match', () => { expect( enhance({ - argType: { type: { name: 'number' } }, + argType: { name: 'input', type: { name: 'number' } }, extractedArgTypes: { foo: { type: { name: 'number' } } }, }) ).toMatchInlineSnapshot(` diff --git a/addons/docs/src/frameworks/common/enhanceArgTypes.ts b/addons/docs/src/frameworks/common/enhanceArgTypes.ts index f1ee242400e..540b80e56b4 100644 --- a/addons/docs/src/frameworks/common/enhanceArgTypes.ts +++ b/addons/docs/src/frameworks/common/enhanceArgTypes.ts @@ -1,17 +1,20 @@ -import mapValues from 'lodash/mapValues'; -import { ArgTypesEnhancer, combineParameters } from '@storybook/client-api'; -import { normalizeArgTypes } from './normalizeArgTypes'; +import { AnyFramework, StoryContextForEnhancers } from '@storybook/csf'; +import { combineParameters } from '@storybook/store'; -export const enhanceArgTypes: ArgTypesEnhancer = (context) => { - const { component, argTypes: userArgTypes = {}, docs = {} } = context.parameters; +export const enhanceArgTypes = ( + context: StoryContextForEnhancers +) => { + const { + component, + argTypes: userArgTypes, + parameters: { docs = {} }, + } = context; const { extractArgTypes } = docs; - const normalizedArgTypes = normalizeArgTypes(userArgTypes); - const namedArgTypes = mapValues(normalizedArgTypes, (val, key) => ({ name: key, ...val })); const extractedArgTypes = extractArgTypes && component ? extractArgTypes(component) : {}; const withExtractedTypes = extractedArgTypes - ? combineParameters(extractedArgTypes, namedArgTypes) - : namedArgTypes; + ? combineParameters(extractedArgTypes, userArgTypes) + : userArgTypes; return withExtractedTypes; }; diff --git a/addons/docs/src/frameworks/common/normalizeArgTypes.ts b/addons/docs/src/frameworks/common/normalizeArgTypes.ts deleted file mode 100644 index 8af93c8e414..00000000000 --- a/addons/docs/src/frameworks/common/normalizeArgTypes.ts +++ /dev/null @@ -1,18 +0,0 @@ -import mapValues from 'lodash/mapValues'; -import { ArgTypes } from '@storybook/api'; -import { SBType } from '@storybook/client-api'; - -const normalizeType = (type: SBType | string) => (typeof type === 'string' ? { name: type } : type); - -const normalizeControl = (control?: any) => - typeof control === 'string' ? { type: control } : control; - -export const normalizeArgTypes = (argTypes: ArgTypes) => - mapValues(argTypes, (argType) => { - if (!argType) return argType; - const normalized = { ...argType }; - const { type, control } = argType; - if (type) normalized.type = normalizeType(type); - if (control) normalized.control = normalizeControl(control); - return normalized; - }); diff --git a/addons/docs/src/frameworks/html/prepareForInline.tsx b/addons/docs/src/frameworks/html/prepareForInline.tsx index 578f42c0bb3..421b9625f68 100644 --- a/addons/docs/src/frameworks/html/prepareForInline.tsx +++ b/addons/docs/src/frameworks/html/prepareForInline.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { StoryFn } from '@storybook/addons'; +import { PartialStoryFn } from '@storybook/csf'; -export function prepareForInline(storyFn: StoryFn) { +export function prepareForInline(storyFn: PartialStoryFn) { const html = storyFn(); if (typeof html === 'string') { // eslint-disable-next-line react/no-danger diff --git a/addons/docs/src/frameworks/html/sourceDecorator.ts b/addons/docs/src/frameworks/html/sourceDecorator.ts index fa45e820ed4..c37143f4ee2 100644 --- a/addons/docs/src/frameworks/html/sourceDecorator.ts +++ b/addons/docs/src/frameworks/html/sourceDecorator.ts @@ -1,9 +1,12 @@ /* global window */ -import { addons, StoryContext, StoryFn } from '@storybook/addons'; +import { addons } from '@storybook/addons'; +import { ArgsStoryFn, PartialStoryFn, StoryContext } from '@storybook/csf'; import dedent from 'ts-dedent'; +import { HtmlFramework } from '@storybook/html'; + import { SNIPPET_RENDERED, SourceType } from '../../shared'; -function skipSourceRender(context: StoryContext) { +function skipSourceRender(context: StoryContext) { const sourceParams = context?.parameters.docs?.source; const isArgsStory = context?.parameters.__isArgsStory; @@ -23,15 +26,18 @@ function defaultTransformSource(source: string) { return dedent(source); } -function applyTransformSource(source: string, context: StoryContext): string { +function applyTransformSource(source: string, context: StoryContext): string { const docs = context.parameters.docs ?? {}; const transformSource = docs.transformSource ?? defaultTransformSource; return transformSource(source, context); } -export function sourceDecorator(storyFn: StoryFn, context: StoryContext) { +export function sourceDecorator( + storyFn: PartialStoryFn, + context: StoryContext +) { const story = context?.parameters.docs?.source?.excludeDecorators - ? context.originalStoryFn(context.args) + ? (context.originalStoryFn as ArgsStoryFn)(context.args, context) : storyFn(); if (typeof story === 'string' && !skipSourceRender(context)) { diff --git a/addons/docs/src/frameworks/react/config.ts b/addons/docs/src/frameworks/react/config.ts index 61a01e8a3af..28ef4afc0e4 100644 --- a/addons/docs/src/frameworks/react/config.ts +++ b/addons/docs/src/frameworks/react/config.ts @@ -1,4 +1,6 @@ -import { StoryFn } from '@storybook/addons'; +import { PartialStoryFn } from '@storybook/csf'; +import { ReactFramework } from '@storybook/react'; + import { extractArgTypes } from './extractArgTypes'; import { extractComponentDescription } from '../../lib/docgen'; import { jsxDecorator } from './jsxDecorator'; @@ -7,7 +9,7 @@ export const parameters = { docs: { inlineStories: true, // NOTE: that the result is a react element. Hooks support is provided by the outer code. - prepareForInline: (storyFn: StoryFn) => storyFn(), + prepareForInline: (storyFn: PartialStoryFn) => storyFn(), extractArgTypes, extractComponentDescription, }, diff --git a/addons/docs/src/frameworks/react/extractArgTypes.ts b/addons/docs/src/frameworks/react/extractArgTypes.ts index 76768f26843..e86dcaa99b8 100644 --- a/addons/docs/src/frameworks/react/extractArgTypes.ts +++ b/addons/docs/src/frameworks/react/extractArgTypes.ts @@ -1,4 +1,4 @@ -import { ArgTypes } from '@storybook/api'; +import { StrictArgTypes } from '@storybook/csf'; import { PropDef, ArgTypesExtractor } from '../../lib/docgen'; import { extractProps } from './extractProps'; @@ -6,7 +6,7 @@ export const extractArgTypes: ArgTypesExtractor = (component) => { if (component) { const { rows } = extractProps(component); if (rows) { - return rows.reduce((acc: ArgTypes, row: PropDef) => { + return rows.reduce((acc: StrictArgTypes, row: PropDef) => { const { name, description, diff --git a/addons/docs/src/frameworks/react/jsxDecorator.tsx b/addons/docs/src/frameworks/react/jsxDecorator.tsx index dedb1505fe6..e3a3cc88846 100644 --- a/addons/docs/src/frameworks/react/jsxDecorator.tsx +++ b/addons/docs/src/frameworks/react/jsxDecorator.tsx @@ -3,8 +3,10 @@ import reactElementToJSXString, { Options } from 'react-element-to-jsx-string'; import dedent from 'ts-dedent'; import deprecate from 'util-deprecate'; -import { addons, StoryContext } from '@storybook/addons'; +import { addons } from '@storybook/addons'; +import { StoryContext, ArgsStoryFn, PartialStoryFn } from '@storybook/csf'; import { logger } from '@storybook/client-logger'; +import { ReactFramework } from '@storybook/react'; import { SourceType, SNIPPET_RENDERED } from '../../shared'; import { getDocgenSection } from '../../lib/docgen'; @@ -22,7 +24,7 @@ type JSXOptions = Options & { /** Deprecated: A function ran after the story is rendered */ onBeforeRender?(dom: string): string; /** A function ran after a story is rendered (prefer this over `onBeforeRender`) */ - transformSource?(dom: string, context?: StoryContext): string; + transformSource?(dom: string, context?: StoryContext): string; }; /** Run the user supplied onBeforeRender function if it exists */ @@ -44,7 +46,11 @@ const applyBeforeRender = (domString: string, options: JSXOptions) => { }; /** Run the user supplied transformSource function if it exists */ -const applyTransformSource = (domString: string, options: JSXOptions, context?: StoryContext) => { +const applyTransformSource = ( + domString: string, + options: JSXOptions, + context?: StoryContext +) => { if (typeof options.transformSource !== 'function') { return domString; } @@ -138,7 +144,7 @@ const defaultOpts = { showDefaultProps: false, }; -export const skipJsxRender = (context: StoryContext) => { +export const skipJsxRender = (context: StoryContext) => { const sourceParams = context?.parameters.docs?.source; const isArgsStory = context?.parameters.__isArgsStory; @@ -165,7 +171,10 @@ const mdxToJsx = (node: any) => { return createElement(originalType, rest, ...jsxChildren); }; -export const jsxDecorator = (storyFn: any, context: StoryContext) => { +export const jsxDecorator = ( + storyFn: PartialStoryFn, + context: StoryContext +) => { const story = storyFn(); // We only need to render JSX if the source block is actually going to @@ -183,7 +192,7 @@ export const jsxDecorator = (storyFn: any, context: StoryContext) => { // Exclude decorators from source code snippet by default const storyJsx = context?.parameters.docs?.source?.excludeDecorators - ? context.originalStoryFn(context.args) + ? (context.originalStoryFn as ArgsStoryFn)(context.args, context) : story; const sourceJsx = mdxToJsx(storyJsx); diff --git a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx index 4006962f1fe..8c576433113 100644 --- a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx +++ b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx @@ -10,8 +10,8 @@ import { Component } from '../../blocks'; const argsTableProps = (component: Component) => { const argTypes = extractArgTypes(component); - const parameters = { __isArgsStory: true, argTypes }; - const rows = inferControls(({ parameters } as unknown) as StoryContext); + const parameters = { __isArgsStory: true }; + const rows = inferControls(({ argTypes, parameters } as unknown) as StoryContext); return { rows }; }; diff --git a/addons/docs/src/frameworks/react/react-properties.test.ts b/addons/docs/src/frameworks/react/react-properties.test.ts index 1417a82b5ab..30bdc5aa69c 100644 --- a/addons/docs/src/frameworks/react/react-properties.test.ts +++ b/addons/docs/src/frameworks/react/react-properties.test.ts @@ -3,8 +3,9 @@ import path from 'path'; import fs from 'fs'; import { transformFileSync, transformSync } from '@babel/core'; -import { inferControls } from '@storybook/client-api'; +import { inferControls } from '@storybook/store'; import { StoryContext } from '@storybook/react'; +import { AnyFramework } from '@storybook/csf'; import requireFromString from 'require-from-string'; import { extractProps } from './extractProps'; @@ -69,8 +70,11 @@ describe('react component properties', () => { // snapshot the output of `extractArgTypes` const argTypes = extractArgTypes(component); - const parameters = { __isArgsStory: true, argTypes }; - const rows = inferControls(({ parameters } as unknown) as StoryContext); + const parameters = { __isArgsStory: true }; + const rows = inferControls(({ + argTypes, + parameters, + } as unknown) as StoryContext); expect(rows).toMatchSpecificSnapshot(path.join(testDir, 'argTypes.snapshot')); }); } diff --git a/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts b/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts index 0f96ae85552..a67b08db35a 100644 --- a/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts +++ b/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts @@ -73,7 +73,8 @@ describe('Extracting Arguments', () => { "category": "events", }, "type": Object { - "name": "void", + "name": "other", + "value": "void", }, }, "event_click": Object { @@ -83,7 +84,8 @@ describe('Extracting Arguments', () => { "category": "events", }, "type": Object { - "name": "void", + "name": "other", + "value": "void", }, }, "rounded": Object { @@ -115,7 +117,8 @@ describe('Extracting Arguments', () => { "category": "slots", }, "type": Object { - "name": "void", + "name": "other", + "value": "void", }, }, "text": Object { diff --git a/addons/docs/src/frameworks/svelte/extractArgTypes.ts b/addons/docs/src/frameworks/svelte/extractArgTypes.ts index 7105ad53236..ca1ee3e3152 100644 --- a/addons/docs/src/frameworks/svelte/extractArgTypes.ts +++ b/addons/docs/src/frameworks/svelte/extractArgTypes.ts @@ -1,4 +1,4 @@ -import { ArgTypes } from '@storybook/api'; +import { SBScalarType, StrictArgTypes } from '@storybook/csf'; import { logger } from '@storybook/client-logger'; import type { SvelteComponentDoc, @@ -31,7 +31,7 @@ export const extractArgTypes: ArgTypesExtractor = (component: ComponentWithDocge }; export const createArgTypes = (docgen: SvelteComponentDoc) => { - const results: ArgTypes = {}; + const results: StrictArgTypes = {}; docgen.data.forEach((item) => { results[item.name] = { control: parseTypeToControl(item.type), @@ -39,7 +39,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => { description: item.description, type: { required: hasKeyword('required', item.keywords), - name: item.type?.text, + name: item.type?.text as SBScalarType['name'], }, table: { type: { @@ -57,7 +57,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => { results[`event_${item.name}`] = { name: item.name, description: item.description, - type: { name: 'void' }, + type: { name: 'other', value: 'void' }, table: { category: 'events', }, @@ -70,7 +70,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => { description: [item.description, item.params?.map((p) => `\`${p.name}\``).join(' ')] .filter((p) => p) .join('\n\n'), - type: { name: 'void' }, + type: { name: 'other', value: 'void' }, table: { category: 'slots', }, diff --git a/addons/docs/src/frameworks/svelte/prepareForInline.ts b/addons/docs/src/frameworks/svelte/prepareForInline.ts index 4d5b6640363..e8f4ce7c5ab 100644 --- a/addons/docs/src/frameworks/svelte/prepareForInline.ts +++ b/addons/docs/src/frameworks/svelte/prepareForInline.ts @@ -1,11 +1,11 @@ -import { StoryFn } from '@storybook/addons'; +import { AnyFramework, StoryFn } from '@storybook/csf'; import React from 'react'; // @ts-ignore import HOC from './HOC.svelte'; -export const prepareForInline = (storyFn: StoryFn) => { +export const prepareForInline = (storyFn: StoryFn) => { const el = React.useRef(null); React.useEffect(() => { const root = new HOC({ diff --git a/addons/docs/src/frameworks/svelte/sourceDecorator.ts b/addons/docs/src/frameworks/svelte/sourceDecorator.ts index 40e96c0cbd1..6eb2d41c2c0 100644 --- a/addons/docs/src/frameworks/svelte/sourceDecorator.ts +++ b/addons/docs/src/frameworks/svelte/sourceDecorator.ts @@ -1,5 +1,5 @@ -import { addons, StoryContext } from '@storybook/addons'; -import { ArgTypes, Args } from '@storybook/api'; +import { addons } from '@storybook/addons'; +import { ArgTypes, Args, StoryContext, AnyFramework } from '@storybook/csf'; import { SourceType, SNIPPET_RENDERED } from '../../shared'; @@ -8,7 +8,7 @@ import { SourceType, SNIPPET_RENDERED } from '../../shared'; * * @param context StoryContext */ -const skipSourceRender = (context: StoryContext) => { +const skipSourceRender = (context: StoryContext) => { const sourceParams = context?.parameters.docs?.source; const isArgsStory = context?.parameters.__isArgsStory; @@ -144,7 +144,7 @@ function getWrapperProperties(component: any) { * @param storyFn Fn * @param context StoryContext */ -export const sourceDecorator = (storyFn: any, context: StoryContext) => { +export const sourceDecorator = (storyFn: any, context: StoryContext) => { const story = storyFn(); if (skipSourceRender(context)) { diff --git a/addons/docs/src/frameworks/vue/extractArgTypes.ts b/addons/docs/src/frameworks/vue/extractArgTypes.ts index fbd1f85fc20..30457c43246 100644 --- a/addons/docs/src/frameworks/vue/extractArgTypes.ts +++ b/addons/docs/src/frameworks/vue/extractArgTypes.ts @@ -1,4 +1,4 @@ -import { ArgTypes } from '@storybook/api'; +import { StrictArgTypes } from '@storybook/csf'; import { ArgTypesExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen'; import { convert } from '../../lib/convert'; @@ -8,7 +8,7 @@ export const extractArgTypes: ArgTypesExtractor = (component) => { if (!hasDocgen(component)) { return null; } - const results: ArgTypes = {}; + const results: StrictArgTypes = {}; SECTIONS.forEach((section) => { const props = extractComponentProps(component, section); props.forEach(({ propDef, docgenInfo, jsDocTags }) => { diff --git a/addons/docs/src/frameworks/vue/prepareForInline.ts b/addons/docs/src/frameworks/vue/prepareForInline.ts index 98931991e96..6627db5ec96 100644 --- a/addons/docs/src/frameworks/vue/prepareForInline.ts +++ b/addons/docs/src/frameworks/vue/prepareForInline.ts @@ -1,6 +1,7 @@ import React from 'react'; import Vue from 'vue'; -import { StoryFn, StoryContext } from '@storybook/addons'; +import { StoryContext, PartialStoryFn } from '@storybook/csf'; +import { VueFramework } from '@storybook/vue'; // Inspired by https://github.com/egoist/vue-to-react, // modified to store args as props in the root store @@ -9,7 +10,10 @@ import { StoryFn, StoryContext } from '@storybook/addons'; const COMPONENT = 'STORYBOOK_COMPONENT'; const VALUES = 'STORYBOOK_VALUES'; -export const prepareForInline = (storyFn: StoryFn, { args }: StoryContext) => { +export const prepareForInline = ( + storyFn: PartialStoryFn, + { args }: StoryContext +) => { const component = storyFn(); const el = React.useRef(null); diff --git a/addons/docs/src/frameworks/vue/sourceDecorator.ts b/addons/docs/src/frameworks/vue/sourceDecorator.ts index 7965dd6beff..0fbab7a1e4e 100644 --- a/addons/docs/src/frameworks/vue/sourceDecorator.ts +++ b/addons/docs/src/frameworks/vue/sourceDecorator.ts @@ -1,14 +1,16 @@ /* eslint no-underscore-dangle: ["error", { "allow": ["_vnode"] }] */ -import { addons, StoryContext } from '@storybook/addons'; +import { StoryContext } from '@storybook/csf'; +import { addons } from '@storybook/addons'; import { logger } from '@storybook/client-logger'; import prettier from 'prettier/standalone'; import prettierHtml from 'prettier/parser-html'; import type Vue from 'vue'; +import { VueFramework } from '@storybook/vue'; import { SourceType, SNIPPET_RENDERED } from '../../shared'; -export const skipSourceRender = (context: StoryContext) => { +export const skipSourceRender = (context: StoryContext) => { const sourceParams = context?.parameters.docs?.source; const isArgsStory = context?.parameters.__isArgsStory; @@ -22,7 +24,7 @@ export const skipSourceRender = (context: StoryContext) => { return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE; }; -export const sourceDecorator = (storyFn: any, context: StoryContext) => { +export const sourceDecorator = (storyFn: any, context: StoryContext) => { const story = storyFn(); // See ../react/jsxDecorator.tsx diff --git a/addons/docs/src/frameworks/vue3/extractArgTypes.ts b/addons/docs/src/frameworks/vue3/extractArgTypes.ts index 1555b6d6acc..039f0c7be2a 100644 --- a/addons/docs/src/frameworks/vue3/extractArgTypes.ts +++ b/addons/docs/src/frameworks/vue3/extractArgTypes.ts @@ -1,4 +1,4 @@ -import { ArgTypes } from '@storybook/api'; +import { StrictArgTypes } from '@storybook/csf'; import { ArgTypesExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen'; import { convert } from '../../lib/convert'; @@ -8,7 +8,7 @@ export const extractArgTypes: ArgTypesExtractor = (component) => { if (!hasDocgen(component)) { return null; } - const results: ArgTypes = {}; + const results: StrictArgTypes = {}; SECTIONS.forEach((section) => { const props = extractComponentProps(component, section); props.forEach(({ propDef, docgenInfo, jsDocTags }) => { diff --git a/addons/docs/src/frameworks/vue3/prepareForInline.ts b/addons/docs/src/frameworks/vue3/prepareForInline.ts index 096dcf1349e..2f414504289 100644 --- a/addons/docs/src/frameworks/vue3/prepareForInline.ts +++ b/addons/docs/src/frameworks/vue3/prepareForInline.ts @@ -1,12 +1,15 @@ import React from 'react'; import * as Vue from 'vue'; -import { StoryFn, StoryContext } from '@storybook/addons'; -import { app } from '@storybook/vue3'; +import { StoryContext, PartialStoryFn } from '@storybook/csf'; +import { app, VueFramework } from '@storybook/vue3'; // This is cast as `any` to workaround type errors caused by Vue 2 types const { render, h } = Vue as any; -export const prepareForInline = (storyFn: StoryFn, { args }: StoryContext) => { +export const prepareForInline = ( + storyFn: PartialStoryFn, + { args }: StoryContext +) => { const component = storyFn(); const vnode = h(component, args); diff --git a/addons/docs/src/frameworks/web-components/prepareForInline.ts b/addons/docs/src/frameworks/web-components/prepareForInline.ts index 715bdb8afef..dcdb86c3432 100644 --- a/addons/docs/src/frameworks/web-components/prepareForInline.ts +++ b/addons/docs/src/frameworks/web-components/prepareForInline.ts @@ -1,8 +1,10 @@ -import type { StoryFn } from '@storybook/addons'; +import type { PartialStoryFn } from '@storybook/csf'; +import { WebComponentsFramework } from '@storybook/web-components'; import React from 'react'; + import { render } from 'lit-html'; -export const prepareForInline = (storyFn: StoryFn) => { +export const prepareForInline = (storyFn: PartialStoryFn) => { class Story extends React.Component { wrapperRef = React.createRef(); diff --git a/addons/docs/src/frameworks/web-components/sourceDecorator.ts b/addons/docs/src/frameworks/web-components/sourceDecorator.ts index d1ab37f8ec2..a6df7744944 100644 --- a/addons/docs/src/frameworks/web-components/sourceDecorator.ts +++ b/addons/docs/src/frameworks/web-components/sourceDecorator.ts @@ -1,9 +1,12 @@ /* global window */ import { render } from 'lit-html'; -import { addons, StoryContext, StoryFn } from '@storybook/addons'; +import { ArgsStoryFn, PartialStoryFn, StoryContext } from '@storybook/csf'; +import { addons } from '@storybook/addons'; +import { WebComponentsFramework } from '@storybook/web-components'; + import { SNIPPET_RENDERED, SourceType } from '../../shared'; -function skipSourceRender(context: StoryContext) { +function skipSourceRender(context: StoryContext) { const sourceParams = context?.parameters.docs?.source; const isArgsStory = context?.parameters.__isArgsStory; @@ -17,15 +20,21 @@ function skipSourceRender(context: StoryContext) { return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE; } -function applyTransformSource(source: string, context: StoryContext): string { +function applyTransformSource( + source: string, + context: StoryContext +): string { const { transformSource } = context.parameters.docs ?? {}; if (typeof transformSource !== 'function') return source; return transformSource(source, context); } -export function sourceDecorator(storyFn: StoryFn, context: StoryContext) { +export function sourceDecorator( + storyFn: PartialStoryFn, + context: StoryContext +) { const story = context?.parameters.docs?.source?.excludeDecorators - ? context.originalStoryFn(context.args) + ? (context.originalStoryFn as ArgsStoryFn)(context.args, context) : storyFn(); if (!skipSourceRender(context)) { diff --git a/addons/docs/src/lib/convert/flow/convert.ts b/addons/docs/src/lib/convert/flow/convert.ts index b9ef15867ea..e61a8c6acd4 100644 --- a/addons/docs/src/lib/convert/flow/convert.ts +++ b/addons/docs/src/lib/convert/flow/convert.ts @@ -1,5 +1,5 @@ /* eslint-disable no-case-declarations */ -import { SBType } from '@storybook/client-api'; +import { SBType } from '@storybook/csf'; import { FlowType, FlowSigType, FlowLiteralType } from './types'; const isLiteral = (type: FlowType) => type.name === 'literal'; diff --git a/addons/docs/src/lib/convert/proptypes/convert.ts b/addons/docs/src/lib/convert/proptypes/convert.ts index bc4da957340..7a7bbd9b3f5 100644 --- a/addons/docs/src/lib/convert/proptypes/convert.ts +++ b/addons/docs/src/lib/convert/proptypes/convert.ts @@ -1,6 +1,6 @@ /* eslint-disable no-case-declarations */ import mapValues from 'lodash/mapValues'; -import { SBType } from '@storybook/client-api'; +import { SBType } from '@storybook/csf'; import { PTType } from './types'; import { trimQuotes } from '../utils'; diff --git a/addons/docs/src/lib/convert/typescript/convert.ts b/addons/docs/src/lib/convert/typescript/convert.ts index 3affe3e7048..bae2e7235e9 100644 --- a/addons/docs/src/lib/convert/typescript/convert.ts +++ b/addons/docs/src/lib/convert/typescript/convert.ts @@ -1,5 +1,5 @@ /* eslint-disable no-case-declarations */ -import { SBType } from '@storybook/client-api'; +import { SBType } from '@storybook/csf'; import { TSType, TSSigType } from './types'; const convertSig = (type: TSSigType) => { diff --git a/addons/docs/src/lib/docgen/types.ts b/addons/docs/src/lib/docgen/types.ts index fcae772fa5b..0fb5fa82373 100644 --- a/addons/docs/src/lib/docgen/types.ts +++ b/addons/docs/src/lib/docgen/types.ts @@ -1,10 +1,10 @@ -import { ArgTypes } from '@storybook/api'; +import { StrictArgTypes } from '@storybook/csf'; import { PropDef } from './PropDef'; import { Component } from '../../blocks/types'; export type PropsExtractor = (component: Component) => { rows?: PropDef[] } | null; -export type ArgTypesExtractor = (component: Component) => ArgTypes | null; +export type ArgTypesExtractor = (component: Component) => StrictArgTypes | null; export interface DocgenType { name: string; diff --git a/lib/preview-web/src/types.ts b/lib/preview-web/src/types.ts index db389e2518a..95992f3eaf2 100644 --- a/lib/preview-web/src/types.ts +++ b/lib/preview-web/src/types.ts @@ -8,7 +8,7 @@ export type WebProjectAnnotations< renderToDOM?: (context: RenderContext, element: Element) => Promise | void; }; -export interface DocsContextProps { +export interface DocsContextProps { id: string; title: string; name: string; diff --git a/lib/store/src/types.ts b/lib/store/src/types.ts index fb8d4d4f953..7484a518a9d 100644 --- a/lib/store/src/types.ts +++ b/lib/store/src/types.ts @@ -26,21 +26,21 @@ export type ModuleExports = Record; export type ModuleImportFn = (path: Path) => Promise | ModuleExports; export type NormalizedProjectAnnotations< - TFramework extends AnyFramework + TFramework extends AnyFramework = AnyFramework > = ProjectAnnotations & { argTypes?: StrictArgTypes; globalTypes?: StrictGlobalTypes; }; export type NormalizedComponentAnnotations< - TFramework extends AnyFramework + TFramework extends AnyFramework = AnyFramework > = ComponentAnnotations & { // Useful to guarantee that id exists id: ComponentId; argTypes?: StrictArgTypes; }; -export type NormalizedStoryAnnotations = Omit< +export type NormalizedStoryAnnotations = Omit< StoryAnnotations, 'storyName' | 'story' > & { @@ -50,12 +50,14 @@ export type NormalizedStoryAnnotations = Omit< userStoryFn?: StoryFn; }; -export type CSFFile = { +export type CSFFile = { meta: NormalizedComponentAnnotations; stories: Record>; }; -export type Story = StoryContextForEnhancers & { +export type Story< + TFramework extends AnyFramework = AnyFramework +> = StoryContextForEnhancers & { originalStoryFn: StoryFn; undecoratedStoryFn: LegacyStoryFn; unboundStoryFn: LegacyStoryFn; @@ -63,11 +65,13 @@ export type Story = StoryContextForEnhancers Promise; }; -export type BoundStory = Story & { +export type BoundStory = Story & { storyFn: PartialStoryFn; }; -export declare type RenderContext = StoryIdentifier & { +export declare type RenderContext< + TFramework extends AnyFramework = AnyFramework +> = StoryIdentifier & { showMain: () => void; showError: (error: { title: string; description: string }) => void; showException: (err: Error) => void; @@ -102,7 +106,7 @@ export interface Selection { viewMode: ViewMode; } -export type DecoratorApplicator = ( +export type DecoratorApplicator = ( storyFn: LegacyStoryFn, decorators: DecoratorFunction[] ) => LegacyStoryFn;