mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-21 05:02:39 +08:00
Merge branch 'next' into upgrades
# Conflicts: # addons/essentials/package.json # addons/notes/package.json # addons/storyshots/storyshots-core/package.json # app/angular/package.json # app/html/package.json # app/marko/package.json # app/preact/package.json # app/react/package.json # app/vue/package.json # app/web-components/package.json # examples/riot-kitchen-sink/package.json # examples/vue-kitchen-sink/package.json # lib/cli/package.json # lib/core/package.json # yarn.lock
This commit is contained in:
commit
746b2a4f11
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,3 +1,31 @@
|
||||
## 5.3.10 (February 2, 2020)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Core: Upgrade `min-css-extract-plugin` to fix SASS loading ([#9652](https://github.com/storybookjs/storybook/pull/9652))
|
||||
* CRA: Fix jsconfig support ([#9324](https://github.com/storybookjs/storybook/pull/9324))
|
||||
* Web-components: Fix default value for docs prop table ([#9655](https://github.com/storybookjs/storybook/pull/9655))
|
||||
* Web-components: Fix types to play nicely with lit-element ([#9557](https://github.com/storybookjs/storybook/pull/9557))
|
||||
* UI: Add support for className prop on Form.Field ([#9665](https://github.com/storybookjs/storybook/pull/9665))
|
||||
* Addon-storyshots: Remove excess slashes from jest transform warning ([#9616](https://github.com/storybookjs/storybook/pull/9616))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* Ember: Migrate to new "import { hbs } from 'ember-cli-htmlbars'" ([#9633](https://github.com/storybookjs/storybook/pull/9633))
|
||||
* Build: Netlify for examples again ([#9585](https://github.com/storybookjs/storybook/pull/9585))
|
||||
* Publish: Remove docs to reduce package size ([#9612](https://github.com/storybookjs/storybook/pull/9612))
|
||||
|
||||
## 6.0.0-alpha.3 (February 2, 2020)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* CRA: Fix jsconfig support ([#9324](https://github.com/storybookjs/storybook/pull/9324))
|
||||
* UI: Check if docsOnly is set to hide the addon panels ([#9687](https://github.com/storybookjs/storybook/pull/9687))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* Addon-notes, addon-info: Move to deprecated-addons repo ([#9673](https://github.com/storybookjs/storybook/pull/9673))
|
||||
|
||||
## 6.0.0-alpha.2 (January 30, 2020)
|
||||
|
||||
### Features
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "a11y addon for storybook",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -32,12 +32,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"axe-core": "^3.3.2",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Action Logger addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -27,12 +27,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-api": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-api": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"global": "^4.3.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-backgrounds",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "A storybook addon to show different backgrounds for your preview",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -31,12 +31,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"memoizerific": "^1.11.3",
|
||||
"react": "^16.8.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-centered",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook decorator to center components",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -29,7 +29,7 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"util-deprecate": "^1.0.2"
|
||||
|
@ -5,6 +5,17 @@ import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered(storyFn: () => ReactNode) {
|
||||
/* eslint-disable no-undef */
|
||||
if (window) {
|
||||
const params = new URL(window.location.href).search;
|
||||
const isInDocsView = params.includes('viewMode=docs');
|
||||
|
||||
if (isInDocsView) {
|
||||
return storyFn();
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-undef */
|
||||
|
||||
return (
|
||||
<div style={styles.style}>
|
||||
<div style={styles.innerStyle}>{storyFn()}</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-contexts",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook Addon Contexts",
|
||||
"keywords": [
|
||||
"preact",
|
||||
@ -27,10 +27,10 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"qs": "^6.6.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-cssresources",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "A storybook addon to switch between css resources at runtime for your story",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -31,11 +31,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"react": "^16.8.3"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-design-assets",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Design asset preview for storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -33,12 +33,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"react": "^16.8.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-docs",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Superior documentation for your components",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -45,14 +45,14 @@
|
||||
"@mdx-js/loader": "^1.5.1",
|
||||
"@mdx-js/mdx": "^1.5.1",
|
||||
"@mdx-js/react": "^1.5.1",
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/csf": "0.0.1",
|
||||
"@storybook/postinstall": "6.0.0-alpha.2",
|
||||
"@storybook/source-loader": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/postinstall": "6.0.0-alpha.3",
|
||||
"@storybook/source-loader": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"acorn": "^7.1.0",
|
||||
"acorn-jsx": "^5.1.0",
|
||||
"acorn-walk": "^7.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-essentials",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Curated addons to bring out the best of Storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -27,11 +27,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addon-backgrounds": "6.0.0-alpha.2",
|
||||
"@storybook/addon-viewport": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/node-logger": "6.0.0-alpha.2",
|
||||
"@storybook/addon-backgrounds": "6.0.0-alpha.3",
|
||||
"@storybook/addon-viewport": "6.0.0-alpha.3",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/node-logger": "6.0.0-alpha.3",
|
||||
"ts-dedent": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-events",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Add events to your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -30,11 +30,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-api": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-api": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"format-json": "^1.0.3",
|
||||
"lodash": "^4.17.15",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-google-analytics",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook addon for google analytics",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -20,8 +20,8 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"react-ga": "^2.5.7"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-graphql",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook addon to display the GraphiQL IDE",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,8 +28,8 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"graphiql": "^0.17.5",
|
||||
|
@ -1,338 +0,0 @@
|
||||
# Storybook Info Addon
|
||||
|
||||
Storybook Info Addon will show additional information for your stories in [Storybook](https://storybook.js.org).
|
||||
Useful when you want to display usage or other types of documentation alongside your story.
|
||||
|
||||
[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
Install the following npm module:
|
||||
|
||||
```sh
|
||||
npm i -D @storybook/addon-info
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
Then, add `withInfo` as a decorator to your book of stories.
|
||||
It is possible to add `info` by default to all or a subsection of stories by using a global or story decorator.
|
||||
|
||||
It is important to declare this decorator as **the first decorator**, otherwise it won't work well.
|
||||
|
||||
```js
|
||||
// Globally in your .storybook/preview.js.
|
||||
import { addDecorator } from '@storybook/react';
|
||||
import { withInfo } from '@storybook/addon-info';
|
||||
|
||||
addDecorator(withInfo);
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
export default {
|
||||
title: 'Component',
|
||||
decorators: [withInfo],
|
||||
};
|
||||
```
|
||||
|
||||
Then, you can use the `info` parameter to either pass certain options or specific documentation text to your stories.
|
||||
A complete list of possible configurations can be found [in a later section](#setting-global-options).
|
||||
This can be done per book of stories:
|
||||
|
||||
```js
|
||||
import Component from './Component';
|
||||
|
||||
export default {
|
||||
title: 'Component',
|
||||
parameters: {
|
||||
info: {},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
...or for each story individually:
|
||||
|
||||
```js
|
||||
import Component from './Component';
|
||||
|
||||
export default {
|
||||
title: 'Component',
|
||||
};
|
||||
|
||||
export const defaultView = () => <Component />;
|
||||
defaultView = {
|
||||
parameters: {
|
||||
info: { inline: true },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
It is also possible to disable the `info` addon entirely.
|
||||
Depending on the scope at which you want to disable the addon, pass the following parameters object either to an individual story or to an `addParameters` call.
|
||||
|
||||
```
|
||||
info: {
|
||||
disable: true,
|
||||
}
|
||||
```
|
||||
|
||||
## Markdown
|
||||
|
||||
The `info` addon also supports markdown.
|
||||
To use markdown as additional textual documentation for your stories, either pass it directly as a String to the `info` parameters, or use the `text` option.
|
||||
|
||||
```js
|
||||
info: {
|
||||
text: `
|
||||
description or documentation about my component, supports markdown
|
||||
|
||||
~~~js
|
||||
<Button>Click Here</Button>
|
||||
~~~
|
||||
`,
|
||||
}
|
||||
```
|
||||
|
||||
## Setting Global Options
|
||||
|
||||
To configure default options for all usage of the info addon, pass a option object along with the decorator in `.storybook/preview.js`.
|
||||
|
||||
```js
|
||||
import { withInfo } from '@storybook/addon-info';
|
||||
|
||||
addDecorator(
|
||||
withInfo({
|
||||
header: false,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
Configuration parameters can be set at 3 different locations: passed as default options along the `addDecorator` call, passed as an object of parameters to a book of stories to the `addParameters` call, and passed as direct parameters to each individual story.
|
||||
In order, all of them will be combined together, with a later call overriding the previous set configurations on a per-key basis.
|
||||
|
||||
## Options and Defaults
|
||||
|
||||
```js
|
||||
{
|
||||
/**
|
||||
* Text to display with storybook component
|
||||
*/
|
||||
text?: string;
|
||||
/**
|
||||
* Displays info inline vs click button to view
|
||||
* @default false
|
||||
*/
|
||||
inline: boolean,
|
||||
/**
|
||||
* Toggles display of header with component name and description
|
||||
* @default true
|
||||
*/
|
||||
header: boolean,
|
||||
/**
|
||||
* Displays the source of story Component
|
||||
* @default true
|
||||
*/
|
||||
source: boolean,
|
||||
/**
|
||||
* Components used in story
|
||||
* Displays Prop Tables with these components
|
||||
* @default []
|
||||
*/
|
||||
propTables: Array<React.ComponentType>,
|
||||
/**
|
||||
* Exclude Components from being shown in Prop Tables section
|
||||
* Accepts an array of component classes or functions
|
||||
* @default []
|
||||
*/
|
||||
propTablesExclude: Array<React.ComponentType>,
|
||||
/**
|
||||
* Overrides styles of addon. The object should follow this shape:
|
||||
* https://github.com/storybookjs/storybook/blob/master/addons/info/src/components/Story.js#L19.
|
||||
* This prop can also accept a function which has the default stylesheet passed as an argument
|
||||
*/
|
||||
styles: Object | Function,
|
||||
/**
|
||||
* Overrides components used to display markdown
|
||||
* @default {}
|
||||
*/
|
||||
components: { [key: string]: React.ComponentType },
|
||||
/**
|
||||
* Max props to display per line in source code
|
||||
* @default 3
|
||||
*/
|
||||
maxPropsIntoLine: number,
|
||||
/**
|
||||
* Displays the first 10 characters of the prop name
|
||||
* @default 3
|
||||
*/
|
||||
maxPropObjectKeys: number,
|
||||
/**
|
||||
* Displays the first 10 items in the default prop array
|
||||
* @default 3
|
||||
*/
|
||||
maxPropArrayLength: number,
|
||||
/**
|
||||
* Displays the first 100 characters in the default prop string
|
||||
* @default 50
|
||||
*/
|
||||
maxPropStringLength: number,
|
||||
/**
|
||||
* Override the component used to render the props table
|
||||
* @default PropTable
|
||||
*/
|
||||
TableComponent: React.ComponentType,
|
||||
/**
|
||||
* Will exclude any respective properties whose name is included in array
|
||||
* @default []
|
||||
*/
|
||||
excludedPropTypes: Array<string>,
|
||||
}
|
||||
```
|
||||
|
||||
### Rendering a Custom Table
|
||||
|
||||
The `TableComponent` option allows you to define how the prop table should be rendered. Your component will be rendered with the following props.
|
||||
|
||||
```js
|
||||
{
|
||||
propDefinitions: Array<{
|
||||
property: string, // The name of the prop
|
||||
propType: Object | string, // The prop type. TODO: info about what this object is...
|
||||
required: boolean, // True if the prop is required
|
||||
description: string, // The description of the prop
|
||||
defaultValue: any // The default value of the prop
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
// button.js
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
const paddingStyles = {
|
||||
small: '4px 8px',
|
||||
medium: '8px 16px',
|
||||
};
|
||||
|
||||
const Button = ({
|
||||
size,
|
||||
...rest
|
||||
}: {
|
||||
/** The size of the button */
|
||||
size: 'small' | 'medium',
|
||||
}) => {
|
||||
const style = {
|
||||
padding: paddingStyles[size] || '',
|
||||
};
|
||||
return <button style={style} {...rest} />;
|
||||
};
|
||||
Button.defaultProps = {
|
||||
size: 'medium',
|
||||
};
|
||||
|
||||
export default Button;
|
||||
```
|
||||
|
||||
```js
|
||||
// stories.js
|
||||
import React from 'react';
|
||||
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import Button from './button';
|
||||
|
||||
export default {
|
||||
title: 'Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
info: TableComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const Red = props => <span style={{ color: 'red' }} {...props} />;
|
||||
|
||||
const TableComponent = ({ propDefinitions }) => {
|
||||
const props = propDefinitions.map(
|
||||
({ property, propType, required, description, defaultValue }) => {
|
||||
return (
|
||||
<tr key={property}>
|
||||
<td>
|
||||
{property}
|
||||
{required ? <Red>*</Red> : null}
|
||||
</td>
|
||||
<td>{propType.name}</td>
|
||||
<td>{defaultValue}</td>
|
||||
<td>{description}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>name</th>
|
||||
<th>type</th>
|
||||
<th>default</th>
|
||||
<th>description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{props}</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export const defaultView = () => <Button />;
|
||||
```
|
||||
|
||||
### React Docgen Integration
|
||||
|
||||
React Docgen is included as part of the @storybook/react package through the use of `babel-plugin-react-docgen` during babel compile time.
|
||||
When rendering a story with a React component commented in this supported format, the Addon Info description will render the comments above the component declaration and the prop table will display the prop's comment in the description column.
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/** Button component description */
|
||||
const DocgenButton = ({ disabled, label, style, onClick }) => (
|
||||
<button disabled={disabled} style={style} onClick={onClick}>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
|
||||
DocgenButton.defaultProps = {
|
||||
disabled: false,
|
||||
onClick: () => {},
|
||||
style: {},
|
||||
};
|
||||
|
||||
DocgenButton.propTypes = {
|
||||
/** Boolean indicating whether the button should render as disabled */
|
||||
disabled: PropTypes.bool,
|
||||
/** button label. */
|
||||
label: PropTypes.string.isRequired,
|
||||
/** onClick handler */
|
||||
onClick: PropTypes.func,
|
||||
/** component styles */
|
||||
style: PropTypes.shape,
|
||||
};
|
||||
|
||||
export default DocgenButton;
|
||||
```
|
||||
|
||||
Comments above flow types are also supported. Storybook Info Addon should now render all the correct types for your component if the PropTypes are in the same file as the React component.
|
||||
|
||||
## The FAQ
|
||||
|
||||
**Components lose their names on static build**
|
||||
|
||||
Component names also get minified with other javascript code when building for production.
|
||||
When creating components, set the `displayName` static property to show the correct component name on static builds.
|
Binary file not shown.
Before Width: | Height: | Size: 122 KiB |
@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "@storybook/addon-info",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"description": "A Storybook addon to show additional information for your stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
"storybook"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/info",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybookjs/storybook.git",
|
||||
"directory": "addons/info"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"README.md",
|
||||
"*.js",
|
||||
"*.d.ts"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"marksy": "^8.0.0",
|
||||
"nested-object-assign": "^1.0.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.3",
|
||||
"react-addons-create-fragment": "^15.6.2",
|
||||
"react-element-to-jsx-string": "^14.0.2",
|
||||
"react-is": "^16.8.3",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-test-renderer": "^16.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,122 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import PropVal from './PropVal';
|
||||
import PrettyPropType from './types/PrettyPropType';
|
||||
|
||||
const Table = props => <table {...props} />;
|
||||
const Td = props => <td style={{ paddingRight: 10, verticalAlign: 'top' }} {...props} />;
|
||||
const Tr = props => <tr {...props} />;
|
||||
const Th = props => <th style={{ textAlign: 'left', verticalAlign: 'top' }} {...props} />;
|
||||
const Tbody = props => <tbody {...props} />;
|
||||
const Thead = props => <thead {...props} />;
|
||||
|
||||
export const multiLineText = input => {
|
||||
if (!input) {
|
||||
return input;
|
||||
}
|
||||
const text = String(input);
|
||||
const arrayOfText = text.split(/\r?\n|\r/g);
|
||||
const isSingleLine = arrayOfText.length < 2;
|
||||
return isSingleLine
|
||||
? text
|
||||
: arrayOfText.map((lineOfText, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<span key={`${lineOfText}.${i}`}>
|
||||
{i > 0 && <br />} {lineOfText}
|
||||
</span>
|
||||
));
|
||||
};
|
||||
|
||||
const determineIncludedPropTypes = (propDefinitions, excludedPropTypes) => {
|
||||
if (excludedPropTypes.length === 0) {
|
||||
return propDefinitions;
|
||||
}
|
||||
|
||||
return propDefinitions.filter(
|
||||
propDefinition => !excludedPropTypes.includes(propDefinition.property)
|
||||
);
|
||||
};
|
||||
|
||||
export default function PropTable(props) {
|
||||
const {
|
||||
type,
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
propDefinitions,
|
||||
excludedPropTypes,
|
||||
} = props;
|
||||
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const includedPropDefinitions = determineIncludedPropTypes(propDefinitions, excludedPropTypes);
|
||||
|
||||
if (!includedPropDefinitions.length) {
|
||||
return <small>No propTypes defined!</small>;
|
||||
}
|
||||
|
||||
const propValProps = {
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
};
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>property</Th>
|
||||
<Th>propType</Th>
|
||||
<Th>required</Th>
|
||||
<Th>default</Th>
|
||||
<Th>description</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{includedPropDefinitions.map(row => (
|
||||
<Tr key={row.property}>
|
||||
<Td>{row.property}</Td>
|
||||
<Td>
|
||||
<PrettyPropType propType={row.propType} />
|
||||
</Td>
|
||||
<Td>{row.required ? 'yes' : '-'}</Td>
|
||||
<Td>
|
||||
{row.defaultValue === undefined ? (
|
||||
'-'
|
||||
) : (
|
||||
<PropVal val={row.defaultValue} {...propValProps} valueStyles={{}} />
|
||||
)}
|
||||
</Td>
|
||||
<Td>{multiLineText(row.description)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
PropTable.displayName = 'PropTable';
|
||||
PropTable.defaultProps = {
|
||||
type: null,
|
||||
propDefinitions: [],
|
||||
excludedPropTypes: [],
|
||||
};
|
||||
PropTable.propTypes = {
|
||||
type: PropTypes.func,
|
||||
maxPropObjectKeys: PropTypes.number.isRequired,
|
||||
maxPropArrayLength: PropTypes.number.isRequired,
|
||||
maxPropStringLength: PropTypes.number.isRequired,
|
||||
excludedPropTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
propDefinitions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
property: PropTypes.string.isRequired,
|
||||
propType: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
required: PropTypes.bool,
|
||||
description: PropTypes.string,
|
||||
defaultValue: PropTypes.any,
|
||||
})
|
||||
),
|
||||
};
|
@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import PropTable, { multiLineText } from './PropTable';
|
||||
|
||||
describe('PropTable', () => {
|
||||
describe('multiLineText', () => {
|
||||
const singleLine = 'Foo bar baz';
|
||||
const unixMultiLineText = 'foo \n bar \n baz';
|
||||
const windowsMultiLineText = 'foo \r bar \r baz';
|
||||
const duplicatedMultiLine = 'foo\nfoo\nfoo';
|
||||
const propDefinitions = [
|
||||
{
|
||||
defaultValue: undefined,
|
||||
description: '',
|
||||
propType: { name: 'string' },
|
||||
property: 'foo',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
const FooComponent = () => <div />;
|
||||
const propTableProps = {
|
||||
type: FooComponent,
|
||||
maxPropArrayLength: 5,
|
||||
maxPropObjectKeys: 5,
|
||||
maxPropStringLength: 5,
|
||||
propDefinitions,
|
||||
};
|
||||
|
||||
it('should include all propTypes by default', () => {
|
||||
const wrapper = shallow(<PropTable {...propTableProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should exclude excluded propTypes', () => {
|
||||
const props = { ...propTableProps, excludedPropTypes: ['foo'] };
|
||||
const wrapper = shallow(<PropTable {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return a blank string for a null input', () => {
|
||||
expect(multiLineText(null)).toBe(null);
|
||||
});
|
||||
it('should return a blank string for an undefined input', () => {
|
||||
expect(multiLineText(undefined)).toBe(undefined);
|
||||
});
|
||||
it('should cast a number to a string', () => {
|
||||
expect(multiLineText(1)).toBe('1');
|
||||
});
|
||||
it('should return its input for a single line of text', () => {
|
||||
expect(multiLineText(singleLine)).toBe(singleLine);
|
||||
});
|
||||
it('should return an array for unix multiline text', () => {
|
||||
expect(multiLineText(unixMultiLineText)).toHaveLength(3);
|
||||
});
|
||||
it('should return an array for windows multiline text', () => {
|
||||
expect(multiLineText(windowsMultiLineText)).toHaveLength(3);
|
||||
});
|
||||
it('should return an array with unique keys for duplicated multiline text', () => {
|
||||
const wrappers = multiLineText(duplicatedMultiLine).map(tag => shallow(tag));
|
||||
const keys = wrappers.map(wrapper => wrapper.key());
|
||||
const deDup = new Set(keys);
|
||||
expect(keys).toHaveLength(deDup.size);
|
||||
});
|
||||
it('should have 2 br tags for 3 lines of text', () => {
|
||||
const tree = renderer.create(multiLineText(unixMultiLineText)).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,86 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable multiLineText should exclude excluded propTypes 1`] = `
|
||||
<small>
|
||||
No propTypes defined!
|
||||
</small>
|
||||
`;
|
||||
|
||||
exports[`PropTable multiLineText should have 2 br tags for 3 lines of text 1`] = `
|
||||
Array [
|
||||
<span>
|
||||
|
||||
foo
|
||||
</span>,
|
||||
<span>
|
||||
<br />
|
||||
|
||||
bar
|
||||
</span>,
|
||||
<span>
|
||||
<br />
|
||||
|
||||
baz
|
||||
</span>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`PropTable multiLineText should include all propTypes by default 1`] = `
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
property
|
||||
</Th>
|
||||
<Th>
|
||||
propType
|
||||
</Th>
|
||||
<Th>
|
||||
required
|
||||
</Th>
|
||||
<Th>
|
||||
default
|
||||
</Th>
|
||||
<Th>
|
||||
description
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr
|
||||
key="foo"
|
||||
>
|
||||
<Td
|
||||
isMonospace={true}
|
||||
>
|
||||
foo
|
||||
</Td>
|
||||
<Td
|
||||
isMonospace={true}
|
||||
>
|
||||
<PrettyPropType
|
||||
depth={1}
|
||||
propType={
|
||||
Object {
|
||||
"name": "string",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td
|
||||
isMonospace={false}
|
||||
>
|
||||
-
|
||||
</Td>
|
||||
<Td
|
||||
isMonospace={false}
|
||||
>
|
||||
-
|
||||
</Td>
|
||||
<Td
|
||||
isMonospace={false}
|
||||
/>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
`;
|
@ -1,12 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import '../style.css';
|
||||
|
||||
const Table = ({ children }) => <table className="info-table">{children}</table>;
|
||||
|
||||
Table.propTypes = {
|
||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)])
|
||||
.isRequired,
|
||||
};
|
||||
|
||||
export default Table;
|
@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Table from './Table';
|
||||
|
||||
describe('PropTable/Table', () => {
|
||||
it('renders a table html node with one child element', () => {
|
||||
const wrapper = shallow(
|
||||
<Table>
|
||||
<div>foo bar</div>
|
||||
</Table>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a table html node with multiple children elements', () => {
|
||||
const wrapper = shallow(
|
||||
<Table>
|
||||
<div>foo bar</div>
|
||||
<div>baz</div>
|
||||
</Table>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const Tbody = ({ children }) => <tbody>{children}</tbody>;
|
||||
|
||||
Tbody.propTypes = {
|
||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)])
|
||||
.isRequired,
|
||||
};
|
||||
|
||||
export default Tbody;
|
@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Tbody from './Tbody';
|
||||
|
||||
describe('PropTable/Tbody', () => {
|
||||
it('renders a tbody html node with children', () => {
|
||||
const wrapper = shallow(
|
||||
<Tbody>
|
||||
<div>foo bar</div>
|
||||
</Tbody>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a tbody html node with multiple children elements', () => {
|
||||
const wrapper = shallow(
|
||||
<Tbody>
|
||||
<div>foo bar</div>
|
||||
<div>baz</div>
|
||||
</Tbody>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,24 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import '../style.css';
|
||||
|
||||
const Td = ({ isMonospace, children }) => (
|
||||
<td className={isMonospace ? 'info-table-monospace' : null}>{children}</td>
|
||||
);
|
||||
|
||||
Td.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.element,
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.arrayOf(PropTypes.element),
|
||||
]),
|
||||
isMonospace: PropTypes.bool,
|
||||
};
|
||||
|
||||
Td.defaultProps = {
|
||||
isMonospace: false,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default Td;
|
@ -1,54 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Td from './Td';
|
||||
|
||||
describe('PropTable/Td', () => {
|
||||
it('renders a td html node child element', () => {
|
||||
const wrapper = shallow(
|
||||
<Td>
|
||||
<div>foo bar</div>
|
||||
</Td>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a monospace td html node child element', () => {
|
||||
const wrapper = shallow(
|
||||
<Td isMonospace>
|
||||
<div>foo bar</div>
|
||||
</Td>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a td html node with multiple children elements', () => {
|
||||
const wrapper = shallow(
|
||||
<Td>
|
||||
<div>foo bar</div>
|
||||
<div>baz</div>
|
||||
</Td>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a monospace td html node with multiple children elements', () => {
|
||||
const wrapper = shallow(
|
||||
<Td isMonospace>
|
||||
<div>foo bar</div>
|
||||
<div>baz</div>
|
||||
</Td>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a td html node with one child node', () => {
|
||||
const wrapper = shallow(<Td>foo bar</Td>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a monospace td html node with one child node', () => {
|
||||
const wrapper = shallow(<Td isMonospace>foo bar</Td>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const Th = ({ children }) => <th>{children}</th>;
|
||||
|
||||
Th.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.element,
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.arrayOf(PropTypes.element),
|
||||
]).isRequired,
|
||||
};
|
||||
|
||||
export default Th;
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Th from './Th';
|
||||
|
||||
describe('PropTable/Th', () => {
|
||||
it('renders a th html node with react element children', () => {
|
||||
const wrapper = shallow(
|
||||
<Th>
|
||||
<div>foo bar</div>
|
||||
<div>baz</div>
|
||||
</Th>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a th html node with html node children', () => {
|
||||
const wrapper = shallow(<Th>foo bar baz</Th>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a th html node with one child node', () => {
|
||||
const wrapper = shallow(<Th>foo bar</Th>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const Thead = ({ children }) => <thead>{children}</thead>;
|
||||
|
||||
Thead.propTypes = {
|
||||
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)])
|
||||
.isRequired,
|
||||
};
|
||||
|
||||
export default Thead;
|
@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Thead from './Thead';
|
||||
|
||||
describe('PropTable/Thead', () => {
|
||||
it('renders a thead html node with children', () => {
|
||||
const wrapper = shallow(
|
||||
<Thead>
|
||||
<div>foo bar</div>
|
||||
</Thead>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a thead html node with multiple children elements', () => {
|
||||
const wrapper = shallow(
|
||||
<Thead>
|
||||
<div>foo bar</div>
|
||||
<div>baz</div>
|
||||
</Thead>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const Tr = ({ children }) => <tr>{children}</tr>;
|
||||
|
||||
Tr.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default Tr;
|
@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Tr from './Tr';
|
||||
import Td from './Td';
|
||||
|
||||
describe('PropTable/Tr', () => {
|
||||
it('renders a tr html node with react element children', () => {
|
||||
const wrapper = shallow(
|
||||
<Tr>
|
||||
<Td>foo bar</Td>
|
||||
<Td>baz</Td>
|
||||
</Tr>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a tr html node with html node children', () => {
|
||||
const wrapper = shallow(<Tr>foo bar baz</Tr>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a tr html node with one child node', () => {
|
||||
const wrapper = shallow(<Tr>foo bar</Tr>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -1,24 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable/Table renders a table html node with multiple children elements 1`] = `
|
||||
<table
|
||||
className="info-table"
|
||||
>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
<div>
|
||||
baz
|
||||
</div>
|
||||
</table>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Table renders a table html node with one child element 1`] = `
|
||||
<table
|
||||
className="info-table"
|
||||
>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
</table>
|
||||
`;
|
@ -1,20 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable/Tbody renders a tbody html node with children 1`] = `
|
||||
<tbody>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
</tbody>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Tbody renders a tbody html node with multiple children elements 1`] = `
|
||||
<tbody>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
<div>
|
||||
baz
|
||||
</div>
|
||||
</tbody>
|
||||
`;
|
@ -1,63 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable/Td renders a monospace td html node child element 1`] = `
|
||||
<td
|
||||
className="info-table-monospace"
|
||||
>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Td renders a monospace td html node with multiple children elements 1`] = `
|
||||
<td
|
||||
className="info-table-monospace"
|
||||
>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
<div>
|
||||
baz
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Td renders a monospace td html node with one child node 1`] = `
|
||||
<td
|
||||
className="info-table-monospace"
|
||||
>
|
||||
foo bar
|
||||
</td>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Td renders a td html node child element 1`] = `
|
||||
<td
|
||||
className={null}
|
||||
>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Td renders a td html node with multiple children elements 1`] = `
|
||||
<td
|
||||
className={null}
|
||||
>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
<div>
|
||||
baz
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Td renders a td html node with one child node 1`] = `
|
||||
<td
|
||||
className={null}
|
||||
>
|
||||
foo bar
|
||||
</td>
|
||||
`;
|
@ -1,24 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable/Th renders a th html node with html node children 1`] = `
|
||||
<th>
|
||||
foo bar baz
|
||||
</th>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Th renders a th html node with one child node 1`] = `
|
||||
<th>
|
||||
foo bar
|
||||
</th>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Th renders a th html node with react element children 1`] = `
|
||||
<th>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
<div>
|
||||
baz
|
||||
</div>
|
||||
</th>
|
||||
`;
|
@ -1,20 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable/Thead renders a thead html node with children 1`] = `
|
||||
<thead>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
</thead>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Thead renders a thead html node with multiple children elements 1`] = `
|
||||
<thead>
|
||||
<div>
|
||||
foo bar
|
||||
</div>
|
||||
<div>
|
||||
baz
|
||||
</div>
|
||||
</thead>
|
||||
`;
|
@ -1,28 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable/Tr renders a tr html node with html node children 1`] = `
|
||||
<tr>
|
||||
foo bar baz
|
||||
</tr>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Tr renders a tr html node with one child node 1`] = `
|
||||
<tr>
|
||||
foo bar
|
||||
</tr>
|
||||
`;
|
||||
|
||||
exports[`PropTable/Tr renders a tr html node with react element children 1`] = `
|
||||
<tr>
|
||||
<Td
|
||||
isMonospace={false}
|
||||
>
|
||||
foo bar
|
||||
</Td>
|
||||
<Td
|
||||
isMonospace={false}
|
||||
>
|
||||
baz
|
||||
</Td>
|
||||
</tr>
|
||||
`;
|
@ -1,121 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from '../types/PrettyPropType';
|
||||
import PropVal from '../PropVal';
|
||||
import Table from './components/Table';
|
||||
import Tbody from './components/Tbody';
|
||||
import Td from './components/Td';
|
||||
import Th from './components/Th';
|
||||
import Thead from './components/Thead';
|
||||
import Tr from './components/Tr';
|
||||
|
||||
export const multiLineText = input => {
|
||||
if (!input) {
|
||||
return input;
|
||||
}
|
||||
const text = String(input);
|
||||
const arrayOfText = text.split(/\r?\n|\r/g);
|
||||
const isSingleLine = arrayOfText.length < 2;
|
||||
return isSingleLine
|
||||
? text
|
||||
: arrayOfText.map((lineOfText, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<span key={`${lineOfText}.${i}`}>
|
||||
{i > 0 && <br />} {lineOfText}
|
||||
</span>
|
||||
));
|
||||
};
|
||||
|
||||
const determineIncludedPropTypes = (propDefinitions, excludedPropTypes) => {
|
||||
if (excludedPropTypes.length === 0) {
|
||||
return propDefinitions;
|
||||
}
|
||||
|
||||
return propDefinitions.filter(
|
||||
propDefinition => !excludedPropTypes.includes(propDefinition.property)
|
||||
);
|
||||
};
|
||||
|
||||
export default function PropTable(props) {
|
||||
const {
|
||||
type,
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
propDefinitions,
|
||||
excludedPropTypes,
|
||||
} = props;
|
||||
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const includedPropDefinitions = determineIncludedPropTypes(propDefinitions, excludedPropTypes);
|
||||
|
||||
if (!includedPropDefinitions.length) {
|
||||
return <small>No propTypes defined!</small>;
|
||||
}
|
||||
|
||||
const propValProps = {
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
};
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>property</Th>
|
||||
<Th>propType</Th>
|
||||
<Th>required</Th>
|
||||
<Th>default</Th>
|
||||
<Th>description</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{includedPropDefinitions.map(row => (
|
||||
<Tr key={row.property}>
|
||||
<Td isMonospace>{row.property}</Td>
|
||||
<Td isMonospace>
|
||||
<PrettyPropType propType={row.propType} />
|
||||
</Td>
|
||||
<Td>{row.required ? 'yes' : '-'}</Td>
|
||||
<Td>
|
||||
{row.defaultValue === undefined ? (
|
||||
'-'
|
||||
) : (
|
||||
<PropVal val={row.defaultValue} {...propValProps} valueStyles={{}} />
|
||||
)}
|
||||
</Td>
|
||||
<Td>{multiLineText(row.description)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
PropTable.displayName = 'PropTable';
|
||||
PropTable.defaultProps = {
|
||||
type: null,
|
||||
propDefinitions: [],
|
||||
excludedPropTypes: [],
|
||||
};
|
||||
PropTable.propTypes = {
|
||||
type: PropTypes.func,
|
||||
maxPropObjectKeys: PropTypes.number.isRequired,
|
||||
maxPropArrayLength: PropTypes.number.isRequired,
|
||||
maxPropStringLength: PropTypes.number.isRequired,
|
||||
excludedPropTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
propDefinitions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
property: PropTypes.string.isRequired,
|
||||
propType: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
required: PropTypes.bool,
|
||||
description: PropTypes.string,
|
||||
defaultValue: PropTypes.any,
|
||||
})
|
||||
),
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import PropTable, { multiLineText } from './index';
|
||||
|
||||
describe('PropTable', () => {
|
||||
describe('multiLineText', () => {
|
||||
const singleLine = 'Foo bar baz';
|
||||
const unixMultiLineText = 'foo \n bar \n baz';
|
||||
const windowsMultiLineText = 'foo \r bar \r baz';
|
||||
const duplicatedMultiLine = 'foo\nfoo\nfoo';
|
||||
const propDefinitions = [
|
||||
{
|
||||
defaultValue: undefined,
|
||||
description: '',
|
||||
propType: { name: 'string' },
|
||||
property: 'foo',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
const FooComponent = () => <div />;
|
||||
const propTableProps = {
|
||||
type: FooComponent,
|
||||
maxPropArrayLength: 5,
|
||||
maxPropObjectKeys: 5,
|
||||
maxPropStringLength: 5,
|
||||
propDefinitions,
|
||||
};
|
||||
|
||||
it('should include all propTypes by default', () => {
|
||||
const wrapper = shallow(<PropTable {...propTableProps} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should exclude excluded propTypes', () => {
|
||||
const props = { ...propTableProps, excludedPropTypes: ['foo'] };
|
||||
const wrapper = shallow(<PropTable {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return a blank string for a null input', () => {
|
||||
expect(multiLineText(null)).toBe(null);
|
||||
});
|
||||
|
||||
it('should return a blank string for an undefined input', () => {
|
||||
expect(multiLineText(undefined)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should cast a number to a string', () => {
|
||||
expect(multiLineText(1)).toBe('1');
|
||||
});
|
||||
|
||||
it('should return its input for a single line of text', () => {
|
||||
expect(multiLineText(singleLine)).toBe(singleLine);
|
||||
});
|
||||
|
||||
it('should return an array for unix multiline text', () => {
|
||||
expect(multiLineText(unixMultiLineText)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should return an array for windows multiline text', () => {
|
||||
expect(multiLineText(windowsMultiLineText)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should return an array with unique keys for duplicated multiline text', () => {
|
||||
const wrappers = multiLineText(duplicatedMultiLine).map(tag => shallow(tag));
|
||||
const keys = wrappers.map(wrapper => wrapper.key());
|
||||
const deDup = new Set(keys);
|
||||
expect(keys).toHaveLength(deDup.size);
|
||||
});
|
||||
|
||||
it('should have 2 br tags for 3 lines of text', () => {
|
||||
const tree = renderer.create(multiLineText(unixMultiLineText)).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,19 +0,0 @@
|
||||
.info-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-table, .info-table td, .info-table th {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #cccccc;
|
||||
color: #444444;
|
||||
margin-top: 0.25rem;
|
||||
padding-right: 0.5rem;
|
||||
padding: 0.25rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.info-table-monospace {
|
||||
font-family: Menlo, Monaco, "Courier New", monospace;
|
||||
font-size: 0.88em;
|
||||
}
|
@ -1,274 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createFragment from 'react-addons-create-fragment';
|
||||
|
||||
const getValueStyles = (codeColors = {}) => ({
|
||||
func: {
|
||||
color: codeColors.func || '#170',
|
||||
},
|
||||
|
||||
attr: {
|
||||
color: codeColors.attr || '#666',
|
||||
},
|
||||
|
||||
object: {
|
||||
color: codeColors.object || '#666',
|
||||
},
|
||||
|
||||
array: {
|
||||
color: codeColors.array || '#666',
|
||||
},
|
||||
|
||||
number: {
|
||||
color: codeColors.number || '#a11',
|
||||
},
|
||||
|
||||
string: {
|
||||
color: codeColors.string || '#22a',
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
|
||||
bool: {
|
||||
color: codeColors.bool || '#a11',
|
||||
},
|
||||
|
||||
empty: {
|
||||
color: '#444',
|
||||
},
|
||||
});
|
||||
|
||||
function indent(breakIntoNewLines, level, isBlock) {
|
||||
return (
|
||||
breakIntoNewLines && (
|
||||
<span>
|
||||
<br />
|
||||
{`${Array(level).join(' ')} `}
|
||||
{!isBlock && ' '}
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewArray({
|
||||
val,
|
||||
level,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
maxPropsIntoLine,
|
||||
valueStyles,
|
||||
}) {
|
||||
const items = {};
|
||||
const breakIntoNewLines = val.length > maxPropsIntoLine;
|
||||
val.slice(0, maxPropArrayLength).forEach((item, i) => {
|
||||
items[`n${i}`] = (
|
||||
<span>
|
||||
{indent(breakIntoNewLines, level)}
|
||||
<PropVal
|
||||
val={item}
|
||||
level={level + 1}
|
||||
valueStyles={valueStyles}
|
||||
maxPropStringLength={maxPropStringLength}
|
||||
maxPropsIntoLine={maxPropsIntoLine}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
items[`c${i}`] = ',';
|
||||
});
|
||||
if (val.length > maxPropArrayLength) {
|
||||
items.last = <span>{indent(breakIntoNewLines, level)}…</span>;
|
||||
} else {
|
||||
delete items[`c${val.length - 1}`];
|
||||
}
|
||||
|
||||
return (
|
||||
<span style={valueStyles.array}>
|
||||
[{createFragment(items)}
|
||||
{indent(breakIntoNewLines, level, true)}]
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
PreviewArray.propTypes = {
|
||||
val: PropTypes.any, // eslint-disable-line
|
||||
maxPropArrayLength: PropTypes.number.isRequired,
|
||||
maxPropStringLength: PropTypes.number.isRequired,
|
||||
maxPropsIntoLine: PropTypes.number.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
valueStyles: PropTypes.shape({
|
||||
func: PropTypes.object,
|
||||
attr: PropTypes.object,
|
||||
object: PropTypes.object,
|
||||
array: PropTypes.object,
|
||||
number: PropTypes.object,
|
||||
string: PropTypes.object,
|
||||
bool: PropTypes.object,
|
||||
empty: PropTypes.object,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
function PreviewObject({
|
||||
val,
|
||||
level,
|
||||
maxPropObjectKeys,
|
||||
maxPropStringLength,
|
||||
maxPropsIntoLine,
|
||||
valueStyles,
|
||||
}) {
|
||||
const names = Object.keys(val);
|
||||
const items = {};
|
||||
const breakIntoNewLines = names.length > maxPropsIntoLine;
|
||||
names.slice(0, maxPropObjectKeys).forEach((name, i) => {
|
||||
items[`k${i}`] = (
|
||||
<span>
|
||||
{indent(breakIntoNewLines, level)}
|
||||
<span style={valueStyles.attr}>{name}</span>
|
||||
</span>
|
||||
);
|
||||
items[`c${i}`] = ': ';
|
||||
items[`v${i}`] = (
|
||||
<PropVal
|
||||
val={val[name]}
|
||||
level={level + 1}
|
||||
valueStyles={valueStyles}
|
||||
maxPropStringLength={maxPropStringLength}
|
||||
maxPropsIntoLine={maxPropsIntoLine}
|
||||
/>
|
||||
);
|
||||
items[`m${i}`] = ',';
|
||||
});
|
||||
if (names.length > maxPropObjectKeys) {
|
||||
items.rest = <span>{indent(breakIntoNewLines, level)}…</span>;
|
||||
} else {
|
||||
delete items[`m${names.length - 1}`];
|
||||
}
|
||||
return (
|
||||
<span style={valueStyles.object}>
|
||||
{'{'}
|
||||
{createFragment(items)}
|
||||
{indent(breakIntoNewLines, level, true)}
|
||||
{'}'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
PreviewObject.propTypes = {
|
||||
val: PropTypes.any, // eslint-disable-line
|
||||
maxPropObjectKeys: PropTypes.number.isRequired,
|
||||
maxPropStringLength: PropTypes.number.isRequired,
|
||||
maxPropsIntoLine: PropTypes.number.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
valueStyles: PropTypes.shape({
|
||||
func: PropTypes.object,
|
||||
attr: PropTypes.object,
|
||||
object: PropTypes.object,
|
||||
array: PropTypes.object,
|
||||
number: PropTypes.object,
|
||||
string: PropTypes.object,
|
||||
bool: PropTypes.object,
|
||||
empty: PropTypes.object,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
function PropVal(props) {
|
||||
const {
|
||||
level,
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
maxPropsIntoLine,
|
||||
theme,
|
||||
} = props;
|
||||
let { val } = props;
|
||||
const { codeColors } = theme || {};
|
||||
let content = null;
|
||||
const valueStyles = props.valueStyles || getValueStyles(codeColors);
|
||||
|
||||
if (typeof val === 'number') {
|
||||
content = <span style={valueStyles.number}>{val}</span>;
|
||||
} else if (typeof val === 'string') {
|
||||
if (val.length > maxPropStringLength) {
|
||||
val = `${val.slice(0, maxPropStringLength)}…`;
|
||||
}
|
||||
if (level > 1) {
|
||||
val = `'${val}'`;
|
||||
}
|
||||
content = <span style={valueStyles.string}>{val}</span>;
|
||||
} else if (typeof val === 'boolean') {
|
||||
content = <span style={valueStyles.bool}>{`${val}`}</span>;
|
||||
} else if (Array.isArray(val)) {
|
||||
content = (
|
||||
<PreviewArray
|
||||
{...{
|
||||
val,
|
||||
level,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
maxPropsIntoLine,
|
||||
valueStyles,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (typeof val === 'function') {
|
||||
content = <span style={valueStyles.func}>{val.name || 'anonymous'}</span>;
|
||||
} else if (!val) {
|
||||
content = <span style={valueStyles.empty}>{`${val}`}</span>;
|
||||
} else if (typeof val !== 'object') {
|
||||
content = <span>…</span>;
|
||||
} else if (React.isValidElement(val)) {
|
||||
content = (
|
||||
<span style={valueStyles.object}>
|
||||
{`<${val.type.displayName || val.type.name || val.type} />`}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<PreviewObject
|
||||
{...{
|
||||
val,
|
||||
level,
|
||||
maxPropObjectKeys,
|
||||
maxPropStringLength,
|
||||
maxPropsIntoLine,
|
||||
valueStyles,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
PropVal.defaultProps = {
|
||||
val: null,
|
||||
maxPropObjectKeys: 3,
|
||||
maxPropArrayLength: 3,
|
||||
maxPropStringLength: 50,
|
||||
maxPropsIntoLine: 3,
|
||||
level: 1,
|
||||
theme: {},
|
||||
valueStyles: null,
|
||||
};
|
||||
|
||||
PropVal.propTypes = {
|
||||
val: PropTypes.any, // eslint-disable-line
|
||||
maxPropObjectKeys: PropTypes.number,
|
||||
maxPropArrayLength: PropTypes.number,
|
||||
maxPropStringLength: PropTypes.number,
|
||||
maxPropsIntoLine: PropTypes.number,
|
||||
level: PropTypes.number,
|
||||
theme: PropTypes.shape({
|
||||
codeColors: PropTypes.object,
|
||||
}),
|
||||
valueStyles: PropTypes.shape({
|
||||
func: PropTypes.object,
|
||||
attr: PropTypes.object,
|
||||
object: PropTypes.object,
|
||||
array: PropTypes.object,
|
||||
number: PropTypes.object,
|
||||
string: PropTypes.object,
|
||||
bool: PropTypes.object,
|
||||
empty: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
export default PropVal;
|
@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropVal from './PropVal';
|
||||
import { getType } from '../react-utils';
|
||||
|
||||
const stylesheet = {
|
||||
propStyle: {},
|
||||
propNameStyle: {},
|
||||
propValueStyle: {},
|
||||
};
|
||||
|
||||
export default function Props(props) {
|
||||
const {
|
||||
maxPropsIntoLine,
|
||||
maxPropArrayLength,
|
||||
maxPropObjectKeys,
|
||||
maxPropStringLength,
|
||||
node,
|
||||
singleLine,
|
||||
} = props;
|
||||
const nodeProps = node.props;
|
||||
const { defaultProps } = getType(node.type);
|
||||
if (!nodeProps || typeof nodeProps !== 'object') {
|
||||
return <span />;
|
||||
}
|
||||
|
||||
const { propValueStyle, propNameStyle } = stylesheet;
|
||||
|
||||
const names = Object.keys(nodeProps).filter(
|
||||
name =>
|
||||
name[0] !== '_' &&
|
||||
name !== 'children' &&
|
||||
(!defaultProps || nodeProps[name] !== defaultProps[name])
|
||||
);
|
||||
|
||||
const breakIntoNewLines = names.length > maxPropsIntoLine;
|
||||
const endingSpace = singleLine ? ' ' : '';
|
||||
|
||||
const items = [];
|
||||
names.forEach((name, i) => {
|
||||
items.push(
|
||||
<span key={name}>
|
||||
{breakIntoNewLines ? (
|
||||
<span>
|
||||
<br />
|
||||
|
||||
</span>
|
||||
) : (
|
||||
' '
|
||||
)}
|
||||
<span style={propNameStyle}>{name}</span>
|
||||
{/* Use implicit true: */}
|
||||
{(!nodeProps[name] || typeof nodeProps[name] !== 'boolean') && (
|
||||
<span>
|
||||
=
|
||||
<span style={propValueStyle}>
|
||||
{typeof nodeProps[name] === 'string' ? '"' : '{'}
|
||||
<PropVal
|
||||
val={nodeProps[name]}
|
||||
maxPropObjectKeys={maxPropObjectKeys}
|
||||
maxPropArrayLength={maxPropArrayLength}
|
||||
maxPropStringLength={maxPropStringLength}
|
||||
maxPropsIntoLine={maxPropsIntoLine}
|
||||
/>
|
||||
{typeof nodeProps[name] === 'string' ? '"' : '}'}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{i === names.length - 1 && (breakIntoNewLines ? <br /> : endingSpace)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
return <span>{items}</span>;
|
||||
}
|
||||
|
||||
Props.defaultProps = {
|
||||
singleLine: false,
|
||||
};
|
||||
|
||||
Props.propTypes = {
|
||||
node: PropTypes.node.isRequired,
|
||||
singleLine: PropTypes.bool,
|
||||
maxPropsIntoLine: PropTypes.number.isRequired,
|
||||
maxPropObjectKeys: PropTypes.number.isRequired,
|
||||
maxPropArrayLength: PropTypes.number.isRequired,
|
||||
maxPropStringLength: PropTypes.number.isRequired,
|
||||
};
|
@ -1,442 +0,0 @@
|
||||
/* eslint no-underscore-dangle: 0 */
|
||||
|
||||
import React, { Fragment, Component, createElement } from 'react';
|
||||
import { isForwardRef } from 'react-is';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
import PropTypes from 'prop-types';
|
||||
import global from 'global';
|
||||
|
||||
import marksy from 'marksy';
|
||||
import jsxToString from 'react-element-to-jsx-string';
|
||||
import { Code } from './markdown';
|
||||
import { getDisplayName, getType } from '../react-utils';
|
||||
|
||||
global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || [];
|
||||
const { STORYBOOK_REACT_CLASSES } = global;
|
||||
|
||||
const stylesheetBase = {
|
||||
button: {
|
||||
base: {
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 12,
|
||||
display: 'block',
|
||||
position: 'fixed',
|
||||
border: 'none',
|
||||
background: '#027ac5',
|
||||
color: '#fff',
|
||||
padding: '5px 15px',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
topRight: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
borderRadius: '0 0 0 5px',
|
||||
},
|
||||
},
|
||||
info: {
|
||||
position: 'fixed',
|
||||
background: 'white',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
overflow: 'auto',
|
||||
zIndex: 99999,
|
||||
},
|
||||
children: {
|
||||
position: 'relative',
|
||||
zIndex: 0,
|
||||
},
|
||||
infoBody: {
|
||||
fontFamily: 'Helvetica Neue, Helvetica, Segoe UI, Arial, freesans, sans-serif',
|
||||
color: 'black',
|
||||
fontWeight: 300,
|
||||
lineHeight: 1.45,
|
||||
fontSize: '15px',
|
||||
padding: '20px 40px 40px',
|
||||
borderRadius: '2px',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
infoContent: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
infoStory: {},
|
||||
jsxInfoContent: {
|
||||
borderTop: '1px solid #eee',
|
||||
margin: '20px 0 0 0',
|
||||
},
|
||||
header: {
|
||||
h1: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '35px',
|
||||
},
|
||||
h2: {
|
||||
margin: '0 0 10px 0',
|
||||
padding: 0,
|
||||
fontWeight: 400,
|
||||
fontSize: '22px',
|
||||
},
|
||||
h3: {
|
||||
margin: '0 0 10px 0',
|
||||
padding: 0,
|
||||
fontWeight: 400,
|
||||
fontSize: '18px',
|
||||
},
|
||||
body: {
|
||||
borderBottom: '1px solid #eee',
|
||||
paddingTop: 10,
|
||||
marginBottom: 10,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
h1: {
|
||||
margin: '20px 0 0 0',
|
||||
padding: '0 0 5px 0',
|
||||
fontSize: '25px',
|
||||
borderBottom: '1px solid #EEE',
|
||||
},
|
||||
},
|
||||
propTableHead: {
|
||||
margin: '20px 0 0 0',
|
||||
},
|
||||
};
|
||||
|
||||
class Story extends Component {
|
||||
constructor(props, ...args) {
|
||||
super(props, ...args);
|
||||
this.state = {
|
||||
open: false,
|
||||
};
|
||||
this.marksy = marksy({
|
||||
createElement,
|
||||
elements: props.components,
|
||||
});
|
||||
}
|
||||
|
||||
_renderStory() {
|
||||
const { stylesheet } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<div id="story-root" style={stylesheet.infoStory}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderInline() {
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{this._renderInlineHeader()}
|
||||
{this._renderStory()}
|
||||
<div style={stylesheet.infoPage}>
|
||||
<div style={stylesheet.infoBody}>
|
||||
{this._getInfoContent()}
|
||||
{this._getComponentDescription()}
|
||||
{this._getSourceCode()}
|
||||
{this._getPropTables()}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
_renderInlineHeader() {
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
const infoHeader = this._getInfoHeader();
|
||||
|
||||
return (
|
||||
infoHeader && (
|
||||
<div style={stylesheet.infoPage}>
|
||||
<div style={stylesheet.infoBody}>{infoHeader}</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_renderOverlay() {
|
||||
const { stylesheet, open } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
const buttonStyle = {
|
||||
...stylesheet.button.base,
|
||||
...stylesheet.button.topRight,
|
||||
};
|
||||
|
||||
const infoStyle = { ...stylesheet.info };
|
||||
if (!open) {
|
||||
infoStyle.display = 'none';
|
||||
}
|
||||
|
||||
const openOverlay = () => {
|
||||
this.setState({ open: true });
|
||||
return false;
|
||||
};
|
||||
|
||||
const closeOverlay = () => {
|
||||
this.setState({ open: false });
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={stylesheet.children}>{children}</div>
|
||||
<button
|
||||
type="button"
|
||||
style={buttonStyle}
|
||||
onClick={openOverlay}
|
||||
className="info__show-button"
|
||||
>
|
||||
Show Info
|
||||
</button>
|
||||
{open ? (
|
||||
<div style={infoStyle} className="info__overlay">
|
||||
<button
|
||||
type="button"
|
||||
style={buttonStyle}
|
||||
onClick={closeOverlay}
|
||||
className="info__close-button"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div style={stylesheet.infoPage}>
|
||||
<div style={stylesheet.infoBody}>
|
||||
{this._getInfoHeader()}
|
||||
{this._getInfoContent()}
|
||||
{this._getComponentDescription()}
|
||||
{this._getSourceCode()}
|
||||
{this._getPropTables()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
_getInfoHeader() {
|
||||
const { stylesheet } = this.state;
|
||||
const { context, showHeader } = this.props;
|
||||
|
||||
if (!context || !showHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={stylesheet.header.body}>
|
||||
<h1 style={stylesheet.header.h1}>{context.kind}</h1>
|
||||
<h2 style={stylesheet.header.h2}>{context.name}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_getInfoContent() {
|
||||
const { info, showInline } = this.props;
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
if (!info) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (React.isValidElement(info)) {
|
||||
return (
|
||||
<div style={showInline ? stylesheet.jsxInfoContent : stylesheet.infoContent}>{info}</div>
|
||||
);
|
||||
}
|
||||
|
||||
const lines = info.split('\n');
|
||||
while (lines[0].trim() === '') {
|
||||
lines.shift();
|
||||
}
|
||||
let padding = 0;
|
||||
const matches = lines[0].match(/^ */);
|
||||
if (matches) {
|
||||
padding = matches[0].length;
|
||||
}
|
||||
const source = lines.map(s => s.slice(padding)).join('\n');
|
||||
|
||||
return <Fragment>{this.marksy(source).tree}</Fragment>;
|
||||
}
|
||||
|
||||
_getComponentDescription() {
|
||||
const {
|
||||
context: { kind, name },
|
||||
} = this.props;
|
||||
let retDiv = null;
|
||||
|
||||
const validMatches = [kind, name];
|
||||
|
||||
if (Object.keys(STORYBOOK_REACT_CLASSES).length) {
|
||||
Object.keys(STORYBOOK_REACT_CLASSES).forEach(key => {
|
||||
if (validMatches.includes(STORYBOOK_REACT_CLASSES[key].name)) {
|
||||
const componentDescription = STORYBOOK_REACT_CLASSES[key].docgenInfo.description;
|
||||
retDiv = <Fragment>{this.marksy(componentDescription).tree}</Fragment>;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return retDiv;
|
||||
}
|
||||
|
||||
_getSourceCode() {
|
||||
const { showSource, children } = this.props;
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
if (!showSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 style={stylesheet.source.h1}>Story Source</h1>
|
||||
<Code code={jsxToString(children)} language="jsx" format={false} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
_getPropTables() {
|
||||
const {
|
||||
children,
|
||||
propTablesExclude,
|
||||
propTableCompare,
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
excludedPropTypes,
|
||||
} = this.props;
|
||||
let { propTables } = this.props;
|
||||
const { stylesheet } = this.state;
|
||||
const types = new Map();
|
||||
|
||||
if (!propTables) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
propTables.forEach(type => {
|
||||
types.set(type, true);
|
||||
});
|
||||
|
||||
// depth-first traverse and collect types
|
||||
const extract = innerChildren => {
|
||||
if (!innerChildren) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(innerChildren)) {
|
||||
innerChildren.forEach(extract);
|
||||
return;
|
||||
}
|
||||
if (innerChildren.props && innerChildren.props.children) {
|
||||
extract(innerChildren.props.children);
|
||||
}
|
||||
if (isForwardRef(innerChildren)) {
|
||||
try {
|
||||
// this might fail because of hooks being used
|
||||
extract(innerChildren.type.render(innerChildren.props));
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof innerChildren === 'string' ||
|
||||
typeof innerChildren.type === 'string' ||
|
||||
(propTables.length > 0 && // if propTables is set and has items in it
|
||||
!propTables.includes(innerChildren.type)) || // ignore types that are missing from propTables
|
||||
(Array.isArray(propTablesExclude) && // also ignore excluded types
|
||||
propTablesExclude.some(Comp => propTableCompare(innerChildren, Comp)))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (innerChildren.type && !types.has(innerChildren.type)) {
|
||||
types.set(innerChildren.type, true);
|
||||
}
|
||||
};
|
||||
|
||||
// extract components from children
|
||||
extract(children);
|
||||
|
||||
const array = Array.from(types.keys());
|
||||
array.sort((a, b) => (getDisplayName(a) > getDisplayName(b) ? 1 : -1));
|
||||
|
||||
propTables = array.map((type, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={`${getDisplayName(type)}_${i}`}>
|
||||
<h3 style={stylesheet.propTableHead}>"{getDisplayName(type)}" Component</h3>
|
||||
<this.props.PropTable
|
||||
type={getType(type)}
|
||||
maxPropObjectKeys={maxPropObjectKeys}
|
||||
maxPropArrayLength={maxPropArrayLength}
|
||||
maxPropStringLength={maxPropStringLength}
|
||||
excludedPropTypes={excludedPropTypes}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
if (!propTables || propTables.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 style={stylesheet.source.h1}>Prop Types</h1>
|
||||
{propTables}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showInline } = this.props;
|
||||
return showInline ? this._renderInline() : this._renderOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
Story.getDerivedStateFromProps = ({ styles }) => ({ stylesheet: styles(stylesheetBase) });
|
||||
|
||||
Story.displayName = 'Story';
|
||||
|
||||
Story.propTypes = {
|
||||
context: PropTypes.shape({
|
||||
kind: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
}),
|
||||
info: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
propTables: PropTypes.arrayOf(PropTypes.func),
|
||||
propTablesExclude: PropTypes.arrayOf(PropTypes.func),
|
||||
propTableCompare: PropTypes.func.isRequired,
|
||||
showInline: PropTypes.bool,
|
||||
showHeader: PropTypes.bool,
|
||||
showSource: PropTypes.bool,
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
styles: PropTypes.func.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
components: PropTypes.shape({}),
|
||||
maxPropObjectKeys: PropTypes.number.isRequired,
|
||||
maxPropArrayLength: PropTypes.number.isRequired,
|
||||
maxPropStringLength: PropTypes.number.isRequired,
|
||||
excludedPropTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
Story.defaultProps = {
|
||||
context: null,
|
||||
info: '',
|
||||
children: null,
|
||||
propTables: null,
|
||||
propTablesExclude: [],
|
||||
showInline: false,
|
||||
showHeader: true,
|
||||
showSource: true,
|
||||
components: {},
|
||||
excludedPropTypes: [],
|
||||
};
|
||||
|
||||
polyfill(Story);
|
||||
|
||||
export default Story;
|
@ -1,76 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropTable multiLineText should exclude excluded propTypes 1`] = `
|
||||
<small>
|
||||
No propTypes defined!
|
||||
</small>
|
||||
`;
|
||||
|
||||
exports[`PropTable multiLineText should have 2 br tags for 3 lines of text 1`] = `
|
||||
Array [
|
||||
<span>
|
||||
|
||||
foo
|
||||
</span>,
|
||||
<span>
|
||||
<br />
|
||||
|
||||
bar
|
||||
</span>,
|
||||
<span>
|
||||
<br />
|
||||
|
||||
baz
|
||||
</span>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`PropTable multiLineText should include all propTypes by default 1`] = `
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
property
|
||||
</Th>
|
||||
<Th>
|
||||
propType
|
||||
</Th>
|
||||
<Th>
|
||||
required
|
||||
</Th>
|
||||
<Th>
|
||||
default
|
||||
</Th>
|
||||
<Th>
|
||||
description
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr
|
||||
key="foo"
|
||||
>
|
||||
<Td>
|
||||
foo
|
||||
</Td>
|
||||
<Td>
|
||||
<PrettyPropType
|
||||
depth={1}
|
||||
propType={
|
||||
Object {
|
||||
"name": "string",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
-
|
||||
</Td>
|
||||
<Td>
|
||||
-
|
||||
</Td>
|
||||
<Td />
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
`;
|
@ -1,98 +0,0 @@
|
||||
/* eslint-disable no-underscore-dangle,react/forbid-foreign-prop-types */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const PropTypesMap = new Map();
|
||||
|
||||
Object.keys(PropTypes).forEach(typeName => {
|
||||
const type = PropTypes[typeName];
|
||||
|
||||
PropTypesMap.set(type, typeName);
|
||||
PropTypesMap.set(type.isRequired, typeName);
|
||||
});
|
||||
|
||||
const isNotEmpty = obj => obj && obj.props && Object.keys(obj.props).length > 0;
|
||||
|
||||
const hasDocgen = type => isNotEmpty(type.__docgenInfo);
|
||||
|
||||
const propsFromDocgen = type => {
|
||||
const props = {};
|
||||
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] = {
|
||||
property,
|
||||
propType,
|
||||
required: docgenInfoProp.required,
|
||||
description: docgenInfoProp.description,
|
||||
defaultValue: defaultValueDesc.value,
|
||||
};
|
||||
});
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
const propsFromPropTypes = type => {
|
||||
const props = {};
|
||||
|
||||
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];
|
||||
const description = docgenInfo ? docgenInfo.description : null;
|
||||
let propType = PropTypesMap.get(typeInfo) || 'other';
|
||||
|
||||
if (propType === 'other') {
|
||||
if (docgenInfo && docgenInfo.type) {
|
||||
propType = docgenInfo.type.name;
|
||||
}
|
||||
}
|
||||
|
||||
props[property] = { property, propType, required, description };
|
||||
});
|
||||
}
|
||||
|
||||
if (type.defaultProps) {
|
||||
Object.keys(type.defaultProps).forEach(property => {
|
||||
const value = type.defaultProps[property];
|
||||
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!props[property]) {
|
||||
props[property] = { property };
|
||||
}
|
||||
|
||||
props[property].defaultValue = value;
|
||||
});
|
||||
}
|
||||
|
||||
return props;
|
||||
};
|
||||
|
||||
export default function makeTableComponent(Component) {
|
||||
const TableComponent = props => {
|
||||
const { type } = props;
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const propDefinitionsMap = hasDocgen(type) ? propsFromDocgen(type) : propsFromPropTypes(type);
|
||||
const propDefinitions = Object.values(propDefinitionsMap);
|
||||
|
||||
return <Component propDefinitions={propDefinitions} {...props} />;
|
||||
};
|
||||
|
||||
TableComponent.propTypes = {
|
||||
type: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
return TableComponent;
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SyntaxHighlighter } from '@storybook/components';
|
||||
import { ThemeProvider, convert } from '@storybook/theming';
|
||||
|
||||
const Code = ({ code, language = 'plaintext', ...rest }) => (
|
||||
<ThemeProvider theme={convert()}>
|
||||
<SyntaxHighlighter bordered copyable format={false} language={language} {...rest}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
Code.propTypes = {
|
||||
language: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export { Code };
|
||||
|
||||
export function Blockquote({ children }) {
|
||||
const style = {
|
||||
fontSize: '1.88em',
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
borderLeft: '8px solid #fafafa',
|
||||
padding: '1rem',
|
||||
};
|
||||
return <blockquote style={style}>{children}</blockquote>;
|
||||
}
|
||||
|
||||
Blockquote.propTypes = { children: PropTypes.node };
|
||||
Blockquote.defaultProps = { children: null };
|
||||
|
||||
export { default as Pre } from './pre/pre';
|
@ -1,115 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const defaultProps = {
|
||||
children: null,
|
||||
id: null,
|
||||
};
|
||||
const propTypes = {
|
||||
children: PropTypes.node,
|
||||
id: PropTypes.string,
|
||||
};
|
||||
|
||||
export function H1({ id, children }) {
|
||||
const styles = {
|
||||
borderBottom: '1px solid #eee',
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '40px',
|
||||
};
|
||||
return (
|
||||
<h1 id={id} style={styles}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
H1.defaultProps = defaultProps;
|
||||
H1.propTypes = propTypes;
|
||||
|
||||
export function H2({ id, children }) {
|
||||
const styles = {
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '30px',
|
||||
};
|
||||
return (
|
||||
<h2 id={id} style={styles}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
H2.defaultProps = defaultProps;
|
||||
H2.propTypes = propTypes;
|
||||
|
||||
export function H3({ id, children }) {
|
||||
const styles = {
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '22px',
|
||||
textTransform: 'uppercase',
|
||||
};
|
||||
return (
|
||||
<h3 id={id} style={styles}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
H3.defaultProps = defaultProps;
|
||||
H3.propTypes = propTypes;
|
||||
|
||||
export function H4({ id, children }) {
|
||||
const styles = {
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '20px',
|
||||
};
|
||||
return (
|
||||
<h4 id={id} style={styles}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
|
||||
H4.defaultProps = defaultProps;
|
||||
H4.propTypes = propTypes;
|
||||
|
||||
export function H5({ id, children }) {
|
||||
const styles = {
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '18px',
|
||||
};
|
||||
return (
|
||||
<h5 id={id} style={styles}>
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
}
|
||||
|
||||
H5.defaultProps = defaultProps;
|
||||
H5.propTypes = propTypes;
|
||||
|
||||
export function H6({ id, children }) {
|
||||
const styles = {
|
||||
fontWeight: 400,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: '18px',
|
||||
};
|
||||
return (
|
||||
<h6 id={id} style={styles}>
|
||||
{children}
|
||||
</h6>
|
||||
);
|
||||
}
|
||||
|
||||
H6.defaultProps = defaultProps;
|
||||
H6.propTypes = propTypes;
|
@ -1,3 +0,0 @@
|
||||
export { H1, H2, H3, H4, H5, H6 } from './htags';
|
||||
export { Code, Pre } from './code';
|
||||
export { P, A, LI, UL } from './text';
|
@ -1,13 +0,0 @@
|
||||
/* eslint-disable no-undef */
|
||||
export default function copy(str) {
|
||||
const tmp = document.createElement('TEXTAREA');
|
||||
const focus = document.activeElement;
|
||||
|
||||
tmp.value = str;
|
||||
|
||||
document.body.appendChild(tmp);
|
||||
tmp.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(tmp);
|
||||
focus.focus();
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function CopyButton({ onClick, toggled }) {
|
||||
const toggleText = 'Copied!';
|
||||
const text = 'Copy';
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
style={{
|
||||
backgroundColor: 'rgb(255, 255, 255)',
|
||||
cursor: 'pointer',
|
||||
fontSize: '13px',
|
||||
alignSelf: 'flex-start',
|
||||
flexShrink: '0',
|
||||
overflow: 'hidden',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'rgb(238, 238, 238)',
|
||||
borderImage: 'initial',
|
||||
borderRadius: 3,
|
||||
padding: '3px 10px',
|
||||
}}
|
||||
>
|
||||
{toggled ? toggleText : text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
CopyButton.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
toggled: PropTypes.bool,
|
||||
};
|
||||
|
||||
CopyButton.defaultProps = {
|
||||
onClick: () => {},
|
||||
toggled: false,
|
||||
};
|
||||
|
||||
export default CopyButton;
|
@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import CopyButton from './copyButton';
|
||||
import copy from './copy';
|
||||
|
||||
const TOGGLE_TIMEOUT = 1800;
|
||||
|
||||
class Pre extends React.Component {
|
||||
state = {
|
||||
copied: false,
|
||||
};
|
||||
|
||||
setRef = elem => {
|
||||
this.pre = elem;
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
const text = this.pre && this.pre.innerText;
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
copy(text);
|
||||
this.setState({ copied: true });
|
||||
|
||||
clearTimeout(this.timeout);
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false });
|
||||
}, TOGGLE_TIMEOUT);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { theme, children } = this.props;
|
||||
const { pre } = theme;
|
||||
const { copied } = this.state;
|
||||
|
||||
return (
|
||||
<pre
|
||||
style={{
|
||||
...{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
fontSize: '.88em',
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
backgroundColor: '#fafafa',
|
||||
padding: '.5rem',
|
||||
lineHeight: 1.5,
|
||||
overflowX: 'scroll',
|
||||
},
|
||||
...pre,
|
||||
}}
|
||||
>
|
||||
<div ref={this.setRef}>{children}</div>
|
||||
<CopyButton onClick={this.handleClick} toggled={copied} />
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Pre.propTypes = {
|
||||
children: PropTypes.node,
|
||||
theme: PropTypes.shape({
|
||||
pre: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
Pre.defaultProps = {
|
||||
children: null,
|
||||
theme: {},
|
||||
};
|
||||
|
||||
export default Pre;
|
@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const defaultProps = { children: null };
|
||||
const propTypes = { children: PropTypes.node };
|
||||
|
||||
export function P({ children }) {
|
||||
return <p>{children}</p>;
|
||||
}
|
||||
|
||||
P.defaultProps = defaultProps;
|
||||
P.propTypes = propTypes;
|
||||
|
||||
export function LI({ children }) {
|
||||
return <li>{children}</li>;
|
||||
}
|
||||
|
||||
LI.defaultProps = defaultProps;
|
||||
LI.propTypes = propTypes;
|
||||
|
||||
export function UL({ children }) {
|
||||
return <ul>{children}</ul>;
|
||||
}
|
||||
|
||||
UL.defaultProps = defaultProps;
|
||||
UL.propTypes = propTypes;
|
||||
|
||||
export function A({ href, children }) {
|
||||
const style = {
|
||||
color: '#3498db',
|
||||
};
|
||||
return (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" style={style}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
A.defaultProps = defaultProps;
|
||||
A.propTypes = { children: PropTypes.node, href: PropTypes.string.isRequired };
|
@ -1,21 +0,0 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const ArrayOf = ({ propType }) => (
|
||||
<span>
|
||||
<span>[</span>
|
||||
<span>
|
||||
<PrettyPropType propType={getPropTypes(propType)} />
|
||||
</span>
|
||||
<span>]</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
ArrayOf.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
|
||||
export default ArrayOf;
|
@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const Enum = ({ propType }) => (
|
||||
<span>
|
||||
{getPropTypes(propType)
|
||||
.map(({ value }) => value)
|
||||
.join(' | ')}
|
||||
</span>
|
||||
);
|
||||
|
||||
Enum.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const InstanceOf = ({ propType }) => <span>{getPropTypes(propType)}</span>;
|
||||
|
||||
InstanceOf.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
|
||||
export default InstanceOf;
|
@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TypeInfo } from './proptypes';
|
||||
|
||||
const Literal = ({ propType }) => <span>{propType.value}</span>;
|
||||
|
||||
Literal.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
|
||||
export default Literal;
|
@ -1,19 +0,0 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const ObjectOf = ({ propType }) => (
|
||||
<span>
|
||||
{'{[<key>]: '}
|
||||
<PrettyPropType propType={getPropTypes(propType)} />
|
||||
{'}'}
|
||||
</span>
|
||||
);
|
||||
|
||||
ObjectOf.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
|
||||
export default ObjectOf;
|
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const joinValues = propTypes => propTypes.map(({ value }) => value).join(' | ');
|
||||
|
||||
const OneOf = ({ propType }) => {
|
||||
const propTypes = getPropTypes(propType);
|
||||
return <span>{`oneOf ${Array.isArray(propTypes) ? joinValues(propTypes) : propTypes}`}</span>;
|
||||
};
|
||||
|
||||
OneOf.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
|
||||
export default OneOf;
|
@ -1,26 +0,0 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const OneOfType = ({ propType }) => {
|
||||
const propTypes = getPropTypes(propType);
|
||||
return (
|
||||
<span>
|
||||
{propTypes
|
||||
.map((value, i) => {
|
||||
const key = `${value.name}${value.value ? `-${value.value}` : ''}`;
|
||||
return [
|
||||
<PrettyPropType key={key} propType={value} />,
|
||||
i < propTypes.length - 1 ? <span key={`${key}-separator`}> | </span> : null,
|
||||
];
|
||||
})
|
||||
.reduce((acc, tuple) => acc.concat(tuple), [])}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
OneOfType.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
export default OneOfType;
|
@ -1,56 +0,0 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Shape from './Shape';
|
||||
import OneOfType from './OneOfType';
|
||||
import ArrayOf from './ArrayOf';
|
||||
import ObjectOf from './ObjectOf';
|
||||
import OneOf from './OneOf';
|
||||
import InstanceOf from './InstanceOf';
|
||||
import Signature from './Signature';
|
||||
import Literal from './Literal';
|
||||
|
||||
import { TypeInfo } from './proptypes';
|
||||
|
||||
// propType -> Component map - these are a bit more complex prop types to display
|
||||
const propTypeComponentMap = new Map([
|
||||
['shape', Shape],
|
||||
['union', OneOfType],
|
||||
['arrayOf', ArrayOf],
|
||||
['objectOf', ObjectOf],
|
||||
// Might be overkill to have below proptypes as separate components *shrug*
|
||||
['literal', Literal],
|
||||
['enum', OneOf],
|
||||
['instanceOf', InstanceOf],
|
||||
['signature', Signature],
|
||||
]);
|
||||
|
||||
const PrettyPropType = props => {
|
||||
const { propType, depth } = props;
|
||||
if (!propType) {
|
||||
return <span>unknown</span>;
|
||||
}
|
||||
|
||||
if (propTypeComponentMap.has(propType.name)) {
|
||||
const Component = propTypeComponentMap.get(propType.name);
|
||||
return <Component propType={propType} depth={depth} />;
|
||||
}
|
||||
|
||||
// Otherwise, propType does not have a dedicated component, display proptype name by default
|
||||
return <span>{propType.name || propType}</span>;
|
||||
};
|
||||
|
||||
PrettyPropType.displayName = 'PrettyPropType';
|
||||
|
||||
PrettyPropType.defaultProps = {
|
||||
propType: null,
|
||||
depth: 1,
|
||||
};
|
||||
|
||||
PrettyPropType.propTypes = {
|
||||
propType: TypeInfo,
|
||||
depth: PropTypes.number,
|
||||
};
|
||||
|
||||
export default PrettyPropType;
|
@ -1,31 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const styles = {
|
||||
hasProperty: {
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
};
|
||||
|
||||
const PropertyLabel = ({ property, required }) => {
|
||||
if (!property) return null;
|
||||
|
||||
return (
|
||||
<span style={styles.hasProperty}>
|
||||
{property}
|
||||
{required ? '' : '?'}:
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
PropertyLabel.propTypes = {
|
||||
property: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
};
|
||||
|
||||
PropertyLabel.defaultProps = {
|
||||
property: '',
|
||||
required: false,
|
||||
};
|
||||
|
||||
export default PropertyLabel;
|
@ -1,76 +0,0 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import PropertyLabel from './PropertyLabel';
|
||||
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
const MARGIN_SIZE = 15;
|
||||
|
||||
const HighlightButton = props => (
|
||||
<button
|
||||
type="button"
|
||||
{...props}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
background: 'none',
|
||||
border: '0 none',
|
||||
color: 'gray',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
class Shape extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
minimized: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleToggle = () => {
|
||||
const { minimized } = this.state;
|
||||
this.setState({
|
||||
minimized: !minimized,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { propType, depth } = this.props;
|
||||
const { minimized } = this.state;
|
||||
|
||||
const propTypes = getPropTypes(propType);
|
||||
return (
|
||||
<span>
|
||||
<HighlightButton onClick={this.handleToggle}>{'{'}</HighlightButton>
|
||||
<HighlightButton onClick={this.handleToggle}>...</HighlightButton>
|
||||
{!minimized &&
|
||||
Object.keys(propTypes).map(childProperty => (
|
||||
<div key={childProperty} style={{ marginLeft: depth * MARGIN_SIZE }}>
|
||||
<PropertyLabel
|
||||
property={childProperty}
|
||||
required={propTypes[childProperty].required}
|
||||
/>
|
||||
<PrettyPropType depth={depth + 1} propType={propTypes[childProperty]} />,
|
||||
</div>
|
||||
))}
|
||||
|
||||
<HighlightButton onClick={this.handleToggle}>{'}'}</HighlightButton>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Shape.propTypes = {
|
||||
propType: TypeInfo,
|
||||
depth: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
Shape.defaultProps = {
|
||||
propType: null,
|
||||
};
|
||||
|
||||
export default Shape;
|
@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TypeInfo } from './proptypes';
|
||||
|
||||
const Signature = ({ propType }) => <span>{propType.raw}</span>;
|
||||
|
||||
Signature.propTypes = {
|
||||
propType: TypeInfo.isRequired,
|
||||
};
|
||||
|
||||
export default Signature;
|
@ -1,12 +0,0 @@
|
||||
import PropTypes, { oneOfType } from 'prop-types';
|
||||
|
||||
export const TypeInfo = oneOfType([
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
}),
|
||||
PropTypes.string,
|
||||
]);
|
||||
|
||||
export const getPropTypes = propType =>
|
||||
typeof propType === 'string' ? propType : propType.value || propType.elements;
|
@ -1,114 +0,0 @@
|
||||
import React from 'react';
|
||||
import nestedObjectAssign from 'nested-object-assign';
|
||||
import deprecate from 'util-deprecate';
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import Story from './components/Story';
|
||||
import PropTable from './components/PropTable/index';
|
||||
import makeTableComponent from './components/makeTableComponent';
|
||||
import { H1, H2, H3, H4, H5, H6, Code, P, UL, A, LI } from './components/markdown';
|
||||
|
||||
const defaultOptions = {
|
||||
inline: false,
|
||||
header: true,
|
||||
source: true,
|
||||
propTables: [],
|
||||
propTableCompare: (element, Component) =>
|
||||
// https://github.com/gaearon/react-hot-loader#checking-element-types
|
||||
typeof reactHotLoaderGlobal === 'undefined'
|
||||
? element.type === Component
|
||||
: // eslint-disable-next-line no-undef
|
||||
reactHotLoaderGlobal.areComponentsEqual(element.type, Component),
|
||||
TableComponent: PropTable,
|
||||
maxPropsIntoLine: 3,
|
||||
maxPropObjectKeys: 3,
|
||||
maxPropArrayLength: 3,
|
||||
maxPropStringLength: 50,
|
||||
};
|
||||
|
||||
const defaultComponents = {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
h4: H4,
|
||||
h5: H5,
|
||||
h6: H6,
|
||||
code: Code,
|
||||
p: P,
|
||||
a: A,
|
||||
li: LI,
|
||||
ul: UL,
|
||||
};
|
||||
|
||||
let hasWarned = false;
|
||||
|
||||
function addInfo(storyFn, context, infoOptions) {
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
...infoOptions,
|
||||
};
|
||||
|
||||
// props.propTables can only be either an array of components or null
|
||||
// propTables option is allowed to be set to 'false' (a boolean)
|
||||
// if the option is false, replace it with null to avoid react warnings
|
||||
if (!options.propTables) {
|
||||
options.propTables = null;
|
||||
}
|
||||
|
||||
const components = { ...defaultComponents };
|
||||
if (options && options.components) {
|
||||
Object.assign(components, options.components);
|
||||
}
|
||||
if (options && options.marksyConf) {
|
||||
if (!hasWarned) {
|
||||
logger.warn('@storybook/addon-info: "marksyConf" option has been renamed to "components"');
|
||||
hasWarned = true;
|
||||
}
|
||||
|
||||
Object.assign(components, options.marksyConf);
|
||||
}
|
||||
const props = {
|
||||
info: options.text,
|
||||
context,
|
||||
showInline: Boolean(options.inline),
|
||||
showHeader: Boolean(options.header),
|
||||
showSource: Boolean(options.source),
|
||||
styles:
|
||||
typeof options.styles === 'function'
|
||||
? options.styles
|
||||
: s => nestedObjectAssign({}, s, options.styles),
|
||||
propTables: options.propTables,
|
||||
propTablesExclude: options.propTablesExclude,
|
||||
propTableCompare: options.propTableCompare,
|
||||
PropTable: makeTableComponent(options.TableComponent),
|
||||
components,
|
||||
maxPropObjectKeys: options.maxPropObjectKeys,
|
||||
maxPropArrayLength: options.maxPropArrayLength,
|
||||
maxPropsIntoLine: options.maxPropsIntoLine,
|
||||
maxPropStringLength: options.maxPropStringLength,
|
||||
excludedPropTypes: options.excludedPropTypes,
|
||||
};
|
||||
return <Story {...props}>{storyFn(context)}</Story>;
|
||||
}
|
||||
|
||||
export const withInfo = makeDecorator({
|
||||
name: 'withInfo',
|
||||
parameterName: 'info',
|
||||
allowDeprecatedUsage: true,
|
||||
wrapper: (getStory, context, { options, parameters }) => {
|
||||
const storyOptions = parameters || options;
|
||||
const infoOptions = typeof storyOptions === 'string' ? { text: storyOptions } : storyOptions;
|
||||
const mergedOptions =
|
||||
typeof infoOptions === 'string' ? infoOptions : { ...options, ...infoOptions };
|
||||
return addInfo(getStory, context, mergedOptions);
|
||||
},
|
||||
});
|
||||
|
||||
export { Story };
|
||||
|
||||
export function setDefaults(newDefaults) {
|
||||
return deprecate(
|
||||
() => Object.assign(defaultOptions, newDefaults),
|
||||
'setDefaults is deprecated. Instead, you can pass options into withInfo(options) directly, or use the info parameter.'
|
||||
)();
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { withInfo, setDefaults } from '.';
|
||||
import externalMdDocs from '../README.md';
|
||||
|
||||
const TestComponent = ({ func, obj, array, number, string, bool, empty }) => (
|
||||
<div>
|
||||
<h1>{String(func)}</h1>
|
||||
<h2>{String(obj)}</h2>
|
||||
<h3>{String(array)}</h3>
|
||||
<h4>{String(number)}</h4>
|
||||
<h5>{String(string)}</h5>
|
||||
<h6>{String(bool)}</h6>
|
||||
<p>{String(empty)}</p>
|
||||
<a href="#">test</a>
|
||||
<code>storiesOf</code>
|
||||
<ul>
|
||||
<li>1</li>
|
||||
<li>2</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
const reactClassPath = 'some/path/TestComponent.jsx';
|
||||
const storybookReactClassMock = {
|
||||
name: 'TestComponent',
|
||||
path: reactClassPath,
|
||||
docgenInfo: {
|
||||
description: `
|
||||
# Awesome test component description
|
||||
## with markdown support
|
||||
**bold** *cursive*
|
||||
\`\`\`js
|
||||
a;
|
||||
\`\`\``,
|
||||
name: 'TestComponent',
|
||||
},
|
||||
};
|
||||
|
||||
const testOptions = { propTables: false };
|
||||
|
||||
const testMarkdown = `# Test story
|
||||
## with markdown info
|
||||
containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)`;
|
||||
|
||||
describe('addon Info', () => {
|
||||
const createStoryFn = Component => ({ name }) => (
|
||||
<div>
|
||||
It's a {name} story:
|
||||
<Component
|
||||
func={x => x + 1}
|
||||
obj={{ a: 'a', b: 'b' }}
|
||||
array={[1, 2, 3]}
|
||||
number={7}
|
||||
string="seven"
|
||||
bool
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const storyFn = createStoryFn(TestComponent);
|
||||
|
||||
it('should render <Info /> and markdown', () => {
|
||||
const Info = withInfo(testMarkdown)(storyFn);
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
});
|
||||
it('should render <Info /> and external markdown', () => {
|
||||
const Info = withInfo(externalMdDocs)(storyFn);
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
});
|
||||
it('should render with text options', () => {
|
||||
const Info = withInfo({ text: 'some text here' })(storyFn);
|
||||
mount(<Info />);
|
||||
});
|
||||
it('should render with missed info', () => {
|
||||
setDefaults(testOptions);
|
||||
const Info = withInfo()(storyFn);
|
||||
mount(<Info />);
|
||||
});
|
||||
it('should render <Info /> for memoized component', () => {
|
||||
const MemoizedTestComponent = React.memo(TestComponent);
|
||||
const Info = withInfo()(createStoryFn(MemoizedTestComponent));
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render component description if story kind matches component', () => {
|
||||
const previousReactClassesValue = global.STORYBOOK_REACT_CLASSES[reactClassPath];
|
||||
Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: storybookReactClassMock });
|
||||
|
||||
const Info = () =>
|
||||
withInfo({ inline: true, propTables: false })(storyFn, {
|
||||
kind: 'TestComponent',
|
||||
name: 'Basic test',
|
||||
});
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
|
||||
Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: previousReactClassesValue });
|
||||
});
|
||||
|
||||
it('should render component description if story name matches component', () => {
|
||||
const previousReactClassesValue = global.STORYBOOK_REACT_CLASSES[reactClassPath];
|
||||
Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: storybookReactClassMock });
|
||||
|
||||
const Info = () =>
|
||||
withInfo({ inline: true, propTables: false })(storyFn, {
|
||||
kind: 'Test Components',
|
||||
name: 'TestComponent',
|
||||
});
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
|
||||
Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: previousReactClassesValue });
|
||||
});
|
||||
});
|
14
addons/info/src/react-utils.js
vendored
14
addons/info/src/react-utils.js
vendored
@ -1,14 +0,0 @@
|
||||
import { isMemo } from 'react-is';
|
||||
|
||||
export function getType(typeOrMemo) {
|
||||
return isMemo(typeOrMemo) ? typeOrMemo.type : typeOrMemo;
|
||||
}
|
||||
|
||||
export function getDisplayName(typeOrMemo) {
|
||||
if (typeof typeOrMemo === 'string') {
|
||||
return typeOrMemo;
|
||||
}
|
||||
|
||||
const type = getType(typeOrMemo);
|
||||
return type.displayName || type.name || 'Unknown';
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-jest",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "React storybook addon that show component jest report",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -34,11 +34,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"react": "^16.8.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-knobs",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook Addon Prop Editor Component",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,12 +28,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-api": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-api": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"@types/react-color": "^3.0.1",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"core-js": "^3.0.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-links",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Story Links addon for storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,11 +28,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/csf": "0.0.1",
|
||||
"@storybook/router": "6.0.0-alpha.2",
|
||||
"@storybook/router": "6.0.0-alpha.3",
|
||||
"@types/qs": "^6.9.0",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
|
@ -1,106 +0,0 @@
|
||||
# Storybook Addon Notes
|
||||
|
||||
Storybook Addon Notes allows you to write notes (text or HTML) for your stories in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
**NOTE: Documentation on `next` branch is for alpha version, stable release is on [master](https://github.com/storybookjs/storybook/tree/master/addons/)**
|
||||
|
||||
```sh
|
||||
yarn add -D @storybook/addon-notes
|
||||
```
|
||||
|
||||
within `.storybook/main.js`:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
addons: ['@storybook/addon-notes/register']
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively register the notes addon into a panel. Choose only one, not both.
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
addons: ['@storybook/addon-notes/register-panel']
|
||||
}
|
||||
```
|
||||
|
||||
Now, you can use the `notes` parameter to add a note to each story.
|
||||
|
||||
|
||||
```js
|
||||
import Component from './Component';
|
||||
|
||||
export default {
|
||||
title: 'Component',
|
||||
parameters: {
|
||||
notes: 'some documentation here',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Upgrading to CSF Format
|
||||
|
||||
Add `notes` to the `parameters` object:
|
||||
|
||||
```js
|
||||
export default {
|
||||
parameters: {
|
||||
notes: 'My notes',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Markdown
|
||||
|
||||
Using Markdown in your notes is supported, Storybook will load Markdown as raw by default.
|
||||
|
||||
```js
|
||||
import Component from './Component';
|
||||
import markdown from './someMarkdownText.md';
|
||||
|
||||
export default {
|
||||
title: 'Component',
|
||||
};
|
||||
|
||||
export const withMarkdown = () => <Component />;
|
||||
withmarkdown.story = {
|
||||
parameters: {
|
||||
notes: { markdown },
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Giphy
|
||||
|
||||
When using Markdown, you can also embed gifs from Giphy into your Markdown. Currently, the value `cheese` of the query prop is used to search and return the first result returned by Giphy.
|
||||
|
||||
```md
|
||||
# Title
|
||||
|
||||
<Giphy query='cheese' />
|
||||
```
|
||||
|
||||
## Multiple Notes Sections
|
||||
|
||||
If you need to display different notes for different consumers of your storybook (e.g design, developers), you can configure multiple notes pages. The following will render a tab with unique notes for both `Introduction` and `Design`.
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import Component from './Component';
|
||||
|
||||
import intro from './intro.md';
|
||||
import design from './design.md';
|
||||
|
||||
export default {
|
||||
title: 'Component',
|
||||
parameters: {
|
||||
notes: { Introduction: intro, 'Design Notes': design },
|
||||
},
|
||||
};
|
||||
```
|
Binary file not shown.
Before Width: | Height: | Size: 93 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-notes",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Write notes for your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -29,13 +29,13 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/router": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/router": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"markdown-to-jsx": "^6.10.3",
|
||||
|
@ -1 +0,0 @@
|
||||
require('./dist/register.js').default('panel');
|
@ -1 +0,0 @@
|
||||
require('./dist/register.js').default('tab');
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { Link } from '@reach/router';
|
||||
import { SyntaxHighlighter as SyntaxHighlighterBase } from '@storybook/components';
|
||||
import { SyntaxHighlighter, NotesLink } from './Panel';
|
||||
|
||||
describe('NotesPanel', () => {
|
||||
describe('SyntaxHighlighter component', () => {
|
||||
it('should return code if className is undefined', () => {
|
||||
const wrapper = shallow(<SyntaxHighlighter>some text</SyntaxHighlighter>);
|
||||
const code = wrapper.find('code');
|
||||
expect(code.exists()).toBeTruthy();
|
||||
expect(code.text()).toBe('some text');
|
||||
});
|
||||
it('should return SyntaxHighlighterBase if there is a className prop', () => {
|
||||
const wrapper = shallow(
|
||||
<SyntaxHighlighter className="lang-jsx">some text</SyntaxHighlighter>
|
||||
);
|
||||
const syntaxHighlighterBase = wrapper.find(SyntaxHighlighterBase);
|
||||
expect(syntaxHighlighterBase.exists()).toBeTruthy();
|
||||
expect(syntaxHighlighterBase.prop('language')).toBe('jsx');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NotesLink component', () => {
|
||||
it('should render storybook links with @storybook/router Link', () => {
|
||||
const component = mount(
|
||||
<NotesLink href="/story/addon-notes" title="title">
|
||||
Storybook Link
|
||||
</NotesLink>
|
||||
);
|
||||
expect(component.find(Link).prop('to')).toBe('/?path=/story/addon-notes');
|
||||
expect(component.find(Link).prop('title')).toBe('title');
|
||||
});
|
||||
it('should render absolute links as <a>', () => {
|
||||
const component = mount(
|
||||
<NotesLink href="https://example.com" title="title">
|
||||
Storybook Link
|
||||
</NotesLink>
|
||||
);
|
||||
expect(component.find('a').prop('href')).toBe('https://example.com');
|
||||
expect(component.find('a').prop('title')).toBe('title');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,218 +0,0 @@
|
||||
import React, { ReactElement, Fragment, ReactNode } from 'react';
|
||||
import { types } from '@storybook/addons';
|
||||
import { API, Consumer, Combo } from '@storybook/api';
|
||||
import { Link as RouterLink } from '@storybook/router';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import {
|
||||
SyntaxHighlighter as SyntaxHighlighterBase,
|
||||
Placeholder,
|
||||
DocumentWrapper,
|
||||
Link,
|
||||
TabWrapper,
|
||||
TabsState,
|
||||
} from '@storybook/components';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import Giphy from './giphy';
|
||||
|
||||
import { formatter } from './formatter';
|
||||
|
||||
import { PARAM_KEY, Parameters } from './shared';
|
||||
|
||||
const Panel = styled.div<{}>(({ theme }) => ({
|
||||
padding: '3rem 40px',
|
||||
boxSizing: 'border-box',
|
||||
width: '100%',
|
||||
maxWidth: 980,
|
||||
margin: '0 auto',
|
||||
...(theme.addonNotesTheme || {}),
|
||||
}));
|
||||
|
||||
interface Props {
|
||||
active: boolean;
|
||||
api: API;
|
||||
}
|
||||
|
||||
function read(param: Parameters | undefined): Record<string, string> | string | undefined {
|
||||
if (!param) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof param === 'string') {
|
||||
return param;
|
||||
}
|
||||
if ('disable' in param) {
|
||||
return undefined;
|
||||
}
|
||||
if ('text' in param) {
|
||||
return param.text;
|
||||
}
|
||||
if ('markdown' in param) {
|
||||
return param.markdown;
|
||||
}
|
||||
if (typeof param === 'object') {
|
||||
return param;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
interface SyntaxHighlighterProps {
|
||||
className?: string;
|
||||
children: ReactElement;
|
||||
[key: string]: any;
|
||||
}
|
||||
export const SyntaxHighlighter = ({ className, children, ...props }: SyntaxHighlighterProps) => {
|
||||
// markdown-to-jsx does not add className to inline code
|
||||
if (typeof className !== 'string') {
|
||||
return <code>{children}</code>;
|
||||
}
|
||||
// className: "lang-jsx"
|
||||
const language = className.split('-');
|
||||
return (
|
||||
<SyntaxHighlighterBase
|
||||
language={language[1] || 'plaintext'}
|
||||
bordered
|
||||
format={false}
|
||||
copyable
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SyntaxHighlighterBase>
|
||||
);
|
||||
};
|
||||
|
||||
interface NotesLinkProps {
|
||||
href: string;
|
||||
children: ReactElement;
|
||||
}
|
||||
export const NotesLink = ({ href, children, ...props }: NotesLinkProps) => {
|
||||
/* https://github.com/sindresorhus/is-absolute-url/blob/master/index.js */
|
||||
const isAbsoluteUrl = /^[a-z][a-z0-9+.-]*:/.test(href);
|
||||
const isAnchorUrl = /^#.*/.test(href);
|
||||
|
||||
if (isAbsoluteUrl || isAnchorUrl) {
|
||||
return (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterLink to={href} {...props}>
|
||||
{children}
|
||||
</RouterLink>
|
||||
);
|
||||
};
|
||||
|
||||
// use our SyntaxHighlighter component in place of a <code> element when
|
||||
// converting markdown to react elements
|
||||
const defaultOptions = {
|
||||
overrides: {
|
||||
code: SyntaxHighlighter,
|
||||
a: NotesLink,
|
||||
Giphy: {
|
||||
component: Giphy,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface Overrides {
|
||||
overrides: {
|
||||
[type: string]: ReactNode;
|
||||
};
|
||||
}
|
||||
type Options = typeof defaultOptions & Overrides;
|
||||
|
||||
const mapper = ({
|
||||
state,
|
||||
api,
|
||||
}: Combo): { value?: string | Record<string, string>; options: Options } => {
|
||||
const extraElements = Object.entries(api.getElements(types.NOTES_ELEMENT)).reduce(
|
||||
(acc, [k, v]) => ({ ...acc, [k]: v.render }),
|
||||
{}
|
||||
);
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
overrides: { ...defaultOptions.overrides, ...extraElements },
|
||||
};
|
||||
|
||||
const story = state.storiesHash[state.storyId];
|
||||
const value = read(story ? api.getParameters(story.id, PARAM_KEY) : undefined);
|
||||
|
||||
return { options, value };
|
||||
};
|
||||
|
||||
const NotesPanel = ({ active }: Props) => {
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Consumer filter={mapper}>
|
||||
{({ options, value }: { options: Options; value?: string | Record<string, string> }) => {
|
||||
if (!value) {
|
||||
return (
|
||||
<Placeholder>
|
||||
<Fragment>No notes yet</Fragment>
|
||||
<Fragment>
|
||||
Learn how to
|
||||
<Link
|
||||
href="https://github.com/storybookjs/storybook/tree/master/addons/notes"
|
||||
target="_blank"
|
||||
withArrow
|
||||
secondary
|
||||
cancel={false}
|
||||
>
|
||||
document components in Markdown
|
||||
</Link>
|
||||
</Fragment>
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof value === 'string' || Object.keys(value).length === 1) {
|
||||
const md = typeof value === 'object' ? Object.values(value)[0] : value;
|
||||
|
||||
return (
|
||||
<Panel className="addon-notes-container">
|
||||
<DocumentWrapper>
|
||||
<Markdown options={options}>{formatter(md)}</Markdown>
|
||||
</DocumentWrapper>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
const groups: { title: string; render: (props: { active: boolean }) => void }[] = [];
|
||||
|
||||
Object.entries(value).forEach(([title, docs]) => {
|
||||
groups.push({
|
||||
title,
|
||||
render: ({ active: isActive }) => (
|
||||
<TabWrapper key={title} active={isActive}>
|
||||
<Panel>
|
||||
<DocumentWrapper>
|
||||
<Markdown options={options}>{formatter(docs)}</Markdown>
|
||||
</DocumentWrapper>
|
||||
</Panel>
|
||||
</TabWrapper>
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="addon-notes-container">
|
||||
<TabsState>
|
||||
{groups.map(group => (
|
||||
<div id={group.title} key={group.title} title={group.title}>
|
||||
{group.render}
|
||||
</div>
|
||||
))}
|
||||
</TabsState>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotesPanel;
|
@ -1,26 +0,0 @@
|
||||
import memoize from 'memoizerific';
|
||||
|
||||
export const formatter = memoize(2)((code: string) => {
|
||||
// code provided to the component is often coming from template literals, which preserve whitespace.
|
||||
// sometimes the first line doesn't have padding, but the second does.
|
||||
// we split the code-string into lines, then if we find padding on line 0 or 1,
|
||||
// we assume that padding is bad, and remove that much padding on all following lines
|
||||
return code
|
||||
.split(/\n/)
|
||||
.reduce(
|
||||
(acc, i, index) => {
|
||||
const match = i.match(/^((:?\s|\t)+)/);
|
||||
const padding = match ? match[1] : '';
|
||||
|
||||
if (acc.firstIndent === '' && padding && index < 3) {
|
||||
return { result: `${acc.result}\n${i.replace(padding, '')}`, firstIndent: padding };
|
||||
}
|
||||
return {
|
||||
result: `${acc.result}\n${i.replace(acc.firstIndent, '').replace(/\s*$/, '')}`,
|
||||
firstIndent: acc.firstIndent,
|
||||
};
|
||||
},
|
||||
{ firstIndent: '', result: '' }
|
||||
)
|
||||
.result.trim();
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
import { fetch } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { logger } from '@storybook/client-logger';
|
||||
|
||||
interface Props {
|
||||
query: string;
|
||||
}
|
||||
interface State {
|
||||
src: string | null;
|
||||
}
|
||||
export default class Giphy extends Component<Props, State> {
|
||||
state: State = {
|
||||
src: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { query } = this.props;
|
||||
// TODO: replace this api_key, and make it configurable
|
||||
// note: I have requested a production api_key:
|
||||
// it's pending: bluXZc8ZAre19mvTtVi900CdsJhbVTEK
|
||||
fetch(`http://api.giphy.com/v1/gifs/search?limit=1&api_key=dc6zaTOxFJmzC&q=${query}`)
|
||||
.then((response: { ok: any; json: () => void }) => response.ok && response.json())
|
||||
.then((data: { data: { images: { original: { url: string } } }[] }) => {
|
||||
this.setState({
|
||||
src: data.data[0].images.original.url,
|
||||
});
|
||||
})
|
||||
.catch((e: any) => logger.error(e));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { src } = this.state;
|
||||
// TODO: we should have a nice looking <Img /> component
|
||||
return src ? <img src={src} alt="" /> : null;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { makeDecorator, StoryContext, StoryGetter, WrapperSettings } from '@storybook/addons';
|
||||
import deprecate from 'util-deprecate';
|
||||
|
||||
// todo resolve any after @storybook/addons and @storybook/channels are migrated to TypeScript
|
||||
export const withNotes = makeDecorator({
|
||||
name: 'withNotes',
|
||||
parameterName: 'notes',
|
||||
skipIfNoParametersOrOptions: true,
|
||||
allowDeprecatedUsage: true,
|
||||
|
||||
wrapper: deprecate(
|
||||
(getStory: StoryGetter, context: StoryContext, { options, parameters }: WrapperSettings) => {
|
||||
const storyOptions = parameters || options;
|
||||
|
||||
const { text, markdown } =
|
||||
typeof storyOptions === 'string'
|
||||
? {
|
||||
text: storyOptions,
|
||||
markdown: undefined,
|
||||
}
|
||||
: storyOptions;
|
||||
|
||||
if (!text && !markdown) {
|
||||
throw new Error(
|
||||
`Parameter 'notes' must must be a string or an object with 'text' or 'markdown' properties`
|
||||
);
|
||||
}
|
||||
|
||||
return getStory(context);
|
||||
},
|
||||
'withNotes is deprecated'
|
||||
),
|
||||
});
|
||||
|
||||
export const withMarkdownNotes = deprecate((text: string, options: any) => {},
|
||||
'withMarkdownNotes is deprecated');
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from '.';
|
@ -1,20 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
|
||||
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './shared';
|
||||
|
||||
// TODO: fix eslint in tslint (igor said he fixed it, should ask him)
|
||||
import Panel from './Panel';
|
||||
|
||||
export default function register(type: types) {
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.add(PANEL_ID, {
|
||||
type,
|
||||
title: 'Notes',
|
||||
route: ({ storyId }) => `/info/${storyId}`, // todo add type
|
||||
match: ({ viewMode }) => viewMode === 'info', // todo add type
|
||||
render: ({ active, key }) => <Panel api={api} active={active} key={key} />,
|
||||
paramKey: PARAM_KEY,
|
||||
});
|
||||
});
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
export const ADDON_ID = 'storybookjs/notes';
|
||||
export const PANEL_ID = `${ADDON_ID}/panel`;
|
||||
export const PARAM_KEY = `notes`;
|
||||
|
||||
interface TextParameter {
|
||||
text: string;
|
||||
}
|
||||
interface MarkdownParameter {
|
||||
markdown: string;
|
||||
}
|
||||
interface DisabledParameter {
|
||||
disable: boolean;
|
||||
}
|
||||
type TabsParameter = Record<string, string>;
|
||||
|
||||
export type Parameters =
|
||||
| string
|
||||
| TextParameter
|
||||
| MarkdownParameter
|
||||
| DisabledParameter
|
||||
| TabsParameter;
|
7
addons/notes/src/typings.d.ts
vendored
7
addons/notes/src/typings.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
// There are no types for markdown-to-jsx
|
||||
declare module 'markdown-to-jsx' {
|
||||
const Markdown: any;
|
||||
export default Markdown;
|
||||
}
|
||||
|
||||
declare module 'global';
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"types": ["webpack-env"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"src/__tests__/**/*"
|
||||
]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-options",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Options addon for storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,7 +28,7 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-queryparams",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "parameter addon for storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -29,12 +29,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"qs": "^6.6.0",
|
||||
|
@ -381,7 +381,7 @@ NOTICE that When using the `asyncJest: true` option, you also must specify a `te
|
||||
|
||||
This is a really powerful technique to write stories of Relay components because it integrates data fetching with component rendering. So instead of passing data props manually, we can let Relay do the job for us as it does in our application.
|
||||
|
||||
Whenever you change you're data requirements by adding (and rendering) or (accidentally) deleting fields in your graphql query fragments, you'll get a different snapshot and thus an error in the StoryShot test.
|
||||
Whenever you change your data requirements by adding (and rendering) or (accidentally) deleting fields in your graphql query fragments, you'll get a different snapshot and thus an error in the StoryShot test.
|
||||
|
||||
## Options
|
||||
|
||||
@ -677,3 +677,22 @@ initStoryshots({
|
||||
### `asyncJest`
|
||||
|
||||
Enables Jest `done()` callback in the StoryShots tests for async testing. See [StoryShots for async rendered components](#storyshots-for-async-rendered-components) for more info.
|
||||
|
||||
|
||||
## Story Parameters
|
||||
|
||||
### `disable`
|
||||
|
||||
Some stories are difficult or impossible to snapshot, such as those covering components that use external DOM-modifying libraries, and those that deliberately throw errors. It is possible to skip stories like these by giving them a parameter of `storyshots: {disable: true}`. There is also a shorthand for this, `storyshots: false`.
|
||||
|
||||
```js
|
||||
export const Exception = () => {
|
||||
throw new Error('storyFn threw an error! WHOOPS');
|
||||
};
|
||||
Exception.story = {
|
||||
name: 'story throws exception',
|
||||
parameters: {
|
||||
storyshots: { disable: true },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-storyshots",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -32,9 +32,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/transform": "^25.1.0",
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/client-api": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/client-api": "6.0.0-alpha.3",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/jest": "^25.1.1",
|
||||
"@types/jest-specific-snapshot": "^0.5.3",
|
||||
@ -49,8 +49,8 @@
|
||||
"ts-dedent": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-docs": "6.0.0-alpha.2",
|
||||
"@storybook/react": "6.0.0-alpha.2",
|
||||
"@storybook/addon-docs": "6.0.0-alpha.3",
|
||||
"@storybook/react": "6.0.0-alpha.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"enzyme-to-json": "^3.4.1",
|
||||
"jest-emotion": "^10.0.17",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-storyshots-puppeteer",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Image snapshots addition to StoryShots based on puppeteer",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -29,7 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/csf": "0.0.1",
|
||||
"@storybook/node-logger": "6.0.0-alpha.2",
|
||||
"@storybook/node-logger": "6.0.0-alpha.3",
|
||||
"@types/jest-image-snapshot": "^2.8.0",
|
||||
"@wordpress/jest-puppeteer-axe": "^1.5.0",
|
||||
"core-js": "^3.0.1",
|
||||
@ -41,7 +41,7 @@
|
||||
"@types/puppeteer": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/addon-storyshots": "6.0.0-alpha.2",
|
||||
"@storybook/addon-storyshots": "6.0.0-alpha.3",
|
||||
"puppeteer": "^1.12.2 || ^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-storysource",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Stories addon for storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,13 +28,13 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/router": "6.0.0-alpha.2",
|
||||
"@storybook/source-loader": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/router": "6.0.0-alpha.3",
|
||||
"@storybook/source-loader": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"loader-utils": "^1.2.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-viewport",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook addon to change the viewport size to mobile",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,12 +28,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/api": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/components": "6.0.0-alpha.2",
|
||||
"@storybook/core-events": "6.0.0-alpha.2",
|
||||
"@storybook/theming": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/api": "6.0.0-alpha.3",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/components": "6.0.0-alpha.3",
|
||||
"@storybook/core-events": "6.0.0-alpha.3",
|
||||
"@storybook/theming": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"memoizerific": "^1.11.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/angular",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -33,9 +33,9 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/node-logger": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"@storybook/node-logger": "6.0.0-alpha.3",
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"core-js": "^3.0.1",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/ember",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/app/ember",
|
||||
"bugs": {
|
||||
@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ember/test-helpers": "^1.7.0",
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/html",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -33,8 +33,8 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/marionette",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook for Marionette: Develop Marionette.js component in isolation with Hot Reloading.",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -25,7 +25,7 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"common-tags": "^1.8.0",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/marko",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook for Marko: Develop Marko Component in isolation with Hot Reloading.",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -33,8 +33,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@marko/webpack": "^2.1.0",
|
||||
"@storybook/client-logger": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/client-logger": "6.0.0-alpha.3",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/mithril",
|
||||
"version": "6.0.0-alpha.2",
|
||||
"version": "6.0.0-alpha.3",
|
||||
"description": "Storybook for Mithril: Develop Mithril Component in isolation.",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -35,8 +35,8 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.8.4",
|
||||
"@babel/plugin-transform-react-jsx": "^7.3.0",
|
||||
"@storybook/addons": "6.0.0-alpha.2",
|
||||
"@storybook/core": "6.0.0-alpha.2",
|
||||
"@storybook/addons": "6.0.0-alpha.3",
|
||||
"@storybook/core": "6.0.0-alpha.3",
|
||||
"@types/mithril": "^2.0.0",
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"core-js": "^3.0.1",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user