├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── bin └── styleguidist ├── contributing.md ├── core ├── build.js ├── index.js ├── lang-jsx.js └── middleware │ └── index.js ├── example ├── lib │ └── components │ │ ├── Button │ │ ├── Button.css │ │ ├── Button.js │ │ ├── Readme.md │ │ └── index.js │ │ └── Placeholder │ │ ├── Placeholder.css │ │ ├── Placeholder.js │ │ ├── Readme.md │ │ └── index.js └── styleguide.config.js ├── gulpfile.js ├── license.md ├── loaders ├── examples.loader.js └── styleguide.loader.js ├── mochasetup.js ├── package.json ├── readme.md ├── src ├── build.js ├── components │ ├── Components │ │ ├── Components.js │ │ └── index.js │ ├── Editor │ │ ├── Editor.css │ │ ├── Editor.js │ │ └── index.js │ ├── Layout │ │ ├── Layout.css │ │ ├── Layout.js │ │ └── index.js │ ├── Playground │ │ ├── Playground.css │ │ ├── Playground.js │ │ └── index.js │ ├── Preview │ │ ├── Preview.css │ │ ├── Preview.js │ │ └── index.js │ ├── Props │ │ ├── Props.css │ │ ├── Props.js │ │ └── index.js │ ├── ReactComponent │ │ ├── ReactComponent.css │ │ ├── ReactComponent.js │ │ └── index.js │ ├── StyleGuide │ │ ├── StyleGuide.js │ │ └── index.js │ └── Wrapper │ │ ├── Wrapper.js │ │ └── index.js ├── index.js ├── make-webpack-config.js ├── server.js ├── styles.css ├── templates │ └── index.html └── utils │ ├── codemirror-jsx.js │ ├── config.js │ ├── server.js │ └── utils.js └── test ├── examples.loader.spec.js ├── mocha.opts └── utils.utils.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": [ 5 | "react-hmre" 6 | ] 7 | } 8 | }, 9 | "presets": ["es2015", "react", "stage-0"] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,yml}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true, 7 | "mocha": true 8 | }, 9 | "ecmaFeatures": { 10 | "modules": true, 11 | "classes": true, 12 | "jsx": true 13 | }, 14 | "plugins": [ 15 | "react" 16 | ], 17 | "rules": { // http://eslint.org/docs/rules/ 18 | // Possible errors 19 | "no-cond-assign": [2, "always"], // Disallow assignment in conditional expressions 20 | "no-console": 0, // Allow use of console 21 | "no-constant-condition": 1, // Disallow use of constant expressions in conditions 22 | "no-control-regex": 2, // Disallow Controls Characters in Regular Expressions 23 | "no-debugger": 1, // Disallow use of debugger 24 | "no-dupe-keys": 2, // Disallow duplicate keys 25 | "no-duplicate-case": 2, // Disallow a duplicate case label 26 | "no-empty-character-class": 2, // Disallow empty character classes in regular expressions 27 | "no-empty": 1, // Disallow empty statements 28 | "no-ex-assign": 1, // Disallow assigning to the exception in a catch block 29 | "no-extra-boolean-cast": 2, // Disallow double-negation boolean casts in a boolean context 30 | "no-extra-semi": 2, // Disallow unnecessary semicolons 31 | "no-func-assign": 2, // Disallow overwriting functions written as function declarations 32 | "no-inner-declarations": [2, "functions"], 33 | "no-invalid-regexp": 2, // Disallow invalid regular expression strings in the RegExp constructor 34 | "no-irregular-whitespace": 2, // Disallow irregular whitespace outside of strings and comments 35 | "no-negated-in-lhs": 2, // Disallow negation of the left operand of an in expression 36 | "no-regex-spaces": 2, // Disallow multiple spaces in a regular expression literal 37 | "no-sparse-arrays": 2, // Disallow sparse arrays 38 | "no-unreachable": 1, // Disallow unreachable statements after a return, throw, continue, or break statement 39 | "use-isnan": 2, // Disallow comparisons with the value NaN 40 | "valid-jsdoc": [1, { // Ensure JSDoc comments are valid 41 | "requireReturnDescription": false 42 | }], 43 | "valid-typeof": 2, // Ensure that the results of typeof are compared against a valid string 44 | "no-unexpected-multiline": 1, // Avoid code that looks like two expressions but is actually one 45 | 46 | // Best practices 47 | "block-scoped-var": 1, // Treat var statements as if they were block scoped 48 | "consistent-return": 1, // Require return statements to either always or never specify values 49 | "curly": [2, "all"], // Require curly braces for all control statements 50 | "dot-notation": 1, // encourages use of dot notation whenever possible 51 | "eqeqeq": [2, "allow-null"], // Require the use of === and !== 52 | "no-alert": 1, // Disallow the use of alert, confirm, and prompt 53 | "no-eval": 2, // Disallow use of eval() 54 | "no-extend-native": 2, // Disallow adding to native types 55 | "no-extra-bind": 2, // Disallow unnecessary function binding 56 | "no-fallthrough": 0, // Disallow fallthrough of case statements 57 | "no-implicit-coercion": [2, { // Disallow the type conversion with shorter notations for numbers and strings 58 | "boolean": false, 59 | "number": true, 60 | "string": true 61 | }], 62 | "no-implied-eval": 2, // Disallow use of eval()-like methods 63 | "no-labels": 2, // Disallow use of labeled statements 64 | "no-loop-func": 2, // Disallow creation of functions within loops 65 | "no-multi-spaces": [1, {"exceptions": { // Disallow use of multiple spaces 66 | "Property": false 67 | }}], 68 | "no-multi-str": 2, // Disallow use of multiline strings 69 | "no-native-reassign": 2, // Disallow reassignments of native objects 70 | "no-new-func": 2, // Disallow use of new operator for Function object 71 | "no-new-wrappers": 1, // Disallows creating new instances of String,Number, and Boolean 72 | "no-new": 0, // Allow use of new operator when not part of the assignment or comparison 73 | "no-octal": 2, // Disallow use of octal literals 74 | "no-redeclare": 2, // Disallow declaring the same variable more than once 75 | "no-return-assign": 2, // Disallow use of assignment in return statement 76 | "no-self-compare": 2, // Disallow comparisons where both sides are exactly the same 77 | "no-sequences": 1, // Disallow use of comma operator 78 | "no-throw-literal": 2, // Restrict what can be thrown as an exception 79 | "no-unused-expressions": 1, // Disallow usage of expressions in statement position 80 | "no-void": 2, // Disallow use of void operator (off by default) 81 | "no-with": 2, // Disallow use of the with statement 82 | "prefer-spread": 2, // Suggest using the spread operator instead of .apply() 83 | "radix": 2, // Require use of the second argument for parseInt() 84 | "wrap-iife": [1, "outside"], // Require immediate function invocation to be wrapped in parentheses 85 | "yoda": [2, "never", { // Disallow Yoda conditions 86 | "exceptRange": true 87 | }], 88 | 89 | // Variables 90 | "no-delete-var": 2, // Disallow deletion of variables 91 | "no-shadow-restricted-names": 2, // Disallow shadowing of names such as arguments 92 | "no-shadow": [1, {"hoist": "functions"}], // Disallow declaration of variables already declared in the outer scope 93 | "no-undef": 2, // Disallow use of undeclared variables unless mentioned in a /*global */ block 94 | "no-unused-vars": 1, // Disallow declaration of variables that are not used in the code 95 | 96 | // Stylistic issues 97 | "array-bracket-spacing": [1, "never"], // Disallow spaces inside of brackets 98 | "brace-style": [1, "stroustrup", { // Stroustrup brace style (else on a separate line) 99 | "allowSingleLine": false 100 | }], 101 | "camelcase": [1, { // Require camel case names except properties 102 | "properties": "never" 103 | }], 104 | "comma-spacing": [1, { // No space before comma, space after coma 105 | "before": false, 106 | "after": true 107 | }], 108 | "comma-style": [1, "last"], // Do not start line with comma 109 | "computed-property-spacing": [1, "never"], // Disallow padding inside computed properties 110 | "eol-last": 0, // Do not require file to end with single newline 111 | "indent": [1, "tab", { // Tabs indentation 112 | "SwitchCase": 1 113 | }], 114 | "key-spacing": [2, { // No space before colon, space after colon 115 | "beforeColon": false, 116 | "afterColon": true 117 | }], 118 | "lines-around-comment": 0, // Do not require new line before comments 119 | "linebreak-style": [2, "unix"], // Unix linebreaks 120 | "max-nested-callbacks": [1, 3], // No more than 3 levels of nested callbacks 121 | "new-cap": 0, // Do not require capital letter for constructors 122 | "new-parens": 2, // Disallow the omission of parentheses when invoking a constructor with no arguments 123 | "no-lonely-if": 1, // Disallow if as the only statement in an else block 124 | "no-mixed-spaces-and-tabs": 1, // Disallow mixed spaces and tabs for indentation 125 | "no-multiple-empty-lines": [1, { // No more than 2 empty lines 126 | "max": 2 127 | }], 128 | "no-nested-ternary": 1, // Disallow nested ternary expressions 129 | "no-new-object": 1, // Disallow use of the Object constructor 130 | "no-spaced-func": 1, // Disallow space between function identifier and application 131 | "no-trailing-spaces": 1, // Disallow trailing whitespace at the end of lines 132 | "no-underscore-dangle": 0, // Allow dangling underscores in identifiers 133 | "no-unneeded-ternary": 1, // Disallow the use of Boolean literals in conditional expressions 134 | "no-useless-concat": 1, // Disallow unncessary concatenation of strings 135 | "operator-assignment": [1, "always"], // Require assignment operator shorthand where possible 136 | "operator-linebreak": [1, "after"], // Operators should be placed before line breaks 137 | "quote-props": 0, // Do not require quotes around object literal property names 138 | "quotes": [1, "single", "avoid-escape"], // Single quotes 139 | "semi-spacing": [1, { // No space before semicolon, space after semicolon 140 | "before": false, 141 | "after": true 142 | }], 143 | "semi": [2, "always"], // Require use of semicolons 144 | "space-after-keywords": [1, "always"], // Require space after keywords (if, else, etc.) 145 | "space-before-blocks": [1, "always"], // Require space after blocks 146 | "space-before-function-paren": [1, { // Disallow space before function opening parenthesis 147 | "anonymous": "never", 148 | "named": "never" 149 | }], 150 | "space-in-parens": [1, "never"], // Disallow spaces inside parentheses 151 | "space-infix-ops": 1, // Require spaces around operators 152 | "space-return-throw-case": 1, // Require space after return, throw, and case 153 | "space-unary-ops": [1, { // Require spaces before/after unary operators 154 | "words": true, 155 | "nonwords": false 156 | }], 157 | "spaced-comment": [1, "always"], // Require space immediately following the // or /* in a comment 158 | 159 | // ECMAScript 6 160 | "arrow-spacing": [2, { // Require space before and after arrow function's arrow 161 | "before": true, 162 | "after": true 163 | }], 164 | "jsx-quotes": [1, "prefer-double"], 165 | "no-var": 0, // Do not require let or const instead of var (becau 166 | 167 | // React: https://github.com/yannickcr/eslint-plugin-react 168 | "react/display-name": 0, 169 | "react/jsx-boolean-value": [2, "never"], 170 | "react/jsx-closing-bracket-location": [1, { 171 | "location": "tag-aligned" 172 | }], 173 | "react/jsx-curly-spacing": [1, "never"], 174 | "react/jsx-indent-props": [1, 4], 175 | "react/jsx-no-duplicate-props": 2, 176 | "react/jsx-no-undef": 2, 177 | "react/jsx-sort-prop-types": 0, 178 | "react/jsx-sort-props": 0, 179 | "react/jsx-uses-react": 1, 180 | "react/jsx-uses-vars": 1, 181 | "react/no-danger": 0, 182 | "react/no-did-mount-set-state": 1, 183 | "react/no-did-update-set-state": 1, 184 | "react/no-multi-comp": 1, 185 | "react/no-unknown-property": 1, 186 | "react/prop-types": [1, { 187 | "ignore": [ 188 | "actions", 189 | "dispatch", 190 | "state" 191 | ] 192 | }], 193 | "react/require-extension": 0, 194 | "react/self-closing-comp": 1, 195 | // "react/sort-comp": [1, { 196 | // "order": [ 197 | // "/^handle.+$/", 198 | // "lifecycle", 199 | // "everything-else", 200 | // "/^render.+$/", 201 | // "render" 202 | // ], 203 | // }], 204 | "react/wrap-multilines": 1 205 | }, 206 | "globals": { 207 | "describe": false, 208 | "it": false, 209 | "expect": false, 210 | "expectReactShallow": false 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example/styleguide/ 2 | .publish/ 3 | node_modules 4 | build 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "4" 5 | notifications: 6 | email: 7 | on_success: never 8 | on_failure: always 9 | -------------------------------------------------------------------------------- /bin/styleguidist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var minimist = require('minimist'); 5 | var chalk = require('chalk'); 6 | 7 | var argv = minimist(process.argv.slice(2)); 8 | 9 | switch (argv._[0]) { 10 | case 'build': 11 | commandBuild(); 12 | break; 13 | case 'server': 14 | commandServer(); 15 | break; 16 | default: 17 | commandHelp(); 18 | } 19 | 20 | function commandBuild() { 21 | console.log('Building style guide...'); 22 | 23 | var build = require('../src/build'); 24 | build(function(err, stats, config) { 25 | if (err) { 26 | console.log(err); 27 | } 28 | else { 29 | console.log('Style guide published to', chalk.underline(config.styleguideDir)); 30 | } 31 | }); 32 | } 33 | 34 | function commandServer() { 35 | var server = require('../src/server'); 36 | server(function(err, config) { 37 | if (err) { 38 | console.log(err); 39 | } 40 | else { 41 | console.log('Listening at', chalk.underline('http://' + config.serverHost + ':' + config.serverPort)); 42 | console.log(); 43 | } 44 | }); 45 | } 46 | 47 | function commandHelp() { 48 | console.log([ 49 | chalk.underline('Usage'), 50 | '', 51 | ' ' + chalk.bold('styleguidist') + ' ' + chalk.cyan('') + ' ' + chalk.yellow('[]'), 52 | '', 53 | chalk.underline('Commands'), 54 | '', 55 | ' ' + chalk.cyan('build') + ' Build style guide', 56 | ' ' + chalk.cyan('server') + ' Run development server', 57 | ' ' + chalk.cyan('help') + ' Display help information about React Styleguidist', 58 | '', 59 | chalk.underline('Options'), 60 | '', 61 | ' ' + chalk.yellow('--config') + ' Config file path', 62 | ' ' + chalk.yellow('--verbose') + ' Print debug information', 63 | ].join('\n')); 64 | } 65 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | I love pull requests. And following this simple guidelines will make your pull request easier to merge. 4 | 5 | 6 | ## Submitting pull requests 7 | 8 | 1. Create a new branch, please don’t work in master directly. 9 | 2. Add failing tests (if there’re any tests in project) for the change you want to make. Run tests (see below) to see the tests fail. 10 | 3. Hack on. 11 | 4. Run tests to see if the tests pass. Repeat steps 2–4 until done. 12 | 5. Update the documentation to reflect any changes. 13 | 6. Push to your fork and submit a pull request. 14 | 15 | 16 | ## JavaScript code style 17 | 18 | See [ESLint](.eslintrc) config files for more details. 19 | 20 | - Tab indentation. 21 | - Single-quotes. 22 | - Semicolon. 23 | - Strict mode. 24 | - No trailing whitespace. 25 | - Variables where needed. 26 | - Multiple variable statements. 27 | - Space after keywords and between arguments and operators. 28 | - Use === and !== over == and !=. 29 | - Return early. 30 | - Limit line lengths to 120 chars. 31 | - Prefer readability over religion. 32 | - Use ES6. 33 | 34 | Example: 35 | 36 | ```js 37 | 'use strict'; 38 | 39 | function foo(bar, fum) { 40 | if (!bar) { 41 | return; 42 | } 43 | 44 | let hello = 'Hello'; 45 | let ret = 0; 46 | for (let barIdx = 0; barIdx < bar.length; barIdx++) { 47 | if (bar[barIdx] === hello) { 48 | ret += fum(bar[barIdx]); 49 | } 50 | } 51 | 52 | return ret; 53 | } 54 | ``` 55 | 56 | 57 | ## Other notes 58 | 59 | - If you have commit access to repo and want to make big change or not sure about something, make a new branch and open pull request. 60 | - Don’t commit generated files: compiled from Stylus CSS, minified JavaScript, etc. 61 | - Don’t change version number and changelog. 62 | - Install [EditorConfig](http://editorconfig.org/) plugin for your code editor. 63 | 64 | ## Building and running tests 65 | 66 | Install dependencies: 67 | 68 | ```bash 69 | npm install 70 | ``` 71 | 72 | Build / run tests: 73 | 74 | ```bash 75 | npm test 76 | ``` 77 | -------------------------------------------------------------------------------- /core/build.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | 5 | global.userPath = global.userPath || process.cwd(); 6 | global.pathToApp = global.pathToApp || path.join(process.cwd(), 'node_modules/sourcejs'); 7 | 8 | var makeWebpackConfig = require('../src/make-webpack-config.js'); 9 | var config = require('../src/utils/config'); 10 | var env = process.env.NODE_ENV || 'development'; 11 | 12 | console.log('ReactStyleguidist: webpack building '+ env +' bundle...'); 13 | 14 | webpack(makeWebpackConfig(process.env.NODE_ENV), function (err, stats) { 15 | if (err) { 16 | console.error('ReactStyleguidist: error building webpack bundle', err, stats); 17 | } 18 | 19 | console.log('ReactStyleguidist: webpack '+ env +' build done'); 20 | }); 21 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var webpack = require('webpack'); 3 | var makeWebpackConfig = require('../src/make-webpack-config.js'); 4 | var env = global.MODE || process.env.NODE_ENV; 5 | var config = require('../src/utils/config'); 6 | 7 | if (config.enabled) { 8 | var compiler = webpack(makeWebpackConfig(env)); 9 | 10 | if (env !== 'production') { 11 | global.app.use(require('webpack-dev-middleware')(compiler, { 12 | noInfo: true 13 | })); 14 | global.app.use(require('webpack-hot-middleware')(compiler)); 15 | } else if (config.preBuild) { 16 | require('./build.js'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/lang-jsx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.processExample = function (code, index) { 4 | return '
'; 5 | }; 6 | -------------------------------------------------------------------------------- /core/middleware/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var specUtils = require(path.join(global.pathToApp,'core/lib/specUtils')); 3 | var config = require('../../src/utils/config.js'); 4 | 5 | /* 6 | * @param {object} req - Request object 7 | * @param {object} res - Response object 8 | * @param {function} next - The callback function 9 | * */ 10 | var processRequest = function (req, res, next) { 11 | if (!config.enabled) { 12 | next(); 13 | return; 14 | } 15 | 16 | // Check if request is targeting Spec 17 | if (req.specData && req.specData.renderedHtml && req.specData.info.role !== 'navigation') { 18 | var append; 19 | var pathToStyleguide = config._styleguideDir.replace(/^\./, ''); 20 | var pathToBundle = config.bundlePath.replace(/^\./, ''); 21 | 22 | if (global.MODE === 'production') { 23 | append = '' 24 | } else { 25 | append = '' 26 | } 27 | 28 | req.specData.renderedHtml += append; 29 | next(); 30 | } else { 31 | next(); 32 | } 33 | }; 34 | 35 | exports.process = processRequest; 36 | -------------------------------------------------------------------------------- /example/lib/components/Button/Button.css: -------------------------------------------------------------------------------- 1 | .root { 2 | padding: .5em 1.5em; 3 | color: #666; 4 | background-color: #fff; 5 | border: 1px solid currentColor; 6 | border-radius: .3em; 7 | text-align: center; 8 | vertical-align: middle; 9 | cursor: pointer; 10 | } 11 | -------------------------------------------------------------------------------- /example/lib/components/Button/Button.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | 3 | import s from './Button.css'; 4 | 5 | /** 6 | * The only true button. 7 | */ 8 | export default class Button extends Component { 9 | static propTypes = { 10 | /** 11 | * Button label. 12 | */ 13 | children: PropTypes.string.isRequired, 14 | color: PropTypes.string, 15 | size: PropTypes.oneOf(['small', 'normal', 'large']), 16 | }; 17 | static defaultProps = { 18 | color: '#333', 19 | size: 'normal' 20 | }; 21 | static sizes = { 22 | small: '10px', 23 | normal: '14px', 24 | large: '18px' 25 | }; 26 | 27 | render() { 28 | let styles = { 29 | color: this.props.color, 30 | fontSize: Button.sizes[this.props.size] 31 | }; 32 | 33 | return ( 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/components/Button/Readme.md: -------------------------------------------------------------------------------- 1 | Basic button: 2 | 3 | 4 | 5 | Big pink button: 6 | 7 | 8 | 9 | And you *can* **use** `any` [Markdown](http://daringfireball.net/projects/markdown/) here. 10 | -------------------------------------------------------------------------------- /example/lib/components/Button/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Button.js'; 2 | -------------------------------------------------------------------------------- /example/lib/components/Placeholder/Placeholder.css: -------------------------------------------------------------------------------- 1 | .root { 2 | background: #ccc; 3 | } 4 | -------------------------------------------------------------------------------- /example/lib/components/Placeholder/Placeholder.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | 3 | import s from './Placeholder.css'; 4 | 5 | /** 6 | * Image placeholders. 7 | */ 8 | export default class Placeholder extends Component { 9 | static propTypes = { 10 | type: PropTypes.oneOf(['animal', 'bacon', 'beard', 'bear', 'cat', 'food', 'city', 'nature', 'people']), 11 | width: PropTypes.number, 12 | height: PropTypes.number 13 | }; 14 | 15 | static defaultProps = { 16 | type: 'animal', 17 | width: 150, 18 | height: 150 19 | }; 20 | 21 | getImageUrl() { 22 | let { type, width, height } = this.props; 23 | let types = { 24 | animal: `http://placeimg.com/${width}/${height}/animals`, 25 | bacon: `http://baconmockup.com/${width}/${height}`, 26 | bear: `http://www.placebear.com/${width}/${height}`, 27 | beard: `http://placebeard.it/${width}/${height}`, 28 | cat: `http://lorempixel.com/${width}/${height}/cats`, 29 | city: `http://lorempixel.com/${width}/${height}/city`, 30 | food: `http://lorempixel.com/${width}/${height}/food`, 31 | nature: `http://lorempixel.com/${width}/${height}/nature`, 32 | people: `http://lorempixel.com/${width}/${height}/people`, 33 | }; 34 | return types[type]; 35 | } 36 | 37 | render() { 38 | let { width, height } = this.props; 39 | return ( 40 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/lib/components/Placeholder/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/lib/components/Placeholder/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Placeholder.js'; 2 | -------------------------------------------------------------------------------- /example/styleguide.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Style guide example', 3 | rootDir: './lib', 4 | components: function(config, glob) { 5 | return glob.sync(config.rootDir + '/components/**/*.js').filter(function(module) { 6 | return /\/[A-Z][a-z]*\.js$/.test(module); 7 | }); 8 | }, 9 | updateWebpackConfig: function(webpackConfig, env) { 10 | return webpackConfig; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var ghPages = require('gulp-gh-pages'); 3 | 4 | gulp.task('gh-pages', function() { 5 | return gulp.src('example/styleguide/**/*') 6 | .pipe(ghPages()); 7 | }); 8 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Copyright © 2013-2015 Sourcejs.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /loaders/examples.loader.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var marked = require('marked'); 3 | 4 | var evalPlaceholder = '<%{#eval#}%>'; 5 | 6 | var requireAnythingTest = 'require\\s*\\(([^)]+)\\)'; 7 | var requireAnythingRegex = new RegExp(requireAnythingTest, 'g'); 8 | var simpleStringRegex = /^"([^"]+)"$|^'([^']+)'$/; 9 | 10 | function readExamples(markdown) { 11 | var codeChunks = []; 12 | var renderer = new marked.Renderer(); 13 | 14 | renderer.code = function(code, language) { 15 | if (language && language === 'jsx') { 16 | codeChunks.push({type: 'code', content: code, evalInContext: evalPlaceholder}); 17 | } 18 | 19 | return code; 20 | }; 21 | 22 | marked(markdown, {renderer: renderer}); 23 | 24 | return codeChunks; 25 | } 26 | 27 | // Returns a list of all strings used in require(...) calls in the given source code. 28 | // If there is any other expression inside the require call, it throws an error. 29 | function findRequires(codeString) { 30 | var requires = {}; 31 | codeString.replace(requireAnythingRegex, function(requireExprMatch, requiredExpr) { 32 | var requireStrMatch = simpleStringRegex.exec(requiredExpr.trim()); 33 | if (!requireStrMatch) { 34 | throw new Error('Requires using expressions are not supported in examples. (Used: ' + requireExprMatch + ')'); 35 | } 36 | var requiredString = requireStrMatch[1] ? requireStrMatch[1] : requireStrMatch[2]; 37 | requires[requiredString] = true; 38 | }); 39 | return Object.keys(requires); 40 | } 41 | 42 | function examplesLoader(source, map) { 43 | this.cacheable && this.cacheable(); 44 | 45 | var examples = readExamples(source); 46 | 47 | // We're analysing the examples' source code to figure out the requires. We do it manually with 48 | // regexes, because webpack unfortunately doesn't expose its smart logic for rewriting requires 49 | // (https://webpack.github.io/docs/context.html). Note that we can't just use require(...) 50 | // directly in runtime, because webpack changes its name to __webpack__require__ or sth. 51 | // Related PR: https://github.com/sapegin/react-styleguidist/pull/25 52 | var codeFromAllExamples = _.map(_.filter(examples, {type: 'code'}), 'content').join('\n'); 53 | var requiresFromExamples = findRequires(codeFromAllExamples); 54 | 55 | return [ 56 | 'if (module.hot) {', 57 | ' module.hot.accept([]);', 58 | '}', 59 | 'var requireMap = {', 60 | requiresFromExamples.map(function(requireRequest) { 61 | return ' ' + JSON.stringify(requireRequest) + ': require(' + JSON.stringify(requireRequest) + ')'; 62 | }).join(',\n'), 63 | '};', 64 | 'function requireInRuntime(path) {', 65 | ' if (!requireMap.hasOwnProperty(path)) {', 66 | ' throw new Error("Sorry, changing requires in runtime is currently not supported.")', 67 | ' }', 68 | ' return requireMap[path];', 69 | '}', 70 | 'module.exports = ' + JSON.stringify(examples).replace( 71 | new RegExp(_.escapeRegExp('"' + evalPlaceholder + '"'), 'g'), 72 | 'function(code) {var require=requireInRuntime; return eval(code);}' 73 | ) + ';', 74 | ].join('\n'); 75 | } 76 | 77 | _.assign(examplesLoader, { 78 | requireAnythingTest: requireAnythingTest, 79 | requireAnythingRegex: requireAnythingRegex, 80 | simpleStringRegex: simpleStringRegex, 81 | readExamples: readExamples, 82 | findRequires: findRequires, 83 | }); 84 | 85 | module.exports = examplesLoader; 86 | -------------------------------------------------------------------------------- /loaders/styleguide.loader.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var glob = require('glob'); 4 | var prettyjson = require('prettyjson'); 5 | var _ = require('lodash'); 6 | var config = require('../src/utils/config'); 7 | 8 | function processComponent(filepath) { 9 | var examplesFile = config.getExampleFilename(filepath); 10 | var hasExamples = !!fs.existsSync(examplesFile); 11 | return '{' + [ 12 | 'filepath: ' + JSON.stringify(filepath), 13 | 'relativePath: ' + JSON.stringify(path.relative(config.rootDir, filepath)), 14 | 'module: ' + requireIt(filepath), 15 | 'examples: ' + (hasExamples ? requireIt('examples!' + examplesFile) : null) 16 | ].join(',') + '}'; 17 | } 18 | 19 | function requireIt(filepath) { 20 | return 'require(' + JSON.stringify(filepath) + ')'; 21 | } 22 | 23 | module.exports = function() {}; 24 | module.exports.pitch = function() { 25 | this.cacheable && this.cacheable(); 26 | 27 | var componentSources; 28 | if (typeof config.components === 'function') { 29 | componentSources = config.components(config, glob); 30 | } 31 | else { 32 | componentSources = glob.sync(path.join(config.rootDir, config.components)); 33 | } 34 | 35 | if (config.verbose) { 36 | console.log(); 37 | console.log('Loading components:'); 38 | console.log(prettyjson.render(componentSources)); 39 | console.log(); 40 | } 41 | 42 | var specPath; 43 | var specId; 44 | var components = componentSources.map(function(item){ 45 | specPath = config.getExampleFilename(item); 46 | 47 | specId = path.dirname(specPath.replace(global.userPath, '')).replace(/\\/g, '/'); 48 | specId = specId.replace(/^\//, '').toLowerCase(); 49 | 50 | return '"' + specId + '":' + processComponent(item); 51 | }); 52 | 53 | var simplifiedConfig = _.pick(config, [ 54 | 'title', 55 | 'highlightTheme' 56 | ]); 57 | 58 | return [ 59 | 'module.exports = {', 60 | ' config: ' + JSON.stringify(simplifiedConfig) + ',', 61 | ' components: {' + components.join(',') + '}', 62 | '};' 63 | ].join('\n'); 64 | }; 65 | -------------------------------------------------------------------------------- /mochasetup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { expect } from 'chai'; 3 | import expectReactShallow from 'expect-react-shallow'; 4 | import 'css-modules-require-hook'; 5 | 6 | global.React = React; 7 | global.expect = expect; 8 | global.expectReactShallow = expectReactShallow; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sourcejs-react-styleguidist", 3 | "description": "SourceJS integration plugin for react-styleuguidist", 4 | "version": "0.5.0", 5 | "homepage": "https://github.com/sourcejs/sourcejs-react-styleguidist", 6 | "contributors": [ 7 | { 8 | "name": "Artem Sapegin", 9 | "url": "http://sapegin.me/" 10 | }, 11 | { 12 | "name": "Robert Haritonov", 13 | "url": "http://rhr.me/" 14 | } 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/sourcejs/sourcejs-react-styleguidist.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/sourcejs/sourcejs-react-styleguidist/issues" 22 | }, 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">=0.12.0" 26 | }, 27 | "dependencies": { 28 | "babel-core": "~6.9.0", 29 | "babel-loader": "6.2.4", 30 | "babel-preset-react-hmre": "^1.1.1", 31 | "babel-plugin-react-transform": "2.0.2", 32 | "babel-preset-es2015": "^6.9.0", 33 | "babel-preset-react": "^6.5.0", 34 | "babel-preset-stage-0": "^6.5.0", 35 | "chalk": "1.1.3", 36 | "codemirror": "5.8.0", 37 | "cross-env": "^1.0.7", 38 | "css-loader": "0.23.1", 39 | "express": "4.13.3", 40 | "extract-text-webpack-plugin": "1.0.1", 41 | "findup": "0.1.5", 42 | "glob": "7.0.3", 43 | "lodash": "4.12.0", 44 | "marked": "0.3.5", 45 | "minimist": "1.2.0", 46 | "postcss-advanced-variables": "1.2.2", 47 | "postcss-loader": "0.9.1", 48 | "prettyjson": "1.1.3", 49 | "react": "^0.14.8", 50 | "react-codemirror": "0.2.6", 51 | "react-dom": "^0.14.8", 52 | "react-transform-catch-errors": "1.0.2", 53 | "react-transform-hmr": "1.0.4", 54 | "redbox-react": "1.1.1", 55 | "sanitize.css": "3.0.0", 56 | "style-loader": "0.13.0", 57 | "webpack": "1.13.1", 58 | "webpack-dev-middleware": "1.6.1", 59 | "webpack-hot-middleware": "2.10.0", 60 | "webpack-merge": "0.12.0" 61 | }, 62 | "peerDependencies": { 63 | "react": ">=0.14.0", 64 | "react-dom": "~0.14.2" 65 | }, 66 | "devDependencies": { 67 | "babel-core": "~6.9.0", 68 | "babel-eslint": "6.0.4", 69 | "babel-preset-react-hmre": "^1.1.1", 70 | "babel-standalone": "^6.7.7", 71 | "chai": "~3.5.0", 72 | "css-modules-require-hook": "~2.0.2", 73 | "eslint": "2.10.2", 74 | "eslint-plugin-react": "5.1.1", 75 | "expect-react-shallow": "~1.0.0", 76 | "gulp": "3.9.1", 77 | "gulp-gh-pages": "0.5.4", 78 | "mocha": "~2.4.5", 79 | "postcss-modules-extract-imports": "~1.0.0", 80 | "postcss-modules-scope": "~1.0.0", 81 | "react": "^0.14.8" 82 | }, 83 | "scripts": { 84 | "test": "npm run lint && npm run mocha && npm run build", 85 | "mocha": "mocha test", 86 | "mocha:watch": "mocha --watch --reporter min test", 87 | "start": "./bin/styleguidist server --config example/styleguide.config.js", 88 | "lint": "eslint src --ext .js", 89 | "build": "./bin/styleguidist build --config example/styleguide.config.js" 90 | }, 91 | "keywords": [ 92 | "sourcejs", 93 | "react", 94 | "jsx", 95 | "styleguide", 96 | "style guide", 97 | "documentation", 98 | "docs", 99 | "component", 100 | "components" 101 | ] 102 | } 103 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React Styleguidist Integration Plugin for SourceJS 2 | 3 | [![Build Status](https://travis-ci.org/sourcejs/sourcejs-react-styleguidist.svg?branch=master)](https://travis-ci.org/sourcejs/sourcejs-react-styleguidist) 4 | 5 | Fork of React Style Guide generator [react-styleguidist](https://github.com/sapegin/react-styleguidist) with integration to [SourceJS](http://sourcejs.com) platform. 6 | 7 | [Original styleguidist example](http://sapegin.github.io/react-styleguidist/). 8 | (example with SourceJS will be available later) 9 | 10 | ![](https://s3.amazonaws.com/f.cl.ly/items/3i0E1D1L1c1m1s2G1d0y/Screen%20Recording%202015-09-24%20at%2009.49%20AM.gif) 11 | 12 | To add automatically generated React props docs use [sourcejs-react-docgen](https://github.com/sourcejs/sourcejs-react-docgen) plugin. Check [SourceJS React bundle example](http://github.com/sourcejs/sourcejs-react-bundle-example) for more insights. 13 | 14 | ## Installation 15 | 16 | ``` 17 | cd sourcejs-project 18 | npm install sourcejs-react-styleguidist --save 19 | ``` 20 | 21 | Add custom markdown renderer conf into SourceJS `options.js` file: 22 | 23 | ```javascript 24 | module.exports = { 25 | core: { 26 | processMd: { 27 | languageRenderers: { 28 | jsx: require('sourcejs-react-styleguidist/core/lang-jsx').processExample 29 | } 30 | } 31 | } 32 | }; 33 | ``` 34 | 35 | After re-running your SourceJS app, plugin will be loaded automatically. 36 | 37 | ### Important configs 38 | 39 | Configure path to components in SourceJS `options.js` file: 40 | 41 | ```javascript 42 | module.exports = { 43 | plugins: { 44 | reactStyleguidist: { 45 | rootDir: './relative/path/to/components', 46 | components: './**/*.jsx' 47 | } 48 | } 49 | }; 50 | ``` 51 | 52 | See Configuration section below for the list of available options. 53 | 54 | ## Documenting components 55 | 56 | Examples are written in Markdown where any code blocks will be rendered as a react components. By default any `readme.md` in the component folder is treated as an examples file but you can change it with the `getExampleFilename` option. 57 | 58 | React component example: 59 | 60 | ```jsx 61 | 62 | ``` 63 | 64 | Any [Markdown](http://daringfireball.net/projects/markdown/): 65 | 66 | * Foo; 67 | * bar; 68 | * baz. 69 | 70 | ## How it works 71 | 72 | SourceJS plugins are loaded together with main application, adding additional initialization steps or changing rendering flow using middleware integration. With this plugin, in development mode, SourceJS in enhanced with webpack middleware, that builds all the React examples on demand and listens to file changes for hot-reloading. 73 | 74 | In production mode webpack is not triggered, expecting that bundle is already built (read configuration section for more). 75 | 76 | Rendering flow with this plugins looks like this: 77 | 78 | * On app start `webpack-dev-middleware` and `webpack-hot-middleware` are loaded from `core/index.js` 79 | * Then happens initial build of `bundle.js` which packs all the components and development tools into one package 80 | * Using custom loaders, webpack gathers info about all component from `readme.md` files, and saves defined code examples 81 | * On Spec page request, common `bundle.js` is loaded onto every page, loading only examples of defined spec 82 | * To determine which component to load from common bundle, all examples are grouped by Spec name, and special middleware (`code/middleware/index.js`) during rendering flow replaces code examples from `readme.md` with special hooks, that are then used as roots to React components 83 | * Using SourceJS as a platform together with this integration plugin you get benefits both from SourceJS ecosystem, and custom client-side rendreding handled by react/webpack and hot module replacement tool 84 | 85 | ## Configuration 86 | 87 | Use SourceJS `options.js` for deep plugin configuration. 88 | 89 | * **`enabled`** 90 | Type: `Boolean` 91 | Set to `false`, if wan't to disable plugin load with SourceJS app. 92 | 93 | * **`preBuild`** 94 | Type: `Boolean` 95 | Set to `true`, if you wan't to automatically build webpack bundle in production mode right after app starts. 96 | 97 | * **`rootDir`** 98 | Type: `String`, required 99 | Your components sources root folder (eg. `./lib`). Should not point to a folder with the `node_modules` folder. 100 | 101 | * **`components`** 102 | Type: `String` or `Function`, required 103 | - when `String`: a [glob pattern](https://github.com/isaacs/node-glob#glob-primer) that matches all your component modules. Relative to the `rootDir`. 104 | - when `Function`: function that returns an array of modules. 105 | 106 | If your components look like `components/Button.jsx` or `components/Button/Button.jsx` or `components/Button/index.jsx`: 107 | 108 | ```javascript 109 | components: './components/**/*.jsx' 110 | ``` 111 | 112 | If your components look like `components/Button/Button.js` + `components/Button/index.js`: 113 | 114 | ```javascript 115 | components: function(config, glob) { 116 | return glob.sync(config.rootDir + '/components/**/*.js').filter(function(module) { 117 | return /\/[A-Z][a-z]*\.js$/.test(module); 118 | }); 119 | }, 120 | ``` 121 | 122 | * **`highlightTheme`** 123 | Type: `String`, default: `base16-light` 124 | [CodeMirror theme](http://codemirror.net/demo/theme.html) name to use for syntax highlighting in examples. 125 | 126 | * **`getExampleFilename`** 127 | Type: `Function`, default: finds `readme.md` in the component folder 128 | Function that returns examples file path for a given component path. 129 | 130 | For example, instead of `readme.md` you can use `ComponentName.examples.md`: 131 | 132 | ```javascript 133 | getExampleFilename: function(componentpath) { 134 | return componentpath.replace(/\.jsx?$/, '.examples.md'); 135 | } 136 | ``` 137 | 138 | * **`updateWebpackConfig`** 139 | Type: `Function`, optional 140 | Function that allows you to modify Webpack config for style guide: 141 | 142 | ```javascript 143 | updateWebpackConfig: function(webpackConfig, env) { 144 | if (env === 'development') { 145 | /* ... modify config ... */ 146 | } 147 | return webpackConfig; 148 | } 149 | ``` 150 | 151 | ### Environment settings 152 | 153 | Running app with `NODE_ENV=production`, initial webpack build won't be triggered. To properly prepare production environment, first run react-styleguidist build command, and only after that run application: 154 | 155 | ``` 156 | NODE_ENV=production node ./node_modules/sourcejs-react-styleguidist/core/build.js 157 | NODE_ENV=production npm start 158 | ``` 159 | 160 | Note: this command should be ran from SourceJS root folder, where `node_modules` is placed. 161 | 162 | Alternatively, you can set `preBuild` to true in plugin configuration, to build webpack bundle once app is ran in production mode. This will require less build steps, but may cause higher load in production environment container. 163 | 164 | ## Contributing 165 | 166 | Everyone is welcome to contribute. Please take a moment to review the [contributing guidelines](contributing.md). 167 | 168 | --- 169 | 170 | ## License 171 | 172 | The MIT License, see the included [license.md](license.md) file. 173 | -------------------------------------------------------------------------------- /src/build.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var makeWebpackConfig = require('./make-webpack-config'); 3 | var config = require('./utils/config'); 4 | 5 | module.exports = function build(callback) { 6 | webpack(makeWebpackConfig('production'), function(err, stats) { 7 | callback(err, stats, config); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/Components/Components.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | import ReactComponent from 'components/ReactComponent'; 3 | 4 | export default class Components extends Component { 5 | static propTypes = { 6 | highlightTheme: PropTypes.string.isRequired, 7 | components: PropTypes.array.isRequired 8 | } 9 | 10 | renderComponents() { 11 | let { highlightTheme, components } = this.props; 12 | 13 | return components.map((component) => { 14 | return ( 15 | 16 | ); 17 | }); 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | {this.renderComponents()} 24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Components/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Components.js'; 2 | -------------------------------------------------------------------------------- /src/components/Editor/Editor.css: -------------------------------------------------------------------------------- 1 | /* Tweak CodeMirror styles */ 2 | .root :global .CodeMirror { 3 | height: auto; 4 | padding: .3rem .8rem; 5 | font-size: .9rem; 6 | } 7 | .root :global .CodeMirror-scroll { 8 | height: auto; 9 | overflow-y: hidden; 10 | overflow-x: auto; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Editor/Editor.js: -------------------------------------------------------------------------------- 1 | // CodeMirror 2 | import '../../utils/codemirror-jsx'; 3 | import 'codemirror/lib/codemirror.css'; 4 | 5 | import _ from 'lodash'; 6 | import { Component, PropTypes } from 'react'; 7 | import Codemirror from 'react-codemirror'; 8 | 9 | import s from './Editor.css'; 10 | 11 | var cssRequire = require.context('codemirror/theme/', false, /^\.\/.*\.css$/); 12 | 13 | let UPDATE_DELAY = 100; 14 | 15 | export default class Editor extends Component { 16 | static propTypes = { 17 | code: PropTypes.string.isRequired, 18 | highlightTheme: PropTypes.string.isRequired, 19 | onChange: PropTypes.func 20 | } 21 | static codemirrorOptions = { 22 | mode: 'jsx', 23 | lineNumbers: false, 24 | lineWrapping: true, 25 | smartIndent: false, 26 | matchBrackets: true, 27 | viewportMargin: Infinity 28 | } 29 | 30 | constructor() { 31 | super(); 32 | this._handleChange = _.debounce(this.handleChange.bind(this), UPDATE_DELAY); 33 | } 34 | 35 | componentWillMount() { 36 | let { highlightTheme } = this.props; 37 | 38 | cssRequire(`./${highlightTheme}.css`); 39 | } 40 | 41 | handleChange(newCode) { 42 | let { onChange } = this.props; 43 | if (onChange) { 44 | onChange(newCode); 45 | } 46 | } 47 | 48 | render() { 49 | let { highlightTheme } = this.props; 50 | let options = _.merge({}, Editor.codemirrorOptions, {theme: highlightTheme}); 51 | 52 | return ( 53 |
54 | 55 |
56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/Editor/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Editor.js'; 2 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.css: -------------------------------------------------------------------------------- 1 | .root { 2 | max-width: 1200px; 3 | padding: 1rem 2rem; 4 | margin-left: auto; 5 | margin-right: auto; 6 | } 7 | 8 | .heading { 9 | margin-bottom: 2rem; 10 | font-size: 3rem; 11 | font-weight: normal; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | import Components from 'components/Components'; 3 | 4 | import s from './Layout.css'; 5 | 6 | export default class Layout extends Component { 7 | static propTypes = { 8 | config: PropTypes.object.isRequired, 9 | components: PropTypes.array.isRequired 10 | } 11 | 12 | // TODO: Blank slate 13 | 14 | render() { 15 | let { config, components } = this.props; 16 | let { title, highlightTheme } = config; 17 | 18 | return ( 19 |
20 |

