├── .storybook ├── preview-head.html ├── addons.js └── config.js ├── src ├── components │ ├── TextPassage │ │ ├── TextPassage.css │ │ ├── TextPassage.js │ │ └── TextPassage.stories.js │ ├── Logo │ │ ├── Logo.css │ │ ├── Logo.stories.js │ │ └── Logo.js │ ├── InlineForm │ │ ├── InlineForm.css │ │ ├── InlineForm.stories.js │ │ └── InlineForm.js │ ├── CardList │ │ ├── CardList.css │ │ ├── CardList.js │ │ └── CardLIst.stories.js │ ├── Header │ │ ├── Header.css │ │ ├── Header.stories.js │ │ └── Header.js │ ├── Footer │ │ ├── Footer.css │ │ ├── Footer.stories.js │ │ └── Footer.js │ ├── Card │ │ ├── Card.css │ │ ├── Card.stories.js │ │ └── Card.js │ ├── Button │ │ ├── Button.css │ │ ├── Button.stories.js │ │ └── Button.js │ ├── PrimaryNav │ │ ├── PrimaryNav.css │ │ ├── PrimaryNav.stories.js │ │ └── PrimaryNav.js │ ├── Section │ │ ├── Section.css │ │ ├── Section.stories.js │ │ └── Section.js │ └── Hero │ │ ├── Hero.css │ │ ├── Hero.stories.js │ │ └── Hero.js ├── index.css ├── images │ ├── fpo-120x60.png │ ├── fpo-500x300.png │ └── fpo-1200x650.png ├── data │ └── globals.json ├── index.js ├── App.test.js ├── css │ └── App.css └── App.js ├── public ├── favicon.ico ├── manifest.json └── index.html ├── .gitignore ├── package.json └── README.md /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/TextPassage/TextPassage.css: -------------------------------------------------------------------------------- 1 | .c-text-passage { 2 | 3 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradfrost/dumb-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/images/fpo-120x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradfrost/dumb-react/HEAD/src/images/fpo-120x60.png -------------------------------------------------------------------------------- /src/images/fpo-500x300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradfrost/dumb-react/HEAD/src/images/fpo-500x300.png -------------------------------------------------------------------------------- /src/images/fpo-1200x650.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradfrost/dumb-react/HEAD/src/images/fpo-1200x650.png -------------------------------------------------------------------------------- /src/components/Logo/Logo.css: -------------------------------------------------------------------------------- 1 | .c-logo { 2 | display: inline; 3 | } 4 | 5 | .c-logo__img { 6 | display: block; 7 | } -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | import '@storybook/addon-knobs/register'; 4 | -------------------------------------------------------------------------------- /src/data/globals.json: -------------------------------------------------------------------------------- 1 | { 2 | "company" : { 3 | "name" : "Company Name", 4 | "url" : "http://companyname.com", 5 | "phone" : "555-555-5555" 6 | } 7 | } -------------------------------------------------------------------------------- /src/components/InlineForm/InlineForm.css: -------------------------------------------------------------------------------- 1 | .c-inline-form { 2 | display: flex; 3 | padding: 0.5rem 0; 4 | } 5 | 6 | .c-inline-form__input { 7 | flex: 1; 8 | padding: 0.5rem; 9 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /src/components/CardList/CardList.css: -------------------------------------------------------------------------------- 1 | .c-card-list { 2 | margin: 0; 3 | padding: 0; 4 | display: grid; 5 | grid-gap: 2rem; 6 | grid-template-columns: repeat(auto-fill, minmax(285px, 1fr)); 7 | } -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | .c-header { 2 | background: #ddd; 3 | padding: 1rem 2rem; 4 | display: grid; 5 | grid-template-columns: 1fr; 6 | grid-auto-flow: column; 7 | gap: 10px; 8 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | .c-footer { 2 | background: #333; 3 | color: #fff; 4 | padding: 2rem; 5 | } 6 | 7 | .c-footer__copyright { 8 | text-align: center; 9 | margin-top: 2rem; 10 | } -------------------------------------------------------------------------------- /src/components/Card/Card.css: -------------------------------------------------------------------------------- 1 | .c-card { 2 | border: 1px solid #808080; 3 | padding: 1rem; 4 | } 5 | 6 | .c-card--dark { 7 | background: #000; 8 | color: #fff; 9 | } 10 | 11 | .c-card__title { 12 | margin-top: 0; 13 | } -------------------------------------------------------------------------------- /src/components/Button/Button.css: -------------------------------------------------------------------------------- 1 | .c-btn { 2 | background: #333; 3 | color: #fff; 4 | border: 0; 5 | } 6 | 7 | .c-btn[disabled] { 8 | opacity: 0.5; 9 | } 10 | 11 | .c-btn--secondary { 12 | background: #eee; 13 | color: #222; 14 | border: 1px solid #eee; 15 | } -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | import '../src/css/App.css'; 3 | 4 | const req = require.context("../src/components", true, /.stories.js$/); 5 | function loadStories() { 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/PrimaryNav/PrimaryNav.css: -------------------------------------------------------------------------------- 1 | .c-primary-nav { 2 | margin-left: auto; 3 | display: flex; 4 | align-items: center; 5 | } 6 | 7 | .c-primary-nav__list { 8 | display: flex; 9 | list-style: none; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | .c-primary-nav__item { 15 | margin-right: 2rem; 16 | } -------------------------------------------------------------------------------- /src/components/Section/Section.css: -------------------------------------------------------------------------------- 1 | .c-section { 2 | padding: 2rem; 3 | } 4 | 5 | .c-section__header { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | margin-bottom: 2rem; 10 | } 11 | 12 | .c-section__title { 13 | font-size: 2rem; 14 | margin: 0; 15 | } 16 | 17 | .c-section__description { 18 | margin: 0; 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/TextPassage/TextPassage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './TextPassage.css'; 4 | 5 | export class TextPassage extends Component { 6 | render() { 7 | return ( 8 |
9 | { this.props.children } 10 |
11 | ); 12 | } 13 | } 14 | 15 | TextPassage.propTypes = { 16 | children: PropTypes.node 17 | } -------------------------------------------------------------------------------- /src/components/Header/Header.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { withKnobs } from '@storybook/addon-knobs/react'; 4 | import { withSmartKnobs } from 'storybook-addon-smart-knobs'; 5 | import { Header } from './Header'; 6 | 7 | let stories = storiesOf('Global/Header', module); 8 | 9 | stories.addDecorator(withSmartKnobs).addDecorator(withKnobs); 10 | 11 | stories.add('Default', () => 12 |
13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { withKnobs } from '@storybook/addon-knobs/react'; 4 | import { withSmartKnobs } from 'storybook-addon-smart-knobs'; 5 | import { Footer } from './Footer'; 6 | 7 | let stories = storiesOf('Global/Footer', module); 8 | 9 | stories.addDecorator(withSmartKnobs).addDecorator(withKnobs); 10 | 11 | stories.add('Default', () => 12 |
13 | Hello Footer 14 |
15 | ); 16 | -------------------------------------------------------------------------------- /src/components/Hero/Hero.css: -------------------------------------------------------------------------------- 1 | .c-hero { 2 | background: #eee; 3 | position: relative; 4 | } 5 | 6 | .c-hero__img { 7 | display: block; 8 | width: 100%; 9 | height: auto; 10 | max-height: 80vh; 11 | object-fit: cover; 12 | } 13 | 14 | .c-hero__body { 15 | position: absolute; 16 | bottom: 0; 17 | left: 0; 18 | padding: 3rem 2rem; 19 | z-index: 1; 20 | } 21 | 22 | .c-hero__title { 23 | font-size: 3rem; 24 | margin: 0; 25 | } 26 | 27 | .c-hero__description { 28 | font-size: 1.4rem; 29 | margin: 0; 30 | } -------------------------------------------------------------------------------- /src/components/Logo/Logo.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { withKnobs } from '@storybook/addon-knobs/react'; 4 | import { withSmartKnobs } from 'storybook-addon-smart-knobs'; 5 | import { Logo } from './Logo'; 6 | 7 | import logoimg from '../../images/fpo-120x60.png'; 8 | 9 | let stories = storiesOf('Global/Logo', module); 10 | 11 | stories.addDecorator(withSmartKnobs).addDecorator(withKnobs); 12 | 13 | stories.add('Default', () => 14 | 15 | ); -------------------------------------------------------------------------------- /src/components/Logo/Logo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './Logo.css'; 4 | 5 | export class Logo extends Component { 6 | 7 | render() { 8 | return ( 9 | 10 | { 11 | 12 | ); 13 | } 14 | } 15 | 16 | Logo.propTypes = { 17 | href: PropTypes.string, 18 | src: PropTypes.string.isRequired, 19 | alt: PropTypes.string.isRequired 20 | } 21 | -------------------------------------------------------------------------------- /src/css/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | font-family: sans-serif; 4 | } 5 | 6 | .l-linelength-container { 7 | max-width: 40rem; 8 | margin: 0 auto; 9 | padding: 0 1rem; 10 | } 11 | 12 | /*------------------------------------*\ 13 |     #UTILITY CLASSES 14 | \*------------------------------------*/ 15 | 16 | /** 17 | * Visibility hidden 18 | * 1) Visible to screenreaders but not to the screen 19 | */ 20 | .u-is-vishidden { 21 | position: absolute !important; 22 | overflow: hidden; 23 | width: 1px; 24 | height: 1px; 25 | padding: 0; 26 | border: 0; 27 | clip: rect(1px, 1px, 1px, 1px); 28 | } -------------------------------------------------------------------------------- /src/components/Button/Button.stories.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { withKnobs } from '@storybook/addon-knobs/react'; 4 | import { withSmartKnobs } from 'storybook-addon-smart-knobs'; 5 | import { Button } from './Button'; 6 | 7 | let stories = storiesOf('Buttons/Button', module); 8 | 9 | stories.addDecorator(withSmartKnobs).addDecorator(withKnobs); 10 | 11 | stories.add('Default', () => 12 | 21 | ); 22 | } 23 | } 24 | 25 | Button.propTypes = { 26 | btnClass: PropTypes.string, 27 | issecondary: PropTypes.bool, 28 | disabled: PropTypes.bool, 29 | text: PropTypes.string 30 | } 31 | 32 | Button.defaultProps = { 33 | disabled: false, 34 | text: 'Button' 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Hero/Hero.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './Hero.css'; 4 | 5 | export class Hero extends Component { 6 | render() { 7 | return ( 8 |
9 | { 10 |
11 |

{ this.props.title }

12 |

{ this.props.description }

13 |
14 |
15 | ); 16 | } 17 | } 18 | 19 | Hero.propTypes = { 20 | heroimgsrc: PropTypes.string.isRequired, 21 | heroimgalt: PropTypes.string.isRequired, 22 | title: PropTypes.string.isRequired, 23 | description: PropTypes.string 24 | } 25 | 26 | Hero.defaultProps = { 27 | heroimgsrc: "../../images/fpo-1200x650.png", 28 | title: "Hero Title", 29 | description: "This is the hero description" 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "node-gyp": "^3.8.0", 7 | "prop-types": "^15.6.2", 8 | "react": "^16.6.0", 9 | "react-dom": "^16.6.0", 10 | "react-scripts": "1.1.4" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject", 17 | "storybook": "start-storybook -p 9009 -s public", 18 | "build-storybook": "build-storybook -s public" 19 | }, 20 | "devDependencies": { 21 | "@storybook/addon-actions": "^3.4.0", 22 | "@storybook/addon-knobs": "^3.4.2", 23 | "storybook-addon-smart-knobs": "^3.3.1", 24 | "@storybook/addon-links": "^3.4.0", 25 | "@storybook/addons": "^3.4.0", 26 | "@storybook/react": "^3.4.0", 27 | "babel-core": "^6.26.0", 28 | "babel-runtime": "^6.26.0", 29 | "classnames": "^2.2.5", 30 | "node-sass": "^4.9.4", 31 | "sass-loader": "^6.0.7" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Card/Card.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import classnames from "classnames"; 3 | import PropTypes from "prop-types"; 4 | import "./Card.css"; 5 | 6 | export class Card extends Component { 7 | render() { 8 | let cardClass = classnames({ 9 | "c-card": true, 10 | "c-card--dark": this.props.theme === "dark" 11 | }); 12 | 13 | return ( 14 |
15 |
16 |

{this.props.title}

17 |

18 | {this.props.description} 19 |

20 |
21 |
{this.props.children}
22 |
23 | ); 24 | } 25 | } 26 | 27 | Card.propTypes = { 28 | cardClass: PropTypes.string, 29 | title: PropTypes.string, 30 | description: PropTypes.string, 31 | children: PropTypes.node 32 | } 33 | 34 | Card.defaultProps = { 35 | title: "Card Title", 36 | description: "This is the card description" 37 | }; -------------------------------------------------------------------------------- /src/components/InlineForm/InlineForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from 'prop-types'; 3 | import "./InlineForm.css"; 4 | import { Button } from "../Button/Button"; 5 | 6 | export class InlineForm extends Component { 7 | render() { 8 | return ( 9 |
14 | 15 | 20 |
41 | ); 42 | } 43 | } 44 | ``` 45 | 46 | __Button.stories.js__ will be: 47 | 48 | ```js 49 | import React from 'react'; 50 | import { storiesOf } from '@storybook/react'; 51 | import { Button } from './Button'; 52 | 53 | let stories = storiesOf('Button', module); 54 | 55 | stories.add('Default', () => 56 | 59 | ); 60 | 61 | ``` 62 | 63 | __Button.css__ is plain CSS, but will automatically be loaded when the component is used. 64 | 65 | ### Run Storybook 66 | 67 | ```bash 68 | npm run storybook 69 | ``` 70 | 71 | ## Add Button to App.js 72 | 73 | ```jsx 74 | import React, { Component } from 'react'; 75 | import { Button } from './components/Button/Button'; 76 | 77 | class App extends Component { 78 | render() { 79 | return ( 80 |
81 | 82 |
83 | ); 84 | } 85 | } 86 | 87 | export default App; 88 | ``` 89 | 90 | ### Run the application 91 | 92 | ```bash 93 | npm start 94 | ``` 95 | 96 | ## Adding Sass 97 | 98 | Adding Sass involves "ejecting" out of create react app. This process is out of the scope of this demo, but I'll include some links below. 99 | 100 | - [Adding Sass support to Create React App](https://medium.com/front-end-hacking/how-to-add-sass-or-scss-to-create-react-app-c303dae4b5bc) 101 | - [Adding Sass support to Storybook](https://storybook.js.org/configurations/custom-webpack-config/) 102 | --------------------------------------------------------------------------------