├── src ├── doc │ ├── redemo.png │ ├── index.scss │ ├── index.js │ └── template.html ├── iconfont │ ├── iconfont.eot │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.svg ├── index.scss └── index.js ├── .gitignore ├── .editorconfig ├── .babelrc ├── webpack.config.js ├── package.json ├── README.md └── webpack-dist.config.js /src/doc/redemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imweb/redemo/HEAD/src/doc/redemo.png -------------------------------------------------------------------------------- /src/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imweb/redemo/HEAD/src/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imweb/redemo/HEAD/src/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | .npm/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /src/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imweb/redemo/HEAD/src/iconfont/iconfont.woff -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=2 7 | -------------------------------------------------------------------------------- /src/doc/index.scss: -------------------------------------------------------------------------------- 1 | .my-demo { 2 | width: 600px; 3 | margin-top: 20px; 4 | left: 50%; 5 | transform: translate(-50%, 0); 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | [ 9 | "transform-runtime", 10 | { 11 | "polyfill": false 12 | } 13 | ], 14 | [ 15 | "import", 16 | { 17 | "libraryName": "antd", 18 | "style": "css" 19 | } 20 | ] 21 | ], 22 | "env": { 23 | "dist": { 24 | "plugins": [ 25 | [ 26 | "replace-require-suffix", 27 | { 28 | "from": "scss", 29 | "to": "css" 30 | } 31 | ] 32 | ] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/doc/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'react-dom'; 3 | 4 | import Button from 'antd/lib/button'; 5 | import 'antd/lib/button/style/index.css'; 6 | 7 | import ReDemo from '../index'; 8 | import './index.scss'; 9 | 10 | const docgen = require('!!docgen-loader!../index'); 11 | 12 | const doc = ` 13 | ### react component used to demonstrate react component 14 | - in top section is demo instance to play 15 | - circle button in right are toggle for **component propTypes** and **demo source code** 16 | - propTypes table show all prop's info for this component 17 | - in bottom is source code for this demo 18 | - red prop name means it's required 19 | ` 20 | 21 | const code = ` 22 | import ReDemo from 'redemo'; 23 | 24 | 30 | 31 | 32 | ` 33 | 34 | const demo = ( 35 | 45 | 46 | 47 | ) 48 | 49 | render(demo, window.document.getElementById('app')); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { WebPlugin } = require('web-webpack-plugin'); 4 | 5 | module.exports = { 6 | output: { 7 | publicPath: '', 8 | filename: '[name].js', 9 | }, 10 | resolve: { 11 | // 加快搜索速度 12 | modules: [path.resolve(__dirname, 'node_modules')], 13 | // es tree-shaking 14 | mainFields: ['jsnext:main', 'browser', 'main'], 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | // cacheDirectory 缓存babel编译结果加快重新编译速度 21 | loader: 'babel-loader?cacheDirectory', 22 | // 只命中src目录里的js文件,加快webpack搜索速度 23 | include: path.resolve(__dirname, 'src') 24 | }, 25 | { 26 | test: /\.scss$/, 27 | loaders: ['style-loader', 'css-loader', 'sass-loader'], 28 | include: path.resolve(__dirname, 'src') 29 | }, 30 | { 31 | test: /\.css$/, 32 | loaders: ['style-loader', 'css-loader'], 33 | }, 34 | { 35 | test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/, 36 | loader: 'file-loader', 37 | }, 38 | ] 39 | }, 40 | entry: { 41 | doc: './src/doc/index.js', 42 | }, 43 | plugins: [ 44 | new WebPlugin({ 45 | template: './src/doc/template.html', 46 | filename: 'index.html' 47 | }), 48 | ], 49 | devtool: 'source-map', 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redemo", 3 | "version": "2.0.0", 4 | "description": "react demo component", 5 | "keywords": [ 6 | "react", 7 | "demo", 8 | "component", 9 | "doc", 10 | "redemo" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:imweb/redemo.git" 15 | }, 16 | "main": "index.js", 17 | "scripts": { 18 | "dev": "webpack-dev-server --open", 19 | "doc:pub": "NODE_ENV=production webpack --config webpack-dist.config.js && rm -rf .doc", 20 | "npm:scss": "node-sass src/ -o .npm/", 21 | "npm:js": "BABEL_ENV=dist babel src/ --out-dir .npm/ --source-maps", 22 | "npm:pub": "npm run npm:scss && npm run npm:js && cp ./package.json .npm/ && cp -R ./src/iconfont ./.npm/iconfont && npm publish .npm/ && rm -rf .npm" 23 | }, 24 | "author": "halwu", 25 | "license": "ISC", 26 | "dependencies": { 27 | "antd": "^2.10.2", 28 | "babel-runtime": "^6.23.0", 29 | "classnames": "^2.2.5", 30 | "prop-types": "^15.5.10", 31 | "react": "^16.0.0", 32 | "react-dom": "^16.0.0", 33 | "prop-types": "^15.6.0", 34 | "react-highlight": "^0.10.0", 35 | "remd": "^1.0.0" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.24.1", 39 | "babel-core": "^6.25.0", 40 | "babel-loader": "^7.1.0", 41 | "babel-plugin-import": "^1.2.1", 42 | "babel-plugin-replace-require-suffix": "^1.0.0", 43 | "babel-plugin-transform-runtime": "^6.23.0", 44 | "babel-preset-es2015": "^6.24.1", 45 | "babel-preset-react": "^6.24.1", 46 | "babel-preset-stage-2": "^6.24.1", 47 | "css-loader": "^0.28.4", 48 | "docgen-loader": "^1.3.4", 49 | "end-webpack-plugin": "^1.0.0", 50 | "file-loader": "^0.11.2", 51 | "gh-pages": "^1.0.0", 52 | "node-sass": "^4.5.3", 53 | "react-docgen": "^3.0.0-beta4", 54 | "sass-loader": "^6.0.6", 55 | "style-loader": "^0.18.2", 56 | "web-webpack-plugin": "^1.7.2", 57 | "webpack": "^3.0.0", 58 | "webpack-dev-server": "^2.5.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/doc/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 11 | react demo component 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Npm Package](https://img.shields.io/npm/v/redemo.svg?style=flat-square)](https://www.npmjs.com/package/redemo) 2 | [![Npm Downloads](http://img.shields.io/npm/dm/redemo.svg?style=flat-square)](https://www.npmjs.com/package/redemo) 3 | [![Dependency Status](https://david-dm.org/gwuhaolin/redemo.svg?style=flat-square)](https://npmjs.org/package/redemo) 4 | 5 | [中文文档](https://github.com/gwuhaolin/blog/issues/1) 6 | 7 | # redemo 8 | When you write a react component, you may need tell others how to use it by write some demos. 9 | Redemo is used to help write demo for react component in a easy way, is't pretty and simple to use. 10 | 11 |

12 | 13 | redemo 14 | 15 |

16 | 17 | ## feature 18 | - doc: write doc in markdown for this demo 19 | - code: show source code for this demo 20 | - propTypes: auto list react component `propTypes` document form comment in your component source instead of write by yourself 21 | 22 | ## install 23 | use it from npm in webpack 24 | ```bash 25 | npm i redemo 26 | ``` 27 | 28 | then install webpack dependencies 29 | ```html 30 | npm i raw-loader react-docgen@next docgen-loader --save-dev 31 | ``` 32 | 33 | ## example 34 | Let's write a demo for a `Button` component 35 | 36 | ```js 37 | import ReDemo from 'redemo'; 38 | import Button from './button'; 39 | 40 | // load propTypes by docgen-loader from button component source code 41 | const docgen = require('!!docgen-loader!../button'); 42 | 43 | // load source code by raw-loader from button component source code 44 | const code = require('!!raw-loader!../button'); 45 | 46 | // write doc for this demo in markdown 47 | const doc = ` 48 | ### react component used to demonstrate react component 49 | #### structure 50 | - in top section is demo instance to play 51 | - circle button in right are toggle for **component propTypes** and **demo source code** 52 | - propTypes table show all prop's info for this component 53 | - in bottom is source code for this demo 54 | ` 55 | 56 | 64 | 65 | 66 | ``` 67 | 68 | ## API 69 | see `ReDemo`'s all props [here](https://imweb.github.io/redemo/) 70 | 71 | ## In practice 72 | - [imuix](http://imweb.github.io/imuix/) 73 | -------------------------------------------------------------------------------- /webpack-dist.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); 4 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 5 | const EndWebpackPlugin = require('end-webpack-plugin'); 6 | const { WebPlugin } = require('web-webpack-plugin'); 7 | const ghpages = require('gh-pages'); 8 | 9 | const outputPath = path.resolve(__dirname, '.doc'); 10 | module.exports = { 11 | output: { 12 | path: outputPath, 13 | publicPath: '', 14 | filename: '[name]_[chunkhash].js', 15 | }, 16 | resolve: { 17 | // 加快搜索速度 18 | modules: [path.resolve(__dirname, 'node_modules')], 19 | // es tree-shaking 20 | mainFields: ['jsnext:main', 'browser', 'main'], 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.js$/, 26 | // cacheDirectory 缓存babel编译结果加快重新编译速度 27 | loader: 'babel-loader?cacheDirectory', 28 | // 只命中src目录里的js文件,加快webpack搜索速度 29 | include: path.resolve(__dirname, 'src') 30 | }, 31 | { 32 | test: /\.scss$/, 33 | loaders: ['style-loader', 'css-loader?minimize', 'sass-loader'], 34 | include: path.resolve(__dirname, 'src') 35 | }, 36 | { 37 | test: /\.css$/, 38 | loaders: ['style-loader', 'css-loader?minimize'], 39 | }, 40 | { 41 | test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/, 42 | loader: 'file-loader', 43 | }, 44 | ] 45 | }, 46 | entry: { 47 | doc: './src/doc/index.js', 48 | }, 49 | plugins: [ 50 | new DefinePlugin({ 51 | 'process.env': { 52 | 'NODE_ENV': JSON.stringify('production') 53 | } 54 | }), 55 | new UglifyJsPlugin({ 56 | // 最紧凑的输出 57 | beautify: false, 58 | // 删除所有的注释 59 | comments: false, 60 | compress: { 61 | // 在UglifyJs删除没有用到的代码时不输出警告 62 | warnings: false, 63 | // 删除所有的 `console` 语句,可以兼容ie浏览器 64 | drop_console: true, 65 | // 内嵌定义了但是只用到一次的变量 66 | collapse_vars: true, 67 | // 提取出出现多次但是没有定义成变量去引用的静态值 68 | reduce_vars: true, 69 | } 70 | }), 71 | new WebPlugin({ 72 | template: './src/doc/template.html', 73 | filename: 'index.html' 74 | }), 75 | new EndWebpackPlugin(() => { 76 | ghpages.publish(outputPath, { dotfiles: true }, (err) => { 77 | if (err) { 78 | console.error('push doc to gh-pages fail'); 79 | } else { 80 | console.error('push doc to gh-pages succ'); 81 | } 82 | }) 83 | }), 84 | ] 85 | }; 86 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | $colorBg: #fff; 2 | $colorBorder: #e9e9e9; 3 | $paddingRL: 15px; 4 | $paddingTB: 20px; 5 | 6 | @mixin ant-icon { 7 | @font-face { 8 | font-family: 'anticon'; 9 | src: url('./iconfont/iconfont.eot') format('embedded-opentype'), /* chrome、firefox */ 10 | url('./iconfont/iconfont.woff') format('woff'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ 11 | url('./iconfont/iconfont.ttf') format('truetype'), /* iOS 4.1- */ 12 | url('./iconfont/iconfont.svg') format('svg'); 13 | } 14 | 15 | .anticon { 16 | display: inline-block; 17 | font-style: normal; 18 | vertical-align: baseline; 19 | text-align: center; 20 | text-transform: none; 21 | line-height: 1; 22 | text-rendering: optimizeLegibility; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | &:before { 26 | display: block; 27 | font-family: "anticon" !important; 28 | } 29 | } 30 | 31 | .anticon-copy:before { 32 | content: "\e648"; 33 | } 34 | 35 | .anticon-exception:before { 36 | content: "\e665"; 37 | } 38 | 39 | .anticon-bars:before { 40 | content: "\e639"; 41 | } 42 | 43 | .anticon-code-o:before { 44 | content: "\e636"; 45 | } 46 | 47 | .anticon-check-circle:before { 48 | content: "\e630"; 49 | } 50 | 51 | .anticon-close-circle:before { 52 | content: "\e62e"; 53 | } 54 | } 55 | 56 | @include ant-icon; 57 | 58 | @mixin reset { 59 | @font-face { 60 | font-family: "Helvetica Neue For Number"; 61 | src: local("Helvetica Neue"); 62 | unicode-range: U+30-39; 63 | } 64 | * { 65 | font-family: "Helvetica Neue For Number", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; 66 | font-size: 12px; 67 | line-height: 1.5; 68 | box-sizing: border-box; 69 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 70 | } 71 | a { 72 | text-decoration: none; 73 | color: #108ee9; 74 | } 75 | } 76 | 77 | .redemo { 78 | position: relative; 79 | border: 1px solid darken($colorBorder, 2%); 80 | border-radius: 4px; 81 | display: inline-block; 82 | 83 | &-toolbar { 84 | position: absolute; 85 | right: 10px; 86 | top: 10px; 87 | 88 | .ant-btn-circle { 89 | margin-left: 5px; 90 | } 91 | } 92 | 93 | &-run { 94 | padding: $paddingTB $paddingRL; 95 | } 96 | 97 | &-md { 98 | @include reset; 99 | color: rgba(0, 0, 0, 0.65); 100 | text-align: left; 101 | position: relative; 102 | padding: $paddingTB $paddingRL; 103 | border-top: 1px solid $colorBorder; 104 | } 105 | 106 | &-code { 107 | @include reset; 108 | text-align: left; 109 | position: relative; 110 | border-top: 1px dashed $colorBorder; 111 | padding: $paddingRL; 112 | 113 | .hljs { 114 | padding: 0; 115 | } 116 | 117 | &-copy { 118 | position: absolute; 119 | right: $paddingRL - 5px; 120 | top: $paddingRL; 121 | } 122 | 123 | pre { 124 | margin: 0; 125 | } 126 | } 127 | 128 | &-table { 129 | @include reset; 130 | color: rgba(0, 0, 0, 0.65); 131 | text-align: left; 132 | border-top: 1px dashed $colorBorder; 133 | 134 | th, td { 135 | word-break: normal !important; 136 | } 137 | 138 | &-required { 139 | color: #f46e65; 140 | } 141 | 142 | .ant-table-small { 143 | border: none; 144 | } 145 | 146 | .ant-table-title { 147 | margin-left: 8px; 148 | color: #000; 149 | font-weight: 500; 150 | } 151 | } 152 | 153 | &-tooltip { 154 | .ant-tooltip-inner { 155 | background-color: $colorBg; 156 | } 157 | 158 | .ant-tooltip-arrow { 159 | border-right-color: $colorBg; 160 | } 161 | } 162 | 163 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import Remd from 'remd'; 5 | 6 | import Highlight from 'react-highlight'; 7 | import 'highlight.js/styles/arduino-light.css'; 8 | 9 | import message from 'antd/lib/message'; 10 | import 'antd/lib/message/style/index.css'; 11 | 12 | import Button from 'antd/lib/button'; 13 | import 'antd/lib/button/style/index.css'; 14 | 15 | import Table from 'antd/lib/table'; 16 | import 'antd/lib/table/style/index.css'; 17 | 18 | import Tag from 'antd/lib/tag'; 19 | import 'antd/lib/tag/style/index.css'; 20 | 21 | import Tooltip from 'antd/lib/tooltip'; 22 | import 'antd/lib/tooltip/style/index.css'; 23 | 24 | import './index.scss'; 25 | 26 | /** 27 | * react component used to demonstrate react component 28 | */ 29 | export default class Redemo extends Component { 30 | 31 | static propTypes = { 32 | /** 33 | * component instance 34 | */ 35 | children: PropTypes.any.isRequired, 36 | /** 37 | * demo source code 38 | * - if it's void will not display 39 | */ 40 | code: PropTypes.string, 41 | /** 42 | * explanation to this demo 43 | * - support markdown 44 | * - if it's void will not display 45 | */ 46 | doc: PropTypes.string, 47 | /** 48 | * component's info load from [react-docgen](https://github.com/reactjs/react-docgen) 49 | * - support markdown 50 | * - if it's void will not display prop types table 51 | */ 52 | docgen: PropTypes.arrayOf(PropTypes.shape({ 53 | description: PropTypes.string, 54 | props: PropTypes.object, 55 | })), 56 | /** 57 | * whether show source code 58 | */ 59 | codeVisible: PropTypes.bool, 60 | /** 61 | * whether show propTypes 62 | */ 63 | propTypeVisible: PropTypes.bool, 64 | /** 65 | * whether show methods 66 | */ 67 | methodsVisible: PropTypes.bool, 68 | /** 69 | * append className to Redemo 70 | */ 71 | className: PropTypes.string, 72 | /** 73 | * className pass to markdown 74 | */ 75 | mdClassName: PropTypes.string, 76 | /** 77 | * set style for Redemo 78 | */ 79 | style: PropTypes.object, 80 | } 81 | 82 | static defaultProps = { 83 | codeVisible: false, 84 | propTypeVisible: true, 85 | methodsVisible: false, 86 | docgen: [] 87 | } 88 | 89 | propTypesTableColumns = [ 90 | { 91 | title: 'name', 92 | key: 'name', 93 | render: (_, record) => { 94 | const { propName, required } = record; 95 | return ( 96 | {propName} 98 | ) 99 | } 100 | }, { 101 | title: 'type', 102 | key: 'type', 103 | render: (_, record) => { 104 | const { type } = record; 105 | return ( 106 | 107 | ) 108 | } 109 | }, { 110 | title: 'description', 111 | dataIndex: 'description', 112 | key: 'description', 113 | render: (_, record) => { 114 | const { description } = record; 115 | if (typeof description === 'string' && description.length > 0) { 116 | const { mdClassName } = this.props; 117 | return {description}; 118 | } 119 | return '-'; 120 | } 121 | }, { 122 | title: 'defaultValue', 123 | dataIndex: 'defaultValue', 124 | key: 'defaultValue', 125 | render: (_, record) => { 126 | const { defaultValue = {}, type = {} } = record; 127 | let { value } = defaultValue; 128 | if (value === undefined) { 129 | return '-'; 130 | } 131 | if (['object', 'shape'].indexOf(type.name) >= 0) { 132 | return ( 133 | 135 | }> 136 | object 137 | 138 | ) 139 | } 140 | return value; 141 | } 142 | } 143 | ]; 144 | 145 | methodsTableColumns = [ 146 | { 147 | title: 'name', 148 | key: 'name', 149 | render: (_, record) => { 150 | const { name } = record; 151 | if (name) { 152 | return name; 153 | } 154 | return '-'; 155 | } 156 | }, { 157 | title: 'description', 158 | key: 'description', 159 | render: (_, record) => { 160 | const { description } = record; 161 | if (description) { 162 | return description; 163 | } 164 | return '-'; 165 | } 166 | }, { 167 | title: 'params', 168 | key: 'params', 169 | render: (_, record) => { 170 | const { params } = record; 171 | if (Array.isArray(params)) { 172 | return params.map(({ description, name, type }) => ( 173 |
174 | 175 | {name} 176 | {description} 177 |
178 | )); 179 | } 180 | return '-'; 181 | } 182 | }, { 183 | title: 'returns', 184 | key: 'returns', 185 | render: (_, record) => { 186 | const { returns } = record; 187 | if (returns) { 188 | const { description, type } = returns; 189 | return ( 190 |
191 | 192 | {description} 193 |
194 | ) 195 | }else { 196 | return null; 197 | } 198 | } 199 | }, 200 | ]; 201 | 202 | state = { 203 | /** 204 | * whether show source code 205 | */ 206 | codeVisible: this.props.codeVisible, 207 | /** 208 | * whether show propTypes 209 | */ 210 | propTypeVisible: this.props.propTypeVisible, 211 | /** 212 | * whether show methods 213 | */ 214 | methodsVisible: this.props.methodsVisible, 215 | } 216 | 217 | componentWillReceiveProps(nextProps) { 218 | const { codeVisible, propTypeVisible, methodsVisible } = this.props; 219 | if (nextProps.codeVisible !== codeVisible) { 220 | this._toggleCode(); 221 | } 222 | if (nextProps.propTypeVisible !== propTypeVisible) { 223 | this._togglePropTypes(); 224 | } 225 | if (nextProps.methodsVisible !== methodsVisible) { 226 | this._toggleMethods(); 227 | } 228 | } 229 | 230 | _getPropTypes = () => { 231 | const { docgen } = this.props; 232 | return (docgen[0] || {}).props; 233 | } 234 | 235 | _getMethods = () => { 236 | const { docgen } = this.props; 237 | return (docgen[0] || {}).methods; 238 | } 239 | 240 | _toggleCode = () => { 241 | this.setState({ 242 | codeVisible: !this.state.codeVisible 243 | }) 244 | } 245 | 246 | _togglePropTypes = () => { 247 | this.setState({ 248 | propTypeVisible: !this.state.propTypeVisible 249 | }) 250 | } 251 | 252 | _toggleMethods = () => { 253 | this.setState({ 254 | methodsVisible: !this.state.methodsVisible 255 | }) 256 | } 257 | 258 | _copyCode = () => { 259 | const { code } = this.props; 260 | try { 261 | copyToClipboard(code); 262 | message.success('copy successful'); 263 | } catch (err) { 264 | message.error(`copy failed, you browser don't support copy`); 265 | selectElementContents(this.refs['code']); 266 | } 267 | } 268 | 269 | _renderDoc = () => { 270 | const { code, doc, mdClassName } = this.props; 271 | const propTypes = this._getPropTypes(); 272 | const methods = this._getMethods(); 273 | return ( 274 |
275 | 276 | {propTypes ?
300 | ) 301 | } 302 | 303 | _renderPropTypeTable = () => { 304 | const { propTypeVisible } = this.state; 305 | const propTypes = this._getPropTypes(); 306 | if (propTypes && propTypeVisible) { 307 | const propTypesKeys = Object.keys(propTypes); 308 | // show PropTypeTable only if has more than one PropType 309 | if (propTypesKeys.length > 0) { 310 | const dataSource = propTypesKeys.map(propName => Object.assign({ propName }, propTypes[propName])); 311 | return ( 312 | 'PropTypes'} 314 | rowKey="propName" 315 | className="redemo-table" 316 | columns={this.propTypesTableColumns} 317 | dataSource={dataSource} 318 | pagination={false} 319 | size="small" 320 | /> 321 | ) 322 | } 323 | } 324 | return null; 325 | } 326 | 327 | _renderMethodsTable = () => { 328 | const { methodsVisible } = this.state; 329 | const methods = this._getMethods(); 330 | if (methods && methodsVisible) { 331 | // show MethodsTable only if has more than one method 332 | if (methods.length > 0) { 333 | return ( 334 |
'Methods'} 336 | rowKey="name" 337 | className="redemo-table" 338 | columns={this.methodsTableColumns} 339 | dataSource={methods} 340 | pagination={false} 341 | size="small" 342 | /> 343 | ) 344 | } 345 | } 346 | return null; 347 | } 348 | 349 | _renderCode = () => { 350 | const { code } = this.props; 351 | const { codeVisible } = this.state; 352 | if (code && codeVisible) { 353 | return ( 354 |
355 | 356 |
366 | ) 367 | } 368 | return null; 369 | } 370 | 371 | render() { 372 | const { className, style, children } = this.props; 373 | return ( 374 |
375 |
{children}
376 | {this._renderDoc()} 377 | {this._renderPropTypeTable()} 378 | {this._renderMethodsTable()} 379 | {this._renderCode()} 380 |
381 | ) 382 | } 383 | } 384 | 385 | /** 386 | * PropTypeValueTag show in propTypes Table for complex prop 387 | */ 388 | class PropTypeValueTag extends Component { 389 | 390 | static propTypes = { 391 | type: PropTypes.object 392 | } 393 | 394 | static defaultProps = { 395 | type: {} 396 | } 397 | 398 | render() { 399 | const { type } = this.props; 400 | if (type) { 401 | const { name, value } = type; 402 | if (value !== undefined) { 403 | return ( 404 | 406 | }> 407 | {name} 408 | 409 | ) 410 | } else { 411 | return {name} 412 | } 413 | } else { 414 | return null; 415 | } 416 | } 417 | } 418 | 419 | /** 420 | * display a JSON in pop 421 | */ 422 | class ObjectView extends Component { 423 | static propTypes = { 424 | object: PropTypes.any 425 | } 426 | 427 | static defaultProps = { 428 | object: {} 429 | } 430 | 431 | render() { 432 | const { object } = this.props; 433 | let code = object; 434 | if (typeof object === 'object') { 435 | code = JSON.stringify(code, null, 4); 436 | } 437 | return ( 438 | {code} 439 | ) 440 | } 441 | } 442 | 443 | /** 444 | * Copies a string to the clipboard. Must be called from within an 445 | * event handler such as click. May return false if it failed, but 446 | * this is not always possible. Browser support for Chrome 43+, 447 | * Firefox 42+, Safari 10+, Edge and IE 10+. 448 | * IE: The clipboard feature may be disabled by an administrator. By 449 | * default a prompt is shown the first time the clipboard is 450 | * used (per session). 451 | * @param text 452 | * @returns {boolean} 453 | */ 454 | function copyToClipboard(text) { 455 | if (window.clipboardData && window.clipboardData.setData) { 456 | // IE specific code path to prevent textarea being shown while dialog is visible. 457 | return clipboardData.setData('Text', text); 458 | 459 | } else { //noinspection JSUnresolvedVariable,JSUnresolvedFunction 460 | if (document.queryCommandSupported && document.queryCommandSupported('copy')) { 461 | let textarea = document.createElement('textarea'); 462 | textarea.textContent = text; 463 | textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in MS Edge. 464 | document.body.appendChild(textarea); 465 | textarea.select(); 466 | try { 467 | return document.execCommand('copy'); // Security exception may be thrown by some browsers. 468 | } catch (err) { 469 | throw err; 470 | } finally { 471 | document.body.removeChild(textarea); 472 | } 473 | } 474 | } 475 | } 476 | 477 | /** 478 | * If you want to select all the content of an element (contenteditable or not) in Chrome, here's how. 479 | * This will also work in Firefox, Safari 3+, Opera 9+ (possibly earlier versions too) and IE 9. 480 | * You can also create selections down to the character level. 481 | * The APIs you need are DOM Range (current spec is DOM Level 2, see also MDN) and Selection, 482 | * which is being specified as part of a new Range spec (MDN docs). 483 | * @param el 484 | * http://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element 485 | */ 486 | function selectElementContents(el) { 487 | let range = document.createRange(); 488 | range.selectNodeContents(el); 489 | let sel = window.getSelection(); 490 | sel.removeAllRanges(); 491 | sel.addRange(range); 492 | } -------------------------------------------------------------------------------- /src/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Created by FontForge 20120731 at Sun Nov 20 19:53:22 2016 6 | By admin 7 | 8 | 9 | 10 | 24 | 26 | 28 | 30 | 32 | 34 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 57 | 60 | 63 | 66 | 70 | 74 | 78 | 82 | 85 | 88 | 91 | 95 | 98 | 101 | 104 | 107 | 110 | 112 | 114 | 116 | 118 | 120 | 122 | 124 | 126 | 129 | 132 | 135 | 137 | 141 | 144 | 147 | 150 | 154 | 157 | 159 | 162 | 166 | 169 | 173 | 176 | 180 | 182 | 184 | 193 | 196 | 199 | 202 | 206 | 210 | 213 | 217 | 222 | 224 | 227 | 231 | 234 | 238 | 246 | 251 | 256 | 259 | 263 | 266 | 272 | 275 | 278 | 282 | 286 | 289 | 292 | 295 | 298 | 301 | 305 | 308 | 311 | 313 | 318 | 321 | 325 | 329 | 332 | 335 | 338 | 341 | 346 | 349 | 354 | 357 | 361 | 367 | 370 | 375 | 379 | 383 | 386 | 389 | 392 | 395 | 401 | 405 | 408 | 412 | 415 | 419 | 423 | 428 | 433 | 436 | 440 | 443 | 447 | 450 | 453 | 455 | 458 | 464 | 467 | 471 | 475 | 479 | 482 | 486 | 489 | 495 | 499 | 504 | 507 | 510 | 514 | 518 | 522 | 527 | 532 | 536 | 539 | 543 | 546 | 549 | 553 | 557 | 562 | 566 | 571 | 575 | 578 | 582 | 584 | 587 | 590 | 593 | 596 | 599 | 602 | 605 | 608 | 611 | 614 | 617 | 620 | 624 | 626 | 630 | 634 | 637 | 640 | 642 | 645 | 648 | 651 | 654 | 657 | 660 | 663 | 666 | 669 | 672 | 675 | 678 | 681 | 684 | 687 | 690 | 693 | 697 | 702 | 706 | 710 | 714 | 717 | 720 | 724 | 728 | 731 | 732 | 733 | --------------------------------------------------------------------------------