Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs changes for on demand store #16009

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion addons/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -139,6 +142,8 @@
},
"peerDependencies": {
"@storybook/angular": "6.4.0-alpha.34",
"@storybook/html": "6.4.0-alpha.34",
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
"@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",
Expand All @@ -155,6 +160,9 @@
"@storybook/angular": {
"optional": true
},
"@storybook/react": {
"optional": true
},
"@storybook/vue": {
"optional": true
},
Expand Down
102 changes: 57 additions & 45 deletions addons/docs/src/blocks/ArgsTable.tsx
Original file line number Diff line number Diff line change
@@ -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 } 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;
Expand All @@ -45,42 +44,46 @@ type ArgsTableProps = BaseProps | OfProps | ComponentsProps | StoryProps;

const useArgs = (
storyId: string,
storyStore: StoryStore
context: DocsContextProps<any>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be DocsContextProps<AnyFramework> as below?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should TFramework be AnyFramework by default? Would eliminate a lot of boilerplate

Copy link
Member Author

@tmeasday tmeasday Sep 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should TFramework be AnyFramework by default? Would eliminate a lot of boilerplate

I think when you are associating two types (e.g. a function that takes a StoryContext and produces a Decorator, you need the type variable so you can guarantee that the two AnyFramework are the same AnyFramework.

The analogy would be to functions that take any vs a typed variable:

// Doesn't matter what `var1` is
function isTruthy(var1: any) {
  return !!var1;
}

// Doesn't matter what `var1` and `var2` are, but they need to be same
function isEqual<T>(var1: T, var2: T) {
  return var1 === var2;
}

// This will give a type error
isEqual(1, '1');

// You could define `isEqual` in terms of `any` but it'd lose some type info
function isEqual2(var1: any, var2: any) {
  return var1 === var2;
}

// This won't give a type error, which is less useful
isEqual2(1, '1');

That's my understanding of it anyway, I could totally be wrong, my TS-fu is still pretty recent.

Copy link
Member Author

@tmeasday tmeasday Sep 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I totally missed what you mean here. You mean to change the various types to like DocsContextProps<TFramework extends AnyFramework = AnyFramework>.

I think that's a good idea for DocsContextProps, but doesn't really help in most other cases for the same reason I posted above. But we may as well default the type I guess.

): [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];
};

export const extractComponentArgTypes = (
component: Component,
{ parameters }: DocsContextProps,
{ id, storyById }: DocsContextProps<any>,
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
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);
}
Expand All @@ -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<any>
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
): 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;
}
Expand All @@ -111,7 +116,7 @@ export const getComponent = (props: ArgsTableProps = {}, context: DocsContextPro
const addComponentTabs = (
tabs: Record<string, PureArgsTableProps>,
components: Record<string, Component>,
context: DocsContextProps,
context: DocsContextProps<any>,
include?: PropDescriptor,
exclude?: PropDescriptor,
sort?: SortType
Expand All @@ -127,47 +132,50 @@ export const StoryTable: FC<
StoryProps & { component: Component; subcomponents: Record<string, Component> }
> = (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 <div>Loading...</div>;
}

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;
Expand Down Expand Up @@ -203,15 +211,19 @@ export const ComponentsTable: FC<ComponentsProps> = (props) => {

export const ArgsTable: FC<ArgsTableProps> = (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 <StoryTable {...(props as StoryProps)} component={main} {...{ subcomponents, sort }} />;
}

Expand Down
8 changes: 4 additions & 4 deletions addons/docs/src/blocks/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -19,10 +19,10 @@ type CanvasProps = PurePreviewProps & {

const getPreviewProps = (
{ withSource, mdxSource, children, ...props }: CanvasProps & { children?: ReactNode },
docsContext: DocsContextProps,
docsContext: DocsContextProps<AnyFramework>,
sourceContext: SourceContextProps
): PurePreviewProps => {
const { mdxComponentMeta, mdxStoryNameToKey } = docsContext;
const { mdxComponentAnnotations, mdxStoryNameToKey } = docsContext;
let sourceState = withSource;
if (sourceState === SourceState.NONE) {
return props;
Expand All @@ -41,7 +41,7 @@ const getPreviewProps = (
(s) =>
s.props.id ||
toId(
mdxComponentMeta.id || mdxComponentMeta.title,
mdxComponentAnnotations.id || mdxComponentAnnotations.title,
storyNameFromExport(mdxStoryNameToKey[s.props.name])
)
);
Expand Down
7 changes: 4 additions & 3 deletions addons/docs/src/blocks/Description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ const noDescription = (component?: Component): string | null => null;

export const getDescriptionProps = (
{ of, type, markdown, children }: DescriptionProps,
{ parameters }: DocsContextProps
{ id, storyById }: DocsContextProps<any>
): 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;

Expand All @@ -63,7 +64,7 @@ ${extractComponentDescription(target) || ''}
case DescriptionType.DOCGEN:
case DescriptionType.AUTO:
default:
return { markdown: extractComponentDescription(target, parameters) };
return { markdown: extractComponentDescription(target, { component, ...parameters }) };
}
};

Expand Down
16 changes: 11 additions & 5 deletions addons/docs/src/blocks/DocsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -14,8 +15,8 @@ import { scrollToElement } from './utils';

const { document, window: globalWindow } = global;

export interface DocsContainerProps {
context: DocsContextProps;
export interface DocsContainerProps<TFramework extends AnyFramework> {
context: DocsContextProps<TFramework>;
}

const defaultComponents = {
Expand All @@ -34,9 +35,14 @@ const warnOptionsTheme = deprecate(
`
);

export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context, children }) => {
const { id: storyId = null, parameters = {} } = context || {};
const { options = {}, docs = {} } = parameters;
export const DocsContainer: FunctionComponent<DocsContainerProps<any>> = ({
context,
children,
}) => {
const { id: storyId, storyById } = context;
const {
parameters: { options = {}, docs = {} },
} = storyById(storyId);
let themeVars = docs.theme;
if (!themeVars && options.theme) {
warnOptionsTheme();
Expand Down
20 changes: 4 additions & 16 deletions addons/docs/src/blocks/DocsContext.ts
Original file line number Diff line number Diff line change
@@ -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 `<Story>`
* and `Preview` doc blocks.
*/
mdxStoryNameToKey?: Record<string, string>;
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)
Expand All @@ -29,4 +17,4 @@ if (globalWindow.__DOCS_CONTEXT__ === undefined) {
globalWindow.__DOCS_CONTEXT__.displayName = 'DocsContext';
}

export const DocsContext: Context<DocsContextProps> = globalWindow.__DOCS_CONTEXT__;
export const DocsContext: Context<DocsContextProps<AnyFramework>> = globalWindow.__DOCS_CONTEXT__;
7 changes: 3 additions & 4 deletions addons/docs/src/blocks/DocsPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Loading