Skip to content

Commit

Permalink
Addon-docs: Vue slots/events props table + generalization (#8489)
Browse files Browse the repository at this point in the history
Addon-docs: Vue slots/events props table + generalization
  • Loading branch information
shilman authored Oct 24, 2019
2 parents 4f2b83f + ee1e00e commit b7f3882
Show file tree
Hide file tree
Showing 41 changed files with 393 additions and 140 deletions.
2 changes: 1 addition & 1 deletion __mocks__/inject-decorator.stories.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import { action } from '@storybook/addon-actions';

import DocgenButton from '../components/DocgenButton';
import { DocgenButton } from '../components/DocgenButton';
import FlowTypeButton from '../components/FlowTypeButton';
import BaseButton from '../components/BaseButton';
import TableComponent from '../components/TableComponent';
Expand Down
4 changes: 2 additions & 2 deletions addons/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ First add the package. Make sure that the versions for your `@storybook/*` packa
yarn add -D @storybook/addon-docs
```

Docs has peer dependencies on `react` and `babel-loader`. If you want to write stories in MDX, you may need to add these dependencies as well:
Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you want to write stories in MDX, you may need to add these dependencies as well:

```sh
yarn add -D react babel-loader
yarn add -D react react-is babel-loader
```

Then add the following to your `.storybook/presets.js` exports:
Expand Down
3 changes: 1 addition & 2 deletions addons/docs/common/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
// FIXME: move this to typescript and src/react folder
module.exports = require('../dist/lib/getPropDefs');
module.exports = require('../dist/frameworks/common');
3 changes: 2 additions & 1 deletion addons/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
},
"peerDependencies": {
"babel-loader": "^8.0.0",
"react": "^16.8.0"
"react": "^16.8.0",
"react-is": "^16.8.0"
},
"publishConfig": {
"access": "public"
Expand Down
21 changes: 12 additions & 9 deletions addons/docs/src/blocks/Props.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React, { FunctionComponent } from 'react';
import { PropsTable, PropsTableError, PropsTableProps, PropDef } from '@storybook/components';
import { PropsTable, PropsTableError, PropsTableProps } from '@storybook/components';
import { DocsContext, DocsContextProps } from './DocsContext';
import { Component, CURRENT_SELECTION } from './shared';
import { getPropDefs as autoPropDefs, PropDefGetter } from '../lib/getPropDefs';

import { PropsExtractor } from '../lib/propsUtils';
import { extractProps as reactExtractProps } from '../frameworks/react/extractProps';
import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps';

interface PropsProps {
exclude?: string[];
of: '.' | Component;
}

const inferPropDefs = (framework: string): PropDefGetter | null => {
// FIXME: remove in SB6.0 & require config
const inferPropsExtractor = (framework: string): PropsExtractor | null => {
switch (framework) {
case 'react':
return reactExtractProps;
case 'vue':
return autoPropDefs;
return vueExtractProps;
default:
return null;
}
Expand All @@ -32,13 +37,11 @@ export const getPropsTableProps = (
throw new Error(PropsTableError.NO_COMPONENT);
}

const { getPropDefs = inferPropDefs(framework) } = params.docs || {};
if (!getPropDefs) {
const { extractProps = inferPropsExtractor(framework) } = params.docs || {};
if (!extractProps) {
throw new Error(PropsTableError.PROPS_UNSUPPORTED);
}
const allRows = getPropDefs(target);
const rows = !exclude ? allRows : allRows.filter((row: PropDef) => !exclude.includes(row.name));
return { rows };
return extractProps(target, { exclude });
} catch (err) {
return { error: err.message };
}
Expand Down
2 changes: 1 addition & 1 deletion addons/docs/src/frameworks/common/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from '../../lib/getPropDefs';
export * from '../../lib/propsUtils';
2 changes: 2 additions & 0 deletions addons/docs/src/frameworks/react/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import { addParameters } from '@storybook/react';
import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks';
import { extractProps } from './extractProps';

addParameters({
docs: {
Expand All @@ -9,5 +10,6 @@ addParameters({
// react is Storybook's "native" framework, so it's stories are inherently prepared to be rendered inline
// NOTE: that the result is a react element. Hooks support is provided by the outer code.
prepareForInline: storyFn => storyFn(),
extractProps,
},
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
/* eslint-disable no-underscore-dangle,react/forbid-foreign-prop-types */

import PropTypes from 'prop-types';
import { isForwardRef, isMemo } from 'react-is';
import { PropDef } from '@storybook/components';
import { Component } from '../blocks/shared';
import { PropDefGetter, PropsExtractor, propsFromDocgen, hasDocgen } from '../../lib/propsUtils';

interface PropDefMap {
export interface PropDefMap {
[p: string]: PropDef;
}

export type PropDefGetter = (type: Component) => PropDef[] | null;

const propTypesMap = new Map();

Object.keys(PropTypes).forEach(typeName => {
Expand All @@ -20,38 +18,15 @@ Object.keys(PropTypes).forEach(typeName => {
propTypesMap.set(type.isRequired, typeName);
});

const hasDocgen = (obj: any) => obj && obj.props && Object.keys(obj.props).length > 0;

const propsFromDocgen: PropDefGetter = type => {
const props: PropDefMap = {};
const docgenInfoProps = type.__docgenInfo.props;

Object.keys(docgenInfoProps).forEach(property => {
const docgenInfoProp = docgenInfoProps[property];
const defaultValueDesc = docgenInfoProp.defaultValue || {};
const propType = docgenInfoProp.flowType || docgenInfoProp.type || 'other';

props[property] = {
name: property,
type: propType,
required: docgenInfoProp.required,
description: docgenInfoProp.description,
defaultValue: defaultValueDesc.value,
};
});

return Object.values(props);
};

const propsFromPropTypes: PropDefGetter = type => {
const propsFromPropTypes: PropDefGetter = (type, section) => {
const props: PropDefMap = {};

if (type.propTypes) {
Object.keys(type.propTypes).forEach(property => {
const typeInfo = type.propTypes[property];
const required = typeInfo.isRequired === undefined;
const docgenInfo =
type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property];
type.__docgenInfo && type.__docgenInfo[section] && type.__docgenInfo[section][property];
const description = docgenInfo ? docgenInfo.description : null;
let propType = propTypesMap.get(typeInfo) || 'other';

Expand Down Expand Up @@ -84,15 +59,22 @@ const propsFromPropTypes: PropDefGetter = type => {
return Object.values(props);
};

export const getPropDefs: PropDefGetter = type => {
export const getPropDefs: PropDefGetter = (type, section) => {
let processedType = type;
if (type.render) {
processedType = type.render().type;
if (!hasDocgen(type)) {
if (isForwardRef(type) || type.render) {
processedType = type.render().type;
}
if (isMemo(type)) {
// (typeof type.type === 'function')?
processedType = type.type().type;
}
}
if (typeof type.type === 'function') {
processedType = type.type().type;
}
return hasDocgen(processedType.__docgenInfo)
? propsFromDocgen(processedType)
: propsFromPropTypes(processedType);
return hasDocgen(processedType)
? propsFromDocgen(processedType, section)
: propsFromPropTypes(processedType, section);
};

export const extractProps: PropsExtractor = component => ({
rows: getPropDefs(component, 'props'),
});
2 changes: 2 additions & 0 deletions addons/docs/src/frameworks/vue/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import toReact from '@egoist/vue-to-react';
import { addParameters } from '@storybook/vue';
import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks';
import { extractProps } from './extractProps';

addParameters({
docs: {
Expand All @@ -12,5 +13,6 @@ addParameters({
const Story = toReact(storyFn());
return <Story />;
},
extractProps,
},
});
15 changes: 15 additions & 0 deletions addons/docs/src/frameworks/vue/extractProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PropDef } from '@storybook/components';
import { PropsExtractor, propsFromDocgen, hasDocgen } from '../../lib/propsUtils';

const SECTIONS = ['props', 'events', 'slots'];

export const extractProps: PropsExtractor = component => {
if (!hasDocgen(component)) {
return null;
}
const sections: Record<string, PropDef[]> = {};
SECTIONS.forEach(section => {
sections[section] = propsFromDocgen(component, section);
});
return { sections };
};
38 changes: 38 additions & 0 deletions addons/docs/src/lib/propsUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable no-underscore-dangle */
import { PropDef, PropsTableProps } from '@storybook/components';
import { Component } from '../blocks/shared';

export type PropsExtractor = (component: Component) => PropsTableProps | null;

export type PropDefGetter = (type: Component, section: string) => PropDef[] | null;

export const hasDocgen = (obj: any) => !!obj.__docgenInfo;

export const hasDocgenSection = (obj: any, section: string) =>
obj.__docgenInfo &&
obj.__docgenInfo[section] &&
Object.keys(obj.__docgenInfo[section]).length > 0;

export const propsFromDocgen: PropDefGetter = (type, section) => {
const props: Record<string, PropDef> = {};
const docgenInfoProps = type.__docgenInfo[section];
if (!docgenInfoProps) {
return null;
}

Object.keys(docgenInfoProps).forEach(property => {
const docgenInfoProp = docgenInfoProps[property];
const defaultValueDesc = docgenInfoProp.defaultValue || {};
const propType = docgenInfoProp.flowType || docgenInfoProp.type || 'other';

props[property] = {
name: property,
type: propType,
required: docgenInfoProp.required,
description: docgenInfoProp.description,
defaultValue: defaultValueDesc.value,
};
});

return Object.values(props);
};
1 change: 1 addition & 0 deletions addons/docs/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
declare module '@mdx-js/react';
declare module '@storybook/addon-docs/mdx-compiler-plugin';
declare module 'global';
declare module 'react-is';
4 changes: 1 addition & 3 deletions examples/official-storybook/components/DocgenButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';

/** DocgenButton component description imported from comments inside the component file */
const DocgenButton = ({ disabled, label, onClick }) => (
export const DocgenButton = ({ disabled, label, onClick }) => (
<button type="button" disabled={disabled} onClick={onClick}>
{label}
</button>
Expand Down Expand Up @@ -145,5 +145,3 @@ DocgenButton.propTypes = {
*/
optionalString: PropTypes.string,
};

export default DocgenButton;
6 changes: 6 additions & 0 deletions examples/official-storybook/components/ForwardRefButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { DocgenButton } from './DocgenButton';

export const ForwardRefButton = React.forwardRef((props, ref) => (
<DocgenButton ref={ref} {...props} />
));
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import DocgenButton from './DocgenButton';
import { DocgenButton } from './DocgenButton';

/** Button component description */
const ImportedPropsButton = ({ disabled, label, onClick }) => (
Expand Down
4 changes: 4 additions & 0 deletions examples/official-storybook/components/MemoButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from 'react';
import { DocgenButton } from './DocgenButton';

export const MemoButton = React.memo(props => <DocgenButton {...props} />);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import notes from '../notes/notes.md';
import mdxNotes from '../notes/notes.mdx';
import DocgenButton from '../../components/DocgenButton';
import { DocgenButton } from '../../components/DocgenButton';

export default {
title: 'Addons|Docs/stories',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { action } from '@storybook/addon-actions';
import { Button } from '@storybook/react/demo';
import FlowTypeButton from '../../components/FlowTypeButton';
import DocgenButton from '../../components/DocgenButton';
import { DocgenButton } from '../../components/DocgenButton';

<Meta
title="Addons|Docs/mdx"
Expand Down

This file was deleted.

18 changes: 18 additions & 0 deletions examples/official-storybook/stories/addon-docs/props.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Meta, Props } from '@storybook/addon-docs/blocks';
import { DocgenButton } from '../../components/DocgenButton';
import { ForwardRefButton } from '../../components/ForwardRefButton';
import { MemoButton } from '../../components/MemoButton';

<Meta title="Addons|Docs/props" />

## Docgen

<Props of={DocgenButton} />

## React.forwardRef

<Props of={ForwardRefButton} />

## React.memo

<Props of={MemoButton} />

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { withInfo } from '@storybook/addon-info';
import { action } from '@storybook/addon-actions';

import DocgenButton from '../../components/DocgenButton';
import { DocgenButton } from '../../components/DocgenButton';
import FlowTypeButton from '../../components/FlowTypeButton';
import BaseButton from '../../components/BaseButton';
import { NamedExportButton } from '../../components/NamedExportButton';
Expand Down
2 changes: 1 addition & 1 deletion examples/vue-kitchen-sink/.storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ addParameters({
hierarchyRootSeparator: /\|/,
},
docs: {
// inlineStories: true,
inlineStories: true,
iframeHeight: '60px',
},
});
Expand Down
Loading

1 comment on commit b7f3882

@vercel
Copy link

@vercel vercel bot commented on b7f3882 Oct 24, 2019

Choose a reason for hiding this comment

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

Please sign in to comment.