{title}

21 |
22 | 23 |
24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Layout/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Layout.js'; 2 | -------------------------------------------------------------------------------- /src/components/Playground/Playground.css: -------------------------------------------------------------------------------- 1 | .root { 2 | overflow: hidden; 3 | margin-bottom: 1.5rem; 4 | border: 1px solid #e8e8e8; 5 | border-radius: .25rem; 6 | } 7 | 8 | .editor { 9 | } 10 | 11 | .preview { 12 | margin-bottom: .2rem; 13 | padding: 1rem; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Playground/Playground.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | import Editor from 'components/Editor'; 3 | import Preview from 'components/Preview'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | export default class Playground extends Component { 7 | static propTypes = { 8 | highlightTheme: PropTypes.string.isRequired, 9 | code: PropTypes.string.isRequired, 10 | evalInContext: PropTypes.func.isRequired, 11 | index: PropTypes.string.isRequired 12 | } 13 | 14 | constructor(props) { 15 | super(); 16 | this.state = { 17 | code: props.code 18 | }; 19 | } 20 | 21 | handleChange = (newCode) => { 22 | this.setState({ 23 | code: newCode 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | this.renderEditor(this.props); 29 | 30 | // Force fix improper editor size, TODO: invesitage the cause 31 | setTimeout(() => { 32 | this.renderEditor(this.props); 33 | }, 1000); 34 | } 35 | 36 | componentDidUpdate() { 37 | this.renderEditor(this.props); 38 | } 39 | 40 | componentWillReceiveProps(nextProps) { 41 | let { code } = nextProps; 42 | if (code) { 43 | this.setState({ 44 | code 45 | }); 46 | } 47 | } 48 | 49 | getEditorContainer() { 50 | return document.querySelector('.source_styleguidist_code__' + this.props.index); 51 | } 52 | 53 | getEditorElement() { 54 | let { highlightTheme } = this.props; 55 | let { code } = this.state; 56 | 57 | return ( 58 | 59 | ); 60 | } 61 | 62 | renderEditor() { 63 | var container = this.getEditorContainer(); 64 | 65 | if (container) { 66 | ReactDOM.unstable_renderSubtreeIntoContainer(this, this.getEditorElement(), this.getEditorContainer()); 67 | } 68 | } 69 | 70 | render() { 71 | let { code } = this.state; 72 | 73 | return ( 74 | 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Playground/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Playground.js'; 2 | -------------------------------------------------------------------------------- /src/components/Preview/Preview.css: -------------------------------------------------------------------------------- 1 | .playgroundError { 2 | margin: -1rem -1rem -1.2rem; 3 | padding: 1rem; 4 | line-height: 1.2; 5 | font-size: .9rem; 6 | color: #fff; 7 | background: #c00; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Preview/Preview.js: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/joelburget/react-live-editor/blob/master/live-compile.jsx 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { transform } from 'babel-standalone'; 6 | import Wrapper from 'components/Wrapper'; 7 | 8 | import s from './Preview.css'; 9 | 10 | export default class Preview extends Component { 11 | static propTypes = { 12 | code: PropTypes.string.isRequired, 13 | evalInContext: PropTypes.func.isRequired, 14 | } 15 | 16 | constructor() { 17 | super(); 18 | this.state = { 19 | error: null 20 | }; 21 | } 22 | 23 | componentDidMount() { 24 | this.executeCode(); 25 | } 26 | 27 | componentDidUpdate(prevProps) { 28 | if (this.props.code !== prevProps.code) { 29 | this.executeCode(); 30 | } 31 | } 32 | 33 | compileCode(code) { 34 | return transform(code, { 35 | presets: ['es2015', 'react', 'stage-0'], 36 | ignore: [/node_modules/] 37 | }).code; 38 | } 39 | 40 | executeCode() { 41 | let mountNode = this.refs.mount; 42 | 43 | ReactDOM.unmountComponentAtNode(mountNode); 44 | 45 | this.setState({ 46 | error: null 47 | }); 48 | 49 | let { code } = this.props; 50 | if (!code) { 51 | return; 52 | } 53 | 54 | try { 55 | let compiledCode = this.compileCode(code); 56 | let component = this.props.evalInContext(compiledCode); 57 | let wrappedComponent = ( 58 | 59 | {component} 60 | 61 | ); 62 | ReactDOM.render(wrappedComponent, mountNode); 63 | } 64 | catch (err) { 65 | ReactDOM.unmountComponentAtNode(mountNode); 66 | this.setState({ 67 | error: err.toString() 68 | }); 69 | } 70 | } 71 | 72 | renderError() { 73 | let { error } = this.state; 74 | if (error) { 75 | return ( 76 |
{error}
77 | ); 78 | } 79 | else { 80 | return null; 81 | } 82 | } 83 | 84 | render() { 85 | return ( 86 |
87 |
88 | {this.renderError()} 89 |
90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/components/Preview/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Preview.js'; 2 | -------------------------------------------------------------------------------- /src/components/Props/Props.css: -------------------------------------------------------------------------------- 1 | .root { 2 | margin-bottom: 2rem; 3 | font-size: .9rem; 4 | } 5 | 6 | .heading { 7 | margin-bottom: .5rem; 8 | font-size: 1.3rem; 9 | font-weight: normal; 10 | } 11 | 12 | .table { 13 | width: 100%; 14 | } 15 | .tableHead { 16 | margin-bottom: 1.5rem; 17 | border-bottom: 1px solid #e8e8e8; 18 | } 19 | .tableBody { 20 | } 21 | .cell { 22 | padding-right: 1rem; 23 | padding-top: .5em; 24 | } 25 | .cellHeading { 26 | padding-right: 1rem; 27 | padding-bottom: .5em; 28 | text-align:left; 29 | } 30 | .cellDesc { 31 | width: 99%; 32 | padding-left: 1rem; 33 | } 34 | 35 | .optional { 36 | color: #999; 37 | } 38 | 39 | .list { 40 | display: inline; 41 | list-style-type: none; 42 | } 43 | .listItem { 44 | display: inline; 45 | } 46 | .listItem:after { 47 | content: ", "; 48 | } 49 | .listItem:last-child:after { 50 | content: ""; 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Props/Props.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | 3 | import s from './Props.css'; 4 | 5 | export default class Props extends Component { 6 | static propTypes = { 7 | props: PropTypes.object.isRequired 8 | } 9 | 10 | unquote(string) { 11 | return string.replace(/^'|'$/g, ''); 12 | } 13 | 14 | renderRows() { 15 | let rows = []; 16 | let { props } = this.props.props; 17 | for (var name in props) { 18 | var prop = props[name]; 19 | rows.push( 20 | 21 | {name} 22 | {this.renderType(prop.type)} 23 | {this.renderDefault(prop)} 24 | {this.renderDescription(prop)} 25 | 26 | ); 27 | } 28 | return rows; 29 | } 30 | 31 | renderType(type) { 32 | let { name } = type; 33 | switch (name) { 34 | case 'arrayOf': 35 | return `${type.value.name}[]`; 36 | case 'instanceOf': 37 | return type.value; 38 | } 39 | return name; 40 | } 41 | 42 | renderDefault(prop) { 43 | if (prop.required) { 44 | return ''; 45 | } 46 | else if (prop.defaultValue) { 47 | return ( 48 | {this.unquote(prop.defaultValue.value)} 49 | ); 50 | } 51 | else { 52 | return ( 53 | Optional 54 | ); 55 | } 56 | } 57 | 58 | renderDescription(prop) { 59 | let isEnum = prop.type.name === 'enum'; 60 | let isUnion = prop.type.name === 'union'; 61 | return ( 62 |
63 | {prop.description} 64 | {prop.description && isEnum && ' '} 65 | {isEnum && this.renderEnum(prop)} 66 | {isUnion && this.renderUnion(prop)} 67 |
68 | ); 69 | } 70 | 71 | renderEnum(prop) { 72 | if (!Array.isArray(prop.type.value)) { 73 | return {prop.type.value}; 74 | } 75 | let values = prop.type.value.map(({ value }) => ( 76 |
  • 77 | {this.unquote(value)} 78 |
  • 79 | )); 80 | return ( 81 | One of:
      {values}
    82 | ); 83 | } 84 | 85 | renderUnion(prop) { 86 | if (!Array.isArray(prop.type.value)) { 87 | return {prop.type.value}; 88 | } 89 | let values = prop.type.value.map((value) => ( 90 |
  • 91 | {this.renderType(value)} 92 |
  • 93 | )); 94 | 95 | return ( 96 | One of type:
      {values}
    97 | ); 98 | } 99 | 100 | render() { 101 | return ( 102 |
    103 |

    Props

    104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | {this.renderRows()} 115 | 116 |
    NameTypeDefaultDescription
    117 |
    118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/components/Props/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Props.js'; 2 | -------------------------------------------------------------------------------- /src/components/ReactComponent/ReactComponent.css: -------------------------------------------------------------------------------- 1 | .root { 2 | margin-bottom: 3rem; 3 | } 4 | 5 | .heading { 6 | font-size: 2rem; 7 | font-weight: normal; 8 | } 9 | 10 | .sourcePath { 11 | color: #aaa; 12 | font-size: 0.8rem; 13 | margin-bottom: 1rem; 14 | font-family: monospace; 15 | } 16 | 17 | .description { 18 | margin-bottom: 1rem; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ReactComponent/ReactComponent.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | import Props from 'components/Props'; 3 | import Playground from 'components/Playground'; 4 | 5 | import s from './ReactComponent.css'; 6 | 7 | export default class ReactComponent extends Component { 8 | static propTypes = { 9 | highlightTheme: PropTypes.string.isRequired, 10 | component: PropTypes.object.isRequired 11 | } 12 | 13 | renderDescription() { 14 | let description = this.props.component.props.description; 15 | if (!description) { 16 | return null; 17 | } 18 | 19 | return ( 20 |
    {description}
    21 | ); 22 | } 23 | 24 | renderExamples() { 25 | let { highlightTheme, component } = this.props; 26 | if (!component.examples) { 27 | return null; 28 | } 29 | 30 | return component.examples.map((example, index) => { 31 | switch (example.type) { 32 | case 'code': 33 | return ( 34 | 35 | ); 36 | case 'html': 37 | return ( 38 |
    39 | ); 40 | } 41 | }); 42 | } 43 | 44 | render() { 45 | let { component } = this.props; 46 | return ( 47 |
    48 |

    {component.name}

    49 |
    {component.relativePath}
    50 | {this.renderDescription()} 51 | 52 | {this.renderExamples()} 53 |
    54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/ReactComponent/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './ReactComponent.js'; 2 | -------------------------------------------------------------------------------- /src/components/StyleGuide/StyleGuide.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import Layout from 'components/Layout'; 3 | 4 | export default class StyleGuide extends Component { 5 | render() { 6 | return ( 7 | 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/StyleGuide/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './StyleGuide.js'; 2 | -------------------------------------------------------------------------------- /src/components/Wrapper/Wrapper.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | 3 | export default class Wrapper extends Component { 4 | static propTypes = { 5 | children: PropTypes.node.isRequired 6 | } 7 | 8 | render() { 9 | return this.props.children; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Wrapper/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Wrapper.js'; 2 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { setComponentsNames, globalizeComponents } from './utils/utils'; 4 | import Playground from 'components/Playground'; 5 | 6 | global.React = React; 7 | 8 | if (module.hot) { 9 | module.hot.accept(); 10 | } 11 | 12 | // Load styleguide 13 | let { config, components } = require('styleguide!'); 14 | 15 | function getSpecName() { 16 | var specName = window.location.pathname; 17 | 18 | specName = specName.split('/'); 19 | specName.splice(specName.length - 1, 1); 20 | specName = specName.join('/').replace(/^\//, ''); 21 | 22 | return specName.toLowerCase(); 23 | } 24 | 25 | components = setComponentsNames(components); 26 | globalizeComponents(components); 27 | 28 | var hooks = Array.prototype.slice.call(document.querySelectorAll('.source_styleguidist_example')); 29 | var component = components[getSpecName()]; 30 | 31 | if (component.examples) { 32 | hooks.forEach((hook) => { 33 | var exampleIndex = hook.getAttribute('data-jsx-example'); 34 | var example = component.examples[exampleIndex]; 35 | 36 | ReactDOM.render( 37 | , 38 | hook 39 | ); 40 | }); 41 | } 42 | else { 43 | console.log('Styleguidist didn\'t fount any code examples for this spec.'); 44 | } 45 | -------------------------------------------------------------------------------- /src/make-webpack-config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var AdvancedVariables = require('postcss-advanced-variables'); 6 | var merge = require('webpack-merge'); 7 | var prettyjson = require('prettyjson'); 8 | var config = require('../src/utils/config'); 9 | 10 | module.exports = function(env) { 11 | var isProd = env === 'production'; 12 | 13 | var codeMirrorPath = path.join(__dirname, '../../codemirror'); // npm 3 14 | if (!fs.existsSync(codeMirrorPath)) { 15 | codeMirrorPath = path.join(__dirname, '../node_modules/codemirror'); // npm 2 or react-styleguidist develop 16 | } 17 | 18 | var includes = [ 19 | __dirname, 20 | config.rootDir 21 | ]; 22 | var webpackConfig = { 23 | output: { 24 | path: config.styleguideDir, 25 | publicPath: '/', 26 | filename: config.bundlePath 27 | }, 28 | resolve: { 29 | root: path.join(__dirname), 30 | extensions: ['', '.js', '.jsx'], 31 | modulesDirectories: [ 32 | path.resolve(__dirname, '../node_modules'), 33 | 'node_modules' 34 | ], 35 | alias: { 36 | 'codemirror': codeMirrorPath 37 | } 38 | }, 39 | resolveLoader: { 40 | modulesDirectories: [ 41 | path.resolve(__dirname, '../loaders'), 42 | path.resolve(__dirname, '../node_modules'), 43 | 'node_modules' 44 | ] 45 | }, 46 | plugins: [ 47 | new webpack.DefinePlugin({ 48 | 'process.env': { 49 | NODE_ENV: JSON.stringify(env) 50 | } 51 | }) 52 | ], 53 | module: { 54 | loaders: [ 55 | ], 56 | noParse: [ 57 | /babel-standalone/ 58 | ] 59 | }, 60 | postcss: function() { 61 | return [ 62 | AdvancedVariables 63 | ]; 64 | } 65 | }; 66 | 67 | var entryScript = path.join(__dirname, 'index'); 68 | 69 | if (isProd) { 70 | webpackConfig = merge(webpackConfig, { 71 | entry: [ 72 | entryScript 73 | ], 74 | devtool: false, 75 | debug: false, 76 | cache: false, 77 | plugins: [ 78 | new webpack.optimize.UglifyJsPlugin({ 79 | compress: { 80 | warnings: false 81 | }, 82 | output: { 83 | comments: false 84 | }, 85 | mangle: false 86 | }), 87 | new webpack.optimize.DedupePlugin(), 88 | new ExtractTextPlugin('build/styles.css', { 89 | allChunks: true 90 | }) 91 | ], 92 | module: { 93 | loaders: [ 94 | { 95 | test: /\.jsx?$/, 96 | include: includes, 97 | loader: 'babel' 98 | }, 99 | { 100 | test: /\.css$/, 101 | include: codeMirrorPath, 102 | loader: ExtractTextPlugin.extract('style', 'css') 103 | }, 104 | { 105 | test: /\.css$/, 106 | include: includes, 107 | loader: ExtractTextPlugin.extract('style', 'css?modules&-minimize&importLoaders=1!postcss') 108 | } 109 | ] 110 | } 111 | }); 112 | } 113 | else { 114 | webpackConfig = merge(webpackConfig, { 115 | entry: [ 116 | 'webpack-hot-middleware/client', 117 | entryScript 118 | ], 119 | debug: true, 120 | cache: true, 121 | devtool: 'eval-source-map', 122 | stats: { 123 | colors: true, 124 | reasons: true 125 | }, 126 | plugins: [ 127 | new webpack.optimize.OccurenceOrderPlugin(), 128 | new webpack.HotModuleReplacementPlugin(), 129 | new webpack.NoErrorsPlugin() 130 | ], 131 | module: { 132 | loaders: [ 133 | { 134 | test: /\.jsx?$/, 135 | include: includes, 136 | loader: 'babel' 137 | }, 138 | { 139 | test: /\.css$/, 140 | include: codeMirrorPath, 141 | loader: 'style!css' 142 | }, 143 | { 144 | test: /\.css$/, 145 | include: includes, 146 | loader: 'style!css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss' 147 | } 148 | ] 149 | } 150 | }); 151 | } 152 | 153 | if (config.updateWebpackConfig) { 154 | webpackConfig = config.updateWebpackConfig(webpackConfig, env); 155 | } 156 | 157 | if (config.verbose) { 158 | console.log(); 159 | console.log('Using Webpack config:'); 160 | console.log(prettyjson.render(webpackConfig)); 161 | console.log(); 162 | } 163 | 164 | return webpackConfig; 165 | }; 166 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var webpack = require('webpack'); 3 | var makeWebpackConfig = require('./make-webpack-config'); 4 | var config = require('./utils/config'); 5 | 6 | module.exports = function server(callback) { 7 | var app = express(); 8 | var compiler = webpack(makeWebpackConfig('development')); 9 | 10 | app.use(require('webpack-dev-middleware')(compiler, { 11 | noInfo: true 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.listen(config.serverPort, config.serverHost, function(err) { 17 | callback(err, config); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @import 'sanitize.css/dist/sanitize.css'; 2 | 3 | :global body { 4 | color: #333; 5 | background: #fff; 6 | } 7 | 8 | :global a, 9 | :global a:link, 10 | :global a:visited { 11 | color: #1978c8; 12 | text-decoration: none; 13 | } 14 | 15 | :global a:hover, 16 | :global a:focus, 17 | :global a:active { 18 | color: #f28a25; 19 | } 20 | 21 | :global p, 22 | :global ul, 23 | :global ol, 24 | :global blockquote, 25 | :global table, 26 | :global hr, 27 | :global h3, 28 | :global h4, 29 | :global h5, 30 | :global h6 { 31 | margin-bottom: 1rem; 32 | } 33 | 34 | :global h3 { 35 | font-size: 1.6rem; 36 | font-weight: normal; 37 | } 38 | :global h4 { 39 | font-size: 1.4rem; 40 | font-weight: normal; 41 | } 42 | :global h5 { 43 | font-size: 1rem; 44 | font-weight: bold; 45 | } 46 | :global h6 { 47 | font-size: 1rem; 48 | font-weight: normal; 49 | font-style: italic; 50 | } 51 | 52 | :global blockquote { 53 | font-size: .85rem; 54 | padding: 0 2rem; 55 | } 56 | 57 | :global hr { 58 | border-bottom: 1px solid #e8e8e8; 59 | } 60 | 61 | :global thead { 62 | margin-bottom: 1.5rem; 63 | border-bottom: 1px solid #e8e8e8; 64 | } 65 | :global td { 66 | padding-right: 1rem; 67 | padding-top: .5em; 68 | } 69 | :global th { 70 | padding-right: 1rem; 71 | padding-bottom: .5em; 72 | text-align:left; 73 | } 74 | -------------------------------------------------------------------------------- /src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {%=o.htmlWebpackPlugin.options.title%} 6 | 7 | 8 |
    9 | 10 | 11 | -------------------------------------------------------------------------------- /src/utils/codemirror-jsx.js: -------------------------------------------------------------------------------- 1 | import CodeMirror from 'codemirror/lib/codemirror'; 2 | import 'codemirror/mode/htmlmixed/htmlmixed'; 3 | import 'codemirror/addon/mode/multiplex'; 4 | 5 | // TODO: Real JSX syntax highlighting 6 | // https://github.com/codemirror/CodeMirror/issues/3122 7 | 8 | CodeMirror.defineMode('jsx', (config) => { 9 | return CodeMirror.multiplexingMode(CodeMirror.getMode(config, 'htmlmixed'), { 10 | open: '{', 11 | close: '}', 12 | delimStyle: 'delimit', 13 | mode: CodeMirror.getMode(config, 'javascript') 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var minimist = require('minimist'); 4 | var prettyjson = require('prettyjson'); 5 | var utils = require('./server'); 6 | var _ = require('lodash'); 7 | 8 | var sourceJSUtils; 9 | var globalConfig; 10 | var pathToSourceJSUser = ''; 11 | 12 | if (global.pathToApp) { 13 | sourceJSUtils = require(path.join(global.pathToApp, 'core/lib/utils')); 14 | globalConfig = global.opts && global.opts.plugins && global.opts.plugins.reactStyleguidist ? global.opts.plugins.reactStyleguidist : {}; 15 | 16 | pathToSourceJSUser = global.userPath; 17 | } 18 | 19 | var config = { 20 | enabled: true, 21 | bundlePath: './build/bundle.js', 22 | 23 | // Public object is exposed to Front-end via options API. 24 | public: {}, 25 | 26 | // Original styleguidist options 27 | rootDir: './specs', 28 | components: './**/*.jsx', 29 | styleguideDir: './build/styleguide', 30 | 31 | highlightTheme: 'base16-light', 32 | verbose: false, 33 | getExampleFilename: function(componentpath) { 34 | return path.join(path.dirname(componentpath), 'readme.md'); 35 | }, 36 | updateWebpackConfig: null 37 | 38 | // Not used in SourceJS integration 39 | // title: 'Style guide', 40 | // template: path.join(__dirname, '../templates/index.html'), 41 | // serverHost: 'localhost', 42 | // serverPort: 3000, 43 | }; 44 | 45 | if (sourceJSUtils) { 46 | sourceJSUtils.extendOptions(config, globalConfig); 47 | } 48 | 49 | function readConfig() { 50 | var argv = minimist(process.argv.slice(2)); 51 | var configFilepath = findConfig(argv); 52 | var customConfig = {}; 53 | var options = config; 54 | 55 | if (configFilepath) { 56 | customConfig = require(configFilepath); 57 | options = _.merge({}, options, customConfig); 58 | options.rootDir = path.resolve(path.dirname(configFilepath), options.rootDir); 59 | } 60 | else { 61 | options.rootDir = path.join(pathToSourceJSUser, options.rootDir); 62 | 63 | options._styleguideDir = config.styleguideDir; 64 | options.styleguideDir = path.join(pathToSourceJSUser, options.styleguideDir); 65 | } 66 | 67 | validateConfig(options); 68 | 69 | if (options.verbose) { 70 | console.log(); 71 | console.log(prettyjson.render(options)); 72 | console.log(); 73 | } 74 | 75 | if (options.rootDir === global.userPath) { 76 | throw Error('ReactStyleguidist: "rootDir" should not point to folder with node_modules'); 77 | } 78 | if (!utils.isDirectoryExists(options.rootDir)) { 79 | throw Error('ReactStyleguidist: "rootDir" directory not found: ' + options.rootDir); 80 | } 81 | 82 | return options; 83 | } 84 | 85 | function findConfig(argv) { 86 | if (argv.config) { 87 | // Custom config location 88 | 89 | var configFilepath = path.join(process.cwd(), argv.config); 90 | if (!fs.existsSync(configFilepath)) { 91 | throw Error('Styleguidist config not found: ' + configFilepath + '.'); 92 | } 93 | 94 | return configFilepath; 95 | } 96 | } 97 | 98 | function validateConfig(options) { 99 | if (!options.rootDir) { 100 | throw Error('ReactStyleguidist: "rootDir" options is required.'); 101 | } 102 | if (!options.components) { 103 | throw Error('ReactStyleguidist: "components" options is required.'); 104 | } 105 | if (options.getExampleFilename && typeof options.getExampleFilename !== 'function') { 106 | throw Error('ReactStyleguidist: "getExampleFilename" options must be a function.'); 107 | } 108 | if (options.updateWebpackConfig && typeof options.updateWebpackConfig !== 'function') { 109 | throw Error('ReactStyleguidist: "updateWebpackConfig" options must be a function.'); 110 | } 111 | } 112 | 113 | module.exports = readConfig(); 114 | -------------------------------------------------------------------------------- /src/utils/server.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports.isDirectoryExists = function(dir) { 4 | try { 5 | var stats = fs.lstatSync(dir); 6 | if (stats.isDirectory()) { 7 | return true; 8 | } 9 | } 10 | finally { 11 | /* */ 12 | } 13 | return false; 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | export function setComponentsNames(components) { 2 | Object.keys(components).forEach(key => { 3 | var component = components[key]; 4 | 5 | let {module} = component; 6 | component.name = module.displayName || module.name || module.default.name; 7 | 8 | if (!component.name) { 9 | throw Error(`Cannot detect component name for ${component.filepath}`); 10 | } 11 | }); 12 | 13 | return components; 14 | } 15 | 16 | export function globalizeComponents(components) { 17 | Object.keys(components).forEach(key => { 18 | var component = components[key]; 19 | 20 | global[component.name] = component.module.default || component.module; 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/examples.loader.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import examplesLoader from '../loaders/examples.loader'; 4 | 5 | /* eslint max-nested-callbacks: [1, 5] */ 6 | 7 | describe('examples loader', () => { 8 | 9 | describe('requireAnythingRegex', () => { 10 | 11 | let regex; 12 | beforeEach(() => { 13 | expect(examplesLoader.requireAnythingRegex).to.be.an.instanceof(RegExp); 14 | // we make a version without the /g flag 15 | regex = new RegExp(examplesLoader.requireAnythingTest); 16 | }); 17 | 18 | it('should match require invocations', () => { 19 | expect(`require("foo")`).to.match(regex); 20 | expect(`require ( "foo" )`).to.match(regex); 21 | expect(`require('foo')`).to.match(regex); 22 | expect(`require(foo)`).to.match(regex); 23 | expect(`require("f" + "o" + "o")`).to.match(regex); 24 | expect(`require("f" + ("o" + "o"))`).to.match(regex); 25 | expect(`function f() { require("foo"); }`).to.match(regex); 26 | }); 27 | 28 | it('should not match other occurences of require', () => { 29 | expect(`"required field"`).not.to.match(regex); 30 | expect(`var f = require;`).not.to.match(regex); 31 | expect(`require.call(module, "foo")`).not.to.match(regex); 32 | }); 33 | 34 | it('should match many requires in the same line correctly', () => { 35 | // Revert to the /g flagged version used by examplesLoader 36 | regex = new RegExp(examplesLoader.requireAnythingRegex); 37 | var replaced = `require('foo');require('bar')`.replace(examplesLoader.requireAnythingRegex, 'x'); 38 | expect(replaced).to.equal('x;x'); 39 | }); 40 | }); 41 | 42 | describe('simpleStringRegex', () => { 43 | it('should match simple strings and nothing else', () => { 44 | let regex = examplesLoader.simpleStringRegex; 45 | 46 | expect(`"foo"`).to.match(regex); 47 | expect(`'foo'`).to.match(regex); 48 | expect(`"fo'o"`).to.match(regex); 49 | expect(`'fo"o'`).to.match(regex); 50 | expect(`'.,:;!§$&/()=@^12345'`).to.match(regex); 51 | 52 | expect(`foo`).not.to.match(regex); 53 | expect(`'foo"`).not.to.match(regex); 54 | expect(`"foo'`).not.to.match(regex); 55 | 56 | // these 2 are actually valid in JS, but don't work with this regex. 57 | // But you shouldn't be using these in your requires anyway. 58 | expect(`"fo\\"o"`).not.to.match(regex); 59 | expect(`'fo\\'o'`).not.to.match(regex); 60 | 61 | expect(`"foo" + "bar"`).not.to.match(regex); 62 | }); 63 | }); 64 | 65 | describe('findRequires', () => { 66 | it('should find calls to require in code', () => { 67 | let findRequires = examplesLoader.findRequires; 68 | expect(findRequires(`require('foo')`)).to.deep.equal(['foo']); 69 | expect(findRequires(`require('./foo')`)).to.deep.equal(['./foo']); 70 | expect(findRequires(`require('foo');require('bar')`)).to.deep.equal(['foo', 'bar']); 71 | expect(() => findRequires(`require('foo' + 'bar')`)).to.throw(Error); 72 | }); 73 | }); 74 | 75 | describe('loader', () => { 76 | it('should return valid, parsable js', () => { 77 | let exampleMarkdown = '# header\n\n
    \n\ntext'; 78 | let output = examplesLoader.call({}, exampleMarkdown); 79 | expect(() => new Function(output)).not.to.throw(SyntaxError); // eslint-disable-line no-new-func 80 | }); 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-core/register 2 | --require mochasetup 3 | -------------------------------------------------------------------------------- /test/utils.utils.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import _ from 'lodash'; 3 | 4 | import * as utils from '../src/utils/utils'; 5 | 6 | describe('utils', () => { 7 | 8 | describe('setComponentsNames', () => { 9 | it('should set name property to each component', () => { 10 | let result = utils.setComponentsNames([ 11 | { 12 | module: {displayName: 'Foo'} 13 | }, 14 | { 15 | module: {name: 'Bar'} 16 | } 17 | ]); 18 | expect(_.map(result, 'name')).to.eql(['Foo', 'Bar']); 19 | }); 20 | }); 21 | 22 | describe('globalizeComponents', () => { 23 | let sourceGlobalLength; 24 | beforeEach(() => { 25 | sourceGlobalLength = Object.keys(global).length; 26 | }); 27 | afterEach(() => { 28 | delete global.Foo; 29 | delete global.Bar; 30 | }); 31 | it('should set each component’s module as a global variable', () => { 32 | utils.globalizeComponents([ 33 | { 34 | name: 'Foo', 35 | module: 13 36 | }, 37 | { 38 | name: 'Bar', 39 | module: 27 40 | } 41 | ]); 42 | expect(Object.keys(global).length).to.eql(sourceGlobalLength + 2); 43 | expect(global.Foo).to.eql(13); 44 | expect(global.Bar).to.eql(27); 45 | }); 46 | }); 47 | 48 | }); 49 | --------------------------------------------------------------------------------