├── .gitignore ├── templates ├── dev-portal.hbs ├── library-docs.hbs └── component-partial.hbs ├── LICENSE ├── scripts └── generate-dev-portal ├── package.json ├── generate-markdown.js ├── dev-portal-generate.js ├── README.md ├── ES6GUIDE.md ├── index.js └── DEVGUIDE.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /templates/dev-portal.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{component}} 3 | category: {{libraryTitle}} 4 | --- 5 | 6 | {{> component }} 7 | -------------------------------------------------------------------------------- /templates/library-docs.hbs: -------------------------------------------------------------------------------- 1 | # {{title}} ({{library}}) 2 | 3 | {{description}} 4 | 5 | {{#each components}} 6 | 7 | {{> component this library=../library }} 8 | 9 |
10 | {{/each}} 11 | -------------------------------------------------------------------------------- /templates/component-partial.hbs: -------------------------------------------------------------------------------- 1 | ## {{component}} 2 | 3 | {{description}} 4 | 5 | {{#if hasProps}} 6 | ### Properties 7 | 8 | | Property | Type | Description | Default | 9 | | -------- | ---- | ----------- | ------- | 10 | {{#each props}} 11 | | *{{@key}}* | {{type.name}} | {{description}} | {{#defaultProp}}{{defaultValue.value}}{{/defaultProp}} 12 | {{/each}} 13 | {{/if}} 14 | 15 | ### import 16 | 17 | ```jsx 18 | import {{import}} from "{{library}}"; 19 | ``` 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 WalmartLabs 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /scripts/generate-dev-portal: -------------------------------------------------------------------------------- 1 | node dev-portal-generate.js --components ../layout/components.json --portal ~/projects/SolutionDocumentation 2 | node dev-portal-generate.js --components ../base/components.json --portal ~/projects/SolutionDocumentation 3 | node dev-portal-generate.js --components ../interactive/components.json --portal ~/projects/SolutionDocumentation 4 | node dev-portal-generate.js --components ../accordion/components.json --portal ~/projects/SolutionDocumentation 5 | node dev-portal-generate.js --components ../carousel/components.json --portal ~/projects/SolutionDocumentation 6 | node dev-portal-generate.js --components ../containers/components.json --portal ~/projects/SolutionDocumentation 7 | node dev-portal-generate.js --components ../infograph/components.json --portal ~/projects/SolutionDocumentation 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electrode-docgen", 3 | "version": "1.0.1", 4 | "description": "Electrode document generator", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "electrode-docgen": "./index.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/electrode-io/electrode-docgen.git" 15 | }, 16 | "author": "jherrington@walmartlabs.com", 17 | "license": "Apache-2.0", 18 | "dependencies": { 19 | "async": "^1.4.0", 20 | "commander": "^2.8.1", 21 | "doctrine": "^0.6.4", 22 | "glob": "^5.0.14", 23 | "handlebars": "^3.0.3", 24 | "indent-string": "^2.1.0", 25 | "markdown-js": "~0.0.3", 26 | "mkdirp": "^0.5.1", 27 | "ncp": "^2.0.0", 28 | "react-docgen": "^2.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /generate-markdown.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var handlebars = require('handlebars'); 3 | 4 | handlebars.registerPartial('component', fs.readFileSync(__dirname + '/templates/component-partial.hbs').toString()); 5 | 6 | handlebars.registerHelper('defaultProp', function(options) { 7 | var str = options.fn(this); 8 | str = str.replace(/\s+/g,' '); 9 | if (str.length > 50) { 10 | str = str.substring(0, 50) + '...'; 11 | } 12 | return str.length > 0 ? '`' + str + '`' : ""; 13 | }); 14 | 15 | function _generateDevPortal(comp) { 16 | var templateSource = fs.readFileSync(__dirname + '/templates/dev-portal.hbs'); 17 | var template = handlebars.compile(templateSource.toString(), {noEscape: true}); 18 | var result = template(comp); 19 | return result; 20 | } 21 | 22 | function _generateLibraryDocs(metadata) { 23 | var templateSource = fs.readFileSync(__dirname + '/templates/library-docs.hbs'); 24 | var template = handlebars.compile(templateSource.toString(), {noEscape: true}); 25 | var result = template(metadata); 26 | return result; 27 | } 28 | 29 | module.exports = { 30 | generateLibraryDocs: _generateLibraryDocs, 31 | generateDevPortal: _generateDevPortal 32 | } -------------------------------------------------------------------------------- /dev-portal-generate.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var mkdirp = require('mkdirp'); 3 | var program = require('commander'); 4 | var generateMarkdown = require('./generate-markdown'); 5 | var path = require('path'); 6 | var ncp = require('ncp').ncp; 7 | 8 | program 9 | .version('0.0.12') 10 | .option('--components ', 'Components metadate location', null, 'components.json') 11 | .option('--portal ', 'Dev portal root directory') 12 | .parse(process.argv); 13 | 14 | var _fixBackticks = function(str) { 15 | str = str.replace(/```jsx/g, "{% highlight html %}"); 16 | str = str.replace(/```/g, "{% endhighlight %}"); 17 | return str; 18 | } 19 | 20 | var metadata = JSON.parse(fs.readFileSync(program.components).toString()); 21 | 22 | var images = path.dirname(program.components) + "/images"; 23 | 24 | var dirName = program.portal + '/source/_references/components/' + metadata.title.toLowerCase(); 25 | mkdirp.sync(dirName); 26 | 27 | if (fs.existsSync(images)) { 28 | mkdirp.sync(dirName + "/images"); 29 | ncp(images, dirName + "/images", function(err){ 30 | }); 31 | } 32 | 33 | for (var c in metadata.components) { 34 | var component = metadata.components[c]; 35 | component.libraryTitle = metadata.title; 36 | if (component.component) { 37 | var fname = dirName + '/' + component.component.toLowerCase() + '.md'; 38 | var md = generateMarkdown.generateDevPortal(component); 39 | md = _fixBackticks(md); 40 | fs.writeFileSync(fname, md); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Electrode Documentation Generator 2 | ================================= 3 | 4 | A custom metadata extractor for the Electrode framework. 5 | 6 | > Before getting started, please make sure you read the [Developer Guide] 7 | 8 | ## Running it 9 | 10 | ``` 11 | electrode-docgen --package ./package.json --src ./src --metadata components.json 12 | ``` 13 | 14 | ``` 15 | electrode-docgen --package ./package.json --src ./src --markdown components.md 16 | ``` 17 | 18 | Or in `package.json` 19 | 20 | ``` 21 | "scripts": { 22 | ... 23 | "generate": "npm run generate-metadata && npm run generate-documentation && npm run generate-demo", 24 | "generate-metadata": "electrode-docgen --package ./package.json --src ./src --metadata components.json", 25 | "generate-documentation": "electrode-docgen --package ./package.json --src ./src --markdown components.md", 26 | "generate-demo": "electrode-docgen --package ./package.json --src ./src --demo demo" 27 | } 28 | ``` 29 | 30 | ## Writing It 31 | 32 | Standard Markdown format in the description. 33 | 34 | ### Metadata 35 | 36 | ``` 37 | @component Fixie 38 | @import {Fixie} 39 | ``` 40 | 41 | ### Private components 42 | 43 | ``` 44 | @private 45 | ``` 46 | 47 | ### Playgrounds 48 | 49 | ``` 50 | @playground 51 | Fixie 52 | `` 53 |
54 |
55 | `` 56 | ``` 57 | 58 | And for `noRender` set to `false`. 59 | 60 | ``` 61 | @playground 62 | Fixie 63 | !noRenderFalse! 64 | `` 65 |
66 |
67 | `` 68 | ``` 69 | 70 | ***NOTE***: In order to generate documentation correctly there should not be any statements between your documentation comment and React class declaration, for e.g. 71 | 72 | ``` 73 | // GOOD 74 | 75 | const foo = []; 76 | 77 | /*JSDoc comment*/ 78 | 79 | class Foo extends React.Component {...} 80 | ``` 81 | 82 | ``` 83 | // BAD 84 | 85 | /*JSDoc comment*/ 86 | 87 | const foo = []; 88 | 89 | class Foo extends React.Component {...} 90 | ``` 91 | 92 | ## Auto generated demo 93 | 94 | ``` 95 | /// start imports 96 | /// end imports 97 | ``` 98 | 99 | ``` 100 | /// start render 101 | /// end render 102 | ``` 103 | 104 | ## Issues 105 | 106 | Before submitting an issue, please see the [Issue Submission Guidelines](https://github.com/electrode-io/electrode-docgen/blob/master/DEVGUIDE.md#submitting-issues) 107 | 108 | ## Contributing 109 | 110 | If you're interested in contributing, see the [Developer Guide's Contribution Guide](https://github.com/electrode-io/electrode-docgen/blob/master/DEVGUIDE.md#contributing) 111 | 112 | Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs. 113 | 114 | [Developer Guide]: https://github.com/electrode-io/electrode-docgen/blob/master/DEVGUIDE.md 115 | -------------------------------------------------------------------------------- /ES6GUIDE.md: -------------------------------------------------------------------------------- 1 | es6-guide 2 | ========= 3 | 4 | A guide to ES6 5 | 6 | ## Functions 7 | 8 | ### Arrow functions 9 | 10 | Arrow functions are shorthand for an anonymous function that keep the current context. E.g. 11 | 12 | ``` 13 | // Drastically simplified for effect 14 | this.a = 2; 15 | 16 | let multiply = function (num) { 17 | return num * this.a; 18 | }.bind(this); 19 | 20 | console.log(multiply(3)) // outputs 6 21 | ``` 22 | 23 | Can be written as 24 | 25 | ``` 26 | this.a = 2; 27 | 28 | let multiply = num => num * this.a; 29 | 30 | console.log(multiply(3)) // outputs 6 31 | ``` 32 | 33 | This is most useful for cases like map or reduce: 34 | 35 | ``` 36 | let numbers = [1, 2, 3, 4]; 37 | let doubled = numbers.map(number => number * 2); // [2, 4, 6, 8] 38 | ``` 39 | 40 | ### Arrow function syntax 41 | 42 | Arrow functions take the following form: `() => `. When there is only a single argument, the parens are optional e.g. `(x) => x * x` and `x => x * x` are both valid. When there are 0 or 2 or more arguments, parens are required. e.g. `() => "blah"` or `(x, y) => x * y` 43 | 44 | ### Concise methods 45 | 46 | In object literals and classes we can condense `render: function() {}` to `render() {}` 47 | 48 | ### Generators 49 | 50 | Generators functions take the following form 51 | 52 | ``` 53 | function* name() {} 54 | 55 | // or the preferred 56 | let name = function* () {}; 57 | // ...since we avoid function declarations in favor of function expressions 58 | 59 | ``` 60 | 61 | Calling a generator doesn't actually run any of it's contents. A call to a generator returns a generator instance. 62 | 63 | ``` 64 | let foo = function* () { 65 | console.log("foo"); 66 | } 67 | 68 | // No console output here 69 | let bar = foo(); 70 | 71 | // bar is now an instance of the generator and the console.log has never been run. 72 | ``` 73 | 74 | To use a generator instance we have to call `.next()` 75 | 76 | ``` 77 | let foo = function* () { 78 | console.log("foo"); 79 | } 80 | 81 | // No console output here 82 | let bar = foo(); 83 | 84 | // outputs "foo" 85 | bar.next(); 86 | ``` 87 | 88 | `next()` returns an object that looks like this: 89 | ``` 90 | { 91 | value: undefined, 92 | done: true 93 | } 94 | ``` 95 | 96 | What do `value` and `done` mean? Glad you asked, `done` means that the generator doesn't have any more code to execute. AKA we are past the last `yield` statement. To understand `value`, we have to look at `yield`. 97 | 98 | ``` 99 | let foo = function* (x) { 100 | yield x; 101 | } 102 | 103 | let bar = foo(3); 104 | 105 | console.log(bar.next()); 106 | // Output is {value: 3, done: false} 107 | 108 | console.log(bar.next()); 109 | // Output is {value: undefined, done: true} 110 | ``` 111 | 112 | A `yield` statement tells the generator to stop executing and return the following value. You can have `yield` statements without a return value. The generator will return `done: true` on the subsequent call to `next` after the last `yield` statement (see above). 113 | 114 | `yield` statements can also be used to pass in new information to the generator. 115 | 116 | ``` 117 | let foo = function* (x) { 118 | let y = x + yield x; 119 | 120 | return y; 121 | } 122 | 123 | let bar = foo(5); 124 | 125 | console.log(bar.next().value) // outputs 5 from 'yield x' 126 | 127 | console.log(bar.next(8).value) //outputs 13 from 'let y = + ' 128 | ``` 129 | 130 | A little confusing right? What happens when we hit the first `yield` is we pass out `x` as `value`. The `yield x` statement then becomes whatever is passed into `.next()`. `x` is still `5` but `yield x` is now `8`. 131 | 132 | Note: `return` statements in generators are not a good idea. Although they are easy to reason about for `done: true`, they don't show up in `for..of` loops. See below 133 | 134 | Generators can be used with `for..of` loops for iterating to completion 135 | 136 | ``` 137 | // Oversimplified example for effect 138 | let foo = function* (x) { 139 | yield x + 1; 140 | yield x + 2; 141 | yield x + 3; 142 | 143 | // Any return statement here would be ignored by the for..of loop 144 | } 145 | 146 | for (let y of foo(6)) { 147 | console.log(y); 148 | } 149 | // Output 150 | // 7 151 | // 8 152 | // 9 153 | ``` 154 | 155 | For a more detailed overview of generators see http://davidwalsh.name/es6-generators 156 | 157 | ## Object literals 158 | 159 | ### Shorthand 160 | 161 | `{name: name, title: title}` can be condensed to `{name, title}` 162 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var reactDocs = require('react-docgen'); 6 | var doctrine = require('doctrine'); 7 | var program = require('commander'); 8 | var glob = require('glob'); 9 | var async = require('async'); 10 | var generateMarkdown = require('./generate-markdown'); 11 | var markdown = require('markdown-js').makeHtml; 12 | var indentString = require('indent-string'); 13 | 14 | var _mergeProps = function(comp1, comp2) { 15 | for(var k in comp2.props) { 16 | if(comp1.props[k] === undefined) { 17 | comp1.props[k] = comp2.props[k]; 18 | } 19 | } 20 | }; 21 | 22 | var _parsePlayground = function(str) { 23 | var code = str.match(/(``|```)([\s\S]*)(``|```)/); 24 | if (code) { 25 | code = code[0]; 26 | code = code.replace(/(``|```)\s*\n/g,''); 27 | code = code.replace(/(```|``)/g,''); 28 | } 29 | str = str.replace(/(``|```)([\s\S]*)(``|```)/, ''); 30 | str = str.replace(/(``|```)/g, ''); 31 | var flags = {}; 32 | str = str.replace(/!([\s\S]*)!/, function(found) { 33 | found = found.replace(/!/g, ''); 34 | flags[found] = true; 35 | return ''; 36 | }); 37 | str = str.replace(/^\s+/, ''); 38 | str = str.replace(/\s+$/, ''); 39 | return { 40 | title: str, 41 | flags: flags, 42 | code: code 43 | }; 44 | }; 45 | 46 | var _parseTags = function(str, target) { 47 | if (str === undefined) { 48 | return; 49 | } 50 | delete target.description; 51 | var parsed = doctrine.parse(str); 52 | target.description = parsed.description; 53 | for (var t in parsed.tags) { 54 | var tk = parsed.tags[t].title; 55 | var tv = parsed.tags[t].description; 56 | if (tk === 'playground') { 57 | if (target[tk] === undefined) { 58 | target[tk] = []; 59 | } 60 | target[tk].push(_parsePlayground(tv)); 61 | } else if (tk === 'synonym') { 62 | if (target[tk]) { 63 | target[tk].push(tv); 64 | } else { 65 | target[tk] = [tv]; 66 | } 67 | } else { 68 | if (target[tk]) { 69 | target[tk] = [target[tk]]; 70 | target[tk].push(tv); 71 | } else { 72 | target[tk] = tv; 73 | } 74 | } 75 | } 76 | }; 77 | 78 | function _createDemo(dir, done) { 79 | glob(dir + '/data/*.*', function(er, files) { 80 | var items = []; 81 | for(var f in files) { 82 | var file = files[f]; 83 | console.log(file); 84 | items.push(" " + 85 | path.basename(file, path.extname(file)) + 86 | ": require(\"./data/" + 87 | path.basename(file) + 88 | "\")"); 89 | } 90 | var data = "module.exports = {\n" + 91 | items.join(",\n") + 92 | "\n};\n"; 93 | console.log('Writing ' + dir + '/data.jsx'); 94 | fs.writeFileSync(dir + '/data.jsx', data); 95 | done(); 96 | }); 97 | } 98 | 99 | function _createImages(dir, done) { 100 | glob(dir + '/images/*.*', function(er, files) { 101 | var items = []; 102 | for(var f in files) { 103 | var file = files[f]; 104 | console.log(file); 105 | items.push(" \"" + 106 | path.basename(file, path.extname(file)) + 107 | "\": require(\"./images/" + 108 | path.basename(file) + 109 | "\")"); 110 | } 111 | var images = "module.exports = {\n" + 112 | items.join(",\n") + 113 | "\n};\n"; 114 | console.log('Writing ' + dir + '/images.jsx'); 115 | fs.writeFileSync(dir + '/images.jsx', images); 116 | done(); 117 | }); 118 | } 119 | 120 | program 121 | .version('0.0.17') 122 | .option('-s --src ', 'Source location', null, 'src') 123 | .option('-p --package ', 'Package file', null, 'package.json') 124 | .option('--metadata ', 'Metadata output file') 125 | .option('--markdown ', 'Markdown output file') 126 | .option('--demo ', 'Demo output directory') 127 | .parse(process.argv); 128 | 129 | var pkgJSON = fs.readFileSync(program.package); 130 | var pkgInfo = JSON.parse(pkgJSON); 131 | 132 | var metadata = { 133 | library: pkgInfo.name, 134 | description: pkgInfo.description, 135 | title: pkgInfo.title, 136 | components: [] 137 | }; 138 | 139 | glob(program.src + '/*/**.jsx', function(er, files) { 140 | async.each(files, function(file, done) { 141 | var src = fs.readFileSync(file); 142 | try { 143 | var componentInfo = reactDocs.parse(src); 144 | componentInfo.fileName = file; 145 | if (componentInfo.description) { 146 | _parseTags(componentInfo.description, componentInfo); 147 | } 148 | delete componentInfo.props.children; 149 | delete componentInfo.props.className; 150 | delete componentInfo.props.style; 151 | componentInfo.hasProps = false; 152 | if (componentInfo.props) { 153 | for (var p in componentInfo.props) { 154 | var pr = componentInfo.props[p]; 155 | componentInfo.hasProps = true; 156 | _parseTags(pr.description, pr); 157 | } 158 | } 159 | if (componentInfo.private === undefined) { 160 | metadata.components.push(componentInfo); 161 | } 162 | } catch(e) { 163 | // console.log(e.stack); 164 | // console.log(e); 165 | } 166 | done(); 167 | }, function() { 168 | metadata.components = metadata.components.sort(function(a, b) { 169 | if (a.component < b.component ) { 170 | return -1; 171 | } else { 172 | return 1; 173 | } 174 | }); 175 | 176 | var componentsByName = {}; 177 | for(var i in metadata.components) { 178 | var comp = metadata.components[i]; 179 | componentsByName[comp.component] = comp; 180 | } 181 | 182 | for(var i in metadata.components) { 183 | var comp = metadata.components[i]; 184 | if(comp.wraps) { 185 | var wrappedComp = componentsByName[comp.wraps]; 186 | if(wrappedComp) { 187 | _mergeProps(comp, wrappedComp); 188 | } 189 | } 190 | } 191 | 192 | if (program.metadata) { 193 | fs.writeFileSync(program.metadata, 194 | JSON.stringify(metadata, null, 2)); 195 | } 196 | 197 | if (program.markdown) { 198 | fs.writeFileSync(program.markdown, generateMarkdown.generateLibraryDocs(metadata)); 199 | } 200 | 201 | if (program.demo) { 202 | async.series([ 203 | function(done) { _createDemo(program.demo, done); }, 204 | function(done) { _createImages(program.demo, done); } 205 | ]); 206 | } 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /DEVGUIDE.md: -------------------------------------------------------------------------------- 1 | Walmart React Dev Guide 2 | ========================= 3 | 4 | - [Goals](#goals) 5 | - [Submitting Issues](#submitting-issues) 6 | - [Contributing](#contributing) 7 | - [Running the code](#running-the-code) 8 | - [Testing](#testing) 9 | - [PR Process](#pr-process) 10 | - [Before You PR!](#before-you-pr) 11 | - [Submitting a PR](#submitting-a-pr) 12 | - [Assign your PRs](#assign-your-prs) 13 | - [Conventions](#conventions) 14 | - [Walmart React Code Style](#walmart-react-code-style) 15 | - [Five Lines](#five-lines) 16 | - [File Naming](#file-naming) 17 | - [Method Organizations](#method-organizations) 18 | - [displayName](#displayname) 19 | - [Conditional HTML](#conditional-html) 20 | - [JSX as a Variable or Return Value](#jsx-as-a-variable-or-return-value) 21 | - [Self-Closing Tags](#self-closing-tags) 22 | - [List Iterations](#list-iterations) 23 | - [Formatting Attributes](#formatting-attributes) 24 | - [ES6](#es6) 25 | - [When In Doubt](#when-in-doubt) 26 | 27 | ## Goals 28 | 29 | The high level goals and ideals of this are: 30 | 31 | * Give developers a resource of best practices and processes 32 | * Make it clear about what we expect from good code. 33 | * Deliver consistency across the code base. 34 | * Make it easier for engineers to get through the PR process the first time. 35 | * Create code that works, is modern and performant, is maintainable, and clearly expresses its function and the intent of the author. 36 | 37 | ## Submitting Issues 38 | 39 | When submitting an issue to any `electrode-io/*` projects, please provide a good description of your issue along with the following information: 40 | 41 | - `Node` version (`node -v`) 42 | - `npm` version (`npm -v`) 43 | - Relevant logs from the output of your issue 44 | - Any `error` output in either terminal or the browser console 45 | - Browser & browser version you experience issue in (if applicable) 46 | 47 | This will help the developers of that project have the information they need to move forward to fix your issue. 48 | 49 | ## Contributing 50 | 51 | The purpose of these libraries is to provide a set of React components to simplify development on top of the Electrode. This includes wrapping all of the controls, as well as responsive helpers, and form validation. The guiding philosophy is; *We write more code so that you write less code*. 52 | 53 | Be sure to read the [PR process](#pr-process) before submitting code for review. 54 | 55 | ### Running the code 56 | 57 | Run the demo with watch task. 58 | ```sh 59 | % gulp hot 60 | ``` 61 | 62 | Run the demo without watch task. 63 | ```sh 64 | % gulp demo 65 | ``` 66 | 67 | These will launch the demo page on http://localhost:4000/ 68 | 69 | ### Testing 70 | 71 | We want all of the components in this library to be fully tested and have at least 80% function and conditional coverage. We use mocha for the tests and there is one test specification per component. 72 | 73 | To run the tests: 74 | 75 | ```sh 76 | % gulp test 77 | ``` 78 | 79 | ## PR Process 80 | 81 | ### Before You PR! 82 | 83 | ```sh 84 | % gulp test 85 | ``` 86 | 87 | Make sure your code *has tests*, passes those tests, and that all of the other components pass their tests, and that the lint runs completely clean. It is acceptable to disable `new-cap`, `no-unused-vars`, `guard-for-in` and `no-unused-expressions` if required. But it not acceptable to disable eslint entirely. 88 | 89 | Functional coverage must be above 90% overall. 90 | 91 | ### Submitting a PR 92 | 93 | #### Assign your PRs 94 | 95 | Assign your PRs to a single person. If needed, tag that person on the pull request. 96 | 97 | #### Conventions 98 | 99 | PRs should follow the naming convention of `[MAJOR]`, `[MINOR]`, `[PATCH]` followed by a short description, ending with any relevant Jira or Github issue. E.g. `[MINOR] add color property github199`. 100 | 101 | The prefix of the title should be based on how the PR will impact the [semantic version](http://semver.org/) of the package. The size of the changes are unimportant. The only things that matters is how the API has changed - in short: 102 | 103 | * Use `[MAJOR]` when you make incompatible API changes. This includes changing type signatures on returns types or arguments, or renaming or deleting components or methods -- even if they are "not used". A good rule of thumb is to ask if running the previous release's tests against the current candidate's code will break. 104 | * Use `[MINOR]` when you add functionality in a backwards-compatible manner. For example adding methods, components or optional arguments. 105 | * Use `[PATCH]` version when you make backwards-compatible non-feature changes (i.e. bug fixes, performance enhancements et cetera). 106 | 107 | In the PR comment include a short description of changes, any relevant screenshots, and a `cc/` list of people who should see the PR. Put the assigned reviewer's name first. 108 | 109 | 110 | ## Walmart React Code Style 111 | 112 | This is our recommendation for React code. It's based on [https://reactjsnews.com/react-style-guide-patterns-i-like](https://reactjsnews.com/react-style-guide-patterns-i-like). 113 | 114 | ### Five Lines 115 | 116 | How big should a method be? How long should a section of JSX be? Think no more than 5 lines. At the point at which you hit five lines think, should I refactor this? And then refactor it. 117 | 118 | ### File Naming 119 | 120 | All JSX files should have the extension `.jsx` instead of `.js` for ease of identification, compiling and so on. 121 | 122 | ### Method Organizations 123 | 124 | We lay out the methods of a component in life-cycle order: 125 | 126 | ```js 127 | React.createClass({ 128 | displayName : '', 129 | mixins: [], 130 | propTypes: {}, 131 | statics: {}, 132 | getDefaultProps() {}, 133 | getInitialState() {}, 134 | componentWillMount() {}, 135 | componentDidMount() {}, 136 | componentWillReceiveProps() {}, 137 | shouldComponentUpdate() {}, 138 | componentWillUpdate() {}, 139 | componentDidUpdate() {}, 140 | componentWillUnmount() {}, 141 | _getFoo() {}, 142 | _fooHelper() {}, 143 | _onClick() {}, 144 | render() {} 145 | }); 146 | ``` 147 | 148 | Event handlers go before the render. Ideally `render` is always the last method. Sub-renders, like `_renderItem()` would go above the main `render()` method. 149 | 150 | Prepend custom functions with an underscore. 151 | 152 | ### displayName 153 | 154 | `displayName` is required and should match the class/file name and how it would appear in JSX. E.g. `displayName: 'Accordion'` or `displayName: 'RadioGroup'`. 155 | 156 | Nested components need to have a `displayName` that matches the way it would appear in JSX e.g. `displayName: 'Accordion.Item'` 157 | 158 | ### Conditional HTML 159 | 160 | For hiding/showing content we most often choose to use conditional CSS classes, usually with a `hide-content` class, so that there is minimal impact on the DOM as the element is hidden and shown. 161 | 162 | However, in the case where you want to remove an item from the DOM follow this pattern when the code is fairly simple: 163 | 164 | ```js 165 | {this.state.show && 'This is Shown'} 166 | ``` 167 | 168 | Or 169 | 170 | ```js 171 | {this.state.on ? 'On' : 'Off'} 172 | ``` 173 | 174 | For more complex examples create a local variable and add that to the render: 175 | 176 | ```js 177 | let dinosaurHtml = ''; 178 | if (this.state.showDinosaurs) { 179 | dinosaurHtml = ( 180 |
181 | 182 | 183 |
184 | ); 185 | } 186 | 187 | return ( 188 |
189 | ... 190 | {dinosaurHtml} 191 | ... 192 |
193 | ); 194 | ``` 195 | 196 | ### JSX as a Variable or Return Value 197 | 198 | JSX spanning multiple lines should be wrapped in parentheses like so: 199 | 200 | ```js 201 | const multilineJsx = ( 202 |
203 | 204 |
206 | ); 207 | ``` 208 | 209 | JSX spanning a single line can disregard the parentheses, 210 | 211 | ``` 212 | const singleLineJsx = Simple JSX; 213 | ``` 214 | 215 | but anything complicated or with a likeliness of expanding could be wrapped in parentheses for readability/convenience. 216 | 217 | ### Self-Closing Tags 218 | 219 | Components without children should simply close themselves, as above with Logo, 220 | 221 | ```js 222 | 223 | ``` 224 | 225 | as opposed to the unnecessarily more verbose 226 | 227 | ```js 228 | 229 | ``` 230 | 231 | ### List Iterations 232 | 233 | I used to do my list iterations like above in dinosaurHtml. I've realized that list iterations are better done inline, especially if each list item will be rendered as a component. You may even be able to reduce to one line with fat arrows: 234 | 235 | ```js 236 | render() { 237 | return ( 238 |
    239 | {this.state.dinosaursList.map(dinosaur => )} 240 |
241 | ); 242 | } 243 | ``` 244 | 245 | Super clean ES6 syntax. So we like that a lot. 246 | 247 | ### Formatting Attributes 248 | 249 | Instead of the long input element above, a cleaner and easier indentation would be: 250 | 251 | ```js 252 | 256 | ``` 257 | 258 | Note the two space indent. This makes sense for us because our component names can get long. 259 | 260 | 261 | ## ES6 262 | 263 | We like ES6 a lot and we prefer its use over the often more verbose ES5. For details check out the ES6 guide https://github.com/electrode-io/electrode-docgen/blob/master/ES6GUIDE.md 264 | 265 | * We prefer `const` over `let` over `var` - `var` is considered deprecated. 266 | * We prefer dropping the `function` keyword when possible. 267 | * We prefer dropping `function` and using the fat arrow/dash rocket (`=>`) syntax instead. 268 | * We prefer `import` and `export` over `require` and `module.exports`. 269 | * We like the spread operator. And prefer to see `{... this.props}` when it's appropriate in the `render`. 270 | 271 | 272 | ## When In Doubt 273 | 274 | * Breathe. 275 | * Do what works. 276 | * Do what is simple. 277 | * Ask the question on github. We are friendly. We don't bite. 278 | * Drink lots of water and eat leafy greens. 279 | * Realize that life is fleeting. 280 | * Go for a run. 281 | * Grill. 282 | --------------------------------------------------------------------------------