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

Addon-docs: Vue slots/events props table + generalization #8489

Merged
merged 12 commits into from
Oct 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
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 @@ -61,7 +61,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