├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── CONTRIBUTE.md ├── LICENSE ├── README.md ├── assets ├── cat-320-240.jpeg ├── cat-640-480.jpeg └── cat-placeholder.jpeg ├── docs ├── Animations.md ├── Components.md ├── Form.md ├── Introduction.md ├── Layout.md ├── Theming.md ├── components │ ├── Animate.md │ ├── AppBar.md │ ├── Button.md │ ├── Card.md │ ├── Dropdown.md │ ├── Form │ │ ├── Checkbox.md │ │ ├── Radio.md │ │ ├── RadioGroup.md │ │ └── TextField.md │ ├── Grid.md │ ├── Icon.md │ ├── Image.md │ ├── Link.md │ ├── List.md │ ├── Modal.md │ ├── Notify.md │ └── Tooltip.md └── template.html ├── package-lock.json ├── package.json ├── plopfile.js ├── scripts ├── release.sh └── semver.sh ├── src ├── Animate │ ├── Animate.js │ └── index.js ├── Animations │ ├── fadeIn.js │ ├── fadeInPulse.js │ ├── fallPerspective.js │ ├── flip.js │ ├── helix.js │ ├── index.js │ ├── moveUp.js │ ├── perspectiveDown.js │ ├── perspectiveLeft.js │ ├── perspectiveRight.js │ ├── perspectiveUp.js │ ├── pop.js │ ├── popDownwards.js │ ├── popUpwards.js │ ├── puffIn.js │ ├── puffOut.js │ ├── pulseShadow.js │ ├── rotate.js │ ├── scaleUp.js │ ├── shake.js │ ├── shimmyShake.js │ ├── slideDown.js │ ├── slideIn.js │ ├── slideInRight.js │ ├── slideLeft.js │ ├── slideOut.js │ ├── slideRight.js │ ├── slideRightLeft.js │ ├── slideUp.js │ ├── vanishIn.js │ └── vanishOut.js ├── AppBar │ ├── AppBar.js │ ├── index.js │ └── styles.js ├── Button │ ├── Button.js │ ├── index.js │ └── styles.js ├── Card │ ├── Card.js │ ├── CardBody.js │ ├── CardFooter.js │ ├── CardHeader.js │ ├── CardImage.js │ ├── index.js │ └── styles.js ├── Dropdown │ ├── Dropdown.js │ ├── index.js │ └── styles.js ├── Form │ ├── Checkbox │ │ ├── Checkbox.js │ │ ├── index.js │ │ └── styles.js │ ├── Radio │ │ ├── Radio.js │ │ ├── index.js │ │ └── styles.js │ ├── RadioGroup │ │ ├── RadioGroup.js │ │ ├── index.js │ │ └── styles.js │ └── TextField │ │ ├── TextField.js │ │ ├── index.js │ │ └── styles.js ├── Icon │ ├── Icon.js │ ├── Icon.spec.js │ ├── index.js │ └── styles.js ├── Image │ ├── Image.js │ ├── index.js │ └── styles.js ├── Layout │ ├── Cell.js │ ├── Grid.js │ ├── index.js │ └── styles.js ├── Link │ ├── Link.js │ ├── index.js │ └── styles.js ├── List │ ├── List.js │ ├── ListFooter.js │ ├── ListHeader.js │ ├── ListItem.js │ ├── ListSection.js │ ├── index.js │ └── styles.js ├── Modal │ ├── Modal.js │ ├── ModalPortal.js │ ├── index.js │ └── styles.js ├── Notify │ ├── Notify.js │ ├── NotifyPanel.js │ ├── NotifyPortal.js │ ├── index.js │ └── styles.js ├── ThemeProvider │ ├── ThemeProvider.js │ └── index.js ├── Tooltip │ ├── Tooltip.js │ ├── index.js │ └── styles.js ├── index.js ├── keyframes.js ├── test-helpers.js └── theme.js ├── styleguide.config.js ├── styleguide └── setup.js └── templates └── component ├── component.txt ├── document.txt ├── index.txt └── styles.txt /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "es2015", "stage-0", "react"], 3 | "plugins": [ 4 | [ 5 | "module-resolver", 6 | { 7 | "root": ["."], 8 | "alias": { 9 | "react": "preact-compat", 10 | "react-dom": "preact-compat", 11 | "create-react-class": "preact-compat/lib/create-react-class" 12 | } 13 | }, 14 | "transform-react-jsx", { "pragma": "h" } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["prettier"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /styleguide/* 4 | !/styleguide/setup.js 5 | /*.log 6 | yarn.lock 7 | /.idea 8 | .idea/workspace.xml 9 | lib 10 | .npmrc 11 | /coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajainvivek/preact-fluid/dbe0c3e44c49fbbffc26ffdd3fb89e0ee6e9a8ba/.npmignore -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "trailingComma": "es5", 5 | "tabWidth": 4 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | branches: 5 | only: 6 | - master 7 | - "/^v[0-9](\\.[0-9]+)(\\.[0-9]+)?$/" 8 | cache: 9 | directories: 10 | - node_modules 11 | install: 12 | - npm install 13 | script: 14 | - npm run build 15 | - npm run test 16 | deploy: 17 | provider: npm 18 | email: ajainvivek07@gmail.com 19 | skip_cleanup: true 20 | api_key: 21 | secure: Dw3CdQVdpQL9MPdyR5S0i3uP45urabZ9F1xlArJDN/NWZHF1Og0qRh52B0fSIWRc01ZrzjSSTumyaMnEFYzzLdhxQuF7TNRJHd4vzLQM8Jv5QMsx0ieJkpLBLxr7mnmjIBgnLgOQ78aZXjMXnxDx581YgxEWKlvVZgX/nre9fch50DDHlT7ezHfxPB0btUrK9UtXx9o951bW1IgZMHhgxeNHK0GZYtxX4a52YkSKz9qDxDAaSDEKJUt3fIiOnH8KVo+UNomok5Ab6j356AEBT/11T9yWXI3RJwINNawCUD6S38zwNdmsD6LAHlagGjSIkH/zGX6VQ90mrBbW42m3RORTaglLlYBomQYk7QjiPOQlgk5KKYnYDqc811xk7zt5SG88tcaKJSfx9uCHSG9NpuCLnHs1QMDUhynHruklCuT387px4TLsgXLIeT0uHrlcjFLFjDCNZ9Zxl/BontaHBeXz8Y8MVnozbbVvh8BUxxwe0U3U9GsOVlVTdRuO3TiZbn5jU2iLUP+02KG3E+MQnyz9Ud20MHwCyFKQY+5G3/T8VPyBMydfhtc54XaN7RscTFPnH56FnT+8jhHeiUxCbGp6cLqa7NG+Ckyq/lDjKp3+Dx13QCLIfn1CFlIRlF2M0ObeV1a2m/Uny9vdpWcfgO3ffuqj2ji3WkBhi9OBXWU= 22 | on: 23 | tags: true 24 | branch: master 25 | repo: ajainvivek/preact-fluid 26 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Here are a few guidelines that will help you along the way. 4 | 5 | ## Submitting a Pull Request 6 | 7 | Preact-Fluid is an open source project, so pull requests are always welcome, but before working on a large change, it is best to open an issue first to discuss it with the maintainers. 8 | 9 | When in doubt, keep your pull requests small. To give a PR the best chance of getting accepted, don't bundle more than one feature or bug fix per pull request. It's always best to create two smaller PRs than one big one. 10 | 11 | As with issues, please begin the title with [ComponentName]. 12 | 13 | 14 | ## Getting started 15 | 16 | Please create a new branch from an up to date master on your fork. 17 | 18 | 1. Fork the Preact-Fluid repository on Github 19 | 2. Clone your fork to your local machine `git clone git@github.com:/preact-fluid.git` 20 | 3. Create a branch `git checkout -b my-topic-branch` 21 | 4. Make your changes, lint, then push to to GitHub with `git push --set-upstream origin my-topic-branch`. 22 | 5. Visit GitHub and make your pull request. 23 | 24 | If you have an existing local repository, please update it before you start, to minimise the chance of merge conflicts. 25 | 26 | ```js 27 | git remote add upstream git@github.com:ajainvivek/preact-fluid.git 28 | git checkout master 29 | git pull upstream master 30 | git checkout -b my-topic-branch 31 | yarn 32 | ``` 33 | 34 | ### Coding style 35 | 36 | Please follow the coding style of the current code base. Preact-Fluid uses eslint, so if possible, enable linting in your editor to get real-time feedback. The linting rules are also run when Webpack recompiles your changes, and can be run manually with `yarn lint`. 37 | 38 | You can also run `yarn prettier` to reformat the code. 39 | 40 | Finally, when you submit a pull request, they are run again by Circle CI, but hopefully by then your code is already clean! 41 | 42 | 43 | ## How do I add new a demo in the documentation? 44 | 45 | Traverse to the docs folder and create properCase filename, if the component name doesn't exist. Then add respective demo. 46 | 47 | example: 48 | 49 | ```js 50 | Default Link 51 | ``` 52 | 53 | ## Create a new component from scratch 54 | 55 | ```js 56 | yarn generate-component 57 | ``` 58 | Creates a boilerplate structure for a new component 59 | 60 | Then finally, hook the component in styleguide.config.js to list in the demo. 61 | 62 | ## Release 63 | 64 | If you are colloborator on the project, you can run release on master, which will publish the latest changes to npm registry 65 | 66 | ``` 67 | yarn release 68 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Preact Fluid 4 | 5 |

6 |

Minimal UI Kit for Preact, with reusable components.

7 | 8 | [![Build Status](https://travis-ci.org/ajainvivek/preact-fluid.svg?branch=master)](https://travis-ci.org/ajainvivek/preact-fluid) 9 | [![Dependencies](https://img.shields.io/david/ajainvivek/preact-fluid.svg)](https://david-dm.org/ajainvivek/preact-fluid) 10 | [![DevDependencies](https://img.shields.io/david/dev/ajainvivek/preact-fluid.svg)](https://david-dm.org/ajainvivek/preact-fluid#info=devDependencies&view=list) 11 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 12 | [![HitCount](http://hits.dwyl.io/ajainvivek/preact-fluid.svg)](http://hits.dwyl.io/ajainvivek/preact-fluid) 13 | 14 | ## Installation 15 | 16 | Preact Fluid is available as an [npm package](https://www.npmjs.com/package/preact-fluid). 17 | 18 | ```sh 19 | npm install preact-fluid --save 20 | ``` 21 | 22 | ## Usage 23 | 24 | Here is a quick example to get you started, it's all you need: 25 | 26 | ```jsx 27 | import { render } from 'preact'; 28 | import { Button } from 'preact-fluid'; 29 | 30 | function App() { 31 | return ( 32 | 35 | ); 36 | } 37 | 38 | render(, document.querySelector('#app')); 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Check out our [documentation website](https://ajainvivek.github.io/preact-fluid/). 44 | 45 | 46 | ## Contributing 47 | 48 | We'd greatly appreciate any contribution you make. :D 49 | 50 | ## License 51 | 52 | This project is licensed under the terms of the 53 | [MIT license](https://github.com/ajainvivek/preact-fluid/blob/v1-beta/LICENSE). 54 | 55 | -------------------------------------------------------------------------------- /assets/cat-320-240.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajainvivek/preact-fluid/dbe0c3e44c49fbbffc26ffdd3fb89e0ee6e9a8ba/assets/cat-320-240.jpeg -------------------------------------------------------------------------------- /assets/cat-640-480.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajainvivek/preact-fluid/dbe0c3e44c49fbbffc26ffdd3fb89e0ee6e9a8ba/assets/cat-640-480.jpeg -------------------------------------------------------------------------------- /assets/cat-placeholder.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajainvivek/preact-fluid/dbe0c3e44c49fbbffc26ffdd3fb89e0ee6e9a8ba/assets/cat-placeholder.jpeg -------------------------------------------------------------------------------- /docs/Animations.md: -------------------------------------------------------------------------------- 1 | A bunch of CSS3 animations with special effects. 2 | 3 | Usage with styled components 4 | 5 | ```js static 6 | import {Animations} from 'preact-fluid'; 7 | // Here we create a component that will rotate everything we pass in over two seconds 8 | const Rotate = styled.div` 9 | display: inline-block; 10 | animation: ${Animations.rotate} 2s linear infinite; 11 | padding: 2rem 1rem; 12 | font-size: 1.2rem; 13 | `; 14 | render( 15 | Hello World! 16 | ); 17 | ``` 18 | 19 | | Supported Animations | 20 | | ------------- | 21 | | slideUp, slideDown, slideLeft, slideRight, shake, rotate, puffIn, puffOut, perspectiveDown, perspectiveUp, perspectiveLeft, perspectiveRight, vanishIn, vanishOut, fadeInPulse, pop, popDownwards, popUpwards, pulseShadow, shimmyShake, fadeIn, fallPerspective, flip, helix, moveUp, scaleUp | 22 | -------------------------------------------------------------------------------- /docs/Components.md: -------------------------------------------------------------------------------- 1 | Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. Preact fluid uses [styled-components](https://github.com/styled-components/styled-components) which allows you to write actual CSS code to style your components. -------------------------------------------------------------------------------- /docs/Form.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajainvivek/preact-fluid/dbe0c3e44c49fbbffc26ffdd3fb89e0ee6e9a8ba/docs/Form.md -------------------------------------------------------------------------------- /docs/Introduction.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Preact Fluid 4 | 5 |

6 |

Minimal UI Kit for Preact, with reusable components.

7 | 8 | [![PeerDependencies](https://img.shields.io/david/peer/ajainvivek/preact-fluid.svg)](https://david-dm.org/ajainvivek/preact-fluid#info=peerDependencies&view=list) 9 | [![Dependencies](https://img.shields.io/david/ajainvivek/preact-fluid.svg)](https://david-dm.org/ajainvivek/preact-fluid) 10 | [![DevDependencies](https://img.shields.io/david/dev/ajainvivek/preact-fluid.svg)](https://david-dm.org/ajainvivek/preact-fluid#info=devDependencies&view=list) 11 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 12 | [![HitCount](http://hits.dwyl.io/ajainvivek/preact-fluid.svg)](http://hits.dwyl.io/ajainvivek/preact-fluid) 13 | 14 | 15 | 16 | 17 | ## Installation 18 | 19 | Preact Fluid is available as an [npm package](https://www.npmjs.com/package/preact-fluid). 20 | 21 | ```sh 22 | npm install preact-fluid --save 23 | ``` 24 | 25 | ## Usage 26 | 27 | Here is a quick example to get you started, it's all you need: 28 | 29 | ```jsx static 30 | import { render } from 'preact'; 31 | import { Button } from 'preact-fluid'; 32 | 33 | function App() { 34 | return ( 35 | 38 | ); 39 | } 40 | 41 | render(, document.querySelector('#app')); 42 | ``` -------------------------------------------------------------------------------- /docs/Layout.md: -------------------------------------------------------------------------------- 1 | Layout is to organise the content. -------------------------------------------------------------------------------- /docs/Theming.md: -------------------------------------------------------------------------------- 1 | Predefined Theme: 2 | 3 | Default theme is located under `preact-fluid/src/theme`. 4 | 5 | Custom Theme: 6 | 7 | Custom themes can be defined by using `ThemeProvider` as described in the usage below. 8 | 9 | ```jsx static 10 | 11 | import { ThemeProvider } from 'preact-fluid'; 12 | 13 | const theme = { 14 | primaryColor: "#E91E63", 15 | primaryColorDark: '#C2185B', 16 | primaryColorLight: '#F8BBD0', 17 | secondaryColor: '#E91E63', 18 | secondaryColorDark: '#C2185B', 19 | secondaryColorLight: '#F8BBD0', 20 | linkColor: '#E040FB', 21 | linkColorDark: '#E040FB', 22 | darkColor: '#212121', 23 | lightColor: '#fffff', 24 | grayColor: '#757575', 25 | grayColorLight: '#757575', 26 | grayColorDark: '#757575', 27 | borderColor: '#BDBDBD', 28 | borderColorDark: '#BDBDBD', 29 | bgColor: '#FFFFFF', 30 | bgColorDark: '#CCCCCC', 31 | bgColorLight: '#FFFFFF', 32 | controlSuccessColor: '#32b643', 33 | controlWarningColor: '#ffb700', 34 | controlErrorColor: '#e85600', 35 | codeColor: '#e06870', 36 | highlightColor: '#ffe9b3', 37 | notifyBgColor: '#ececec', 38 | listActiveColor: '#f0f3f5' 39 | }; 40 | 41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | ThemeProvider 48 | 49 | This component takes a theme as a property and passes it down with context. This should preferably be at the root of your component tree. The example demonstrates it's usage. -------------------------------------------------------------------------------- /docs/components/Animate.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | Usage: 4 | 5 | ```js static 6 | const AnimatedButton = ; 7 | 8 | render( 9 | 15 | ); 16 | ``` 17 | 18 | 1. Simple Component Animation: 19 | 20 | ```js 21 | const AnimatedButton = ; 37 | 43 | ``` 44 | 45 | ```js 46 | initialState = { toggleAnimation: true }; 47 | const AnimatedButton = ; 52 | 60 | 61 | ``` 62 | ```js 63 | initialState = { toggleAnimation: true }; 64 | const AnimatedButton = ; 81 | 89 | ``` 90 | 91 | ```js 92 | initialState = { 93 | toggleAnimation: true, 94 | animation: 'slideIn' 95 | }; 96 | const AnimatedCard = (image) => { 97 | return ( 98 | 99 | 102 | 106 | 107 | Irish girl and Irish boy. Is all you need to break toys. That's all you need. That's all you want. That's all you need. Inside this grief. 108 | 109 | 110 | ); 111 | }; 112 | 113 | 114 | 115 | 121 | 122 | 123 | 131 | 132 | 133 | ``` 134 | 135 | ```js 136 | initialState = { 137 | toggleAnimation: true, 138 | animation: 'moveUp' 139 | }; 140 | const AnimatedCard = (image) => { 141 | return ( 142 | 143 | 146 | 150 | 151 | Irish girl and Irish boy. Is all you need to break toys. That's all you need. That's all you want. That's all you need. Inside this grief. 152 | 153 | 154 | ); 155 | }; 156 | 157 |
158 | 159 | 160 | 166 | 167 | 168 | {state.animation} 169 | 170 | 171 | 172 | 173 | 181 | 182 | 183 | 191 | 192 | 193 | 201 | 202 | 203 |
204 | ``` 205 | -------------------------------------------------------------------------------- /docs/components/AppBar.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | 1. Default AppBar: 4 | 5 | ```js 6 | 9 | ``` 10 | 11 | 2. Primary AppBar: 12 | 13 | ```js 14 | const logoutBtn = ; 15 | const welcomeBtn = ; 16 | 23 | ``` -------------------------------------------------------------------------------- /docs/components/Button.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | 5 | ``` 6 | 7 | ```js 8 | 9 | ``` 10 | 11 | ```js 12 | 13 | ``` 14 | 15 | ```js 16 | 17 | ``` 18 | 19 | ```js 20 | 21 | ``` 22 | 23 | ```js 24 | 33 | ``` 34 | 35 | ```js 36 | 45 | ``` 46 | 47 | Badge 48 | 49 | ```js 50 | const badge = { value: 3 }; 51 | 52 | 53 | ``` 54 | 55 | Loading 56 | 57 | ```js 58 | 59 | ``` 60 | 61 | ```js 62 | 63 | ``` -------------------------------------------------------------------------------- /docs/components/Card.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | 5 | 6 | 7 | 11 | 12 | Irish girl and Irish boy. Is all you need to break toys. That's all you need. That's all you want. That's all you need. Inside this grief. 13 | 14 | 17 | 18 | 19 | 20 | 21 | 24 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | Irish girl and Irish boy. Is all you need to break toys. That's all you need. That's all you want. That's all you need. Inside this grief. 38 | 39 | Share 42 | } 43 | right={ 44 | 53 | } 54 | /> 55 | 56 | 57 | 58 | 59 | 66 | 67 | 68 | 69 | ``` -------------------------------------------------------------------------------- /docs/components/Dropdown.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | const sections = [ 5 | [ 6 | { 7 | iconName: 'bookmark-o', 8 | text: 'Bookmark' 9 | }, 10 | { 11 | iconName: 'commenting-o', 12 | text: 'Live Chat' 13 | } 14 | ], 15 | [ 16 | { 17 | iconName: 'cog', 18 | text: 'Settings' 19 | }, 20 | { 21 | iconName: 'sign-out', 22 | text: 'Logout' 23 | } 24 | ] 25 | ]; 26 | 27 | const selectItem = (item) => { 28 | console.log(item); 29 | dropdown.closeDropdown(); 30 | }; 31 | 32 | const listSections = () => { 33 | return sections.map((section) => { 34 | const items = section.map((item) => ( 35 | 41 | } 42 | onClick={selectItem} 43 | > 44 | {item.text} 45 | 46 | )); 47 | return ( 48 | 49 | {items} 50 | 51 | ); 52 | }); 53 | }; 54 | 55 | 58 | {listSections()} 59 | 60 | } 61 | key='dropdown-1' 62 | ref={(comp) => dropdown = comp} 63 | > 64 | 80 | 81 | ``` 82 | 83 | 84 | ```js 85 | initialState = { 86 | selectedCountry: 'Choose Country' 87 | }; 88 | 89 | const sections = [ 90 | [ 91 | { 92 | iconName: 'flag', 93 | text: 'Australia' 94 | }, 95 | { 96 | iconName: 'flag', 97 | text: 'USA' 98 | }, 99 | { 100 | iconName: 'flag', 101 | text: 'INDIA' 102 | } 103 | ] 104 | ]; 105 | 106 | const selectItem = (item) => { 107 | setState({ 108 | selectedCountry: item.innerText 109 | }, () => { 110 | btndropdown.closeDropdown(); 111 | }); 112 | }; 113 | 114 | const listSections = () => { 115 | return sections.map((section) => { 116 | const items = section.map((item) => ( 117 | 123 | } 124 | onClick={selectItem} 125 | > 126 | {item.text} 127 | 128 | )); 129 | return ( 130 | 131 | {items} 132 | 133 | ); 134 | }); 135 | }; 136 | 137 | 140 | {listSections()} 141 | 142 | } 143 | key='dropdown-2' 144 | ref={(comp) => btndropdown = comp} 145 | > 146 | 155 | 156 | ``` -------------------------------------------------------------------------------- /docs/components/Form/Checkbox.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | * Default Checkbox - Simple 4 | 5 | ```js 6 | initialState = { 7 | checked: true 8 | }; 9 | const toggleSelection = () => { 10 | setState({ 11 | checked: !state.checked 12 | }); 13 | }; 14 |
15 | 21 |
22 | ``` -------------------------------------------------------------------------------- /docs/components/Form/Radio.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | * Default Radio - Simple Effect 4 | 5 | ```js 6 | initialState = { 7 | checked: true 8 | }; 9 | const toggleSelection = () => { 10 | setState({ 11 | checked: !state.checked 12 | }); 13 | }; 14 |
15 | 21 |
22 | ``` 23 | 24 | * Radio - Circle Effect 25 | 26 | ```js 27 | initialState = { 28 | checked: true 29 | }; 30 | const toggleSelection = () => { 31 | setState({ 32 | checked: !state.checked 33 | }); 34 | }; 35 |
36 | 43 |
44 | ``` 45 | 46 | * Radio - Drop Effect 47 | 48 | ```js 49 | initialState = { 50 | checked: true 51 | }; 52 | const toggleSelection = () => { 53 | setState({ 54 | checked: !state.checked 55 | }); 56 | }; 57 |
58 | 65 |
66 | ``` -------------------------------------------------------------------------------- /docs/components/Form/RadioGroup.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | * Radio Group - Selection 4 | 5 | ```js 6 | 7 | 11 | 15 | 16 | ``` 17 | 18 | * Radio Group - Horizontal 19 | 20 | ```js 21 | 22 | 27 | 32 | 33 | ``` 34 | 35 | * Radio Group - Full width without label 36 | 37 | ```js 38 | 46 | 52 | 58 | 59 | ``` 60 | 61 | * Radio Group - Disabled 62 | 63 | ```js 64 | 69 | 73 | 77 | 78 | ``` -------------------------------------------------------------------------------- /docs/components/Form/TextField.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ##### TextField Variations 4 | 5 | ```js 6 |
7 | 11 | 12 | 17 | 18 | 23 | 24 | 29 | 30 | 39 | } 40 | /> 41 | 42 | 51 |
52 | ``` 53 | 54 | ##### TextField Disabled 55 | 56 | ```js 57 |
58 | 63 | 64 | 69 | 70 | 76 |
77 | ``` 78 | 79 | ##### TextField Error Message 80 | 81 | ```js 82 |
83 | 88 | 89 | 95 |
96 | ``` 97 | 98 | ##### TextField Controlled 99 | 100 | ```js 101 | initialState = { value: 'John Doe' }; 102 | 103 | const handleChange = (input) => { 104 | setState({ 105 | value: input.target.value 106 | }); 107 | }; 108 | 109 | 115 | ``` -------------------------------------------------------------------------------- /docs/components/Grid.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | 1. Simple Example 4 | 5 | ```js 6 |
7 | 8 | foo 9 | bar 10 | baz 11 | 12 |
13 | ``` 14 | 15 | 2. Traditional Grid 16 | 17 | ```js 18 | const rows = counts => 19 | lodash.flatMap(counts, number => 20 | lodash.range(number).map(i => 21 | 22 | {i + 1}/{number} 23 | 24 | ) 25 | ); 26 |
27 | 28 | {rows([12, 6, 4, 2, 1])} 29 | 30 |
31 | ``` 32 | 33 | 3. Layout 34 | 35 | ```js 36 |
37 | 45 | Header 46 | 47 |
Content
48 |
49 | 50 |
Navigation
51 |
52 | 53 |
Sidebar
54 |
55 | Footer 56 |
57 |
58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /docs/components/Icon.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | 1. Icon Sizes: 4 | 5 | ```js 6 |
7 | 11 | 15 | 19 | 23 | 27 |
28 | ``` 29 | 30 | 2. Icon Custom Colors: 31 | 32 | ```js 33 |
34 | 39 | 44 | 49 | 54 | 59 |
60 | ``` -------------------------------------------------------------------------------- /docs/components/Image.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | 5 | 6 |

With preload placeholder

7 | 17 |
18 | 19 |

Without preload placeholder

20 | 29 |
30 | 31 |

Without responsive image loading

32 | 35 |
36 |
37 | ``` -------------------------------------------------------------------------------- /docs/components/Link.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | Default Link 5 | ``` -------------------------------------------------------------------------------- /docs/components/List.md: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | const sections = [ 5 | [ 6 | { 7 | active: 'true', 8 | iconName: 'bookmark-o', 9 | text: 'Bookmark' 10 | }, 11 | { 12 | iconName: 'commenting-o', 13 | text: 'Live Chat' 14 | } 15 | ], 16 | [ 17 | { 18 | iconName: 'cog', 19 | text: 'Settings' 20 | }, 21 | { 22 | iconName: 'sign-out', 23 | text: 'Logout' 24 | } 25 | ] 26 | ]; 27 | 28 | const customHeaderProfile = ( 29 |
30 | 36 |

Ajain Vivek

37 |
38 | ); 39 | 40 | const customHeaderSearch = ( 41 | 50 | } 51 | cell={{ 52 | middle: true, 53 | width: 12 54 | }} 55 | /> 56 | ); 57 | 58 | const listSections = () => { 59 | return sections.map((section) => { 60 | const items = section.map((item) => ( 61 | 68 | } 69 | > 70 | {item.text} 71 | 72 | )); 73 | return ( 74 | 75 | {items} 76 | 77 | ); 78 | }); 79 | }; 80 | 81 | 82 | 83 | 84 | 85 | {listSections()} 86 | 87 | 88 | 89 | 90 | 91 | 94 | {listSections()} 95 | 96 | 97 | 98 | 99 | 102 | {listSections()} 103 | 104 | 105 | 106 | 107 | {listSections()} 108 | 109 | 110 | 111 | 112 | {listSections()} 113 | Share 116 | } 117 | right={ 118 | 127 | } 128 | /> 129 | 130 | 131 | 132 | ``` -------------------------------------------------------------------------------- /docs/components/Modal.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Examples: 4 | 5 | ```js 6 | const handleOk = () => { 7 | // do your stuff here 8 | modal.hide() 9 | } 10 | 11 | const handleCancel = () => { 12 | modal.hide() 13 | } 14 | 15 | const showModal = () => { 16 | modal.show( 17 | 18 | 22 | 23 | This is a modal-card. 24 | 25 | Cancel 28 | } 29 | right={ 30 | 31 | } 32 | /> 33 | 34 | ) 35 | }; 36 | 37 |
38 | 39 | modal = comp}/> 40 |
41 | ``` -------------------------------------------------------------------------------- /docs/components/Notify.md: -------------------------------------------------------------------------------- 1 | 2 | Examples: 3 | 4 | * Notification - Dismiss after 3 sec 5 | 6 | ```js 7 | const addNotification = () => { 8 | notification.add({ 9 | title: "New Message", 10 | message: "Hey there!", 11 | type: "info", 12 | autoDismiss: 3 //Default is 5sec 13 | }); 14 | }; 15 | 16 |
17 | 18 | notification = comp}/> 19 |
20 | ``` 21 | 22 | * Notification - Non dismissible 23 | 24 | ```js 25 | const addNotification = () => { 26 | notification.add({ 27 | title: "New Message", 28 | message: "Hey there!", 29 | type: "info", 30 | autoDismiss: 0 //Default is 5sec 31 | }); 32 | }; 33 | 34 |
35 | 36 | notification = comp}/> 37 |
38 | ``` 39 | 40 | * Notification with custom icon 41 | 42 | ```js 43 | const addNotification = () => { 44 | notification.add({ 45 | title: "New Message", 46 | message: "You have received a mail!", 47 | type: "info", 48 | iconName: 'envelope', 49 | iconSize: 'xsmall', 50 | iconColor: '#1D9C72' 51 | }); 52 | }; 53 | 54 |
55 | 56 | notification = comp}/> 57 |
58 | ``` 59 | 60 | * Notification with various type i.e info, success, error, warning 61 | 62 | ```js 63 | const addNotification = (type, message) => { 64 | notification.add({ 65 | title: "New Message", 66 | message, 67 | type 68 | }); 69 | }; 70 | 71 |
72 | 75 | 78 | 81 | 84 | notification = comp}/> 85 |
86 | ``` -------------------------------------------------------------------------------- /docs/components/Tooltip.md: -------------------------------------------------------------------------------- 1 |

Examples:

2 | 3 | * Simple Tooltip 4 | 5 | ```js 6 | 9 | 10 | 11 | ``` 12 | 13 | * Tooltip Effect 14 | 15 | ```js 16 | 17 | 18 |

Fade

19 | 23 | 24 | 25 |
26 | 27 |

Expand

28 | 32 | 33 | 34 |
35 |
36 | ``` 37 | 38 | * Tooltip Position 39 | 40 | ```js 41 | 42 | 43 |

Top

44 | 49 | 50 | 51 |
52 | 53 |

Bottom

54 | 59 | 60 | 61 |
62 | 63 |

Left

64 | 69 | 70 | 71 |
72 | 73 |

Right

74 | 79 | 80 | 81 |
82 |
83 | ``` 84 | 85 | * Fixed Width Tooltip 86 | 87 | ```js 88 | 92 | 93 | 94 | ``` -------------------------------------------------------------------------------- /docs/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%=htmlWebpackPlugin.options.title%> 9 | 17 | 58 | 59 | 60 |
61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-fluid", 3 | "version": "0.9.1", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "npm run styleguide", 8 | "generate-component": "plop component", 9 | "test": "npm run prettier-check && jest --coverage", 10 | "gh-pages": "./node_modules/.bin/gh-pages -d styleguide", 11 | "styleguide": "styleguidist server", 12 | "styleguide-build": "NODE_ENV=production styleguidist build", 13 | "build": "babel src --out-dir lib --source-maps true", 14 | "prebuild": "rimraf ./lib", 15 | "precommit": "lint-staged", 16 | "prettier": "prettier --write src/**/*.js", 17 | "prettier-check": "prettier --list-different src/**/*.js", 18 | "publish-docs": "npm run styleguide-build && npm run gh-pages", 19 | "release": "./scripts/release.sh" 20 | }, 21 | "lint-staged": { 22 | "*.{js}": [ 23 | "prettier --write", 24 | "git add" 25 | ] 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/ajainvivek/preact-fluid.git" 30 | }, 31 | "keywords": [], 32 | "author": "", 33 | "license": "ISC", 34 | "devDependencies": { 35 | "babel-cli": "^6.26.0", 36 | "babel-core": "^6.26.0", 37 | "babel-loader": "^7.1.2", 38 | "babel-plugin-module-resolver": "^2.7.1", 39 | "babel-plugin-transform-react-jsx": "^6.24.1", 40 | "babel-preset-env": "^1.6.1", 41 | "babel-preset-es2015": "^6.24.1", 42 | "babel-preset-react": "^6.24.1", 43 | "babel-preset-stage-0": "^6.24.1", 44 | "eslint": "^4.2.0", 45 | "eslint-config-synacor": "^1.0.1", 46 | "gh-pages": "^1.0.0", 47 | "husky": "^0.14.3", 48 | "if-env": "^1.0.0", 49 | "jest": "^23.5.0", 50 | "lint-staged": "^6.0.0", 51 | "plop": "^1.9.0", 52 | "preact": "^8.2.0", 53 | "preact-compat": "^3.16.0", 54 | "prettier": "1.9.1", 55 | "prop-types": "^15.5.10", 56 | "react": "^15.6.1", 57 | "react-dom": "^15.6.1", 58 | "react-styleguidist": "^6.0.21", 59 | "rimraf": "^2.6.2", 60 | "sass-resources-loader": "^1.3.0", 61 | "webpack": "^3.5.5" 62 | }, 63 | "dependencies": { 64 | "lodash": "^4.17.4", 65 | "preact": "^8.2.5", 66 | "preact-router": "^2.5.4", 67 | "styled-components": "^2.1.2" 68 | }, 69 | "jest": { 70 | "moduleNameMapper": { 71 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", 72 | "\\.(css|less|scss)$": "identity-obj-proxy", 73 | "^react$": "preact-compat", 74 | "^react-dom$": "preact-compat" 75 | }, 76 | "testMatch": [ 77 | "**/**/*.spec.js" 78 | ], 79 | "collectCoverageFrom": [ 80 | "src/**/*.{js,jsx}" 81 | ], 82 | "moduleFileExtensions": [ 83 | "js", 84 | "jsx" 85 | ], 86 | "moduleDirectories": [ 87 | "node_modules" 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /plopfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (plop) { 2 | plop.setGenerator('component', { 3 | description: 'Generate a Fluid Component', 4 | prompts: [{ 5 | type: 'input', 6 | name: 'name', 7 | message: 'What should it be called?', 8 | validate: function (value) { 9 | if ((/.+/).test(value)) { return true; } 10 | return 'name is required'; 11 | } 12 | }], 13 | actions: [{ 14 | type: 'add', 15 | path: 'src/{{properCase name}}/{{properCase name}}.js', 16 | templateFile: 'templates/component/component.txt' 17 | }, { 18 | type: 'add', 19 | path: 'src/{{properCase name}}/index.js', 20 | templateFile: 'templates/component/index.txt' 21 | }, { 22 | type: 'add', 23 | path: 'src/{{properCase name}}/styles.js', 24 | templateFile: 'templates/component/styles.txt' 25 | }, { 26 | type: 'add', 27 | path: 'docs/components/{{properCase name}}.md', 28 | templateFile: 'templates/component/document.txt' 29 | }] 30 | }); 31 | } -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ -z $1 ]]; then 5 | echo "Please choose release: " 6 | options=("patch" "minor" "major" "skip") 7 | select opt in "${options[@]}" 8 | do 9 | case "$REPLY" in 10 | "patch") 11 | RELEASE_TYPE="patch" 12 | break 13 | ;; 14 | "minor") 15 | RELEASE_TYPE="minor" 16 | break 17 | ;; 18 | "major") 19 | RELEASE_TYPE="major" 20 | break 21 | ;; 22 | "skip") 23 | break 24 | ;; 25 | *) echo invalid option;; 26 | esac 27 | done 28 | else 29 | RELEASE_TYPE=$1 30 | fi 31 | 32 | read -p "Releasing $RELEASE_TYPE - are you sure? (y/n) " -n 1 -r 33 | echo 34 | if [[ $REPLY =~ ^[Yy]$ ]]; then 35 | echo "Releasing $RELEASE_TYPE ..." 36 | 37 | # get current branch 38 | GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2) 39 | 40 | # reset local changes 41 | git reset --hard 42 | 43 | # pull latest changes 44 | git pull 45 | 46 | # get new tags from remote 47 | git fetch --tags 48 | 49 | # get latest tag name released 50 | LAST_PUBLISHED_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) 51 | 52 | # pluck version number from tag 53 | LAST_VERSION=${LAST_PUBLISHED_TAG//v} 54 | 55 | # bump version 56 | VERSION=$(./scripts/semver.sh -${RELEASE_TYPE} ${LAST_VERSION}) 57 | 58 | # bump version 59 | git tag -a v"$VERSION" -m "release(v${VERSION}): build `date +%d-%m-%Y`" 60 | 61 | # publish 62 | git push origin refs/tags/v"$VERSION" 63 | fi -------------------------------------------------------------------------------- /scripts/semver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Increment a version string using Semantic Versioning (SemVer) terminology. 4 | 5 | # Parse command line options. 6 | 7 | while getopts ":Mmp" Option 8 | do 9 | case $Option in 10 | M ) major=true;; 11 | m ) minor=true;; 12 | p ) patch=true;; 13 | esac 14 | done 15 | 16 | shift $(($OPTIND - 1)) 17 | 18 | version=$1 19 | 20 | # Build array from version string. 21 | 22 | a=( ${version//./ } ) 23 | 24 | # If version string is missing or has the wrong number of members, show usage message. 25 | 26 | if [ ${#a[@]} -ne 3 ] 27 | then 28 | echo "usage: $(basename $0) [-Mmp] major.minor.patch" 29 | exit 1 30 | fi 31 | 32 | # Increment version numbers as requested. 33 | 34 | if [ ! -z $major ] 35 | then 36 | ((a[0]++)) 37 | a[1]=0 38 | a[2]=0 39 | fi 40 | 41 | if [ ! -z $minor ] 42 | then 43 | ((a[1]++)) 44 | a[2]=0 45 | fi 46 | 47 | if [ ! -z $patch ] 48 | then 49 | ((a[2]++)) 50 | fi 51 | 52 | echo "${a[0]}.${a[1]}.${a[2]}" -------------------------------------------------------------------------------- /src/Animate/Animate.js: -------------------------------------------------------------------------------- 1 | import React, { Component, cloneElement } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import styled, { css } from 'styled-components'; 4 | import Animations from './../Animations'; 5 | 6 | /** 7 | * Wrapper for animating the external component 8 | * 9 | * @example ./../../docs/components/Animate.md 10 | */ 11 | class Animate extends Component { 12 | static propTypes = { 13 | /** 14 | * Component to be animated 15 | */ 16 | component: PropTypes.element.isRequired, 17 | 18 | /** 19 | * Animation properties 20 | */ 21 | animation: PropTypes.shape({ 22 | active: PropTypes.bool, 23 | duration: PropTypes.number, 24 | timingFunction: PropTypes.string, 25 | delay: PropTypes.string, 26 | iterationCount: PropTypes.number, 27 | direction: PropTypes.string, 28 | fillMode: PropTypes.string, 29 | playState: PropTypes.string, 30 | onStart: PropTypes.func, 31 | onComplete: PropTypes.func, 32 | onIteration: PropTypes.func, 33 | }), 34 | 35 | /** 36 | * Element attributes 37 | */ 38 | attrs: PropTypes.object, 39 | }; 40 | 41 | static defaultProps = { 42 | animation: {}, 43 | attrs: {}, 44 | }; 45 | 46 | componentWillMount() { 47 | const { component } = this.props; 48 | this.component = props => 49 | cloneElement(component, { 50 | ...props, 51 | }); 52 | } 53 | 54 | componentDidMount() { 55 | if (this.comp) { 56 | this.comp 57 | .getDOMNode() 58 | .addEventListener('animationstart', this.handleAnimationStart); 59 | this.comp 60 | .getDOMNode() 61 | .addEventListener('animationend', this.handleAnimationComplete); 62 | this.comp 63 | .getDOMNode() 64 | .addEventListener( 65 | 'animationiteration', 66 | this.handleAnimationIteration 67 | ); 68 | } 69 | } 70 | 71 | handleAnimationStart = () => { 72 | const { animation } = this.props; 73 | if (animation && typeof animation.onStart === 'function') { 74 | animation.onStart(this.comp, animation); 75 | } 76 | }; 77 | 78 | handleAnimationComplete = () => { 79 | const { animation } = this.props; 80 | if (animation && typeof animation.onComplete === 'function') { 81 | animation.onComplete(this.comp, animation); 82 | } 83 | }; 84 | 85 | handleAnimationIteration = () => { 86 | const { animation } = this.props; 87 | if (animation && typeof animation.onIteration === 'function') { 88 | animation.onIteration(this.comp, animation); 89 | } 90 | }; 91 | 92 | render() { 93 | const { animation, attrs = {} } = this.props; 94 | 95 | const { 96 | duration = '500ms', 97 | timingFunction = 'linear', 98 | delay = '0s', 99 | iterationCount = 0, 100 | direction = '', 101 | fillMode = '', 102 | playState = '', 103 | active = true, 104 | } = animation; 105 | 106 | const name = Animations[animation.name] || animation.name || ''; 107 | 108 | const AnimatedComponent = styled(this.component).attrs({ 109 | ...attrs, 110 | })` 111 | ${active && 112 | css` 113 | animation-name: ${name}; 114 | animation-duration: ${duration}; 115 | animation-timing-function: ${timingFunction}; 116 | animation-delay: ${delay}; 117 | animation-iteration-count: ${iterationCount}; 118 | animation-direction: ${direction}; 119 | animation-fill-mode: ${fillMode}; 120 | animation-play-state: ${playState}; 121 | `}; 122 | `; 123 | 124 | return (this.comp = comp)} />; 125 | } 126 | } 127 | 128 | export default Animate; 129 | -------------------------------------------------------------------------------- /src/Animate/index.js: -------------------------------------------------------------------------------- 1 | export default from './Animate'; 2 | -------------------------------------------------------------------------------- /src/Animations/fadeIn.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const fadeIn = keyframes` 4 | 0% { 5 | opacity: 0; 6 | } 7 | 8 | 100% { 9 | opacity: 1; 10 | } 11 | `; 12 | 13 | export default fadeIn; 14 | -------------------------------------------------------------------------------- /src/Animations/fadeInPulse.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const fadeInPulse = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform: scale(.8) 7 | } 8 | 9 | 50% { 10 | opacity: 1 11 | } 12 | 13 | 100%,70% { 14 | transform: scale(1) 15 | } 16 | `; 17 | 18 | export default fadeInPulse; 19 | -------------------------------------------------------------------------------- /src/Animations/fallPerspective.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const fallPerspective = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform-style: preserve-3d; 7 | transform: translateZ(400px) translateY(300px) rotateX(-90deg); 8 | } 9 | 10 | 100% { 11 | transform: translateZ(0px) translateY(0px) rotateX(0deg); 12 | opacity: 1; 13 | } 14 | `; 15 | 16 | export default fallPerspective; 17 | -------------------------------------------------------------------------------- /src/Animations/flip.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const flip = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform-style: preserve-3d; 7 | transform-origin: 0% 0%; 8 | transform: rotateX(-80deg); 9 | } 10 | 11 | 100% { 12 | transform: rotateX(0deg); 13 | opacity: 1; 14 | } 15 | `; 16 | 17 | export default flip; 18 | -------------------------------------------------------------------------------- /src/Animations/helix.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const helix = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform-style: preserve-3d; 7 | transform: rotateY(-180deg); 8 | } 9 | 10 | 100% { 11 | transform: rotateY(0deg); 12 | opacity: 1; 13 | } 14 | `; 15 | 16 | export default helix; 17 | -------------------------------------------------------------------------------- /src/Animations/index.js: -------------------------------------------------------------------------------- 1 | import shake from './shake'; 2 | import slideIn from './slideIn'; 3 | import slideOut from './slideOut'; 4 | import slideDown from './slideDown'; 5 | import slideLeft from './slideLeft'; 6 | import slideRight from './slideRight'; 7 | import slideInRight from './slideInRight'; 8 | import slideRightLeft from './slideRightLeft'; 9 | import slideUp from './slideUp'; 10 | import perspectiveDown from './perspectiveDown'; 11 | import perspectiveLeft from './perspectiveLeft'; 12 | import perspectiveRight from './perspectiveRight'; 13 | import perspectiveUp from './perspectiveUp'; 14 | import puffIn from './puffIn'; 15 | import puffOut from './puffOut'; 16 | import vanishIn from './vanishIn'; 17 | import vanishOut from './vanishOut'; 18 | import rotate from './rotate'; 19 | import pulseShadow from './pulseShadow'; 20 | import fadeInPulse from './fadeInPulse'; 21 | import shimmyShake from './shimmyShake'; 22 | import pop from './pop'; 23 | import popDownwards from './popDownwards'; 24 | import popUpwards from './popUpwards'; 25 | import flip from './flip'; 26 | import moveUp from './moveUp'; 27 | import fadeIn from './fadeIn'; 28 | import scaleUp from './scaleUp'; 29 | import fallPerspective from './fallPerspective'; 30 | import helix from './helix'; 31 | 32 | export default { 33 | shake, 34 | slideDown, 35 | slideLeft, 36 | slideRight, 37 | slideRightLeft, 38 | slideInRight, 39 | slideUp, 40 | perspectiveDown, 41 | perspectiveLeft, 42 | perspectiveRight, 43 | perspectiveUp, 44 | puffIn, 45 | puffOut, 46 | vanishIn, 47 | vanishOut, 48 | rotate, 49 | pulseShadow, 50 | fadeInPulse, 51 | shimmyShake, 52 | pop, 53 | popDownwards, 54 | popUpwards, 55 | slideIn, 56 | slideOut, 57 | flip, 58 | moveUp, 59 | fadeIn, 60 | scaleUp, 61 | fallPerspective, 62 | helix, 63 | }; 64 | -------------------------------------------------------------------------------- /src/Animations/moveUp.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const moveUp = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform: translateY(200px); 7 | } 8 | 9 | 100% { 10 | transform: translateY(0); 11 | opacity: 1; 12 | } 13 | `; 14 | 15 | export default moveUp; 16 | -------------------------------------------------------------------------------- /src/Animations/perspectiveDown.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const perspectiveDown = keyframes` 4 | 0% { 5 | transform-origin: 0 100%; 6 | transform: perspective(800px) rotateX(0deg); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 100%; 11 | transform: perspective(800px) rotateX(-180deg); 12 | } 13 | `; 14 | 15 | export default perspectiveDown; 16 | -------------------------------------------------------------------------------- /src/Animations/perspectiveLeft.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const perspectiveLeft = keyframes` 4 | 0% { 5 | transform-origin: 0 100%; 6 | transform: perspective(800px) rotateX(0deg); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 100%; 11 | transform: perspective(800px) rotateX(-180deg); 12 | } 13 | `; 14 | 15 | export default perspectiveLeft; 16 | -------------------------------------------------------------------------------- /src/Animations/perspectiveRight.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const perspectiveRight = keyframes` 4 | 0% { 5 | transform-origin: 100% 0; 6 | transform: perspective(800px) rotateY(0deg); 7 | } 8 | 9 | 100% { 10 | transform-origin: 100% 0; 11 | transform: perspective(800px) rotateY(180deg); 12 | } 13 | `; 14 | 15 | export default perspectiveRight; 16 | -------------------------------------------------------------------------------- /src/Animations/perspectiveUp.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const perspectiveUp = keyframes` 4 | 0% { 5 | transform-origin: 100% 0; 6 | transform: perspective(800px) rotateY(0deg); 7 | } 8 | 9 | 100% { 10 | transform-origin: 100% 0; 11 | transform: perspective(800px) rotateY(180deg); 12 | } 13 | `; 14 | 15 | export default perspectiveUp; 16 | -------------------------------------------------------------------------------- /src/Animations/pop.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const pop = keyframes` 4 | 0% { 5 | transform: scale(1) 6 | } 7 | 8 | 50% { 9 | transform: scale(1.1) 10 | } 11 | 12 | 100% { 13 | transform: scale(1) 14 | } 15 | `; 16 | 17 | export default pop; 18 | -------------------------------------------------------------------------------- /src/Animations/popDownwards.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const popDownwards = keyframes` 4 | 0% { 5 | transform: matrix(.97,0,0,1,0,-12); 6 | opacity: 0 7 | } 8 | 9 | 20% { 10 | transform: matrix(.99,0,0,1,0,-2); 11 | opacity: .7 12 | } 13 | 14 | 40% { 15 | transform: matrix(1,0,0,1,0,1); 16 | opacity: 1 17 | } 18 | 19 | 70% { 20 | transform: matrix(1,0,0,1,0,0); 21 | opacity: 1 22 | } 23 | 24 | 100% { 25 | transform: matrix(1,0,0,1,0,0); 26 | opacity: 1 27 | } 28 | `; 29 | 30 | export default popDownwards; 31 | -------------------------------------------------------------------------------- /src/Animations/popUpwards.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const popUpwards = keyframes` 4 | 0% { 5 | transform: matrix(.97,0,0,1,0,12); 6 | opacity: 0 7 | } 8 | 9 | 20% { 10 | transform: matrix(.99,0,0,1,0,2); 11 | opacity: .7 12 | } 13 | 14 | 40% { 15 | transform: matrix(1,0,0,1,0,-1); 16 | opacity: 1 17 | } 18 | 19 | 70% { 20 | transform: matrix(1,0,0,1,0,0); 21 | opacity: 1 22 | } 23 | 24 | 100% { 25 | transform: matrix(1,0,0,1,0,0); 26 | opacity: 1 27 | } 28 | `; 29 | 30 | export default popUpwards; 31 | -------------------------------------------------------------------------------- /src/Animations/puffIn.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const puffIn = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform-origin: 50% 50%; 7 | transform: scale(2,2); 8 | filter: blur(2px); 9 | } 10 | 11 | 100% { 12 | opacity: 1; 13 | transform-origin: 50% 50%; 14 | transform: scale(1,1); 15 | filter: blur(0px); 16 | } 17 | `; 18 | 19 | export default puffIn; 20 | -------------------------------------------------------------------------------- /src/Animations/puffOut.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const puffOut = keyframes` 4 | 0% { 5 | opacity: 1; 6 | transform-origin: 50% 50%; 7 | transform: scale(1,1); 8 | filter: blur(0px); 9 | } 10 | 11 | 100% { 12 | opacity: 0; 13 | transform-origin: 50% 50%; 14 | transform: scale(2,2); 15 | filter: blur(2px); 16 | } 17 | `; 18 | 19 | export default puffOut; 20 | -------------------------------------------------------------------------------- /src/Animations/pulseShadow.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const pulseShadow = keyframes` 4 | 0% { 5 | box-shadow: 0 0; 6 | } 7 | 8 | 30% { 9 | box-shadow: 0 0; 10 | } 11 | 12 | 80% { 13 | box-shadow: 0 0 5px 10px rgba(255,255,255,0); 14 | } 15 | 16 | 100% { 17 | box-shadow: 0 0 0 0 rgba(255,255,255,0); 18 | } 19 | `; 20 | 21 | export default pulseShadow; 22 | -------------------------------------------------------------------------------- /src/Animations/rotate.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const rotate = keyframes` 4 | from { 5 | transform: rotate(0deg); 6 | } 7 | to { 8 | transform: rotate(359deg); 9 | } 10 | `; 11 | 12 | export default rotate; 13 | -------------------------------------------------------------------------------- /src/Animations/scaleUp.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const scaleUp = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform: scale(0.6); 7 | } 8 | 9 | 100% { 10 | transform: scale(1); 11 | opacity: 1; 12 | } 13 | `; 14 | 15 | export default scaleUp; 16 | -------------------------------------------------------------------------------- /src/Animations/shake.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const shake = keyframes` 4 | 10%, 90% { 5 | transform: translate3d(-1px, 0, 0); 6 | } 7 | 8 | 20%, 80% { 9 | transform: translate3d(2px, 0, 0); 10 | } 11 | 12 | 30%, 50%, 70% { 13 | transform: translate3d(-4px, 0, 0); 14 | } 15 | 16 | 40%, 60% { 17 | transform: translate3d(4px, 0, 0); 18 | } 19 | `; 20 | 21 | export default shake; 22 | -------------------------------------------------------------------------------- /src/Animations/shimmyShake.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const shimmyShake = keyframes` 4 | 0% { 5 | transform: translateX(-1%) 6 | } 7 | 8 | 20% { 9 | transform: translateX(1%) 10 | } 11 | 12 | 40% { 13 | transform: translateX(-1%) 14 | } 15 | 16 | 60% { 17 | transform: translateX(1%) 18 | } 19 | 20 | 80% { 21 | transform: translateX(-1%) 22 | } 23 | 24 | 100% { 25 | transform: translateX(0) 26 | } 27 | `; 28 | 29 | export default shimmyShake; 30 | -------------------------------------------------------------------------------- /src/Animations/slideDown.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideDown = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateY(0%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateY(100%); 12 | } 13 | `; 14 | 15 | export default slideDown; 16 | -------------------------------------------------------------------------------- /src/Animations/slideIn.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideIn = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateX(-100%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateX(0%); 12 | } 13 | `; 14 | 15 | export default slideIn; 16 | -------------------------------------------------------------------------------- /src/Animations/slideInRight.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideInRight = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateX(100%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateX(0%); 12 | } 13 | `; 14 | 15 | export default slideInRight; 16 | -------------------------------------------------------------------------------- /src/Animations/slideLeft.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideLeft = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateX(0%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateX(-100%); 12 | } 13 | `; 14 | 15 | export default slideLeft; 16 | -------------------------------------------------------------------------------- /src/Animations/slideOut.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideOut = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateX(0%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateX(-100%); 12 | } 13 | `; 14 | 15 | export default slideOut; 16 | -------------------------------------------------------------------------------- /src/Animations/slideRight.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideRight = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateX(0%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateX(100%); 12 | } 13 | `; 14 | 15 | export default slideRight; 16 | -------------------------------------------------------------------------------- /src/Animations/slideRightLeft.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideRightLeft = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateX(0%); 7 | } 8 | 9 | 35% { 10 | transform-origin: 0 0; 11 | transform: translateX(100%); 12 | } 13 | 14 | 70% { 15 | transform-origin: 0 0; 16 | transform: translateX(0%); 17 | } 18 | 19 | 100% { 20 | transform-origin: 0 0; 21 | transform: translateX(-100%); 22 | } 23 | `; 24 | 25 | export default slideRightLeft; 26 | -------------------------------------------------------------------------------- /src/Animations/slideUp.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const slideUp = keyframes` 4 | 0% { 5 | transform-origin: 0 0; 6 | transform: translateY(0%); 7 | } 8 | 9 | 100% { 10 | transform-origin: 0 0; 11 | transform: translateY(-100%); 12 | } 13 | `; 14 | 15 | export default slideUp; 16 | -------------------------------------------------------------------------------- /src/Animations/vanishIn.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const vanishIn = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform-origin: 50% 50%; 7 | transform: scale(2, 2); 8 | filter: blur(90px); 9 | } 10 | 11 | 100% { 12 | opacity: 1; 13 | transform-origin: 50% 50%; 14 | transform: scale(1, 1); 15 | filter: blur(0px); 16 | } 17 | `; 18 | 19 | export default vanishIn; 20 | -------------------------------------------------------------------------------- /src/Animations/vanishOut.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const vanishOut = keyframes` 4 | 0% { 5 | opacity: 1; 6 | transform-origin: 50% 50%; 7 | transform: scale(1, 1); 8 | filter: blur(0px); 9 | } 10 | 11 | 100% { 12 | opacity: 0; 13 | transform-origin: 50% 50%; 14 | transform: scale(2, 2); 15 | filter: blur(20px); 16 | } 17 | `; 18 | 19 | export default vanishOut; 20 | -------------------------------------------------------------------------------- /src/AppBar/AppBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import defaultTheme from '../theme'; 4 | 5 | import { StyledHeader, StyledSection, StyledLogo } from './styles'; 6 | import { Link } from '../index'; 7 | 8 | /** 9 | * A toolbar that’s used for branding, navigation, search, and actions. 10 | * 11 | * @example ./../../docs/components/AppBar.md 12 | */ 13 | class AppBar extends Component { 14 | static propTypes = { 15 | /** 16 | * title 17 | */ 18 | title: PropTypes.string, 19 | 20 | /** 21 | * title style 22 | */ 23 | titleStyle: PropTypes.object, 24 | 25 | /** 26 | * logo image src 27 | */ 28 | logo: PropTypes.string, 29 | 30 | /** 31 | * logo style 32 | */ 33 | logoStyle: PropTypes.object, 34 | 35 | /** 36 | * components on the left side of appbar 37 | */ 38 | leftSection: PropTypes.element, 39 | 40 | /** 41 | * components on the right side of appbar 42 | */ 43 | rightSection: PropTypes.element, 44 | }; 45 | 46 | static defaultProps = { 47 | title: '', 48 | titleStyle: {}, 49 | logo: null, 50 | logoStyle: {}, 51 | leftSection: '', 52 | rightSection: '', 53 | }; 54 | 55 | static contextTypes = { 56 | theme: PropTypes.object, 57 | }; 58 | 59 | renderLogo() { 60 | const { logo = '', logoStyle = {} } = this.props; 61 | 62 | if (logo) { 63 | return ; 64 | } 65 | 66 | return ''; 67 | } 68 | 69 | render() { 70 | const { 71 | title, 72 | titleStyle, 73 | logo, 74 | leftSection, 75 | rightSection, 76 | } = this.props; 77 | 78 | const { theme } = this.context; 79 | 80 | return ( 81 | 82 | 83 | 88 | {this.renderLogo()} 89 | {title} 90 | 91 | {leftSection} 92 | 93 | {rightSection} 94 | 95 | ); 96 | } 97 | } 98 | 99 | AppBar.propTypes = { 100 | title: PropTypes.string, 101 | left: PropTypes.array, 102 | titleStyle: PropTypes.object, 103 | }; 104 | AppBar.defaultProps = {}; 105 | 106 | export default AppBar; 107 | -------------------------------------------------------------------------------- /src/AppBar/index.js: -------------------------------------------------------------------------------- 1 | export default from './AppBar'; 2 | -------------------------------------------------------------------------------- /src/AppBar/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const StyledHeader = styled.header` 5 | align-items: stretch; 6 | display: flex; 7 | flex-wrap: wrap; 8 | justify-content: space-between; 9 | box-shadow: 0 2px 0 0 rgba(5, 45, 73, 0.06999999999999995); 10 | height: 60px; 11 | z-index: 9999; 12 | white-space: nowrap; 13 | padding: 0 10px; 14 | background: ${props => props.theme.lightColor}; 15 | 16 | .header-link { 17 | align-items: center; 18 | display: inline-flex; 19 | justify-content: center; 20 | } 21 | 22 | ${props => 23 | props.primary && 24 | css` 25 | background: ${props => props.theme.primaryColor}; 26 | color: ${props => props.theme.lightColor}; 27 | `} ${props => 28 | props.secondary && 29 | css` 30 | background: ${props => props.theme.secondaryColor}; 31 | color: ${props => props.theme.lightColor}; 32 | `}; 33 | `; 34 | 35 | const StyledLogo = styled.img` 36 | display: inline-flex; 37 | padding: 0 5px; 38 | `; 39 | 40 | const StyledSection = styled.section` 41 | align-items: center; 42 | display: flex; 43 | flex: 1 0 0; 44 | 45 | &:last-child { 46 | justify-content: flex-end; 47 | } 48 | `; 49 | 50 | StyledHeader.defaultProps = { 51 | theme: defaultTheme, 52 | }; 53 | 54 | export { StyledHeader, StyledSection, StyledLogo }; 55 | -------------------------------------------------------------------------------- /src/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledButton } from './styles'; 4 | 5 | /** 6 | * This button is used to add dimension to mostly flat layouts and emphasizes important functions on your page. 7 | * 8 | * @example ./../../docs/components/Button.md 9 | */ 10 | class Button extends Component { 11 | static propTypes = { 12 | /** 13 | * Button label. 14 | */ 15 | children: PropTypes.string.isRequired, 16 | 17 | /** 18 | * The size of the Button 19 | */ 20 | size: PropTypes.oneOf(['small', 'normal', 'large']), 21 | 22 | /** 23 | * If true, the button will be rounded corners 24 | */ 25 | rounded: PropTypes.bool, 26 | 27 | /** 28 | * If true, the button will use the theme's primary color 29 | */ 30 | primary: PropTypes.bool, 31 | 32 | /** 33 | * If true, the button will use the theme's secondary color 34 | */ 35 | secondary: PropTypes.bool, 36 | 37 | /** 38 | * If true, the button will be disabled 39 | */ 40 | disabled: PropTypes.bool, 41 | 42 | /** 43 | * Gets called when the user clicks on the button 44 | * 45 | * @param {SyntheticEvent} event The react `SyntheticEvent` 46 | */ 47 | onClick: PropTypes.func, 48 | }; 49 | 50 | static defaultProps = { 51 | primary: false, 52 | secondary: false, 53 | clicked: false, 54 | }; 55 | 56 | static contextTypes = { 57 | theme: PropTypes.object, 58 | }; 59 | 60 | _handleClick = event => { 61 | this.setState({ 62 | clicked: true, 63 | }); 64 | 65 | clearTimeout(this.timeout); 66 | this.timeout = setTimeout(() => this.setState({ clicked: false }), 500); 67 | 68 | const onClick = this.props.onClick; 69 | if (onClick) { 70 | onClick(event); 71 | } 72 | }; 73 | 74 | componentWillUnmount() { 75 | if (this.timeout) { 76 | clearTimeout(this.timeout); 77 | } 78 | } 79 | 80 | render() { 81 | const clicked = this.state.clicked ? 'clicked' : ''; 82 | const { 83 | badge = '', 84 | loading = false, 85 | className, 86 | left, 87 | right, 88 | } = this.props; 89 | const { theme } = this.context; 90 | 91 | return ( 92 | 99 | {left} 100 | {this.props.children} 101 | {right} 102 | 103 | {badge && badge.value} 104 | 105 | 106 | ); 107 | } 108 | } 109 | 110 | export default Button; 111 | -------------------------------------------------------------------------------- /src/Button/index.js: -------------------------------------------------------------------------------- 1 | export default from './Button'; 2 | -------------------------------------------------------------------------------- /src/Button/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import keyframes from '../keyframes'; 3 | import defaultTheme from '../theme'; 4 | 5 | const StyledButton = styled.button` 6 | position: relative; 7 | appearance: none; 8 | cursor: pointer; 9 | display: inline-block; 10 | outline: none; 11 | text-align: center; 12 | text-decoration: none; 13 | user-select: none; 14 | vertical-align: middle; 15 | white-space: nowrap; 16 | font-size: 14px; 17 | line-height: 14px; 18 | padding: 10px 20px; 19 | background: transparent; 20 | border: 1px solid 21 | ${props => props.theme.primaryColor || context.theme.color}; 22 | border-radius: 2px; 23 | color: ${props => props.theme.linkColor}; 24 | 25 | &.clicked:after { 26 | content: ''; 27 | position: absolute; 28 | top: -1px; 29 | left: -1px; 30 | bottom: -1px; 31 | right: -1px; 32 | border-radius: inherit; 33 | border: 0 solid ${props => props.theme.primaryColor}; 34 | opacity: 0.4; 35 | animation: ${keyframes.buttonEffect} 0.4s; 36 | display: block; 37 | } 38 | 39 | .badge { 40 | position: absolute; 41 | right: 0; 42 | top: 0; 43 | transform: translate(50%, -50%); 44 | height: 18px; 45 | line-height: 1.5; 46 | min-width: 18px; 47 | text-align: center; 48 | white-space: nowrap; 49 | background-clip: padding-box; 50 | border-radius: 12px; 51 | box-shadow: 0 0 0 0.1rem #fff; 52 | color: #fff; 53 | display: inline-block; 54 | font-size: 12px; 55 | ${props => 56 | props.badge && 57 | css` 58 | background: ${props.badge.color 59 | ? props.badge.color 60 | : props.theme.primaryColor}; 61 | padding: ${props.badge.value && 62 | props.badge.value.toString().length > 1 63 | ? '3px 8px' 64 | : '3px'}; 65 | `}; 66 | } 67 | 68 | &.loading { 69 | color: transparent !important; 70 | pointer-events: none; 71 | } 72 | 73 | &.loading:after { 74 | animation: ${keyframes.loading} 500ms infinite linear; 75 | border: 2px solid ${props => props.theme.primaryColorDark}; 76 | border-radius: 50%; 77 | border-right-color: transparent; 78 | border-top-color: transparent; 79 | content: ''; 80 | display: block; 81 | height: 14px; 82 | left: 50%; 83 | margin-left: -9px; 84 | margin-top: -9px; 85 | position: absolute; 86 | top: 50%; 87 | width: 14px; 88 | z-index: 1; 89 | } 90 | 91 | &:disabled, 92 | &[disabled], 93 | &.disabled { 94 | opacity: 0.5; 95 | } 96 | 97 | .item-left { 98 | padding-right: 8px; 99 | } 100 | 101 | .item-right { 102 | padding-left: 8px; 103 | } 104 | 105 | ${props => 106 | props.rounded && 107 | css` 108 | border-radius: 20px; 109 | `} ${props => 110 | props.primary && 111 | css` 112 | background: ${props => props.theme.primaryColor}; 113 | border: 1px solid ${props => props.theme.primaryColorDark}; 114 | border-radius: 2px; 115 | color: ${props => props.theme.lightColor}; 116 | 117 | &.loading:after { 118 | border: 2px solid ${props => props.theme.lightColor}; 119 | border-right-color: ${props => props.theme.primaryColor}; 120 | border-top-color: ${props => props.theme.primaryColor}; 121 | } 122 | `} ${props => 123 | props.secondary && 124 | css` 125 | background: ${props => props.theme.secondaryColor}; 126 | border: 1px solid ${props => props.theme.secondaryColorDark}; 127 | border-radius: 2px; 128 | color: ${props => props.theme.lightColor}; 129 | 130 | &.loading:after { 131 | border: 2px solid ${props => props.theme.lightColor}; 132 | border-right-color: ${props => props.theme.primaryColor}; 133 | border-top-color: ${props => props.theme.primaryColor}; 134 | } 135 | `} ${props => 136 | props.style && 137 | css` 138 | ${props.style}; 139 | `}; 140 | `; 141 | 142 | StyledButton.defaultProps = { 143 | theme: defaultTheme, 144 | }; 145 | 146 | export { StyledButton }; 147 | -------------------------------------------------------------------------------- /src/Card/Card.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledCard } from './styles'; 4 | 5 | /** 6 | * Cards are content containers to display information 7 | * 8 | * @example ./../../docs/components/Card.md 9 | */ 10 | class Card extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.string, 16 | 17 | /** 18 | * Gets called when the user clicks on the button 19 | * 20 | * @param {SyntheticEvent} event The react `SyntheticEvent` 21 | */ 22 | onClick: PropTypes.func, 23 | }; 24 | 25 | static contextTypes = { 26 | theme: PropTypes.object, 27 | }; 28 | 29 | render() { 30 | const { style, children, className } = this.props; 31 | const { theme } = this.context; 32 | return ( 33 | 34 | {children} 35 | 36 | ); 37 | } 38 | } 39 | 40 | export default Card; 41 | -------------------------------------------------------------------------------- /src/Card/CardBody.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledBody } from './styles'; 4 | 5 | /** 6 | * Cards header displays title information 7 | */ 8 | class CardBody extends Component { 9 | static propTypes = { 10 | /** 11 | * Custom styles 12 | */ 13 | style: PropTypes.object, 14 | }; 15 | 16 | static contextTypes = { 17 | theme: PropTypes.object, 18 | }; 19 | 20 | render() { 21 | const { style, children } = this.props; 22 | const { theme } = this.context; 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | } 29 | } 30 | 31 | export default CardBody; 32 | -------------------------------------------------------------------------------- /src/Card/CardFooter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledFooter } from './styles'; 4 | import Grid from './../Layout/Grid'; 5 | import Cell from './../Layout/Cell'; 6 | 7 | /** 8 | * Cards header displays title information 9 | */ 10 | class CardFooter extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.object, 16 | }; 17 | 18 | static contextTypes = { 19 | theme: PropTypes.object, 20 | }; 21 | 22 | render() { 23 | const { style, left, right } = this.props; 24 | const { theme } = this.context; 25 | return ( 26 | 27 | 28 | 29 | {left} 30 | 31 | 32 | {right} 33 | 34 | 35 | 36 | ); 37 | } 38 | } 39 | 40 | export default CardFooter; 41 | -------------------------------------------------------------------------------- /src/Card/CardHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledHeader, StyledTitle, StyledSubTitle } from './styles'; 4 | 5 | /** 6 | * Cards header displays title information 7 | */ 8 | class CardHeader extends Component { 9 | static propTypes = { 10 | /** 11 | * Custom styles 12 | */ 13 | style: PropTypes.object, 14 | }; 15 | 16 | static contextTypes = { 17 | theme: PropTypes.object, 18 | }; 19 | 20 | render() { 21 | const { style, title, subtitle } = this.props; 22 | const { theme } = this.context; 23 | return ( 24 | 25 | {title} 26 | {subtitle} 27 | 28 | ); 29 | } 30 | } 31 | 32 | export default CardHeader; 33 | -------------------------------------------------------------------------------- /src/Card/CardImage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import Image from './../Image'; 4 | import { StyledImageWrapper, StyledTitle, StyledSubTitle } from './styles'; 5 | 6 | /** 7 | * Cards header displays title information 8 | */ 9 | class CardImage extends Component { 10 | static propTypes = { 11 | /** 12 | * Custom styles 13 | */ 14 | style: PropTypes.object, 15 | }; 16 | 17 | static contextTypes = { 18 | theme: PropTypes.object, 19 | }; 20 | 21 | renderOverlay = overlay => { 22 | if (Object.keys(overlay).length === 0) { 23 | return ''; 24 | } 25 | return ( 26 |
27 | {overlay.title || ''} 28 | {overlay.subtitle || ''} 29 |
30 | ); 31 | }; 32 | 33 | render() { 34 | const { 35 | style, 36 | src, 37 | responsive, 38 | placeholder, 39 | overlay = {}, 40 | } = this.props; 41 | const { theme } = this.context; 42 | return ( 43 | 44 | 49 | {this.renderOverlay(overlay)} 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default CardImage; 56 | -------------------------------------------------------------------------------- /src/Card/index.js: -------------------------------------------------------------------------------- 1 | export default from './Card'; 2 | -------------------------------------------------------------------------------- /src/Card/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const StyledCard = styled.div` 5 | background: ${props => props.theme.lightColor}; 6 | border: 1px solid ${props => props.theme.borderColor}; 7 | border-radius: 3px; 8 | display: flex; 9 | flex-direction: column; 10 | box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); 11 | 12 | &:focus { 13 | text-decoration: none; 14 | } 15 | 16 | &:hover { 17 | text-decoration: none; 18 | } 19 | 20 | ${props => 21 | props.style && 22 | css` 23 | ${props.style}; 24 | `}; 25 | `; 26 | 27 | const StyledHeader = styled.div` 28 | padding: 16px; 29 | padding-bottom: 0; 30 | margin: 0; 31 | 32 | &:last-child { 33 | padding-bottom: 16px; 34 | } 35 | `; 36 | 37 | const StyledTitle = styled.h1` 38 | font-size: 20px; 39 | line-height: 32px; 40 | font-weight: 500; 41 | color: ${props => props.theme.darkColor}; 42 | margin: 0; 43 | 44 | ${props => 45 | props.light && 46 | css` 47 | color: ${props => props.theme.lightColor}; 48 | `} ${props => 49 | props.style && 50 | css` 51 | ${props.style}; 52 | `}; 53 | `; 54 | 55 | const StyledSubTitle = styled.h2` 56 | font-size: 14px; 57 | line-height: 18px; 58 | font-weight: 300; 59 | color: ${props => props.theme.grayColor}; 60 | margin: 0; 61 | 62 | ${props => 63 | props.light && 64 | css` 65 | color: ${props => props.theme.lightColor}; 66 | `} ${props => 67 | props.style && 68 | css` 69 | ${props.style}; 70 | `}; 71 | `; 72 | 73 | const StyledImageWrapper = styled.div` 74 | position: relative; 75 | padding: 0; 76 | margin: 0; 77 | overflow: hidden; 78 | 79 | &:not(:first-child):last-child { 80 | padding-top: 16px; 81 | } 82 | 83 | .image-overlay { 84 | position: absolute; 85 | bottom: 0; 86 | right: 0; 87 | padding: 16px; 88 | text-align: right; 89 | background: rgba(0, 0, 0, 0.4); 90 | width: 100%; 91 | object-fit: cover; 92 | } 93 | 94 | ${props => 95 | props.style && 96 | css` 97 | ${props.style}; 98 | `}; 99 | `; 100 | 101 | const StyledBody = styled.div` 102 | font-size: 16px; 103 | line-height: 24px; 104 | padding: 16px; 105 | padding-bottom: 0; 106 | margin: 0; 107 | color: ${props => props.theme.darkColor}; 108 | 109 | &:last-child { 110 | padding-bottom: 16px; 111 | } 112 | 113 | ${props => 114 | props.style && 115 | css` 116 | ${props.style}; 117 | `}; 118 | `; 119 | 120 | const StyledFooter = styled.div` 121 | padding: 16px; 122 | padding-bottom: 0; 123 | margin: 0; 124 | 125 | &:last-child { 126 | padding-bottom: 16px; 127 | } 128 | 129 | ${props => 130 | props.style && 131 | css` 132 | ${props.style}; 133 | `}; 134 | `; 135 | 136 | StyledCard.defaultProps = { 137 | theme: defaultTheme, 138 | }; 139 | 140 | StyledHeader.defaultProps = { 141 | theme: defaultTheme, 142 | }; 143 | 144 | StyledTitle.defaultProps = { 145 | theme: defaultTheme, 146 | }; 147 | 148 | StyledSubTitle.defaultProps = { 149 | theme: defaultTheme, 150 | }; 151 | 152 | StyledImageWrapper.defaultProps = { 153 | theme: defaultTheme, 154 | }; 155 | 156 | StyledBody.defaultProps = { 157 | theme: defaultTheme, 158 | }; 159 | 160 | StyledFooter.defaultProps = { 161 | theme: defaultTheme, 162 | }; 163 | 164 | export { 165 | StyledCard, 166 | StyledHeader, 167 | StyledTitle, 168 | StyledSubTitle, 169 | StyledImageWrapper, 170 | StyledBody, 171 | StyledFooter, 172 | }; 173 | -------------------------------------------------------------------------------- /src/Dropdown/Dropdown.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import Animate from './../Animate'; 4 | import { StyledDropdown, slideDown, slideUp } from './styles'; 5 | 6 | /** 7 | * Simple Dropdown component 8 | * 9 | * @example ./../../docs/components/Dropdown.md 10 | */ 11 | class Dropdown extends Component { 12 | static propTypes = { 13 | /** 14 | * Dropdown component to be toggled eg. List 15 | */ 16 | component: PropTypes.element, 17 | 18 | /** 19 | * Open dropdown dropdown callback function 20 | */ 21 | openDropdown: PropTypes.func, 22 | 23 | /** 24 | * Close dropdown dropdown callback function 25 | */ 26 | closeDropdown: PropTypes.func, 27 | 28 | /** 29 | * Toggle dropdown callback function 30 | */ 31 | toggleDropdown: PropTypes.func, 32 | 33 | /** 34 | * Custom styles 35 | */ 36 | style: PropTypes.object, 37 | }; 38 | 39 | static defaultProps = { 40 | component: '', 41 | }; 42 | 43 | static contextTypes = { 44 | theme: PropTypes.object, 45 | }; 46 | 47 | componentDidMount() { 48 | document.body.addEventListener('click', this.handleClickOutside); 49 | } 50 | 51 | componentWillUnmount() { 52 | document.body.removeEventListener('click', this.handleClickOutside); 53 | } 54 | 55 | handleClickOutside = event => { 56 | const outsideDropdown = 57 | this.dropdown && !this.dropdown.contains(event.target); 58 | const outsideWrapper = 59 | this.wrapper && !this.wrapper.contains(event.target); 60 | 61 | if (outsideDropdown && outsideWrapper) { 62 | this.closeDropdown(); 63 | } 64 | }; 65 | 66 | toggleDropdown = () => { 67 | const { toggleDropdown } = this.props; 68 | 69 | this.setState( 70 | { 71 | isOpened: !this.state.isOpened, 72 | }, 73 | () => { 74 | if (typeof toggleDropdown === 'function') { 75 | toggleDropdown(this.state.isOpened); 76 | } 77 | } 78 | ); 79 | }; 80 | 81 | closeDropdown = () => { 82 | const { closeDropdown } = this.props; 83 | this.setState( 84 | { 85 | isOpened: false, 86 | }, 87 | () => { 88 | if (typeof closeDropdown === 'function') { 89 | closeDropdown(); 90 | } 91 | } 92 | ); 93 | }; 94 | 95 | openDropdown = () => { 96 | const { openDropdown } = this.props; 97 | 98 | this.setState( 99 | { 100 | isOpened: true, 101 | }, 102 | () => { 103 | if (typeof openDropdown() === 'function') { 104 | openDropdown(); 105 | } 106 | } 107 | ); 108 | }; 109 | 110 | renderDropDown = () => { 111 | const { isOpened = null } = this.state; 112 | const { component } = this.props; 113 | 114 | return ( 115 | 125 | ); 126 | }; 127 | 128 | render() { 129 | const { style = {}, className, children } = this.props; 130 | const { theme } = this.context; 131 | 132 | return ( 133 | (this.wrapper = comp)} 138 | > 139 |
{ 141 | this.toggleDropdown(); 142 | }} 143 | > 144 | {children} 145 |
146 |
(this.dropdown = comp)} 149 | > 150 | {this.renderDropDown()} 151 |
152 |
153 | ); 154 | } 155 | } 156 | 157 | export default Dropdown; 158 | -------------------------------------------------------------------------------- /src/Dropdown/index.js: -------------------------------------------------------------------------------- 1 | export default from './Dropdown'; 2 | -------------------------------------------------------------------------------- /src/Dropdown/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | import { keyframes } from 'styled-components'; 5 | 6 | const slideDown = keyframes` 7 | 0% { 8 | visibility: visible; 9 | transition: all 0.3s ease; 10 | transform: scale(0); 11 | transform-origin: center top; 12 | } 13 | 100%{ 14 | visibility: visible; 15 | height: 100%; 16 | transform: scale(1); 17 | } 18 | `; 19 | 20 | const slideUp = keyframes` 21 | 0% { 22 | transform: scale(1); 23 | } 24 | 100%{ 25 | transition: all 0.3s ease; 26 | transform: scale(0); 27 | transform-origin: center top; 28 | } 29 | `; 30 | 31 | const StyledDropdown = styled.div` 32 | position: relative; 33 | display: table; 34 | 35 | .dropdown { 36 | position: absolute; 37 | left: 50%; 38 | margin-top: 8px; 39 | min-width: 200px; 40 | transform: translate(-50%, 0%); 41 | z-index: 999; 42 | visibility: hidden; 43 | } 44 | 45 | ${props => 46 | props.style && 47 | css` 48 | ${props.style}; 49 | `}; 50 | `; 51 | 52 | StyledDropdown.defaultProps = { 53 | theme: defaultTheme, 54 | }; 55 | 56 | export { StyledDropdown, slideDown, slideUp }; 57 | -------------------------------------------------------------------------------- /src/Form/Checkbox/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import Grid from '../../Layout/Grid'; 4 | import Cell from '../../Layout/Cell'; 5 | import { StyledCheckbox, StyledLabel } from './styles'; 6 | 7 | /** 8 | * A checkbox is used to verify which options you want selected. 9 | * 10 | * @example ./../../../docs/components/Form/Checkbox.md 11 | */ 12 | class Checkbox extends Component { 13 | static propTypes = { 14 | /** 15 | * Custom styles 16 | */ 17 | style: PropTypes.string, 18 | 19 | /** 20 | * onChange 21 | */ 22 | onChange: PropTypes.func, 23 | 24 | grid: PropTypes.object, 25 | 26 | cell: PropTypes.object, 27 | 28 | /** 29 | * Gets called when the user clicks on the button 30 | * 31 | * @param {SyntheticEvent} event The react `SyntheticEvent` 32 | */ 33 | onClick: PropTypes.func 34 | }; 35 | 36 | static defaultProps = { 37 | checked: false, 38 | disabled: false, 39 | grid :{ 40 | columns: '36px 2fr', 41 | gap: '15px' 42 | }, 43 | cell : { 44 | middle: true 45 | } 46 | }; 47 | 48 | static contextTypes = { 49 | theme: PropTypes.object 50 | }; 51 | 52 | get label () { 53 | const { 54 | label='', 55 | hideLabel=false, 56 | cell={} 57 | } = this.props; 58 | 59 | if (hideLabel) { 60 | return ''; 61 | } 62 | 63 | return ( 64 | 65 | {label} 66 | 67 | ); 68 | } 69 | 70 | handleOptionChange = (input) => { 71 | if (typeof this.props.onChange === 'function') { 72 | this.props.onChange(input); 73 | } 74 | } 75 | 76 | render() { 77 | const { 78 | style = '', 79 | checked, 80 | className, 81 | value='', 82 | disabled, 83 | cell, 84 | grid 85 | } = this.props; 86 | const { theme } = this.context; 87 | 88 | return ( 89 | 90 | 91 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 108 | 109 | 110 | {this.label} 111 | 112 | ); 113 | } 114 | } 115 | 116 | export default Checkbox; -------------------------------------------------------------------------------- /src/Form/Checkbox/index.js: -------------------------------------------------------------------------------- 1 | export default from './Checkbox'; -------------------------------------------------------------------------------- /src/Form/Checkbox/styles.js: -------------------------------------------------------------------------------- 1 | import styled, {css, keyframes} from 'styled-components'; 2 | import defaultTheme from '../../theme'; 3 | 4 | const drawCheckbox = keyframes` 5 | 0% { 6 | stroke-dashoffset: 33; 7 | } 8 | 100% { 9 | stroke-dashoffset: 0; 10 | } 11 | `; 12 | 13 | const height = 36; 14 | const width = 36; 15 | 16 | const StyledCheckbox= styled.span` 17 | position: relative; 18 | display: inline-block; 19 | height: ${height}px; 20 | width: ${width}px; 21 | 22 | .checkbox { 23 | position: absolute; 24 | border: 0; 25 | clip: rect(0 0 0 0); 26 | height: 1px; margin: -1px; 27 | overflow: hidden; 28 | padding: 0; 29 | width: 1px; 30 | 31 | &:checked + label > svg { 32 | display: block; 33 | height: ${height / 2}px; 34 | animation: ${drawCheckbox} ease-in-out 0.2s forwards; 35 | } 36 | } 37 | 38 | label { 39 | color: ${props => props.theme.primaryColor}; 40 | line-height: ${height}px; 41 | cursor: pointer; 42 | position: relative; 43 | 44 | &:active::after { 45 | background-color: ${props => props.theme.grayColor}; 46 | } 47 | 48 | &:after { 49 | content: ""; 50 | height: ${height - 4}px; 51 | width: ${width - 4}px; 52 | margin-right: 1rem; 53 | float: left; 54 | border: 2px solid ${props => props.theme.primaryColor}; 55 | border-radius: 3px; 56 | transition: 0.15s all ease-out; 57 | } 58 | } 59 | 60 | svg { 61 | display: none; 62 | stroke: ${props => props.theme.primaryColor}; 63 | stroke-width: 3px; 64 | height: 0; //Firefox fix 65 | width: ${height / 2}px; 66 | position: absolute; 67 | top: 8px; 68 | left: 9px; 69 | stroke-dasharray: 33; //Firefox fix 70 | } 71 | 72 | ${props => props.style && css` 73 | ${props.style} 74 | `} 75 | `; 76 | 77 | const StyledLabel = styled.label` 78 | font-size: 16px; 79 | overflow: hidden; 80 | white-space: nowrap; 81 | text-overflow: ellipsis; 82 | `; 83 | 84 | StyledCheckbox.defaultProps = { 85 | theme: defaultTheme 86 | }; 87 | 88 | StyledLabel.defaultProps = { 89 | theme: defaultTheme 90 | }; 91 | 92 | export { 93 | StyledCheckbox, 94 | StyledLabel 95 | }; -------------------------------------------------------------------------------- /src/Form/Radio/Radio.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import Grid from '../../Layout/Grid'; 4 | import Cell from '../../Layout/Cell'; 5 | import { StyledRadio, StyledLabel } from './styles'; 6 | 7 | /** 8 | * Radio are switches used for selection 9 | * 10 | * @example ./../../../docs/components/Form/Radio.md 11 | */ 12 | class Radio extends Component { 13 | static propTypes = { 14 | 15 | /** 16 | * Custom styles 17 | */ 18 | style: PropTypes.string, 19 | 20 | /** 21 | * onChange 22 | */ 23 | onChange: PropTypes.func, 24 | 25 | grid: PropTypes.object, 26 | 27 | cell: PropTypes.object, 28 | 29 | effect: PropTypes.oneOf(['default', 'circle', 'drop']), 30 | 31 | bgColor: PropTypes.string, 32 | 33 | highlightColor: PropTypes.string 34 | 35 | }; 36 | 37 | static defaultProps = { 38 | grid :{ 39 | columns: '36px 2fr', 40 | gap: '15px' 41 | }, 42 | cell : { 43 | middle: true 44 | }, 45 | effect: 'default', 46 | checked: false 47 | }; 48 | 49 | static contextTypes = { 50 | theme: PropTypes.object 51 | }; 52 | 53 | get label () { 54 | const { 55 | label='', 56 | hideLabel=false, 57 | cell={} 58 | } = this.props; 59 | 60 | if (hideLabel) { 61 | return ''; 62 | } 63 | 64 | return ( 65 | 66 | {label} 67 | 68 | ); 69 | } 70 | 71 | handleOptionChange = (input) => { 72 | if (typeof this.props.onChange === 'function') { 73 | this.props.onChange(input); 74 | } 75 | } 76 | 77 | render() { 78 | const { 79 | style = {}, 80 | className, 81 | value='', 82 | disabled=false, 83 | grid, 84 | cell, 85 | effect, 86 | checked 87 | } = this.props; 88 | const { theme } = this.context; 89 | 90 | return ( 91 | 92 | 93 | {this.handleOptionChange(this.radioBtn)}} {...this.props}> 94 | { this.radioBtn = input; }} 102 | /> 103 | 104 | 105 | 106 | {this.label} 107 | 108 | ); 109 | } 110 | } 111 | 112 | export default Radio; -------------------------------------------------------------------------------- /src/Form/Radio/index.js: -------------------------------------------------------------------------------- 1 | export default from './Radio'; -------------------------------------------------------------------------------- /src/Form/Radio/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css, keyframes } from 'styled-components'; 2 | import defaultTheme from '../../theme'; 3 | 4 | const checkedDrop = keyframes` 5 | 0% { 6 | top: -30px; 7 | transform: scale(0); 8 | } 9 | 100% { 10 | top: 0; 11 | transform: scale(1); 12 | } 13 | `; 14 | 15 | const uncheckedDrop = keyframes` 16 | 0% { 17 | bottom: 0; 18 | transform: scale(1); 19 | } 20 | 100% { 21 | bottom: -30px; 22 | transform: scale(0); 23 | } 24 | `; 25 | 26 | const StyledRadio = styled.span` 27 | position: relative; 28 | display: inline-flex; 29 | cursor: pointer; 30 | 31 | .radio { 32 | display: none; 33 | 34 | &:checked + label:after { 35 | background: transparent; 36 | transition: all .5s; 37 | transform: scale(1); 38 | } 39 | 40 | &.default + label { 41 | background: ${props => props.theme.primaryColor}; 42 | animation-delay: 0s; 43 | ${props => props.bgColor && css` 44 | background: ${props.bgColor}; 45 | `} 46 | } 47 | 48 | &.default + label:before { 49 | transform: scale(0); 50 | } 51 | 52 | &.default:checked + label:before { 53 | transform: scale(1); 54 | transition: all .4s; 55 | } 56 | 57 | &.circle + label { 58 | background: ${props => props.theme.primaryColor}; 59 | border-color: ${props => props.theme.primaryColor}!important; 60 | animation-delay: .2s; 61 | border: 3px solid; 62 | width: 30px; 63 | height: 30px; 64 | ${props => props.bgColor && css` 65 | background: ${props.bgColor}; 66 | border-color: ${props.bgColor}!important; 67 | `} 68 | } 69 | 70 | &.circle:checked + label { 71 | background: ${props => props.theme.lightColor}; 72 | box-shadow: inset rgba(0, 0, 0, 0.117647) 0 0 1px 0, inset rgba(0, 0, 0, 0.239216) 0 1px 2px 0; 73 | transition: all .2s; 74 | ${props => props.highlightColor && css` 75 | background: ${props.highlightColor}; 76 | `} 77 | } 78 | 79 | &.circle:checked + label:before { 80 | width: 15px; 81 | height: 15px; 82 | background: ${props => props.theme.primaryColor}; 83 | transition: all .4s; 84 | ${props => props.bgColor && css` 85 | background: ${props.bgColor}; 86 | `} 87 | } 88 | 89 | &.circle:not(:checked) + label:before { 90 | width: 16px; 91 | height: 16px; 92 | background: ${props => props.theme.primaryColor}; 93 | box-shadow: none; 94 | ${props => props.bgColor && css` 95 | background: ${props.bgColor}; 96 | `} 97 | } 98 | 99 | &.drop + label { 100 | background: ${props => props.theme.primaryColor}; 101 | animation-delay: .4s; 102 | ${props => props.bgColor && css` 103 | background: ${props.bgColor}; 104 | `} 105 | } 106 | 107 | &.drop+ label:before { 108 | transform: scale(0); 109 | animation-name: ${uncheckedDrop}; 110 | animation-duration: .2s; 111 | animation-timing-function: ease-in-out; 112 | } 113 | 114 | &.drop:checked + label:before { 115 | animation-name: ${checkedDrop}; 116 | animation-duration: .4s; 117 | animation-timing-function: ease-in-out; 118 | animation-fill-mode: both; 119 | } 120 | } 121 | 122 | 123 | label { 124 | display: inline-block; 125 | width: 36px; 126 | height: 36px; 127 | border-radius: 50%; 128 | cursor: pointer; 129 | 130 | &:before, &:after { 131 | position: absolute; 132 | top: 0; 133 | bottom: 0; 134 | left: 0; 135 | right: 0; 136 | margin: auto; 137 | padding: 0; 138 | outline: 0; 139 | overflow: hidden; 140 | box-sizing: border-box; 141 | } 142 | 143 | &:before { 144 | content: ""; 145 | position: absolute; 146 | width: 15px; 147 | height: 15px; 148 | background: ${props => props.theme.lightColor}; 149 | border-radius: 50%; 150 | box-shadow: rgba(0, 0, 0, 0.117647) 0 0 2px 0, rgba(0, 0, 0, 0.239216) 0 2px 2px 0; 151 | transition: all .2s; 152 | ${props => props.highlightColor && css` 153 | background: ${props.highlightColor}; 154 | `} 155 | } 156 | 157 | &:hover:before { 158 | box-shadow: rgba(0, 0, 0, 0.0784314) 0 0 3px 0, rgba(0, 0, 0, 0.239216) 0 3px 3px 0; 159 | } 160 | 161 | &:after { 162 | content: ""; 163 | position: absolute; 164 | width: 30px; 165 | height: 30px; 166 | background: rgba(255, 255, 255, .5); 167 | border-radius: 50%; 168 | transform: scale(0); 169 | } 170 | } 171 | 172 | ${props => props.style && css` 173 | ${props.style} 174 | `} 175 | 176 | ${props => props.disabled && css` 177 | pointer-events: none; 178 | cursor: not-allowed; 179 | opacity: 0.65; 180 | filter: alpha(opacity=65); 181 | box-shadow: none; 182 | `} 183 | `; 184 | 185 | const StyledLabel = styled.label` 186 | font-size: 16px; 187 | overflow: hidden; 188 | white-space: nowrap; 189 | text-overflow: ellipsis; 190 | `; 191 | 192 | StyledRadio.defaultProps = { 193 | theme: defaultTheme 194 | }; 195 | 196 | StyledLabel.defaultProps = { 197 | theme: defaultTheme 198 | }; 199 | 200 | export { 201 | StyledRadio, 202 | StyledLabel 203 | }; -------------------------------------------------------------------------------- /src/Form/RadioGroup/RadioGroup.js: -------------------------------------------------------------------------------- 1 | import React, { Component, cloneElement } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledRadioGroup, StyledLabel } from './styles'; 4 | 5 | /** 6 | * Radio are switches used for selection from multiple options 7 | * 8 | * @example ./../../../docs/components/Form/RadioGroup.md 9 | */ 10 | class RadioGroup extends Component { 11 | static propTypes = { 12 | 13 | /** 14 | * Custom styles 15 | */ 16 | style: PropTypes.string, 17 | 18 | /** 19 | * Default selected value 20 | */ 21 | defaultSelected: PropTypes.string, 22 | 23 | onChange: PropTypes.func, 24 | 25 | horizontal: PropTypes.bool, 26 | 27 | grid: PropTypes.object, 28 | 29 | cell: PropTypes.object, 30 | 31 | hideLabel: PropTypes.bool, 32 | 33 | disabled: PropTypes.bool 34 | }; 35 | 36 | static defaultProps = { 37 | defaultSelected: null, 38 | style: '', 39 | horizontal: false, 40 | grid :{ 41 | columns: '1fr 2fr' 42 | }, 43 | cell : { 44 | middle: true 45 | }, 46 | hideLabel: false, 47 | disabled: false 48 | }; 49 | 50 | get label () { 51 | const { 52 | label='', 53 | hideLabel=false, 54 | cell={} 55 | } = this.props; 56 | 57 | if (hideLabel) { 58 | return ''; 59 | } 60 | 61 | return ( 62 | 63 | {label} 64 | 65 | ); 66 | } 67 | 68 | get selectedValue () { 69 | const { defaultSelected } = this.props; 70 | const { selected } = this.state; 71 | if (defaultSelected) { 72 | return defaultSelected; 73 | } 74 | return selected; 75 | } 76 | 77 | handleOnChange = (input) => { 78 | const { onChange } = this.props; 79 | this.setState({ 80 | selected: input.value 81 | }, () => { 82 | if (typeof onChange === 'function') { 83 | onChange(input.value, input); 84 | } 85 | }); 86 | } 87 | 88 | renderRadio = (child, index) => { 89 | const selectedValue = this.selectedValue; 90 | 91 | return cloneElement(child, { 92 | checked: selectedValue ? child.props.value === selectedValue : index === 0, 93 | key: index, 94 | onChange: this.handleOnChange, 95 | disabled: this.props.disabled, 96 | ...child.props 97 | }); 98 | } 99 | 100 | render() { 101 | const { style = {}, className, children, horizontal, grid, cell, disabled } = this.props; 102 | 103 | return ( 104 | 105 | {this.label} 106 | 107 | 113 | { 114 | children.map((child, index) => { 115 | return (
{this.renderRadio(child, index)}
) 116 | }) 117 | } 118 |
119 |
120 |
121 | ); 122 | } 123 | } 124 | 125 | export default RadioGroup; -------------------------------------------------------------------------------- /src/Form/RadioGroup/index.js: -------------------------------------------------------------------------------- 1 | export default from './RadioGroup'; -------------------------------------------------------------------------------- /src/Form/RadioGroup/styles.js: -------------------------------------------------------------------------------- 1 | import styled, {css} from 'styled-components'; 2 | 3 | const StyledRadioGroup = styled.div` 4 | .radio-item { 5 | margin: 10px 0; 6 | 7 | ${props => props.horizontal && css` 8 | margin: 0 10px; 9 | display: inline-block; 10 | `} 11 | } 12 | 13 | ${props => props.style && css` 14 | ${props.style} 15 | `} 16 | 17 | ${props => props.disabled && css` 18 | cursor: not-allowed; 19 | `} 20 | `; 21 | 22 | const StyledLabel = styled.label` 23 | font-size: 16px; 24 | overflow: hidden; 25 | white-space: nowrap; 26 | text-overflow: ellipsis; 27 | `; 28 | 29 | export { 30 | StyledRadioGroup, 31 | StyledLabel 32 | }; -------------------------------------------------------------------------------- /src/Form/TextField/TextField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import Grid from '../../Layout/Grid'; 4 | import Cell from '../../Layout/Cell'; 5 | import { StyledInput, StyledLabel, StyledIcon, StyledBorder, StyledMessage } from './styles'; 6 | 7 | /** 8 | * TextField allow users to input text. 9 | * 10 | * @example ./../../../docs/components/Form/TextField.md 11 | */ 12 | class TextField extends Component { 13 | static propTypes = { 14 | 15 | /** 16 | * Custom styles 17 | */ 18 | style: PropTypes.string, 19 | 20 | /** 21 | * onChange 22 | */ 23 | onChange: PropTypes.func, 24 | 25 | grid: PropTypes.object, 26 | 27 | cell: PropTypes.object 28 | 29 | }; 30 | 31 | static defaultProps = { 32 | style: '', 33 | grid :{ 34 | columns: '1fr 2fr' 35 | }, 36 | cell : { 37 | middle: true 38 | }, 39 | hideLabel: false 40 | }; 41 | 42 | static contextTypes = { 43 | theme: PropTypes.object 44 | }; 45 | 46 | get label () { 47 | const { 48 | label='', 49 | hideLabel=false, 50 | cell={} 51 | } = this.props; 52 | 53 | if (hideLabel) { 54 | return ''; 55 | } 56 | 57 | return ( 58 | 59 | {label} 60 | 61 | ); 62 | } 63 | 64 | onChange = (input) => { 65 | if (typeof this.props.onChange === 'function') { 66 | this.props.onChange(input); 67 | } 68 | } 69 | 70 | renderErrorMessage = (errorText) => { 71 | if (!errorText) { 72 | return ''; 73 | } 74 | return ( 75 | {errorText} 76 | ); 77 | } 78 | 79 | render() { 80 | const { 81 | className, 82 | style, 83 | type, 84 | grid, 85 | cell, 86 | effect='', 87 | placeholder='', 88 | icon='', 89 | disabled=false, 90 | value='', 91 | errorText='' 92 | } = this.props; 93 | const { theme } = this.context; 94 | 95 | return ( 96 | 97 | {this.label} 98 | 99 |
100 | 112 | {icon} 113 | 119 | 120 | 121 |
122 | {this.renderErrorMessage(errorText)} 123 |
124 |
125 | ); 126 | } 127 | } 128 | 129 | export default TextField; 130 | -------------------------------------------------------------------------------- /src/Form/TextField/index.js: -------------------------------------------------------------------------------- 1 | export default from './TextField'; -------------------------------------------------------------------------------- /src/Form/TextField/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../../theme'; 3 | 4 | const StyledInput = styled.input` 5 | -webkit-appearance: none; 6 | color: ${props => props.theme.darkColor}; 7 | display: block; 8 | font-size: 16px; 9 | height: 30px; 10 | height: 42px; 11 | max-width: 100%; 12 | width: 100%; 13 | background: transparent; 14 | border-width: 1px; 15 | border-style: solid; 16 | border-radius: 2px; 17 | border-color: ${props => props.theme.grayColor}; 18 | border-image: initial; 19 | outline: none; 20 | padding: 7px 8px; 21 | transition: all 0.2s ease; 22 | box-sizing : border-box; 23 | 24 | &:focus { 25 | border-width: 2px; 26 | box-shadow: rgba(87, 85, 217, 0.2) 0px 0px 0px 2px; 27 | border-color: ${props => props.theme.primaryColor}; 28 | } 29 | 30 | ${props => props.disabled && css` 31 | color: rgba(0, 0, 0, 0.3); 32 | cursor: not-allowed; 33 | border-style: dashed; 34 | 35 | &::placeholder { 36 | color: rgba(0, 0, 0, 0.3); 37 | } 38 | `} 39 | 40 | ${props => props.errorText && css` 41 | border-color: ${props => props.theme.controlErrorColor}; 42 | `} 43 | 44 | ${props => props.effect === 'line' && css` 45 | border-radius: 0; 46 | border: 0; 47 | border-bottom: 1px solid ${props => props.theme.grayColor}; 48 | padding: 7px 0; 49 | 50 | ${props => props.disabled && css` 51 | border-bottom: 1px dashed ${props => props.theme.grayColor}; 52 | `} 53 | 54 | ${props => props.errorText && css` 55 | border-bottom: 1px dashed ${props => props.theme.controlErrorColor}; 56 | `} 57 | 58 | &:focus { 59 | box-shadow: none; 60 | border-color: ${props => props.theme.grayColor}; 61 | } 62 | 63 | &:focus ~ .focus-border { 64 | width: 100%; 65 | transition: 0.4s; 66 | } 67 | `} 68 | 69 | ${props => props.effect === 'lineOutwards' && css` 70 | border-radius: 0; 71 | border: 0; 72 | border-bottom: 1px solid ${props => props.theme.grayColor}; 73 | padding: 7px 0; 74 | 75 | ${props => props.disabled && css` 76 | border-bottom: 1px dashed ${props => props.theme.grayColor}; 77 | `} 78 | 79 | &:focus { 80 | box-shadow: none; 81 | border-color: ${props => props.theme.grayColor}; 82 | } 83 | 84 | &:focus ~ .focus-border { 85 | width: 100%; 86 | transition: 0.4s; 87 | left: 0; 88 | } 89 | `} 90 | 91 | 92 | ${props => props.effect === 'border' && css` 93 | border: 1px solid ${props => props.theme.grayColor}; 94 | padding: 7px 14px 9px; 95 | transition: 0.4s; 96 | 97 | ${props => props.disabled && css` 98 | border: 1px dashed ${props => props.theme.grayColor}; 99 | `} 100 | 101 | &:focus { 102 | box-shadow: none; 103 | border-color: ${props => props.theme.grayColor}; 104 | } 105 | 106 | &:focus ~ .focus-border:after{ 107 | top: auto; 108 | bottom: 0; 109 | right: auto; 110 | left: 0; 111 | transition-delay: 0.6s; 112 | } 113 | 114 | &:focus ~ .focus-border:before, 115 | &:focus ~ .focus-border:after { 116 | width: 100%; 117 | transition: 0.3s; 118 | } 119 | 120 | &:focus ~ .focus-border i:before, 121 | &:focus ~ .focus-border i:after { 122 | height: 100%; 123 | transition: 0.4s; 124 | } 125 | `} 126 | 127 | ${props => props.errorText && css` 128 | border-color: ${props => props.theme.controlErrorColor}; 129 | &:focus { 130 | border-color: ${props => props.theme.controlErrorColor}; 131 | } 132 | `} 133 | 134 | 135 | ${props => props.style && css` 136 | ${props.style} 137 | `} 138 | `; 139 | 140 | const StyledMessage = styled.span` 141 | font-size: 12px; 142 | padding: 5px; 143 | color: ${props => props.theme.controlErrorColor}; 144 | `; 145 | 146 | const StyledLabel = styled.label` 147 | font-size: 16px; 148 | overflow: hidden; 149 | white-space: nowrap; 150 | text-overflow: ellipsis; 151 | `; 152 | 153 | const StyledIcon = styled.span` 154 | position: absolute; 155 | right: 8px; 156 | bottom: 8px; 157 | `; 158 | 159 | const StyledBorder = styled.span` 160 | ${props => props.effect === 'line' && css` 161 | position: absolute; 162 | bottom: 0; 163 | left: 0; 164 | width: 0; 165 | height: 2px; 166 | background-color: ${props => props.theme.primaryColor}; 167 | transition: 0.4s; 168 | 169 | ${props => props.errorText && css` 170 | background-color: ${props => props.theme.controlErrorColor}; 171 | `} 172 | `} 173 | 174 | ${props => props.effect === 'lineOutwards' && css` 175 | position: absolute; 176 | bottom: 0; 177 | left: 50%; 178 | width: 0; 179 | height: 2px; 180 | background-color: ${props => props.theme.primaryColor}; 181 | transition: 0.4s; 182 | 183 | ${props => props.errorText && css` 184 | background-color: ${props => props.theme.controlErrorColor}; 185 | `} 186 | `} 187 | 188 | ${props => props.effect === 'border' && css` 189 | &:before, 190 | &:after { 191 | content: ""; 192 | position: absolute; 193 | top: 0; 194 | left: 0; 195 | width: 0; 196 | height: 2px; 197 | background-color: ${props => props.theme.primaryColor}; 198 | transition: 0.3s; 199 | 200 | ${props => props.errorText && css` 201 | background-color: ${props => props.theme.controlErrorColor}; 202 | `} 203 | } 204 | 205 | &:after { 206 | top: auto; 207 | bottom: 0; 208 | left: auto; 209 | right: 0; 210 | } 211 | 212 | i:before, 213 | i:after { 214 | content: ""; 215 | position: absolute; 216 | top: 0; 217 | left: 0; 218 | width: 2px; 219 | height: 0; 220 | background-color: ${props => props.theme.primaryColor}; 221 | transition: 0.4s; 222 | 223 | ${props => props.errorText && css` 224 | background-color: ${props => props.theme.controlErrorColor}; 225 | `} 226 | } 227 | 228 | i:after{ 229 | left: auto; 230 | right: 0; 231 | top: auto; 232 | bottom: 0; 233 | } 234 | `} 235 | `; 236 | 237 | StyledInput.defaultProps = { 238 | theme: defaultTheme 239 | }; 240 | 241 | StyledMessage.defaultProps = { 242 | theme: defaultTheme 243 | }; 244 | 245 | StyledLabel.defaultProps = { 246 | theme: defaultTheme 247 | }; 248 | 249 | StyledIcon.defaultProps = { 250 | theme: defaultTheme 251 | }; 252 | 253 | StyledBorder.defaultProps = { 254 | theme: defaultTheme 255 | }; 256 | 257 | export { 258 | StyledInput, 259 | StyledMessage, 260 | StyledLabel, 261 | StyledIcon, 262 | StyledBorder 263 | } -------------------------------------------------------------------------------- /src/Icon/Icon.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { StyledIcon } from './styles'; 5 | 6 | /** 7 | * This component will render font awesome icons only, include font awesome in your project. 8 | * 9 | * @example ./../../docs/components/Icon.md 10 | */ 11 | class Icon extends Component { 12 | static propTypes = { 13 | /** 14 | * The name of the icon - refer font awesome for list of supported icons 15 | */ 16 | name: PropTypes.string.isRequired, 17 | 18 | /** 19 | * Custom icon color 20 | */ 21 | color: PropTypes.string, 22 | 23 | /** 24 | * The size of the Button 25 | */ 26 | size: PropTypes.oneOf(['xsmall', 'small', 'normal', 'large', 'xlarge']), 27 | 28 | /** 29 | * Gets called when the user clicks on the button 30 | * 31 | * @param {SyntheticEvent} event The react `SyntheticEvent` 32 | */ 33 | onClick: PropTypes.func, 34 | }; 35 | 36 | static defaultProps = {}; 37 | 38 | render() { 39 | const { name, className } = this.props; 40 | return ( 41 | 48 | ); 49 | } 50 | } 51 | 52 | export default Icon; 53 | -------------------------------------------------------------------------------- /src/Icon/Icon.spec.js: -------------------------------------------------------------------------------- 1 | import React, { h } from 'preact'; 2 | import { createShallowRenderer, simulate } from '../test-helpers'; 3 | import Icon from './Icon'; 4 | 5 | test('Icon Component', async () => { 6 | // shallow rendering 7 | let render = createShallowRenderer(); 8 | render( 9 | , 10 | document.body 11 | ); 12 | 13 | // with shallow rendering, the becomes because we use 14 | // styled components 15 | let icon = document.querySelector('h-styledcomponent'); 16 | expect(icon.getAttribute('size')).toBe('xsmall'); 17 | expect(icon.getAttribute('name')).toBe('smile-o'); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Icon/index.js: -------------------------------------------------------------------------------- 1 | export default from './Icon'; 2 | -------------------------------------------------------------------------------- /src/Icon/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | const size = { 4 | xsmall: 16, 5 | small: 24, 6 | medium: 40, 7 | large: 48, 8 | xlarge: 72, 9 | }; 10 | 11 | const StyledIcon = styled.i` 12 | position: relative; 13 | cursor: pointer; 14 | text-align: center; 15 | 16 | ${props => 17 | props.size && 18 | css` 19 | height: ${size[props.size]}px; 20 | width: ${size[props.size]}px; 21 | font-size: ${size[props.size]}px; 22 | `} ${props => 23 | props.color && 24 | css` 25 | color: ${props.color}; 26 | `}; 27 | `; 28 | 29 | export { StyledIcon }; 30 | -------------------------------------------------------------------------------- /src/Image/Image.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledImageWrapper, StyledImage } from './styles'; 4 | 5 | /** 6 | * Progressive Image Loading with a blur effect option to reduce the page load time 7 | * 8 | * @example ./../../docs/components/Image.md 9 | */ 10 | class Image extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.string, 16 | 17 | /** 18 | * Gets called when the user clicks on the button 19 | * 20 | * @param {SyntheticEvent} event The react `SyntheticEvent` 21 | */ 22 | onClick: PropTypes.func, 23 | }; 24 | handleImageLoaded = () => { 25 | this.setState({ 26 | placeholderHeight: 0, 27 | placeholderWidth: 0, 28 | imageLoaded: true, 29 | }); 30 | }; 31 | handleImageErrored = () => { 32 | // TODO - Should re-fetch the image 33 | }; 34 | renderResponsiveImages = (images = []) => { 35 | return images.map(image => { 36 | return ; 37 | }); 38 | }; 39 | placeholderOnLoad = element => { 40 | if (!this.state.imageLoaded) { 41 | this.setState({ 42 | placeholderHeight: element.target.offsetHeight, 43 | placeholderWidth: element.target.offsetWidth, 44 | }); 45 | } 46 | }; 47 | renderPlaceholder = placeholder => { 48 | if (placeholder) { 49 | return ( 50 | 63 | ); 64 | } 65 | return ''; 66 | }; 67 | render() { 68 | const { responsive, placeholder = '', className, inline } = this.props; 69 | const { placeholderHeight = 0, placeholderWidth = 0 } = this.state; 70 | return ( 71 | 72 |
78 | 79 | {this.renderResponsiveImages(responsive)} 80 | 85 | 86 | {this.renderPlaceholder(placeholder)} 87 | 88 | ); 89 | } 90 | } 91 | 92 | export default Image; 93 | -------------------------------------------------------------------------------- /src/Image/index.js: -------------------------------------------------------------------------------- 1 | export default from './Image'; 2 | -------------------------------------------------------------------------------- /src/Image/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | const StyledImageWrapper = styled.div` 4 | position: relative; 5 | overflow: hidden; 6 | 7 | ${props => 8 | props.inline && 9 | css` 10 | display: inline-flex; 11 | height: 100%; 12 | justify-content: center; 13 | align-items: center; 14 | `}; 15 | `; 16 | 17 | const StyledImage = styled.img.attrs({ 18 | src: props => props.src || '', 19 | onLoad: props => props.handleImageLoaded, 20 | onError: props => props.handleImageErrored, 21 | })` 22 | display: block; 23 | height: auto; 24 | width: 100%; 25 | margin: 0 auto; 26 | object-fit: cover; 27 | 28 | ${props => 29 | props.rounded && 30 | css` 31 | border-radius: 50%; 32 | `} ${props => 33 | props.style && 34 | css` 35 | ${props.style}; 36 | `}; 37 | `; 38 | 39 | export { StyledImageWrapper, StyledImage }; 40 | -------------------------------------------------------------------------------- /src/Layout/Cell.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import { StyledCell } from './styles'; 3 | 4 | /** 5 | * Cards are content containers to display information 6 | * 7 | * @example ./../../docs/components/Card.md 8 | */ 9 | class Cell extends Component { 10 | render() { 11 | return ; 12 | } 13 | } 14 | 15 | export default Cell; 16 | -------------------------------------------------------------------------------- /src/Layout/Grid.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import { StyledGrid } from './styles'; 3 | 4 | /** 5 | * Layout layout 6 | * 7 | * @example ./../../docs/components/Grid.md 8 | */ 9 | class Grid extends Component { 10 | render() { 11 | return ; 12 | } 13 | } 14 | 15 | export default Grid; 16 | -------------------------------------------------------------------------------- /src/Layout/index.js: -------------------------------------------------------------------------------- 1 | export default from './Grid'; 2 | -------------------------------------------------------------------------------- /src/Layout/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | const autoRows = ({ minRowHeight = '20px' }) => `minmax(${minRowHeight}, auto)`; 4 | 5 | const columns = ({ columns = 12 }) => 6 | typeof columns === 'number' ? `repeat(${columns}, 1fr)` : columns; 7 | 8 | const gap = ({ gap = '8px' }) => `${gap} ${gap}`; 9 | 10 | const flow = ({ flow = 'row' }) => flow; 11 | 12 | const formatAreas = areas => areas.map(area => `"${area}"`).join(' '); 13 | 14 | const StyledGrid = styled.div` 15 | display: grid; 16 | grid-auto-flow: ${flow}; 17 | grid-auto-rows: ${autoRows}; 18 | ${({ rows }) => rows && `grid-template-rows: ${rows}`}; 19 | grid-template-columns: ${columns}; 20 | grid-gap: ${gap}; 21 | ${({ areas }) => areas && `grid-template-areas: ${formatAreas(areas)}`}; 22 | ${({ justifyContent }) => 23 | justifyContent && `justify-content: ${justifyContent}`}; 24 | ${({ alignContent }) => alignContent && `align-content: ${alignContent}`}; 25 | ${({ alignItems }) => 26 | alignItems && 27 | css` 28 | align-items: ${alignItems}; 29 | -webkit-box-align: ${alignItems}; 30 | -ms-flex-align: ${alignItems}; 31 | `}; 32 | ${({ style }) => 33 | style && 34 | css` 35 | ${style}; 36 | `}; 37 | `; 38 | 39 | const StyledCell = styled.section` 40 | height: 100%; 41 | min-width: 0; 42 | overflow: hidden; 43 | align-content: space-around; 44 | grid-column-end: ${({ width = 1 }) => `span ${width}`}; 45 | grid-row-end: ${({ height = 1 }) => `span ${height}`}; 46 | ${({ left }) => left && `grid-column-start: ${left}`}; 47 | ${({ top }) => top && `grid-row-start: ${top}`}; 48 | ${({ center }) => center && `text-align: center`}; 49 | ${({ area }) => area && `grid-area: ${area}`}; 50 | ${({ middle }) => 51 | middle && 52 | css` 53 | display: inline-flex; 54 | flex-flow: column wrap; 55 | justify-content: center; 56 | justify-self: stretch; 57 | `}; 58 | 59 | ${({ overflow }) => 60 | overflow && 61 | css` 62 | overflow: visible; 63 | `}; 64 | 65 | ${({ style }) => 66 | style && 67 | css` 68 | ${style}; 69 | `}; 70 | `; 71 | 72 | export { StyledGrid, StyledCell }; 73 | -------------------------------------------------------------------------------- /src/Link/Link.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledLink } from './styles'; 4 | 5 | /** 6 | * Provides declarative, accessible navigation around your application. 7 | * 8 | * @example ./../../docs/components/Link.md 9 | */ 10 | class Link extends Component { 11 | static propTypes = { 12 | /** 13 | * Anchor link 14 | */ 15 | href: PropTypes.string, 16 | 17 | /** 18 | * Custom styles 19 | */ 20 | style: PropTypes.object, 21 | 22 | /** 23 | * Gets called when the user clicks on the button 24 | * 25 | * @param {SyntheticEvent} event The react `SyntheticEvent` 26 | */ 27 | onClick: PropTypes.func, 28 | }; 29 | 30 | static contextTypes = { 31 | theme: PropTypes.object, 32 | }; 33 | 34 | render() { 35 | const { 36 | style = {}, 37 | url = '#', 38 | target = '', 39 | onClick, 40 | className, 41 | } = this.props; 42 | const { theme } = this.context; 43 | 44 | return ( 45 | 53 | {this.props.children} 54 | 55 | ); 56 | } 57 | } 58 | 59 | export default Link; 60 | -------------------------------------------------------------------------------- /src/Link/index.js: -------------------------------------------------------------------------------- 1 | export default from './Link'; 2 | -------------------------------------------------------------------------------- /src/Link/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const StyledLink = styled.a` 5 | color: ${props => props.theme.linkColor}; 6 | appearance: none; 7 | cursor: pointer; 8 | display: inline-block; 9 | outline: none; 10 | text-align: center; 11 | text-decoration: none; 12 | user-select: none; 13 | vertical-align: middle; 14 | white-space: nowrap; 15 | font-size: 14px; 16 | line-height: 14px; 17 | 18 | &:focus { 19 | text-decoration: none; 20 | } 21 | 22 | &:hover { 23 | text-decoration: none; 24 | } 25 | 26 | ${props => 27 | props.style && 28 | css` 29 | ${props.style}; 30 | `}; 31 | `; 32 | 33 | StyledLink.defaultProps = { 34 | theme: defaultTheme, 35 | }; 36 | 37 | export { StyledLink }; 38 | -------------------------------------------------------------------------------- /src/List/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledList } from './styles'; 4 | 5 | /** 6 | * List groups are a flexible and powerful component for displaying not only simple lists of elements, but complex ones with custom content. 7 | * 8 | * @example ./../../docs/components/List.md 9 | */ 10 | class List extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.object, 16 | }; 17 | 18 | static contextTypes = { 19 | theme: PropTypes.object, 20 | }; 21 | 22 | render() { 23 | const { style = '', className, children } = this.props; 24 | const { theme } = this.context; 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | } 32 | } 33 | 34 | export default List; 35 | -------------------------------------------------------------------------------- /src/List/ListFooter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledFooter } from './styles'; 4 | import Grid from './../Layout/Grid'; 5 | import Cell from './../Layout/Cell'; 6 | 7 | /** 8 | * List Footer - https://dribbble.com/shots/3935136-MrWhite-UI-Kit-Dropdown-s/attachments/897751 9 | */ 10 | class ListFooter extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.object, 16 | }; 17 | 18 | static contextTypes = { 19 | theme: PropTypes.object, 20 | }; 21 | 22 | render() { 23 | const { style = '', className, left, right } = this.props; 24 | const { theme } = this.context; 25 | 26 | return ( 27 | 28 | 29 | 30 | {left} 31 | 32 | 33 | {right} 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | 41 | export default ListFooter; 42 | -------------------------------------------------------------------------------- /src/List/ListHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledHeader, StyledTitle, StyledSubTitle } from './styles'; 4 | 5 | /** 6 | * List header 7 | */ 8 | class ListHeader extends Component { 9 | static propTypes = { 10 | /** 11 | * Custom styles 12 | */ 13 | style: PropTypes.object, 14 | }; 15 | 16 | static contextTypes = { 17 | theme: PropTypes.object, 18 | }; 19 | 20 | get content() { 21 | const { custom, title = '', subtitle = '' } = this.props; 22 | const { theme } = this.context; 23 | 24 | if (custom && custom.nodeName) { 25 | // if node exists 26 | return custom; 27 | } 28 | 29 | return ( 30 |
31 | {title} 32 | {subtitle} 33 |
34 | ); 35 | } 36 | 37 | render() { 38 | const { style = '', className } = this.props; 39 | const { theme } = this.context; 40 | 41 | return ( 42 | 43 | {this.content} 44 | 45 | ); 46 | } 47 | } 48 | 49 | export default ListHeader; 50 | -------------------------------------------------------------------------------- /src/List/ListItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledItem } from './styles'; 4 | 5 | /** 6 | * List Item 7 | */ 8 | class ListItem extends Component { 9 | static propTypes = { 10 | /** 11 | * Custom styles 12 | */ 13 | style: PropTypes.object, 14 | }; 15 | 16 | static contextTypes = { 17 | theme: PropTypes.object, 18 | }; 19 | 20 | get left() { 21 | const { left } = this.props; 22 | 23 | if (left && left.nodeName) { 24 | // if node exists 25 | return
{left}
; 26 | } 27 | 28 | return ''; 29 | } 30 | 31 | get right() { 32 | const { right } = this.props; 33 | 34 | if (right && right.nodeName) { 35 | // if node exists 36 | return
{right}
; 37 | } 38 | 39 | return ''; 40 | } 41 | 42 | handleClick = () => { 43 | const { onClick } = this.props; 44 | 45 | if (typeof onClick === 'function') { 46 | onClick(this.item); 47 | } 48 | }; 49 | 50 | render() { 51 | const { style = '', className, children, active } = this.props; 52 | const { theme } = this.context; 53 | 54 | return ( 55 | (this.item = item)} 62 | > 63 | {this.left} 64 | {children} 65 | {this.right} 66 | 67 | ); 68 | } 69 | } 70 | 71 | export default ListItem; 72 | -------------------------------------------------------------------------------- /src/List/ListSection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledSection } from './styles'; 4 | 5 | /** 6 | * List Section 7 | */ 8 | class ListSection extends Component { 9 | static propTypes = { 10 | /** 11 | * Custom styles 12 | */ 13 | style: PropTypes.object, 14 | }; 15 | 16 | static contextTypes = { 17 | theme: PropTypes.object, 18 | }; 19 | 20 | render() { 21 | const { style = '', className, children } = this.props; 22 | const { theme } = this.context; 23 | 24 | return ( 25 | 26 | {children} 27 | 28 | ); 29 | } 30 | } 31 | 32 | export default ListSection; 33 | -------------------------------------------------------------------------------- /src/List/index.js: -------------------------------------------------------------------------------- 1 | export default from './List'; 2 | -------------------------------------------------------------------------------- /src/List/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const StyledList = styled.div` 5 | background: ${props => props.theme.lightColor}; 6 | border: 1px solid ${props => props.theme.borderColor}; 7 | border-radius: 3px; 8 | display: flex; 9 | flex-direction: column; 10 | box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); 11 | 12 | &:focus { 13 | text-decoration: none; 14 | } 15 | 16 | &:hover { 17 | text-decoration: none; 18 | } 19 | 20 | ${props => 21 | props.style && 22 | css` 23 | ${props.style}; 24 | `}; 25 | `; 26 | 27 | const StyledHeader = styled.div` 28 | padding: 15px 20px; 29 | border-bottom: 1px solid ${props => props.theme.borderColor}; 30 | 31 | ${props => 32 | props.style && 33 | css` 34 | ${props.style}; 35 | `}; 36 | `; 37 | 38 | const StyledSection = styled.ul` 39 | padding: 0; 40 | margin: 0; 41 | 42 | &:not(:last-child) { 43 | border-bottom: 1px solid ${props => props.theme.borderColor}; 44 | } 45 | 46 | ${props => 47 | props.style && 48 | css` 49 | ${props.style}; 50 | `}; 51 | `; 52 | 53 | const StyledItem = styled.li` 54 | padding: 0 20px; 55 | color: ${props => props.theme.grayColorDark}; 56 | height: 42px; 57 | line-height: 42px; 58 | list-style: none; 59 | 60 | &:hover { 61 | cursor: pointer; 62 | background: ${props => props.theme.listActiveColor}; 63 | } 64 | 65 | .list-item-left { 66 | display: inline-flex; 67 | height: 100%; 68 | padding-right: 15px; 69 | float: left; 70 | justify-content: center; 71 | align-items: center; 72 | 73 | i { 74 | color: ${props => props.theme.grayColorDark}; 75 | } 76 | } 77 | 78 | .list-item-right { 79 | display: inline-flex; 80 | height: 100%; 81 | padding-left: 15px; 82 | float: right; 83 | justify-content: center; 84 | align-items: center; 85 | 86 | i { 87 | color: ${props => props.theme.grayColorDark}; 88 | } 89 | } 90 | 91 | ${props => 92 | props.active && 93 | css` 94 | background: ${props => props.theme.listActiveColor}; 95 | `} ${props => 96 | props.style && 97 | css` 98 | ${props.style}; 99 | `}; 100 | `; 101 | 102 | const StyledFooter = styled.div` 103 | padding: 15px 20px; 104 | 105 | &:not(:last-child) { 106 | border-top: 1px solid ${props => props.theme.borderColor}; 107 | } 108 | 109 | ${props => 110 | props.style && 111 | css` 112 | ${props.style}; 113 | `}; 114 | `; 115 | 116 | const StyledTitle = styled.h1` 117 | font-size: 20px; 118 | line-height: 32px; 119 | font-weight: 500; 120 | color: ${props => props.theme.darkColor}; 121 | margin: 0; 122 | 123 | ${props => 124 | props.light && 125 | css` 126 | color: ${props => props.theme.lightColor}; 127 | `} ${props => 128 | props.style && 129 | css` 130 | ${props.style}; 131 | `}; 132 | `; 133 | 134 | const StyledSubTitle = styled.h2` 135 | font-size: 14px; 136 | line-height: 18px; 137 | font-weight: 300; 138 | color: ${props => props.theme.grayColor}; 139 | margin: 0; 140 | 141 | ${props => 142 | props.light && 143 | css` 144 | color: ${props => props.theme.lightColor}; 145 | `} ${props => 146 | props.style && 147 | css` 148 | ${props.style}; 149 | `}; 150 | `; 151 | 152 | StyledList.defaultProps = { 153 | theme: defaultTheme, 154 | }; 155 | 156 | StyledHeader.defaultProps = { 157 | theme: defaultTheme, 158 | }; 159 | 160 | StyledSection.defaultProps = { 161 | theme: defaultTheme, 162 | }; 163 | 164 | StyledItem.defaultProps = { 165 | theme: defaultTheme, 166 | }; 167 | 168 | StyledFooter.defaultProps = { 169 | theme: defaultTheme, 170 | }; 171 | 172 | StyledTitle.defaultProps = { 173 | theme: defaultTheme, 174 | }; 175 | 176 | StyledSubTitle.defaultProps = { 177 | theme: defaultTheme, 178 | }; 179 | 180 | export { 181 | StyledList, 182 | StyledHeader, 183 | StyledSection, 184 | StyledItem, 185 | StyledFooter, 186 | StyledTitle, 187 | StyledSubTitle, 188 | }; 189 | -------------------------------------------------------------------------------- /src/Modal/Modal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledModal } from './styles'; 4 | import ModalPortal from './ModalPortal'; 5 | 6 | /** 7 | * Description 8 | * 9 | * @example ./../../docs/components/Modal.md 10 | */ 11 | class Modal extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | content: false, 16 | }; 17 | } 18 | 19 | static propTypes = { 20 | /** 21 | * Custom styles 22 | */ 23 | style: PropTypes.object, 24 | }; 25 | 26 | static defaultProps = {}; 27 | 28 | static contextTypes = { 29 | theme: PropTypes.object, 30 | }; 31 | 32 | show = content => { 33 | this.setState({ content }); 34 | }; 35 | 36 | hide = () => { 37 | this.setState({ content: false }); 38 | }; 39 | 40 | render() { 41 | const { style = {}, className } = this.props; 42 | const { theme } = this.context; 43 | 44 | return this.state.content ? ( 45 | 46 | 47 | {this.state.content} 48 | 49 | 50 | ) : null; 51 | } 52 | } 53 | 54 | export default Modal; 55 | -------------------------------------------------------------------------------- /src/Modal/ModalPortal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledModalWrapper } from './styles'; 4 | 5 | /** 6 | * Modal Portal Component 7 | */ 8 | class ModalPortal extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.portal = null; 12 | } 13 | componentDidMount() { 14 | let portal = 15 | this.props.portalId && document.getElementById(this.props.portalId); 16 | if (!portal) { 17 | portal = document.createElement('div'); 18 | portal.id = this.props.portalId; 19 | portal.style.position = 'fixed'; 20 | portal.style[this.props.position] = '0'; 21 | portal.style.top = '0'; 22 | portal.style.left = '0'; 23 | portal.style.width = '100vw'; 24 | portal.style.height = '100vh'; 25 | portal.style.overflow = 'hidden'; 26 | portal.style.display = 'flex'; 27 | portal.style.alignItems = 'center'; 28 | portal.style.justifyContent = 'center'; 29 | portal.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; 30 | document.body.appendChild(portal); 31 | } 32 | this.portal = portal; 33 | this.componentDidUpdate(); 34 | } 35 | componentWillUnmount() { 36 | const element = document.getElementById(this.props.portalId); 37 | if (element) { 38 | document.body.removeChild(this.portal); 39 | } 40 | } 41 | componentDidUpdate() { 42 | if (!this.props.children.length) { 43 | return; 44 | } 45 | React.render( 46 | 47 | {this.props.children} 48 | , 49 | this.portal 50 | ); 51 | } 52 | render = () => null; 53 | } 54 | 55 | export default ModalPortal; 56 | -------------------------------------------------------------------------------- /src/Modal/index.js: -------------------------------------------------------------------------------- 1 | export default from './Modal'; 2 | -------------------------------------------------------------------------------- /src/Modal/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const StyledModal = styled.div` 5 | ${props => 6 | props.style && 7 | css` 8 | ${props.style}; 9 | `}; 10 | `; 11 | 12 | StyledModal.defaultProps = { 13 | theme: defaultTheme, 14 | }; 15 | 16 | export { StyledModal }; 17 | -------------------------------------------------------------------------------- /src/Notify/Notify.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import NotifyPortal from './NotifyPortal'; 4 | import NotifyPanel from './NotifyPanel'; 5 | 6 | /** 7 | * Notification system for application with customizable features 8 | * 9 | * @example ./../../docs/components/Notify.md 10 | */ 11 | class Notify extends Component { 12 | static propTypes = { 13 | /** 14 | * Unique id for the notification wrapper 15 | */ 16 | id: PropTypes.string.required, 17 | 18 | /** 19 | * Custom styles 20 | */ 21 | style: PropTypes.object, 22 | 23 | /** 24 | * Title 25 | */ 26 | title: PropTypes.string, 27 | 28 | /** 29 | * Delay in seconds for the notification go away. Set this to 0 to not auto-dismiss the notification. 30 | */ 31 | autoDismiss: PropTypes.number, 32 | 33 | /** 34 | * Message 35 | */ 36 | message: PropTypes.string, 37 | 38 | /** 39 | * Level of the notification 40 | */ 41 | type: PropTypes.oneOf(['success', 'warning', 'error', 'info']), 42 | 43 | /** 44 | * Callback function on click of notification 45 | */ 46 | onClick: PropTypes.func, 47 | 48 | /** 49 | * Icon name from font awesome - default is bell-o 50 | */ 51 | iconName: PropTypes.string, 52 | 53 | /** 54 | * Size of the icon 55 | */ 56 | iconSize: PropTypes.oneOf([ 57 | 'xsmall', 58 | 'small', 59 | 'medium', 60 | 'large', 61 | 'xlarge', 62 | ]), 63 | 64 | /** 65 | * Icon color 66 | */ 67 | iconColor: PropTypes.string, 68 | 69 | /** 70 | * A callback function that will be called when the notification is successfully added. The first argument is the original notification e.g. `function (notification) { console.log(notification.title + 'was added'); }` 71 | */ 72 | onAdd: PropTypes.func, 73 | 74 | /** 75 | * A callback function that will be called when the notification is about to be removed. The first argument is the original notification e.g. `function (notification) { console.log(notification.title + 'was removed'); }` 76 | */ 77 | onDismiss: PropTypes.func, 78 | }; 79 | 80 | static defaultProps = { 81 | type: 'info', 82 | style: {}, 83 | title: null, 84 | message: null, 85 | autoDismiss: 5, 86 | iconName: 'bell-o', 87 | iconColor: 'dark gray', 88 | iconSize: 'xsmall', 89 | }; 90 | 91 | constructor(props) { 92 | super(props); 93 | this.state = { 94 | notifications: [], 95 | }; 96 | } 97 | 98 | get guid() { 99 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { 100 | const r = (Math.random() * 16) | 0, 101 | v = c == 'x' ? r : (r & 0x3) | 0x8; 102 | return v.toString(16); 103 | }); 104 | } 105 | 106 | add = notification => { 107 | const notifications = this.state.notifications; 108 | notifications.push( 109 | Object.assign(notification, { 110 | guid: this.guid, 111 | close: () => { 112 | this.dismiss(notification); 113 | }, 114 | }) 115 | ); 116 | 117 | // onAdd callback 118 | if (typeof notification.onAdd === 'function') { 119 | notification.onAdd(notification); 120 | } 121 | 122 | // set the state to display the notification 123 | this.setState( 124 | { 125 | notifications, 126 | }, 127 | () => { 128 | const { autoDismiss = 5 } = notification; 129 | if (autoDismiss !== 0 && typeof autoDismiss === 'number') { 130 | setTimeout(() => { 131 | this.dismiss(notification); 132 | }, autoDismiss * 1000); 133 | } 134 | // clear notification state 135 | this.setState({ 136 | notifications: [], 137 | }); 138 | } 139 | ); 140 | }; 141 | 142 | dismiss = notification => { 143 | const { id } = this.props; 144 | const notifyWrapper = document.getElementById(id); 145 | const notificationElement = document.getElementById(notification.guid); 146 | 147 | // onDismiss callback 148 | if (typeof notification.onDismiss === 'function') { 149 | notification.onDismiss(notification); 150 | } 151 | 152 | // close the notitication 153 | notificationElement.classList.add('close'); 154 | 155 | // destroy the notification after 500ms 156 | setTimeout(() => { 157 | notifyWrapper.removeChild(notificationElement.parentNode); 158 | }, 500); 159 | }; 160 | 161 | renderNotification = () => { 162 | return this.state.notifications.map((notification, i) => ( 163 | 164 | )); 165 | }; 166 | 167 | render() { 168 | const { id, position = 'bottom' } = this.props; 169 | return ( 170 | 171 | {this.renderNotification()} 172 | 173 | ); 174 | } 175 | } 176 | 177 | export default Notify; 178 | -------------------------------------------------------------------------------- /src/Notify/NotifyPanel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import Icon from './../Icon'; 4 | import colors from './../theme'; 5 | import { StyledNotify } from './styles'; 6 | 7 | /** 8 | * Notification panel 9 | */ 10 | class NotifyPanel extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.object, 16 | 17 | /** 18 | * Title 19 | */ 20 | title: PropTypes.string, 21 | 22 | /** 23 | * Delay in seconds for the notification go away. Set this to 0 to not auto-dismiss the notification. 24 | */ 25 | autoDismiss: PropTypes.integer, 26 | 27 | /** 28 | * Message 29 | */ 30 | message: PropTypes.string, 31 | 32 | /** 33 | * Level of the notification 34 | */ 35 | type: PropTypes.oneOf(['success', 'warning', 'error', 'info']), 36 | 37 | /** 38 | * Callback function on click of notification 39 | */ 40 | onClick: PropTypes.func, 41 | 42 | /** 43 | * Icon name from font awesome - default is bell-o 44 | */ 45 | iconName: PropTypes.string, 46 | 47 | /** 48 | * Size of the icon 49 | */ 50 | iconSize: PropTypes.oneOf([ 51 | 'xsmall', 52 | 'small', 53 | 'medium', 54 | 'large', 55 | 'xlarge', 56 | ]), 57 | 58 | /** 59 | * Icon color 60 | */ 61 | iconColor: PropTypes.string, 62 | }; 63 | 64 | static defaultProps = { 65 | type: 'info', 66 | }; 67 | 68 | static contextTypes = { 69 | theme: PropTypes.object, 70 | }; 71 | 72 | get iconColor() { 73 | const colorsList = { 74 | info: colors.grayColorDark, 75 | success: colors.controlSuccessColor, 76 | error: colors.controlErrorColor, 77 | warning: colors.controlWarningColor, 78 | }; 79 | return colorsList[this.props.type]; 80 | } 81 | 82 | close = () => { 83 | if (typeof this.props.close === 'function') { 84 | this.props.close(); 85 | } 86 | }; 87 | 88 | onClick = () => { 89 | if (typeof this.props.onClick === 'function') { 90 | this.props.onClick(); 91 | } 92 | }; 93 | 94 | render() { 95 | const { 96 | style, 97 | className, 98 | title = '', 99 | type = 'info', 100 | message, 101 | guid, 102 | iconColor = this.iconColor, 103 | iconName = 'bell-o', 104 | iconSize = 'small', 105 | } = this.props; 106 | const { theme } = this.context; 107 | 108 | return ( 109 | 117 |
118 | 119 |
120 |
121 |
122 |

{title}

123 |

{message}

124 |
125 | 126 | ); 127 | } 128 | } 129 | 130 | export default NotifyPanel; 131 | -------------------------------------------------------------------------------- /src/Notify/NotifyPortal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledNotifyWrapper } from './styles'; 4 | 5 | /** 6 | * Notification Portal Component 7 | */ 8 | class NotifyPortal extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.portal = null; 12 | } 13 | componentDidMount() { 14 | let portal = 15 | this.props.portalId && document.getElementById(this.props.portalId); 16 | if (!portal) { 17 | portal = document.createElement('div'); 18 | portal.id = this.props.portalId; 19 | portal.style.position = 'fixed'; 20 | portal.style[this.props.position] = '10px'; 21 | portal.style.right = '10px'; 22 | portal.style.overflow = 'hidden'; 23 | document.body.appendChild(portal); 24 | } 25 | this.portal = portal; 26 | this.componentDidUpdate(); 27 | } 28 | componentWillUnmount() { 29 | const element = document.getElementById(this.props.portalId); 30 | if (element) { 31 | document.body.removeChild(this.portal); 32 | } 33 | } 34 | componentDidUpdate() { 35 | if (!this.props.children.length) { 36 | return; 37 | } 38 | React.render( 39 | 40 | {this.props.children} 41 | , 42 | this.portal 43 | ); 44 | } 45 | render = () => null; 46 | } 47 | 48 | export default NotifyPortal; 49 | -------------------------------------------------------------------------------- /src/Notify/index.js: -------------------------------------------------------------------------------- 1 | export default from './Notify'; 2 | -------------------------------------------------------------------------------- /src/Notify/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import animations from './../Animations'; 3 | import defaultTheme from '../theme'; 4 | 5 | const StyledNotifyWrapper = styled.div` 6 | display: grid; 7 | margin: 5px; 8 | `; 9 | 10 | const StyledNotify = styled.div` 11 | display: table; 12 | background: ${props => props.theme.notifyBgColor}; 13 | border-radius: 4px; 14 | padding: 3px; 15 | height: 100%; 16 | min-height: 60px; 17 | min-width: 250px; 18 | max-width: 350px; 19 | cursor: pointer; 20 | animation: ${animations.slideInRight} 500ms ease-in; 21 | 22 | &.close { 23 | animation: ${animations.slideRight} 500ms ease-in forwards; 24 | } 25 | 26 | .notification-icon { 27 | animation: ${animations.shake} 1s linear; 28 | height: 100%; 29 | width: 60px; 30 | float: left; 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | } 35 | 36 | .notification-body { 37 | position: relative; 38 | height: 100%; 39 | width: calc(100% - 68px); 40 | float: left; 41 | border-radius: 4px; 42 | padding: 4px; 43 | background-color: ${props => props.theme.lightColor}; 44 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 6px, 45 | rgba(0, 0, 0, 0.12) 0px 1px 4px; 46 | } 47 | 48 | .notification-close { 49 | position: absolute; 50 | top: 0px; 51 | right: 5px; 52 | font-size: 18px; 53 | cursor: pointer; 54 | } 55 | .notification-close:before { 56 | content: '\00d7'; 57 | color: ${props => props.theme.grayColor}; 58 | } 59 | 60 | .notification-title { 61 | font-size: 16px; 62 | padding: 5px; 63 | margin: 0; 64 | color: ${props => props.theme.grayColorDark}; 65 | 66 | ${props => 67 | props.type === 'error' && 68 | css` 69 | color: ${props => props.theme.controlErrorColor}; 70 | `} ${props => 71 | props.type === 'warning' && 72 | css` 73 | color: ${props => props.theme.controlWarningColor}; 74 | `} ${props => 75 | props.type === 'success' && 76 | css` 77 | color: ${props => props.theme.controlSuccessColor}; 78 | `}; 79 | } 80 | 81 | .notification-message { 82 | padding: 0 5px; 83 | margin: 0; 84 | font-size: 14px; 85 | color: ${props => props.theme.grayColorDark}; 86 | } 87 | 88 | ${props => 89 | props.style && 90 | css` 91 | ${props.style}; 92 | `}; 93 | `; 94 | 95 | StyledNotifyWrapper.defaultProps = { 96 | theme: defaultTheme, 97 | }; 98 | 99 | StyledNotify.defaultProps = { 100 | theme: defaultTheme, 101 | }; 102 | 103 | export { StyledNotifyWrapper, StyledNotify }; 104 | -------------------------------------------------------------------------------- /src/ThemeProvider/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import defaultTheme from '../theme'; 4 | 5 | /** 6 | * Custom theme provider for application 7 | */ 8 | class ThemeProvider extends Component { 9 | static propTypes = { 10 | theme: PropTypes.object.isRequired, 11 | }; 12 | getChildContext() { 13 | let { children, ...context } = this.props; 14 | if (context && context.theme) { 15 | context.theme = Object.assign(defaultTheme, context.theme || {}); 16 | } 17 | return context; 18 | } 19 | render({ children }) { 20 | return (children && children[0]) || null; 21 | } 22 | } 23 | 24 | export default ThemeProvider; 25 | -------------------------------------------------------------------------------- /src/ThemeProvider/index.js: -------------------------------------------------------------------------------- 1 | export default from './ThemeProvider'; 2 | -------------------------------------------------------------------------------- /src/Tooltip/Tooltip.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { StyledTooltip } from './styles'; 4 | 5 | /** 6 | * Description 7 | * 8 | * @example ./../../docs/components/Tooltip.md 9 | */ 10 | class Tooltip extends Component { 11 | static propTypes = { 12 | /** 13 | * Title 14 | */ 15 | title: PropTypes.object.isRequired, 16 | 17 | /** 18 | * Position - default is `bottom` 19 | */ 20 | position: PropTypes.oneOf(['top', 'bottom', 'left', 'right']), 21 | 22 | /** 23 | * Animation effect - default is `fade` 24 | */ 25 | effect: PropTypes.oneOf(['fade', 'expand']), 26 | 27 | /** 28 | * Specify fixed width 29 | */ 30 | width: PropTypes.string, 31 | 32 | /** 33 | * Custom styles 34 | */ 35 | style: PropTypes.object, 36 | }; 37 | 38 | static defaultProps = { 39 | title: '', 40 | effect: 'fade', 41 | position: 'bottom', 42 | width: null, 43 | }; 44 | 45 | static contextTypes = { 46 | theme: PropTypes.object, 47 | }; 48 | 49 | render() { 50 | const { 51 | style = {}, 52 | className, 53 | children, 54 | title, 55 | effect, 56 | position, 57 | width, 58 | } = this.props; 59 | const { theme } = this.context; 60 | 61 | return ( 62 | 70 | {children} 71 | 72 | ); 73 | } 74 | } 75 | 76 | export default Tooltip; 77 | -------------------------------------------------------------------------------- /src/Tooltip/index.js: -------------------------------------------------------------------------------- 1 | export default from './Tooltip'; 2 | -------------------------------------------------------------------------------- /src/Tooltip/styles.js: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const StyledTooltip = styled.div` 5 | position: relative; 6 | display: table; 7 | cursor: pointer; 8 | 9 | &:before, 10 | &:after { 11 | display: block; 12 | opacity: 0; 13 | pointer-events: none; 14 | position: absolute; 15 | } 16 | 17 | &:after { 18 | content: ''; 19 | height: 0; 20 | width: 0; 21 | 22 | ${props => 23 | props.position === 'top' && 24 | css` 25 | border-right: 6px solid transparent; 26 | border-top: 6px solid rgba(0, 0, 0, 0.75); 27 | border-left: 6px solid transparent; 28 | bottom: 100%; 29 | left: 50%; 30 | margin-bottom: 5px; 31 | `} ${props => 32 | props.position === 'bottom' && 33 | css` 34 | border-right: 6px solid transparent; 35 | border-bottom: 6px solid rgba(0, 0, 0, 0.75); 36 | border-left: 6px solid transparent; 37 | top: 100%; 38 | left: 50%; 39 | margin-top: 5px; 40 | `} ${props => 41 | props.position === 'left' && 42 | css` 43 | border-top: 6px solid transparent; 44 | border-left: 6px solid rgba(0, 0, 0, 0.75); 45 | border-bottom: 6px solid transparent; 46 | top: 50%; 47 | right: 100%; 48 | margin-right: 5px; 49 | `} ${props => 50 | props.position === 'right' && 51 | css` 52 | border-top: 6px solid transparent; 53 | border-right: 6px solid rgba(0, 0, 0, 0.75); 54 | border-bottom: 6px solid transparent; 55 | top: 50%; 56 | left: 100%; 57 | margin-left: 5px; 58 | `}; 59 | } 60 | 61 | &:before { 62 | background: rgba(0, 0, 0, 0.75); 63 | border-radius: 4px; 64 | color: #fff; 65 | content: attr(data-title); 66 | font-size: 14px; 67 | padding: 6px 10px; 68 | white-space: nowrap; 69 | 70 | ${props => 71 | props.position === 'top' && 72 | css` 73 | bottom: 100%; 74 | left: 50%; 75 | margin-bottom: 11px; 76 | `} ${props => 77 | props.position === 'bottom' && 78 | css` 79 | top: 100%; 80 | left: 50%; 81 | margin-top: 11px; 82 | `} ${props => 83 | props.position === 'left' && 84 | css` 85 | top: 50%; 86 | right: 100%; 87 | margin-right: 11px; 88 | `} ${props => 89 | props.position === 'right' && 90 | css` 91 | top: 50%; 92 | left: 100%; 93 | margin-left: 11px; 94 | `} ${props => 95 | props.width && 96 | css` 97 | width: ${props.width}; 98 | white-space: normal; 99 | `}; 100 | } 101 | 102 | &.fade:after, 103 | &.fade:before { 104 | transition: all 0.15s ease-in-out; 105 | 106 | ${props => 107 | props.position === 'top' && 108 | css` 109 | transform: translate3d(-50%, -10px, 0); 110 | `} ${props => 111 | props.position === 'bottom' && 112 | css` 113 | transform: translate3d(-50%, -10px, 0); 114 | `} ${props => 115 | props.position === 'left' && 116 | css` 117 | transform: translate3d(-10px, -50%, 0); 118 | `} ${props => 119 | props.position === 'right' && 120 | css` 121 | transform: translate3d(-10px, -50%, 0); 122 | `}; 123 | } 124 | 125 | &.fade:hover:after, 126 | &.fade:hover:before { 127 | opacity: 1; 128 | 129 | ${props => 130 | props.position === 'top' && 131 | css` 132 | transform: translate3d(-50%, 0, 0); 133 | `} ${props => 134 | props.position === 'bottom' && 135 | css` 136 | transform: translate3d(-50%, 0, 0); 137 | `} ${props => 138 | props.position === 'left' && 139 | css` 140 | transform: translate3d(0, -50%, 0); 141 | `} ${props => 142 | props.position === 'right' && 143 | css` 144 | transform: translate3d(0, -50%, 0); 145 | `}; 146 | } 147 | 148 | &.expand:before { 149 | transition: all 0.2s ease-in-out; 150 | 151 | ${props => 152 | props.position === 'top' && 153 | css` 154 | transform: translate3d(-50%, 0, 0) scale3d(0.2, 0.2, 1); 155 | `} ${props => 156 | props.position === 'bottom' && 157 | css` 158 | transform: translate3d(-50%, 0, 0) scale3d(0.2, 0.2, 1); 159 | `} ${props => 160 | props.position === 'left' && 161 | css` 162 | transform: translate3d(0, -50%, 0) scale3d(0.2, 0.2, 1); 163 | `} ${props => 164 | props.position === 'right' && 165 | css` 166 | transform: translate3d(0, -50%, 0) scale3d(0.2, 0.2, 1); 167 | `}; 168 | } 169 | 170 | &.expand:after { 171 | transition: all 0.1s ease-in-out; 172 | 173 | ${props => 174 | props.position === 'top' && 175 | css` 176 | transform: translate3d(-50%, 0, 0); 177 | `} ${props => 178 | props.position === 'bottom' && 179 | css` 180 | transform: translate3d(-50%, 0, 0); 181 | `} ${props => 182 | props.position === 'left' && 183 | css` 184 | transform: translate3d(0, -50%, 0); 185 | `} ${props => 186 | props.position === 'right' && 187 | css` 188 | transform: translate3d(0, -50%, 0); 189 | `}; 190 | } 191 | 192 | &.expand:hover:before, 193 | &.expand:hover:after { 194 | opacity: 1; 195 | ${props => 196 | props.position === 'top' && 197 | css` 198 | transform: translate3d(-50%, 0, 0) scale3d(1, 1, 1); 199 | `} ${props => 200 | props.position === 'bottom' && 201 | css` 202 | transform: translate3d(-50%, 0, 0) scale3d(1, 1, 1); 203 | `} ${props => 204 | props.position === 'left' && 205 | css` 206 | transform: translate3d(0, -50%, 0) scale3d(1, 1, 1); 207 | `} ${props => 208 | props.position === 'right' && 209 | css` 210 | transform: translate3d(0, -50%, 0) scale3d(1, 1, 1); 211 | `}; 212 | } 213 | 214 | &.expand:hover:after { 215 | transition: all 0.2s 0.1s ease-in-out; 216 | } 217 | 218 | ${props => 219 | props.style && 220 | css` 221 | ${props.style}; 222 | `}; 223 | `; 224 | 225 | StyledTooltip.defaultProps = { 226 | theme: defaultTheme, 227 | }; 228 | 229 | export { StyledTooltip }; 230 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export ThemeProvider from './ThemeProvider'; 2 | export AppBar from './AppBar'; 3 | export Button from './Button'; 4 | export Link from './Link'; 5 | export Notify from './Notify'; 6 | export Image from './Image'; 7 | export Icon from './Icon'; 8 | export Animation from './Animations'; 9 | export Animate from './Animate'; 10 | export Card from './Card'; 11 | export CardHeader from './Card/CardHeader'; 12 | export CardBody from './Card/CardBody'; 13 | export CardFooter from './Card/CardFooter'; 14 | export CardImage from './Card/CardImage'; 15 | export List from './List'; 16 | export ListHeader from './List/ListHeader'; 17 | export ListSection from './List/ListSection'; 18 | export ListItem from './List/ListItem'; 19 | export ListFooter from './List/ListFooter'; 20 | export Grid from './Layout/Grid'; 21 | export Cell from './Layout/Cell'; 22 | export TextField from './Form/TextField'; 23 | export Radio from './Form/Radio'; 24 | export RadioGroup from './Form/RadioGroup'; 25 | export Checkbox from './Form/Checkbox'; 26 | export Modal from './Modal'; 27 | 28 | -------------------------------------------------------------------------------- /src/keyframes.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | const buttonEffect = keyframes` 4 | to { 5 | opacity: 0; 6 | top: -6px; 7 | left: -6px; 8 | bottom: -6px; 9 | right: -6px; 10 | border-width: 6px; 11 | } 12 | `; 13 | 14 | const loading = keyframes` 15 | 0% { 16 | transform: rotate(0deg); 17 | } 18 | 100% { 19 | transform: rotate(360deg); 20 | } 21 | `; 22 | 23 | export default { 24 | buttonEffect, 25 | loading 26 | }; 27 | -------------------------------------------------------------------------------- /src/test-helpers.js: -------------------------------------------------------------------------------- 1 | import React, { options as preactOptions, render, h } from 'preact'; 2 | 3 | let shouldRenderShallow; 4 | let previousVNodeHook; 5 | 6 | // wrap the Preact.render() to make it render shallow with a vnode hook 7 | // adapted from https://gist.github.com/robertknight/88e9d10cff9269c55d453e5fb8364f47 8 | export function createShallowRenderer({ prefix = 'h-', context = {} } = {}) { 9 | shouldRenderShallow = 2; 10 | const vnodeHook = node => { 11 | if (previousVNodeHook) { 12 | previousVNodeHook(node); 13 | } 14 | if (typeof node.nodeName === 'string') { 15 | return; 16 | } 17 | if (shouldRenderShallow <= 0) { 18 | node.nodeName = prefix + node.nodeName.name; 19 | } 20 | shouldRenderShallow--; 21 | }; 22 | preactOptions.vnode = vnodeHook; 23 | return (vnode, parent, merge) => { 24 | const vnodeWithContext = h(ContextWrapper, { context }, vnode); 25 | render(vnodeWithContext, parent, merge); 26 | preactOptions.vnode = previousVNodeHook; 27 | }; 28 | } 29 | 30 | // handy function to dispatch events and return a promise which is resolved after next tick 31 | export function simulate(node, event) { 32 | // pass a selector or a dom node 33 | if (typeof node === 'string') { 34 | node = document.querySelector(node); 35 | } 36 | 37 | // pass an event object or an event name 38 | event = event instanceof window.Event ? event : new window.Event(event); 39 | node.dispatchEvent(event); 40 | // return a promise that resolves on next tick so the invoking code see the update 41 | return new Promise(resolve => { 42 | process.nextTick(() => { 43 | resolve(); 44 | }); 45 | }); 46 | } 47 | 48 | // wraps the node under test so a test context can be injected 49 | function ContextWrapper({ context, children }) { 50 | this.context = Object.assign(this.context, context); 51 | return
{children}
; 52 | } 53 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // Theme 2 | function shadeColor (color, percent) { 3 | 4 | let R = parseInt(color.substring(1,3),16); 5 | let G = parseInt(color.substring(3,5),16); 6 | let B = parseInt(color.substring(5,7),16); 7 | 8 | R = parseInt(R * (100 + percent) / 100, 10); 9 | G = parseInt(G * (100 + percent) / 100, 10); 10 | B = parseInt(B * (100 + percent) / 100, 10); 11 | 12 | R = (R<255)?R:255; 13 | G = (G<255)?G:255; 14 | B = (B<255)?B:255; 15 | 16 | const RR = ((R.toString(16).length === 1)?'0'+R.toString(16):R.toString(16)); 17 | const GG = ((G.toString(16).length === 1)?'0'+G.toString(16):G.toString(16)); 18 | const BB = ((B.toString(16).length === 1)?'0'+B.toString(16):B.toString(16)); 19 | 20 | return '#'+RR+GG+BB; 21 | }; 22 | 23 | const primaryColor = '#5A33A7'; 24 | const secondaryColor = '#FF3776'; 25 | const linkColor = primaryColor; 26 | const darkColor = '#454d5d'; 27 | const lightColor = '#ffffff'; 28 | const grayColor = "#acb3c2"; 29 | const borderColor = '#e7e9ed'; 30 | const bgColor = shadeColor(darkColor, -66); 31 | const controlSuccessColor = '#32b643'; 32 | const controlWarningColor = '#ffb700'; 33 | const controlErrorColor = '#e85600'; 34 | const codeColor = '#e06870'; 35 | const highlightColor = '#ffe9b3'; 36 | const notifyBgColor = '#ececec'; 37 | const listActiveColor = '#f0f3f5'; 38 | 39 | export default { 40 | primaryColor, 41 | primaryColorDark: shadeColor(primaryColor, 3), 42 | primaryColorLight: shadeColor(primaryColor, -3), 43 | secondaryColor, 44 | secondaryColorDark: shadeColor(secondaryColor, 3), 45 | secondaryColorLight: shadeColor(secondaryColor, -3), 46 | linkColor: primaryColor, 47 | linkColorDark: shadeColor(linkColor, 10), 48 | darkColor, 49 | lightColor, 50 | grayColor, 51 | grayColorLight: shadeColor(grayColor, 20), 52 | grayColorDark: shadeColor(grayColor, -50), 53 | borderColor, 54 | borderColorDark: shadeColor(borderColor, 15), 55 | bgColor: shadeColor(darkColor, -66), 56 | bgColorDark: shadeColor(bgColor, 3), 57 | bgColorLight: lightColor, 58 | controlSuccessColor, 59 | controlWarningColor, 60 | controlErrorColor, 61 | codeColor, 62 | highlightColor, 63 | notifyBgColor, 64 | listActiveColor 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*global __dirname*/ 3 | 4 | var path = require('path'); 5 | 6 | module.exports = { 7 | title: '(P)react Fluid', 8 | sections: [ 9 | { 10 | name: 'Introduction', 11 | content: './docs/Introduction.md', 12 | }, 13 | { 14 | name: 'Components', 15 | content: './docs/Components.md', 16 | components: function() { 17 | return [ 18 | './src/Button/Button.js', 19 | './src/Link/Link.js', 20 | './src/Icon/Icon.js', 21 | './src/Image/Image.js', 22 | './src/AppBar/AppBar.js', 23 | './src/Card/Card.js', 24 | './src/List/List.js', 25 | './src/Modal/Modal.js', 26 | './src/Notify/Notify.js', 27 | './src/Tooltip/Tooltip.js', 28 | './src/Dropdown/Dropdown.js', 29 | ]; 30 | }, 31 | }, 32 | { 33 | name: 'Form', 34 | content: './docs/Form.md', 35 | components: function() { 36 | return [ 37 | './src/Form/TextField/TextField.js', 38 | './src/Form/Radio/Radio.js', 39 | './src/Form/RadioGroup/RadioGroup.js', 40 | './src/Form/Checkbox/Checkbox.js', 41 | ]; 42 | }, 43 | }, 44 | { 45 | name: 'Layout', 46 | content: './docs/Layout.md', 47 | components: function() { 48 | return ['./src/Layout/Grid.js']; 49 | }, 50 | }, 51 | { 52 | name: 'Animations', 53 | content: './docs/Animations.md', 54 | components: function() { 55 | return ['./src/Animate/Animate.js']; 56 | }, 57 | }, 58 | { 59 | name: 'Theming', 60 | content: './docs/Theming.md', 61 | }, 62 | ], 63 | theme: { 64 | baseBackground: '#fdfdfc', 65 | link: '#454d5d', 66 | linkHover: '#90a7bf', 67 | border: '#e0d2de', 68 | font: ['Helvetica', 'sans-serif'], 69 | }, 70 | styles: { 71 | Playground: { 72 | preview: { 73 | padding: '20px', 74 | borderWidth: [[0, 0, 1, 0]], 75 | borderRadius: 0, 76 | background: '#f7f7f7', 77 | }, 78 | }, 79 | Markdown: { 80 | pre: { 81 | border: 0, 82 | background: 'none', 83 | }, 84 | code: { 85 | fontSize: 14, 86 | }, 87 | }, 88 | StyleGuide: { 89 | sidebar: { 90 | backgroundColor: '#fff', 91 | boxShadow: 92 | 'rgba(0, 0, 0, 0.16) 0px 3px 10px, rgba(0, 0, 0, 0.23) 0px 3px 10px', 93 | width: '250px', 94 | }, 95 | }, 96 | ComponentsList: { 97 | item: { 98 | color: '#454d5d', 99 | padding: '5px 2px', 100 | }, 101 | }, 102 | Link: { 103 | link: { 104 | '&, &:link, &:visited': { 105 | color: '#000', 106 | }, 107 | }, 108 | }, 109 | Logo: { 110 | logo: { 111 | color: '#653ab0', 112 | fontSize: '22px', 113 | textAlign: 'center', 114 | }, 115 | }, 116 | SectionHeading: { 117 | heading: { 118 | fontSize: '24px', 119 | fontWeight: 'normal', 120 | paddingBottom: '15px', 121 | marginBottom: '10px', 122 | width: '100%', 123 | '&:hover, &:active': { 124 | textDecoration: 'none', 125 | }, 126 | }, 127 | isPrimary: { 128 | fontSize: '30px', 129 | borderBottom: '1px solid #eee', 130 | }, 131 | }, 132 | }, 133 | template: './docs/template.html', 134 | showUsage: true, 135 | serverPort: 3013, 136 | require: [path.resolve(__dirname, 'styleguide/setup.js')], 137 | assetsDir: './assets', 138 | webpackConfig: function(env) { 139 | var dir = path.join(__dirname, 'src'); 140 | 141 | return { 142 | module: { 143 | loaders: [ 144 | { 145 | test: /\.js?$/, 146 | include: dir, 147 | loader: 'babel-loader', 148 | }, 149 | ], 150 | }, 151 | resolve: { 152 | extensions: ['.jsx', '.js', '.json', '.less'], 153 | modules: ['node_modules'], 154 | alias: { 155 | react: 'preact-compat', 156 | 'react-dom': 'preact-compat', 157 | // Not necessary unless you consume a module using `createClass` 158 | 'create-react-class': 159 | 'preact-compat/lib/create-react-class', 160 | }, 161 | }, 162 | }; 163 | }, 164 | }; 165 | -------------------------------------------------------------------------------- /styleguide/setup.js: -------------------------------------------------------------------------------- 1 | // styleguide/setup.js 2 | import CardHeader from './../src/Card/CardHeader'; 3 | import CardImage from './../src/Card/CardImage'; 4 | import CardBody from './../src/Card/CardBody'; 5 | import CardFooter from './../src/Card/CardFooter'; 6 | import ListHeader from './../src/List/ListHeader'; 7 | import ListSection from './../src/List/ListSection'; 8 | import ListItem from './../src/List/ListItem'; 9 | import ListFooter from './../src/List/ListFooter'; 10 | import Cell from './../src/Layout/Cell'; 11 | import ThemeProvider from './../src/ThemeProvider/ThemeProvider'; 12 | import lodash from 'lodash'; 13 | 14 | global.lodash = lodash; 15 | global.CardHeader = CardHeader; 16 | global.CardImage = CardImage; 17 | global.CardBody = CardBody; 18 | global.CardFooter = CardFooter; 19 | global.ListHeader = ListHeader; 20 | global.ListSection = ListSection; 21 | global.ListItem = ListItem; 22 | global.ListFooter = ListFooter; 23 | global.Cell = Cell; 24 | global.ThemeProvider = ThemeProvider; -------------------------------------------------------------------------------- /templates/component/component.txt: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'preact'; 2 | import PropTypes from 'prop-types'; 3 | import { Styled{{ properCase name }} } from './styles'; 4 | 5 | /** 6 | * Description 7 | * 8 | * @example ./../../docs/components/{{ properCase name }}.md 9 | */ 10 | class {{ properCase name }} extends Component { 11 | static propTypes = { 12 | /** 13 | * Custom styles 14 | */ 15 | style: PropTypes.object 16 | 17 | }; 18 | 19 | static defaultProps = { 20 | }; 21 | 22 | static contextTypes = { 23 | theme: PropTypes.object 24 | }; 25 | 26 | render() { 27 | const { style = {}, className, children } = this.props; 28 | const { theme } = this.context; 29 | 30 | return ( 31 | 36 | {children} 37 | 38 | ); 39 | } 40 | } 41 | 42 | export default {{ properCase name }}; -------------------------------------------------------------------------------- /templates/component/document.txt: -------------------------------------------------------------------------------- 1 | Examples: 2 | 3 | ```js 4 | <{{ properCase name }}>Newly generated component {{ name }} 5 | ``` -------------------------------------------------------------------------------- /templates/component/index.txt: -------------------------------------------------------------------------------- 1 | export default from './{{ properCase name }}'; -------------------------------------------------------------------------------- /templates/component/styles.txt: -------------------------------------------------------------------------------- 1 | import styled, {css} from 'styled-components'; 2 | import defaultTheme from '../theme'; 3 | 4 | const Styled{{ properCase name }} = styled.div` 5 | 6 | ${props => props.style && css` 7 | ${props.style} 8 | `} 9 | `; 10 | 11 | Styled{{ properCase name }}.defaultProps = { 12 | theme: defaultTheme 13 | }; 14 | 15 | export { 16 | Styled{{ properCase name }} 17 | }; --------------------------------------------------------------------------------