├── .babelrc ├── .gitignore ├── package-lock.json ├── package.json ├── readme.md └── src ├── assets └── logo_dube.png ├── components ├── Button.jsx ├── Card.jsx ├── Container.jsx ├── Flex.jsx ├── Heading.jsx └── Loader.jsx ├── index.html ├── index.js └── workspace ├── Application ├── GlobalStyle.jsx ├── Header.jsx ├── Routes.jsx └── index.jsx └── Components ├── Button.jsx ├── Card.jsx ├── Container.jsx ├── Flex.jsx ├── Heading.jsx └── index.jsx /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["react-hot-loader/babel", "babel-plugin-styled-components"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .git/ 3 | .cache/ 4 | dist/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-system", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel src/index.html" 8 | }, 9 | "author": "Lukas Gisder-Dubé", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.2.2", 13 | "@babel/preset-env": "^7.2.3", 14 | "@babel/preset-react": "^7.0.0", 15 | "babel-plugin-styled-components": "^1.10.0", 16 | "parcel-bundler": "^1.11.0", 17 | "react-hot-loader": "^4.6.3" 18 | }, 19 | "dependencies": { 20 | "faker": "^4.1.0", 21 | "react": "^16.7.0", 22 | "react-dom": "^16.7.0", 23 | "react-router": "^4.3.1", 24 | "react-router-dom": "^4.3.1", 25 | "styled-components": "^4.1.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React Component System 2 | 3 | This repository contains the code for the [accompanying article](https://levelup.gitconnected.com/building-a-reusable-component-system-with-react-js-and-styled-components-4e9f1018a31c) 4 | 5 | To run: 6 | 7 | 1. `npm install` 8 | 2. `npm run start` 9 | -------------------------------------------------------------------------------- /src/assets/logo_dube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisderdube/react-component-system/7f7fbf2ac1c99cef0353d0dc6d61d58f76708d2c/src/assets/logo_dube.png -------------------------------------------------------------------------------- /src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css } from 'styled-components' 3 | import Loader from './Loader' 4 | 5 | const StyledButton = styled.button` 6 | border-radius: 5px; 7 | background-color: ${props => (props.secondary ? '#F7A072' : '#a1cdf1')}; 8 | color: #fff; 9 | padding: 10px 15px; 10 | font-size: ${props => { 11 | if (props.big) return '20px' 12 | return '16px' 13 | }}; 14 | outline: none; 15 | border: none; 16 | cursor: pointer; 17 | margin: 15px; 18 | border: 2px solid ${props => (props.secondary ? '#F7A072' : '#a1cdf1')}; 19 | 20 | ${props => { 21 | return ( 22 | props.inverse && 23 | css` 24 | background-color: #fff; 25 | color: #a1cdf1; 26 | ` 27 | ) 28 | }} 29 | ` 30 | 31 | const Button = ({ secondary, big, inverse, loading, children, ...props }) => { 32 | return ( 33 | 34 | {loading ? : children} 35 | 36 | ) 37 | } 38 | 39 | export default Button 40 | -------------------------------------------------------------------------------- /src/components/Card.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css, keyframes } from 'styled-components' 3 | 4 | const animatedCss = css` 5 | opacity: 1; 6 | transform: translateY(0); 7 | ` 8 | 9 | const primaryCss = css` 10 | background-color: #008bf8; 11 | color: #fff; 12 | ` 13 | 14 | const StyledCard = styled.div` 15 | width: ${props => (props.big ? '450px' : '300px')}; 16 | padding: 15px; 17 | opacity: 0; 18 | transform: translateY(50px); 19 | transition: 500ms all ease-in-out; 20 | margin: ${props => (props.noMargin ? 0 : '15px')}; 21 | box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 1); 22 | border-radius: 5px; 23 | 24 | ${props => props.animated && animatedCss} 25 | ${props => props.primary && primaryCss} 26 | ` 27 | 28 | class Card extends React.Component { 29 | constructor(props) { 30 | super(props) 31 | this.state = { 32 | animated: false, 33 | } 34 | } 35 | 36 | componentDidMount() { 37 | setTimeout(() => { 38 | this.setState(() => { 39 | return { animated: true } 40 | }) 41 | }, this.props.delay) 42 | } 43 | 44 | render() { 45 | const { delay = 0, noAnimation, primary, noMargin, big, ...props } = this.props 46 | return ( 47 | 56 | ) 57 | } 58 | } 59 | 60 | export default Card 61 | -------------------------------------------------------------------------------- /src/components/Container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | padding-left: ${props => { 6 | if (props.full) return 0 7 | return 'calc((100vw - 960px) / 2)' 8 | }}; 9 | 10 | padding-right: ${props => { 11 | if (props.full) return 0 12 | return 'calc((100vw - 960px) / 2)' 13 | }}; 14 | 15 | padding-top: ${props => { 16 | if (props.fullVertical) return 0 17 | if (props.small) return '15px' 18 | return '25px' 19 | }}; 20 | 21 | padding-bottom: ${props => { 22 | if (props.fullVertical) return 0 23 | if (props.small) return '15px' 24 | return '25px' 25 | }}; 26 | ` 27 | 28 | export default Container 29 | -------------------------------------------------------------------------------- /src/components/Flex.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Flex = styled.div` 5 | display: flex; 6 | flex-wrap: ${props => { 7 | if (props.wrapReverse) return 'wrap-reverse' 8 | else if (props.noWrap) return 'nowrap' 9 | return 'wrap' 10 | }}; 11 | justify-content: ${props => { 12 | if (props.justifyContent) return props.justifyContent 13 | if (props.justifyCenter) return 'center' 14 | else if (props.justifyAround) return 'space-around' 15 | else if (props.justifyBetween) return 'space-between' 16 | else if (props.justifyEnd) return 'flex-end' 17 | return 'flex-start' 18 | }}; 19 | align-items: ${props => { 20 | if (props.alignItems) return props.alignItems 21 | else if (props.alignStretch) return 'stretch' 22 | else if (props.alignEnd) return 'flex-end' 23 | if (props.alignCenter) return 'center' 24 | else if (props.alignBaseline) return 'baseline' 25 | return 'flex-start' 26 | }}; 27 | align-content: ${props => { 28 | if (props.alignContent) return props.content 29 | else if (props.contentStart) return 'flex-start' 30 | else if (props.contentEnd) return 'flex-end' 31 | else if (props.contentCenter) return 'center' 32 | else if (props.contentBetween) return 'space-between' 33 | else if (props.contentAround) return 'contentAround' 34 | return 'stretch' 35 | }}; 36 | flex-direction: ${props => (props.column ? 'column' : 'row')}; 37 | ` 38 | 39 | export const Column = styled.div` 40 | width: ${props => { 41 | if (props.three) return '33.33%' 42 | if (props.four) return '25%' 43 | return '50%' 44 | }}; 45 | padding: ${props => (props.noPadding ? 0 : '15px')}}; 46 | ` 47 | 48 | export default Flex 49 | -------------------------------------------------------------------------------- /src/components/Heading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css } from 'styled-components' 3 | 4 | const baseStyle = css` 5 | margin-bottom: ${props => props.noMargin && '0'}; 6 | color: #202020; 7 | font-family: 'Poppins', sans-serif; 8 | font-weight: 600; 9 | margin-top: 0; 10 | text-align: ${props => { 11 | if (props.center) return 'center' 12 | if (props.right) return 'right' 13 | return 'left' 14 | }}; 15 | 16 | max-width: 100%; 17 | ` 18 | 19 | const HeadingOne = styled.h1` 20 | font-size: 42px; 21 | font-weight: bold; 22 | margin-bottom: 25px; 23 | ${baseStyle}; 24 | 25 | @media (max-width: 480px) { 26 | font-size: 40px; 27 | } 28 | ` 29 | 30 | const HeadingTwo = styled.h2` 31 | font-size: 36px; 32 | font-weight: bold; 33 | margin-bottom: 20px; 34 | ${baseStyle}; 35 | ` 36 | 37 | const HeadingThree = styled.h3` 38 | font-size: 28px; 39 | font-weight: bold; 40 | margin-bottom: 15px; 41 | ${baseStyle}; 42 | ` 43 | 44 | const HeadingFour = styled.h4` 45 | font-size: 22px; 46 | font-weight: bold; 47 | margin-bottom: 10px; 48 | ${baseStyle}; 49 | ` 50 | 51 | const HeadingFive = styled.h5` 52 | font-size: 18px; 53 | font-weight: bold; 54 | margin-bottom: 5px; 55 | ${baseStyle}; 56 | ` 57 | 58 | const Heading = ({ h2, h3, h4, h5, noMargin, right, center, ...props }) => { 59 | if (h2) return 60 | if (h3) return 61 | if (h4) return 62 | if (h5) return 63 | return 64 | } 65 | 66 | export default Heading 67 | -------------------------------------------------------------------------------- /src/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { keyframes } from 'styled-components' 3 | 4 | const Bounce = keyframes` 5 | 0%, 80%, 100% { 6 | transform: scale(0); 7 | } 40% { 8 | transform: scale(1.0); 9 | } 10 | ` 11 | 12 | const StyledDotsLoader = styled.div` 13 | display: inline-block; 14 | ` 15 | 16 | const Dot = styled.span` 17 | width: ${props => (props.big ? '20px' : '12px')}; 18 | height: ${props => (props.big ? '20px' : '12px')}; 19 | background-color: ${props => (props.white ? '#FFF' : '#000')}; 20 | border-radius: 100%; 21 | display: inline-block; 22 | animation: ${Bounce} 1s infinite ease-in-out both; 23 | &:first-child { 24 | animation-delay: -0.32s; 25 | } 26 | 27 | &:nth-child(2) { 28 | animation-delay: -0.16s; 29 | } 30 | ` 31 | 32 | const Loader = props => { 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default Loader 43 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | React Component System by dube.io 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import Application from './workspace/Application' 5 | 6 | ReactDOM.render(, document.getElementById('app')) 7 | -------------------------------------------------------------------------------- /src/workspace/Application/GlobalStyle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createGlobalStyle } from 'styled-components' 3 | 4 | const GlobalStyle = createGlobalStyle` 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | html, body { 10 | margin: 0; 11 | font-size: 20px; 12 | font-family: 'Work Sans', sans-serif; 13 | font-weight: 300; 14 | } 15 | ` 16 | 17 | export default GlobalStyle 18 | -------------------------------------------------------------------------------- /src/workspace/Application/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import styled from 'styled-components' 4 | import Container from '../../components/Container' 5 | 6 | const StyledHeader = styled(Container)` 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | ` 11 | 12 | const StyledLink = styled(Link)` 13 | color: #04e762; 14 | font-weight: 500; 15 | text-decoration: none; 16 | font-family: 'Work Sans', sans-serif; 17 | display: inline-block; 18 | margin-right: 25px; 19 | ` 20 | 21 | const Header = () => { 22 | return ( 23 | 24 |
25 | 26 | Components 27 | 28 |
29 | 30 | 31 | 32 |
33 | ) 34 | } 35 | 36 | export default Header 37 | -------------------------------------------------------------------------------- /src/workspace/Application/Routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, Switch } from 'react-router-dom' 3 | import Components from '../Components' 4 | 5 | class Routes extends React.Component { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | } 14 | 15 | export default Routes 16 | -------------------------------------------------------------------------------- /src/workspace/Application/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Routes from './Routes' 3 | import Header from './Header' 4 | import GlobalStyle from './GlobalStyle' 5 | import { BrowserRouter } from 'react-router-dom' 6 | 7 | const Application = () => { 8 | return ( 9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | 19 | export default Application 20 | -------------------------------------------------------------------------------- /src/workspace/Components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from '../../components/Container' 3 | import Heading from '../../components/Heading' 4 | import Button from '../../components/Button' 5 | 6 | const ButtonSample = () => { 7 | return ( 8 |
9 | 10 | Button 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | 23 | export default ButtonSample 24 | -------------------------------------------------------------------------------- /src/workspace/Components/Card.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import faker from 'faker' 3 | import Container from '../../components/Container' 4 | import Heading from '../../components/Heading' 5 | import Flex from '../../components/Flex' 6 | import Card from '../../components/Card' 7 | 8 | const randomCards = [] 9 | 10 | for (let i = 0; i < 9; i++) { 11 | randomCards.push({ 12 | title: faker.lorem.words(2), 13 | copy: faker.lorem.sentences(3), 14 | }) 15 | } 16 | 17 | const CardSample = () => { 18 | return ( 19 |
20 | 21 | Card 22 | 23 | 24 | 25 | 26 | Normal Card 27 |

28 | Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi 29 | porta gravida at eget metus. Donec sed odio dui. 30 |

31 |
32 | 33 | Big Card 34 |

35 | Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec sed 36 | odio dui. Donec id elit non mi porta gravida at eget metus. 37 |

38 |
39 | 40 | 41 | Primary Delayed Card 42 | 43 |

44 | Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi 45 | porta gravida at eget metus. Donec sed odio dui. 46 |

47 |
48 |
49 |
50 | 51 | Card List 52 | 53 | {randomCards.map((el, index) => { 54 | return ( 55 | 56 | 57 | {el.title} 58 | 59 |

{el.copy}

60 |
61 | ) 62 | })} 63 |
64 |
65 |
66 | ) 67 | } 68 | 69 | export default CardSample 70 | -------------------------------------------------------------------------------- /src/workspace/Components/Container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from '../../components/Container' 3 | import Heading from '../../components/Heading' 4 | 5 | const ContainerSample = () => { 6 | return ( 7 |
8 | 9 | Container 10 | 11 | 12 |
13 | Default Container 14 |
15 |
16 | 17 |
18 | Full Vertical Container 19 |
20 |
21 | 22 |
23 | Full Small Container 24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default ContainerSample 31 | -------------------------------------------------------------------------------- /src/workspace/Components/Flex.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from '../../components/Container' 3 | import Heading from '../../components/Heading' 4 | import Flex, { Column } from '../../components/Flex' 5 | 6 | const FlexSample = () => { 7 | return ( 8 |
9 | 10 | Flex & Column 11 | 12 | 13 | 14 | 15 |
23 |
31 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | ) 61 | } 62 | 63 | export default FlexSample 64 | -------------------------------------------------------------------------------- /src/workspace/Components/Heading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from '../../components/Container' 3 | import Heading from '../../components/Heading' 4 | 5 | const CardSample = () => { 6 | return ( 7 |
8 | 9 | Heading 10 | 11 | 12 | h1 - Heading 1 13 | h2 - Heading 2 14 | h3 - Heading 3 15 | h4 - Heading 4 16 | h5 - Heading 5 17 | 18 |
19 | ) 20 | } 21 | 22 | export default CardSample 23 | -------------------------------------------------------------------------------- /src/workspace/Components/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from '../../components/Container' 3 | import ContainerSample from './Container' 4 | import Flex from './Flex' 5 | import Button from './Button' 6 | import Card from './Card' 7 | import Heading from './Heading' 8 | 9 | const Components = () => { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 |
20 | ) 21 | } 22 | 23 | export default Components 24 | --------------------------------------------------------------------------------