├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example ├── .storybook │ └── config.js └── stories │ ├── index.js │ └── initFela.js ├── npm-debug.log.2678678117 ├── package.json ├── screenshot-props.png ├── src ├── __snapshots__ │ └── index.test.js.snap ├── index.js └── index.test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", "react" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | lib 4 | *.log 5 | .idea 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .babelrc 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Your Name. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storybook-addon-props-fela 2 | 3 | Document the props of your components in storybook. 4 | 5 | ![Screenshot](screenshot-props.png) 6 | 7 | ### Why not [react-storybook-addon-info](https://github.com/storybooks/react-storybook-addon-info) ? 8 | 9 | Quite simple, because he doesn't handle fela correctly and i use it on my project. 10 | 11 | ## Getting started 12 | 13 | Install `storybook-addon-props-fela` : 14 | 15 | ``` 16 | yarn add storybook-addon-props-fela 17 | // OR 18 | npm i --save storybook-addon-props-fela 19 | ``` 20 | 21 | Then in your storybook import and add the addon : 22 | 23 | ```javascript 24 | import { setAddon, storiesOf } from '@kadira/storybook'; 25 | import PropsAddon from 'storybook-addon-props-fela'; 26 | 27 | setAddon(PropsAddon); 28 | ``` 29 | 30 | Once you added the addon, a new method is available for your stories `addWithProps`. 31 | 32 | `addWithProps` is the same as the default function of storybook `add` except the fact that it takes a third parameter. This third parameters is the component from which you want the props. 33 | 34 | ## Usage 35 | 36 | To use the full power of this addon, your component need to provide `propTypes` and `defaultProps`. 37 | The addon support flow and use it for the required property (it's quite a minimum support for now, i think we can do more with flow later). 38 | 39 | ```javascript 40 | import React, { PropTypes } from 'react'; 41 | import { setAddon, storiesOf } from '@kadira/storybook'; 42 | import { createComponent } from 'react-fela'; 43 | 44 | import initFelaProvider from './initFela'; 45 | import PropsAddon from '../../lib/index'; 46 | 47 | const FelaProvider = initFelaProvider(); 48 | 49 | const test = ({ color }) => ({ 50 | fontSize: 35, 51 | color, 52 | }); 53 | 54 | const Test = createComponent(test, 'h1'); 55 | Test.defaultProps = { color: '#333' }; 56 | Test.propsTypes = { 57 | color: PropTypes.string, 58 | }; 59 | 60 | setAddon(PropsAddon); 61 | 62 | storiesOf('test', module) 63 | .addDecorator(FelaProvider) 64 | .addWithProps( 65 | 'Paris', 66 | () => Hello, 67 | Test, 68 | ) 69 | .addWithProps('Orleans', () => Hello, Test); 70 | 71 | storiesOf('test 2', module).addWithProps('Paris', () =>
test
); 72 | ``` 73 | 74 | If your component is enhanced with a decorator for example, you'll need to pass the rawComponent. 75 | 76 | For example for a component like this: 77 | ```javascript 78 | 79 | import React, {PropTypes} from 'react'; 80 | import {createComponent, connect} from 'react-fela'; 81 | import R from 'ramda'; 82 | import getContextThemeDecorator from 'layout/themes/getContextThemeDecorator.react'; 83 | 84 | import {Text} from 'components/baseComponents'; 85 | const badge = ({theme}: BadgeProps) => ({ 86 | position: 'absolute', 87 | top: 0, 88 | right: 0, 89 | opacity: 0.85, 90 | color: 'white', 91 | padding: '3px 10px', 92 | borderRadius: '20%/50%', 93 | transform: 'translate(50%)', 94 | lineHeight: 1, 95 | overflow: 'hidden', 96 | whiteSpace: 'nowrap', 97 | }); 98 | 99 | const container = ({small, tiny}: ContainerProps) => ({ 100 | display: 'flex', 101 | position: 'relative', 102 | width: small ? 45 : tiny ? 25 : 70, 103 | height: small ? 45 : tiny ? 25 : 70, 104 | }); 105 | 106 | const Badge = createComponent(badge, Text); 107 | const Container = createComponent(container, 'div'); 108 | export const Avatar = ({badge, border, small, tiny, theme, ...props}) => ( 109 | 110 | 111 | {badge ? {badge} : null} 112 | 113 | ); 114 | 115 | Avatar.defaultProps = { 116 | border: true, 117 | }; 118 | Avatar.propTypes = { 119 | avatar: PropTypes.string, 120 | badge: PropTypes.string, 121 | border: PropTypes.bool, 122 | small: PropTypes.bool, 123 | tiny: PropTypes.bool, 124 | }; 125 | 126 | const enhance = R.pipe(getContextThemeDecorator); 127 | export default enhance(Avatar); 128 | ``` 129 | 130 | you write your story like this : 131 | 132 | ```javascript 133 | import React from 'react'; 134 | import {storiesOf} from '@kadira/storybook'; 135 | import Avatar, {Avatar as RawAvatar} from 'components/material/avatar.react'; 136 | 137 | export default FelaProvider => 138 | storiesOf('Avatar', module) 139 | .addDecorator(FelaProvider) 140 | .addWithProps('without picture', () => , RawAvatar) 141 | .addWithProps('without picture small', () => , RawAvatar) 142 | .addWithProps('without picture tiny', () => , RawAvatar) 143 | .addWithProps( 144 | 'with picture', 145 | () => , 146 | RawAvatar, 147 | ) 148 | [...] 149 | .addWithProps('with badge', () => , RawAvatar); 150 | 151 | ``` 152 | 153 | ## API 154 | 155 | ### addWithProps(kind, story, rawComponent) 156 | 157 | Show the story with the props. -------------------------------------------------------------------------------- /example/.storybook/config.js: -------------------------------------------------------------------------------- 1 | // IMPORTANT 2 | // --------- 3 | // This is an auto generated file with React CDK. 4 | // Do not modify this file. 5 | 6 | import { configure } from '@kadira/storybook'; 7 | 8 | function loadStories() { 9 | require('../stories'); 10 | } 11 | 12 | configure(loadStories, module); 13 | -------------------------------------------------------------------------------- /example/stories/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { setAddon, storiesOf } from '@kadira/storybook'; 3 | import { createComponent } from 'react-fela'; 4 | 5 | import initFelaProvider from './initFela'; 6 | import PropsAddon from '../../lib/index'; 7 | 8 | const FelaProvider = initFelaProvider(); 9 | 10 | const test = ({ color }) => ({ 11 | fontSize: 35, 12 | color, 13 | }); 14 | 15 | const Test = createComponent(test, 'h1'); 16 | Test.defaultProps = { color: '#333' }; 17 | Test.propTypes = { 18 | align: PropTypes.string, 19 | color: PropTypes.string.isRequired, 20 | fontFamily: PropTypes.string, 21 | fontSize: PropTypes.number, 22 | }; 23 | 24 | setAddon(PropsAddon); 25 | 26 | storiesOf('test', module) 27 | .addDecorator(FelaProvider) 28 | .addWithProps( 29 | 'Paris', 30 | () => Hello, 31 | Test, 32 | ) 33 | .addWithProps('Orleans', () => Hello, Test); 34 | 35 | storiesOf('test 2', module).addWithProps('Paris', () =>
test
); 36 | -------------------------------------------------------------------------------- /example/stories/initFela.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import React from 'react'; 3 | import { createRenderer } from 'fela'; 4 | import { Provider } from 'react-fela'; 5 | 6 | const renderer = createRenderer(); 7 | const ss = document.createElement('style'); 8 | ss.id = 'stylesheet'; 9 | document.body.appendChild(ss); 10 | const stylesheet = document.querySelector('#stylesheet'); 11 | 12 | export default () => 13 | story => { 14 | return ( 15 | 16 | {story()} 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /npm-debug.log.2678678117: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilix/storybook-addon-props-fela/b73250b7830ec92817bd5e999f4d4b74bb818c58/npm-debug.log.2678678117 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook-addon-props-fela", 3 | "version": "1.0.3", 4 | "description": "Show props of a fela component in storybook", 5 | "main": "lib/index.js", 6 | "author": "wcastand", 7 | "license": "MIT", 8 | "keywords": [ 9 | "fela", 10 | "storybook", 11 | "react", 12 | "addon", 13 | "props" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Kilix/storybook-addon-props-fela" 18 | }, 19 | "scripts": { 20 | "build": "babel src --out-dir lib --ignore spec.js,test.js", 21 | "build:dev": "babel -w src --out-dir lib --ignore spec.js,test.js", 22 | "format": "prettier --single-quote --trailing-comma all --tab-width 2 --bracket-spacing true --print-width 100 --write 'src/**/*.js'", 23 | "test": "npm run format && npm run test:all", 24 | "test:all": "jest", 25 | "test:dev": "jest --watch", 26 | "prebuild": "rimraf lib", 27 | "storybook": "start-storybook -p 9009 -c ./example/.storybook", 28 | "preversion": "npm run build", 29 | "release": "npm run release", 30 | "2npm": "publish" 31 | }, 32 | "devDependencies": { 33 | "@kadira/storybook": "^2.35.3", 34 | "babel-cli": "^6.24.0", 35 | "babel-jest": "^19.0.0", 36 | "babel-preset-env": "^1.2.2", 37 | "babel-preset-react": "^6.23.0", 38 | "fela": "4.3.2", 39 | "jest": "^19.0.2", 40 | "prettier": "^0.22.0", 41 | "publish": "^0.6.0", 42 | "react": "^15.4.2", 43 | "react-addons-test-utils": "^15.4.2", 44 | "react-dom": "^15.4.2", 45 | "react-fela": "^4.3.2", 46 | "react-test-renderer": "^15.4.2", 47 | "regenerator-runtime": "^0.10.3", 48 | "release": "^1.1.7", 49 | "rimraf": "^2.6.1" 50 | }, 51 | "dependencies": { 52 | "lodash": "4.17.4" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /screenshot-props.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kilix/storybook-addon-props-fela/b73250b7830ec92817bd5e999f4d4b74bb818c58/screenshot-props.png -------------------------------------------------------------------------------- /src/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renderList 1`] = ` 4 |
5 |
17 | 25 | color 26 | 27 | 35 | #000 36 | 37 | 45 | ✗ 46 | 47 | 55 | #333 56 | 57 |
58 |
70 | 78 | children 79 | 80 | 88 | Hello 89 | 90 | 98 | ✗ 99 | 100 | 108 | - 109 | 110 |
111 |
112 | `; 113 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import compose from 'lodash/fp/compose'; 3 | import omit from 'lodash/fp/omit'; 4 | import isNil from 'lodash/fp/isNil'; 5 | 6 | const filterProps = omit(['children']); 7 | export const parseProps = (props, defaultProps, propTypes) => 8 | Object.keys(!isNil(propTypes) ? propTypes : props).reduce( 9 | (arr, x) => 10 | Object.assign({}, arr, { 11 | [x]: { 12 | prop: props[x], 13 | required: !isNil(propTypes) ? propTypes[x].required : null, 14 | defaultProps: !isNil(defaultProps) ? defaultProps[x] : null, 15 | }, 16 | }), 17 | {}, 18 | ); 19 | export const pickProps = raw => 20 | story => { 21 | const defaultProps = !isNil(raw.defaultProps) ? raw.defaultProps : null; 22 | const propTypes = !isNil(story.type.__docgenInfo) 23 | ? story.type.__docgenInfo.props 24 | : !isNil(raw.propTypes) ? raw.propTypes : null; 25 | return parseProps(story.props, defaultProps, propTypes); 26 | }; 27 | 28 | export const renderList = props => 29 | Object.keys(props).reduce( 30 | (arr, prop) => { 31 | arr.push( 32 |
33 | {prop} 34 | 35 | {!isNil(props[prop].prop) ? props[prop].prop.toString() : '-'} 36 | 37 | 38 | {!isNil(props[prop].required) ? String.fromCharCode(10004) : '-'} 39 | 40 | 41 | {!isNil(props[prop].defaultProps) ? props[prop].defaultProps.toString() : '-'} 42 | 43 |
, 44 | ); 45 | return arr; 46 | }, 47 | [], 48 | ); 49 | export default { 50 | addWithProps(kind, story, raw = {}) { 51 | const customStory = () => ( 52 |
53 | {story()} 54 |
55 |
56 | Prop 57 | Value 58 | IsRequired 59 | Default 60 |
61 |
62 | {compose(renderList, filterProps, pickProps(raw))(story())} 63 |
64 |
65 |
66 | ); 67 | return this.add(kind, customStory); 68 | }, 69 | }; 70 | 71 | const styles = { 72 | props: { 73 | fontFamily: 'Roboto, sans-serif', 74 | color: '#333', 75 | borderTop: '1px solid #AFAFAF', 76 | marginTop: 35, 77 | }, 78 | header: { 79 | flex: 1, 80 | display: 'flex', 81 | flexDirection: 'row', 82 | justifyContent: 'flex-start', 83 | alignItems: 'flex-start', 84 | borderBottom: '1px solid #AFAFAF', 85 | fontSize: '18px', 86 | }, 87 | head: { 88 | flex: 1, 89 | padding: '12px 5px', 90 | }, 91 | body: { 92 | flex: 1, 93 | display: 'flex', 94 | flexDirection: 'column', 95 | justifyContent: 'flex-start', 96 | alignItems: 'stretch', 97 | fontSize: '14px', 98 | }, 99 | row: { 100 | flex: 1, 101 | display: 'flex', 102 | flexDirection: 'row', 103 | justifyContent: 'flex-start', 104 | alignItems: 'flex-start', 105 | borderBottom: '1px solid rgba(175, 175, 175, .3)', 106 | }, 107 | item: { 108 | flex: 1, 109 | padding: '12px 5px', 110 | }, 111 | }; 112 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { pickProps, parseProps, renderList } from './index'; 4 | 5 | const Provider = ({ children }) =>
{children}
; 6 | const Test = ({ color, children }) =>
{children}
; 7 | const TestWithDefault = ({ color, children }) =>
{children}
; 8 | TestWithDefault.defaultProps = { color: '#333' }; 9 | 10 | const root = () => ( 11 | 12 | Hello 13 | 14 | ); 15 | const deepRoot = () => ( 16 | 17 | 18 | Hello 19 | 20 | 21 | ); 22 | const noRoot = () => Hello; 23 | const withDefault = () => Hello; 24 | 25 | test('pickProps', () => { 26 | const result = pickProps(TestWithDefault)(withDefault()); 27 | const expected = { 28 | color: { prop: '#000', required: null, defaultProps: '#333' }, 29 | children: { prop: 'Hello', required: null, defaultProps: undefined }, 30 | }; 31 | expect(result).toEqual(expected); 32 | }); 33 | 34 | test('parseProps', () => { 35 | const result = parseProps({}, { cover: '#333' }, { cover: { required: true } }); 36 | const result2 = parseProps({ cover: '#123' }, { cover: '#333' }); 37 | expect(result).toEqual({ cover: { prop: undefined, required: true, defaultProps: '#333' } }); 38 | expect(result2).toEqual({ cover: { prop: '#123', required: null, defaultProps: '#333' } }); 39 | }); 40 | 41 | test('renderList', () => { 42 | const base = { 43 | color: { prop: '#000', required: null, defaultProps: '#333' }, 44 | children: { prop: 'Hello', required: null, defaultProps: undefined }, 45 | }; 46 | const result = renderList(base); 47 | const component = renderer.create(
{result}
); 48 | const tree = component.toJSON(); 49 | expect(tree).toMatchSnapshot(); 50 | }); 51 | --------------------------------------------------------------------------------