├── .babelrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .storybook ├── addons.js ├── config.js └── webpack.config.js ├── CONTRIBUTING.md ├── README.md ├── head.html ├── login.sh ├── package-lock.json ├── package.json ├── path ├── setupJest.js ├── src ├── Button │ ├── Button.test.js │ ├── button.stories.js │ └── index.js ├── Checkbox │ ├── index.js │ └── index.stories.js ├── Drawer │ ├── index.js │ └── index.stories.js ├── Icon │ ├── index.js │ ├── index.stories.js │ └── types.js ├── Input │ ├── index.js │ └── index.stories.js ├── Jumbotron │ ├── index.js │ └── index.stories.js ├── Loader │ ├── index.js │ └── index.stories.js ├── Menu │ ├── MenuItem.js │ ├── index.js │ └── index.stories.js ├── Navbar │ ├── NavItem.js │ ├── index.js │ └── index.stories.js ├── Popover │ ├── index.js │ └── index.stories.js ├── Radio │ ├── index.js │ └── index.stories.js ├── Sites │ ├── index.js │ └── index.stories.js ├── SurahTitle │ ├── Surahs │ │ ├── 1.js │ │ ├── 10.js │ │ ├── 100.js │ │ ├── 101.js │ │ ├── 102.js │ │ ├── 103.js │ │ ├── 104.js │ │ ├── 105.js │ │ ├── 106.js │ │ ├── 107.js │ │ ├── 108.js │ │ ├── 109.js │ │ ├── 11.js │ │ ├── 110.js │ │ ├── 111.js │ │ ├── 112.js │ │ ├── 113.js │ │ ├── 114.js │ │ ├── 12.js │ │ ├── 13.js │ │ ├── 14.js │ │ ├── 15.js │ │ ├── 16.js │ │ ├── 17.js │ │ ├── 18.js │ │ ├── 19.js │ │ ├── 2.js │ │ ├── 20.js │ │ ├── 21.js │ │ ├── 22.js │ │ ├── 23.js │ │ ├── 24.js │ │ ├── 25.js │ │ ├── 26.js │ │ ├── 27.js │ │ ├── 28.js │ │ ├── 29.js │ │ ├── 3.js │ │ ├── 30.js │ │ ├── 31.js │ │ ├── 32.js │ │ ├── 33.js │ │ ├── 34.js │ │ ├── 35.js │ │ ├── 36.js │ │ ├── 37.js │ │ ├── 38.js │ │ ├── 39.js │ │ ├── 4.js │ │ ├── 40.js │ │ ├── 41.js │ │ ├── 42.js │ │ ├── 43.js │ │ ├── 44.js │ │ ├── 45.js │ │ ├── 46.js │ │ ├── 47.js │ │ ├── 48.js │ │ ├── 49.js │ │ ├── 5.js │ │ ├── 50.js │ │ ├── 51.js │ │ ├── 52.js │ │ ├── 53.js │ │ ├── 54.js │ │ ├── 55.js │ │ ├── 56.js │ │ ├── 57.js │ │ ├── 58.js │ │ ├── 59.js │ │ ├── 6.js │ │ ├── 60.js │ │ ├── 61.js │ │ ├── 62.js │ │ ├── 63.js │ │ ├── 64.js │ │ ├── 65.js │ │ ├── 66.js │ │ ├── 67.js │ │ ├── 68.js │ │ ├── 69.js │ │ ├── 7.js │ │ ├── 70.js │ │ ├── 71.js │ │ ├── 72.js │ │ ├── 73.js │ │ ├── 74.js │ │ ├── 75.js │ │ ├── 76.js │ │ ├── 77.js │ │ ├── 78.js │ │ ├── 79.js │ │ ├── 8.js │ │ ├── 80.js │ │ ├── 81.js │ │ ├── 82.js │ │ ├── 83.js │ │ ├── 84.js │ │ ├── 85.js │ │ ├── 86.js │ │ ├── 87.js │ │ ├── 88.js │ │ ├── 89.js │ │ ├── 9.js │ │ ├── 90.js │ │ ├── 91.js │ │ ├── 92.js │ │ ├── 93.js │ │ ├── 94.js │ │ ├── 95.js │ │ ├── 96.js │ │ ├── 97.js │ │ ├── 98.js │ │ └── 99.js │ ├── index.js │ └── index.stories.js ├── Tabs │ ├── Tab.js │ ├── index.js │ └── index.stories.js ├── Toggle │ ├── index.js │ └── index.stories.js └── images │ └── background.jpg └── utils ├── generateModulesList.js ├── surahTitleSvg ├── 001.svg ├── 002.svg ├── 003.svg ├── 004.svg ├── 005.svg ├── 006.svg ├── 007.svg ├── 008.svg ├── 009.svg ├── 010.svg ├── 011.svg ├── 012.svg ├── 013.svg ├── 014.svg ├── 015.svg ├── 016.svg ├── 017.svg ├── 018.svg ├── 019.svg ├── 020.svg ├── 021.svg ├── 022.svg ├── 023.svg ├── 024.svg ├── 025.svg ├── 026.svg ├── 027.svg ├── 028.svg ├── 029.svg ├── 030.svg ├── 031.svg ├── 032.svg ├── 033.svg ├── 034.svg ├── 035.svg ├── 036.svg ├── 037.svg ├── 038.svg ├── 039.svg ├── 040.svg ├── 041.svg ├── 042.svg ├── 043.svg ├── 044.svg ├── 045.svg ├── 046.svg ├── 047.svg ├── 048.svg ├── 049.svg ├── 050.svg ├── 051.svg ├── 052.svg ├── 053.svg ├── 054.svg ├── 055.svg ├── 056.svg ├── 057.svg ├── 058.svg ├── 059.svg ├── 060.svg ├── 061.svg ├── 062.svg ├── 063.svg ├── 064.svg ├── 065.svg ├── 066.svg ├── 067.svg ├── 068.svg ├── 069.svg ├── 070.svg ├── 071.svg ├── 072.svg ├── 073.svg ├── 074.svg ├── 075.svg ├── 076.svg ├── 077.svg ├── 078.svg ├── 079.svg ├── 080.svg ├── 081.svg ├── 082.svg ├── 083.svg ├── 084.svg ├── 085.svg ├── 086.svg ├── 087.svg ├── 088.svg ├── 089.svg ├── 090.svg ├── 091.svg ├── 092.svg ├── 093.svg ├── 094.svg ├── 095.svg ├── 096.svg ├── 097.svg ├── 098.svg ├── 099.svg ├── 100.svg ├── 101.svg ├── 102.svg ├── 103.svg ├── 104.svg ├── 105.svg ├── 106.svg ├── 107.svg ├── 108.svg ├── 109.svg ├── 110.svg ├── 111.svg ├── 112.svg ├── 113.svg └── 114.svg └── test_helper.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "es2015", "stage-2", "react"], 3 | "plugins": ["transform-react-require"] 4 | } 5 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8.4.0 6 | steps: 7 | - checkout 8 | - run: 9 | name: install 10 | command: npm install 11 | - run: 12 | name: test & lint 13 | command: npm test && npm run lint 14 | deploy: 15 | docker: 16 | - image: circleci/node:8.4.0 17 | steps: 18 | - checkout 19 | - run: 20 | name: install 21 | command: npm install --no-save 22 | - run: 23 | name: login to npm 24 | command: | 25 | echo "$NPM_USERNAME\n$NPM_EMAIL" 26 | sh login.sh 27 | - run: 28 | name: setup git globals 29 | command: | 30 | git config --global push.default matching 31 | git config --global user.email "noreply@quran.com" 32 | git config --global user.name "circleci-qurandev" 33 | - run: 34 | name: patch update and commit package.json 35 | command: | 36 | echo "doing a patch update!" 37 | npm version patch 38 | git add . 39 | git commit --amend -m 'automated patching [ci skip]' 40 | git push 41 | - run: 42 | name: deploy to npm and gh 43 | command: | 44 | npm run deploy:npm 45 | npm run deploy:gp 46 | workflows: 47 | version: 2 48 | test_and_build: 49 | jobs: 50 | - build 51 | - deploy: 52 | filters: 53 | branches: 54 | only: master 55 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | ./build/** 3 | build/ 4 | node_modules 5 | test_helper.js 6 | webpack.config.js 7 | lib/** 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "comma-dangle": 0, 6 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 7 | "import/no-extraneous-dependencies": 0, 8 | "import/extensions": 0, 9 | "import/no-unresolved": 0, 10 | "strict": 0, 11 | "import/first": 0, 12 | " no-confusing-arrow": 0, 13 | "react/react-in-jsx-scope": 0, 14 | "no-multi-assign": 0 15 | }, 16 | "ecmaFeatures": { 17 | "classes": true, 18 | "jsx": true 19 | }, 20 | "plugins": ["react", "jest"], 21 | "settings": { 22 | "import/resolver": "webpack" 23 | }, 24 | "parserOptions": { 25 | "ecmaFeatures": { 26 | "experimentalObjectRestSpread": true 27 | } 28 | }, 29 | "globals": { 30 | "__DEVELOPMENT__": true, 31 | "__CLIENT__": true, 32 | "__SERVER__": true, 33 | "__DISABLE_SSR__": true, 34 | "__DEVTOOLS__": true, 35 | "socket": true, 36 | "webpackIsomorphicTools": true, 37 | "ga": true, 38 | "Raven": true, 39 | "mixpanel": true, 40 | "expect": true, 41 | "document": true, 42 | "window": true, 43 | "browser": true, 44 | "FB": true, 45 | "sinon": true, 46 | "jest": true 47 | }, 48 | "env": { 49 | "mocha": true, 50 | "amd": true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .tags* 3 | *.log 4 | tmp 5 | .idea 6 | coverage 7 | build 8 | build/** 9 | .nyc_output 10 | *.iml 11 | tests_output 12 | node_modules/ 13 | .vscode 14 | lib 15 | lib/* 16 | storybook-static 17 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ./build/main/* 2 | dist/main/* 3 | src/** 4 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | import '@storybook/addon-options/register'; 4 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, setAddon } from '@storybook/react'; 2 | import { setOptions } from '@storybook/addon-options'; 3 | 4 | setOptions({ 5 | name: 'Common Components', 6 | url: 'https://github.com/quran/common-components', 7 | goFullScreen: false, 8 | showLeftPanel: true, 9 | showDownPanel: true, 10 | showSearchBox: false, 11 | downPanelInRight: false, 12 | sortStoriesByKind: true, 13 | }); 14 | 15 | const req = require.context('../src', true, /.stories.js$/); 16 | 17 | function loadStories() { 18 | req.keys().forEach(filename => req(filename)); 19 | } 20 | 21 | configure(loadStories, module); 22 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | // you can use this file to add your custom webpack plugins, loaders and anything you like. 2 | // This is just the basic way to add addional webpack configurations. 3 | // For more information refer the docs: https://getstorybook.io/docs/configurations/custom-webpack-config 4 | 5 | // IMPORTANT 6 | // When you add this file, we won't add the default configurations which is similar 7 | // to "React Create App". This only has babel loader to load JavaScript. 8 | 9 | module.exports = { 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.js$/, 14 | use: 'eslint-loader', 15 | exclude: /node_modules/ 16 | }, 17 | { 18 | test: /\.jpg$/, 19 | use: 'url-loader?limit=8192&name=[name].[ext]' 20 | }, 21 | { 22 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 23 | use: 'url?name=fonts/[name].[ext]&limit=10000&mimetype=application/font-woff' 24 | }, 25 | { 26 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 27 | use: 'url?name=fonts/[name].[ext]&limit=10000&mimetype=application/font-woff' 28 | }, 29 | { 30 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 31 | use: 'url?name=fonts/[name].[ext]&limit=10000&mimetype=application/octet-stream' 32 | }, 33 | { 34 | test: /\.otf(\?v=\d+\.\d+\.\d+)?$/, 35 | use: 'url?name=fonts/[name].[ext]&limit=10000&mimetype=application/octet-stream' 36 | }, 37 | { 38 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 39 | use: 'file?name=fonts/[name].[ext]' 40 | }, 41 | { 42 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 43 | use: 'url?name=images/[name].[ext]&limit=10000&mimetype=image/svg+xml' 44 | } 45 | ] 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You want to help? You rock! Now, take a moment to be sure your contributions make sense to everyone else. 4 | 5 | ## Reporting Issues 6 | 7 | Found a problem? Want a new feature? 8 | 9 | - See if your issue or idea has [already been reported]. 10 | 11 | 12 | ## Submitting Pull Requests 13 | 14 | Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. 15 | 16 | 1. To begin, [fork this project], clone your fork, and add our [upstream]. 17 | 18 | ```bash 19 | # Clone your fork of the repository into the current directory 20 | git clone https://github.com//common-components 21 | # Navigate to the newly cloned directory 22 | cd common-components 23 | #Assign the forked repository to a remote call "origin" 24 | git remote add origin git://github.com//common-components.git 25 | # Assign the original repository to a remote called "upstream" 26 | git remote add upstream https://github.com/quran/common-components 27 | # Install the tools necessary for development 28 | yarn install 29 | # or via NPM 30 | npm install 31 | ``` 32 | 2. Pull latest changes from "upstream" and push these changes to your "origin"(forked) 33 | > NOTE - This step will repeat every time **you plan to contribute** 34 | 35 | ```bash 36 | # Pull latest changes from "upstream" repository 37 | git pull upstream master 38 | # Push latest changes to Your "origin" repository 39 | git push origin master 40 | ``` 41 | 3. Create a branch for your feature or fix: 42 | 43 | ```bash 44 | # Move into a new branch for a feature 45 | git checkout -b feature/thing 46 | ``` 47 | ```bash 48 | # Move into a new branch for a fix 49 | git checkout -b fix/something 50 | ``` 51 | 52 | 4. Be sure your code follows our practices. 53 | 54 | ```bash 55 | # Test current code 56 | npm run test 57 | # Ensure you meet our linting rules 58 | npm run lint 59 | ``` 60 | 61 | Our `npm run test:watch` tests and _watch_. 62 | You can run `npm run lint:fix` which will fix all eslint errors. 63 | 64 | 5. Running our demo page. 65 | 66 | We use the awesome [react-story](https://github.com/storybooks/react-storybook)! 67 | 68 | ```bash 69 | npm run start 70 | ``` 71 | 72 | 6. To create a PR you need to push your branch to the origin(forked) remote and then press some buttons on GitHub: 73 | 74 | ```bash 75 | # Push a feature branch 76 | git push -u origin feature/thing 77 | ``` 78 | ```bash 79 | # Push a fix branch 80 | git push -u origin fix/something 81 | ``` 82 | 83 | This will create the branch on your GitHub project. The ```-u``` flag links this branch with the remote one, so that in the future, you can simply type ```git push origin```. 84 | 85 | 7. Now [open a pull request] with a clear title and description. 86 | 87 | 88 | [upstream]: https://help.github.com/articles/syncing-a-fork/ 89 | [already been reported]: https://github.com/quran/quran.com-frontend/issues 90 | [fork this project]: https://github.com/quran/quran.com-frontend/fork 91 | [open a pull request]: https://help.github.com/articles/using-pull-requests/ 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Quran Components](https://quran.github.io/common-components/#/) [![CircleCI](https://circleci.com/gh/quran/common-components.svg?style=svg)](https://circleci.com/gh/quran/common-components) [![npm version](https://badge.fury.io/js/quran-components.svg)](https://www.npmjs.com/package/quran-components) [![NPM](https://img.shields.io/npm/dm/quran-components.svg?style=flat-square)](https://www.npmjs.com/package/quran-components) [![NPM](http://i.imgur.com/Lk5HsBo.png)](https://quranslack.herokuapp.com) 2 | 3 | > Quran Common Components are a set of React UI components. The aim is to promote a consistent and reusable component library. 4 | 5 | ## Installation 6 | 7 | #### NPM 8 | 9 | > `npm install --save quran-components` 10 | 11 | #### YARN 12 | 13 | > `yarn add quran-components` 14 | 15 | ## Usage 16 | 17 | #### ES6 18 | 19 | ```js 20 | import React from 'React'; 21 | import { Button, Jumbotron } from 'quran-components'; 22 | 23 | const CustomBody = () => ( 24 |
25 |
28 | ); 29 | ``` 30 | 31 | Also to reduce file size, you can require individual components: 32 | 33 | ```js 34 | import React from 'React'; 35 | import Button from 'quran-components/lib/Button'; 36 | 37 | const MyButton = () => ( 38 |
39 |
41 | ); 42 | ``` 43 | 44 | #### ES5 45 | 46 | ```js 47 | var React = require('react'); 48 | var component = require('quran-components'); 49 | var Button = component.Button; 50 | 51 | var MyButton = React.createClass({ 52 | render: function() { 53 | return ( 54 |
55 | ); 8 | 9 | component.simulate('click'); 10 | 11 | expect(clickSpy).toBeCalled(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/Button/button.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { storiesOf } from '@storybook/react'; 4 | import { action } from '@storybook/addon-actions'; 5 | import Button from './index'; 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Button', module) 9 | .add( 10 | 'simple info', 11 | withInfo('Basic usage of the button')(() => ( 12 | 13 | )), 14 | ) 15 | .add( 16 | 'with some emoji', 17 | withInfo('Basic usage of the button')(() => ( 18 | 19 | )), 20 | ) 21 | .add( 22 | 'square', 23 | withInfo('Basic usage of the button')(() => ( 24 | 27 | )), 28 | ) 29 | .add('disabled', withInfo('Basic usage of the button')(() => )) 30 | .add( 31 | 'Inverted', 32 | withInfo('Basic usage of the button')(() => ), 33 | ) 34 | .add( 35 | 'With href', 36 | withInfo('Basic usage of the button')(() => ( 37 | 40 | )), 41 | ) 42 | .add( 43 | 'With href (disabled)', 44 | withInfo('Basic usage of the button')(() => ( 45 | 48 | )), 49 | ); 50 | -------------------------------------------------------------------------------- /src/Button/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled, { css } from 'styled-components'; 4 | 5 | const inverted = css` 6 | background: white; 7 | border-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 8 | color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 9 | &:hover { 10 | color: white; 11 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 12 | border-color: white; 13 | } 14 | `; 15 | 16 | const disabled = css` 17 | background-color: #ccc; 18 | color: #6a6a6a; 19 | cursor: not-allowed; 20 | `; 21 | 22 | const StyledBase = styled.button` 23 | display: inline-block; 24 | outline: 0; 25 | border: 0; 26 | background: none; 27 | cursor: pointer; 28 | width: 220px; 29 | height: 40px; 30 | border-radius: ${props => (props.square ? 0 : '50px')}; 31 | border: 2px solid white; 32 | color: white; 33 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 34 | font-size: 14px; 35 | font-style: normal; 36 | font-stretch: normal; 37 | text-align: center; 38 | vertical-align: middle; 39 | touch-action: manipulation; 40 | 41 | &:hover { 42 | text-decoration: none; 43 | color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 44 | border-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 45 | background-color: white; 46 | } 47 | 48 | &:disabled { 49 | ${disabled} 50 | } 51 | 52 | ${props => (props.color === 'inverted' ? inverted : '')} 53 | ${props => (props.disabled ? disabled : '')} 54 | `; 55 | 56 | const StyledLink = StyledBase.withComponent('a').extend` 57 | text-align: center; 58 | line-height: 40px; 59 | text-decoration: none; 60 | `; 61 | 62 | function Button({ className, color, children, href, ...props }) { 63 | if (href) { 64 | const attributes = props.disabled && { 65 | onClick: (event) => { 66 | event.preventDefault(); 67 | } 68 | }; 69 | 70 | return ( 71 | 78 | {children} 79 | 80 | ); 81 | } 82 | 83 | return ( 84 | 85 | {children} 86 | 87 | ); 88 | } 89 | 90 | Button.propTypes = { 91 | className: PropTypes.string, 92 | color: PropTypes.string, 93 | href: PropTypes.string, 94 | square: PropTypes.bool, 95 | disabled: PropTypes.bool, 96 | children: PropTypes.oneOfType([ 97 | PropTypes.arrayOf(PropTypes.element), 98 | PropTypes.arrayOf(PropTypes.node), 99 | PropTypes.node, 100 | PropTypes.string 101 | ]).isRequired 102 | }; 103 | 104 | Button.defaultProps = { 105 | className: '', 106 | color: '', 107 | disabled: false, 108 | href: null, 109 | square: false 110 | }; 111 | 112 | export default Button; 113 | -------------------------------------------------------------------------------- /src/Checkbox/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | 4 | const Label = styled.label` 5 | cursor: pointer; 6 | display: block; 7 | white-space: nowrap; 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | text-transform: capitalize; 11 | `; 12 | 13 | const Input = styled.input` 14 | -webkit-appearance: none; 15 | -moz-appearance: none; 16 | -ms-appearance: none; 17 | appearance: none; 18 | -webkit-appearance: none; 19 | background-color: #fafafa; 20 | border: 1px solid #cacece; 21 | padding: 9px; 22 | border-radius: 3px; 23 | display: inline-block; 24 | height: 10px; 25 | width: 10px; 26 | outline: none; 27 | margin: 0px; 28 | position: relative; 29 | margin-right: 12px; 30 | vertical-align: bottom; 31 | &:hover { 32 | cursor: pointer; 33 | border: 1px solid #adb8c0; 34 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 35 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px 1px 3px rgba(0, 0, 0, 0.1); 36 | } 37 | &:disabled { 38 | background-image: linear-gradient(to bottom, #d8d8d8, #d8d8d8); 39 | } 40 | &:focus { 41 | outline: none; 42 | border-color: rgba(0, 126, 255, 0.55); 43 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 1px rgba(0, 126, 255, 0.1); 44 | } 45 | &:checked { 46 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 47 | border: 1px solid #adb8c0; 48 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05), 49 | inset 15px 10px -12px rgba(255, 255, 255, 0.1); 50 | color: #fff; 51 | &:active { 52 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05), inset 0px 1px 2px rgba(0, 0, 0, 0.1); 53 | } 54 | &:focus { 55 | outline: none; 56 | border-color: rgba(0, 126, 255, 0.55); 57 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 1px rgba(0, 126, 255, 0.1); 58 | } 59 | &:after { 60 | content: '✔'; 61 | font-size: 12px; 62 | margin: 0.1em 0 0.1em 0; 63 | line-height: 1.5em; 64 | font-weight: 500; 65 | position: absolute; 66 | top: 0px; 67 | right: 4px; 68 | } 69 | } 70 | `; 71 | 72 | const Checkbox = ({ id, name, checked, className, handleChange, children, ...props }) => ( 73 | 85 | ); 86 | 87 | Checkbox.propTypes = { 88 | id: PropTypes.string.isRequired, 89 | className: PropTypes.string, 90 | name: PropTypes.string.isRequired, 91 | checked: PropTypes.bool.isRequired, 92 | handleChange: PropTypes.func.isRequired, 93 | children: PropTypes.element.isRequired, 94 | disabled: PropTypes.bool, 95 | }; 96 | 97 | Checkbox.defaultProps = { 98 | className: '', 99 | disabled: false, 100 | }; 101 | 102 | export default Checkbox; 103 | -------------------------------------------------------------------------------- /src/Checkbox/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Checkbox from './index'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Checkbox', module) 9 | .add('with text', withInfo('with text')(() => ( 10 | 11 | translation 12 | 13 | ))) 14 | .add('with lots of text', withInfo('with lots of text')(() => ( 15 |
16 | 17 | translationasdasdasdasdsadasdasdasdasd 18 | 19 |
20 | ))) 21 | .add('Disabled', withInfo('Disabled')(() => ( 22 |
23 | 30 | I am disabled 31 | 32 |
33 | ))); 34 | -------------------------------------------------------------------------------- /src/Drawer/index.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import styled, { css } from 'styled-components'; 6 | 7 | import Icon from '../Icon'; 8 | 9 | const width = 350; 10 | 11 | const rightSide = css` 12 | right: ${width * -1}px; 13 | left: initial; 14 | transition: right 0.35s cubic-bezier(0.24, 1, 0.32, 1), visibility 0.2s; 15 | 16 | ${props => (props.open ? 'left: initial; right: 0px;' : '')}; 17 | `; 18 | // eslint-disable-next-line no-confusing-arrow 19 | const Container = styled.div` 20 | position: fixed; 21 | left: ${width * -1}px; 22 | top: 0px; 23 | bottom: 0px; 24 | background: #fff; 25 | z-index: 1031; 26 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 27 | 0 8px 10px -5px rgba(0, 0, 0, 0.2); 28 | visibility: hidden; 29 | 30 | background: #fff; 31 | width: ${width}px; 32 | transition: left 0.35s cubic-bezier(0.24, 1, 0.32, 1), visibility 0.2s; 33 | overflow: auto; 34 | height: 100%; 35 | 36 | .navbar-text { 37 | margin-left: 0px; 38 | .backToHome { 39 | margin-right: 23px; 40 | font-size: 17px; 41 | } 42 | } 43 | 44 | ${props => (props.open ? 'left: 0px; visibility: visible;' : '')} ${props => 45 | (props.right ? rightSide : '')} @media(max-width: 768px) { 46 | width: ${width}px; 47 | left: ${width * -1}px; 48 | 49 | .navbar-text { 50 | padding-left: 15px; 51 | } 52 | } 53 | `; 54 | 55 | const Header = styled.div`height: 50px;`; 56 | 57 | const HeaderText = styled.div` 58 | border-bottom: 1px solid rgba(0, 0, 0, 0.12); 59 | padding-left: 10px; 60 | `; 61 | 62 | const CloseButton = styled(Icon)` 63 | position: absolute; 64 | top: 10px; 65 | right: 15px; 66 | padding: 5px; 67 | 68 | &:hover { 69 | cursor: pointer; 70 | } 71 | `; 72 | 73 | class Drawer extends Component { 74 | state = { 75 | open: this.props.open || false, 76 | }; 77 | 78 | componentDidMount() { 79 | document.body.addEventListener('click', this.onBodyClick.bind(this), true); 80 | } 81 | 82 | componentWillUnmount() { 83 | document.body.removeEventListener('click', this.onBodyClick.bind(this), true); 84 | } 85 | 86 | onBodyClick = (event) => { 87 | if (!this.props.drawerClickClose) { 88 | if (this.content && this.content.contains(event.target)) { 89 | return false; 90 | } 91 | } 92 | 93 | if (this.getOpen()) { 94 | return this.setOpen(false); 95 | } 96 | 97 | return false; 98 | }; 99 | 100 | onToggleClick = () => { 101 | this.setOpen(!this.getOpen()); 102 | }; 103 | 104 | onCloseClick = () => { 105 | this.setOpen(false); 106 | }; 107 | 108 | getOpen() { 109 | return this.props.handleOpen ? this.props.open : this.state.open; 110 | } 111 | 112 | // eslint-disable-next-line 113 | setOpen(open) { 114 | return this.props.handleOpen ? this.props.handleOpen(open) : this.setState({ open }); 115 | } 116 | 117 | renderToggle() { 118 | const { toggle } = this.props; 119 | 120 | if (toggle) { 121 | return React.cloneElement(toggle, { onClick: this.onToggleClick }); 122 | } 123 | 124 | return ; 125 | } 126 | 127 | renderHeader() { 128 | const { header } = this.props; 129 | 130 | return ( 131 |
132 | 133 | {header} 134 | 135 | 136 |
137 | ); 138 | } 139 | 140 | render() { 141 | const { children, right } = this.props; 142 | 143 | return ( 144 |
{ 146 | this.content = ref; 147 | }} 148 | > 149 | {this.renderToggle()} 150 | 151 | {this.renderHeader()} 152 | {children} 153 | 154 |
155 | ); 156 | } 157 | } 158 | 159 | Drawer.propTypes = { 160 | open: PropTypes.bool.isRequired, 161 | handleOpen: PropTypes.func, 162 | children: PropTypes.node.isRequired, 163 | right: PropTypes.bool, 164 | toggle: PropTypes.element, 165 | drawerClickClose: PropTypes.bool, 166 | header: PropTypes.element, 167 | }; 168 | 169 | Drawer.defaultProps = { 170 | handleOpen: null, 171 | right: false, 172 | drawerClickClose: true, 173 | toggle: null, 174 | header: null, 175 | }; 176 | 177 | export default Drawer; 178 | -------------------------------------------------------------------------------- /src/Drawer/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Drawer from './index'; 5 | import Menu, { MenuItem } from '../Menu'; 6 | 7 | import { withInfo } from '@storybook/addon-info'; 8 | 9 | storiesOf('Drawer', module) 10 | .add('default', withInfo('default')(() => ( 11 | 12 |

Drawer content

13 |
14 | ))) 15 | .add('with handleOpen function', withInfo('with handleOpen function')(() => ( 16 | 17 |

Drawer

18 |
19 | ))) 20 | .add('right side', withInfo('right side')(() => ( 21 | 22 |

Drawer content

23 |
24 | ))) 25 | .add('with menu', withInfo('with menu')(() => ( 26 | 27 | 28 | Item 1 29 | Item 2 30 | Item 3 31 | 32 | 33 | ))) 34 | .add('with toggle', withInfo('with toggle')(() => ( 35 | A link toggle}> 36 | 37 | Item 1 38 | Item 2 39 | Item 3 40 | 41 | 42 | ))) 43 | .add('do not close when content clicked', withInfo('do not close when content clicked')(() => ( 44 | 45 | 46 | Item 1 47 | Item 2 48 | Item 3 49 | 50 | 51 | ))) 52 | .add('With header', withInfo('With header')(() => ( 53 | A header

}> 54 | 55 | Item 1 56 | Item 2 57 | Item 3 58 | 59 |
60 | ))); 61 | -------------------------------------------------------------------------------- /src/Icon/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | import types from './types'; 4 | 5 | 6 | const I = styled.i` 7 | // SS Standard documentation: https://romancandletours.com/assets/css/ss-standard/documentation.html 8 | @font-face { 9 | font-family: "SSStandard"; 10 | src: url('https://assets-1f14.kxcdn.com/fonts/ss-standard.eot'); 11 | src: url('https://assets-1f14.kxcdn.com/fonts/ss-standard.eot?#iefix') format('embedded-opentype'), 12 | url('https://assets-1f14.kxcdn.com/fonts/ss-standard.woff') format('woff'), 13 | url('https://assets-1f14.kxcdn.com/fonts/ss-standard.ttf') format('truetype'), 14 | url('https://assets-1f14.kxcdn.com/fonts/ss-standard.svg#SSStandard') format('svg'); 15 | font-weight: normal; 16 | font-style: normal; 17 | } 18 | 19 | &, 20 | &:before, &:before, 21 | &.ss-standard:before, &.ss-standard:before, 22 | &.right:after, &.right:after, 23 | &.ss-standard.right:after, &.ss-standard.right:after { 24 | font-family: "SSStandard"; 25 | font-style: normal; 26 | font-weight: normal; 27 | text-decoration: none; 28 | text-rendering: optimizeLegibility; 29 | white-space: nowrap; 30 | vertical-align: middle; 31 | /*-webkit-font-feature-settings: "liga"; Currently broken in Chrome >= v22. Falls back to text-rendering. Safari is unaffected. */ 32 | -moz-font-feature-settings: "liga=1"; 33 | -moz-font-feature-settings: "liga"; 34 | -ms-font-feature-settings: "liga" 1; 35 | -o-font-feature-settings: "liga"; 36 | font-feature-settings: "liga"; 37 | -webkit-font-smoothing: antialiased; 38 | } 39 | 40 | &.right:before, 41 | &.right:before { 42 | display: none; 43 | content: ''; 44 | } 45 | 46 | &:before, &.right:after { 47 | content: '${props => (props.type ? types[props.type] : '')}'; 48 | } 49 | 50 | `; 51 | 52 | const Icon = ({ type, children, ...props }) => ( 53 | 54 | 55 | {children} 56 | 57 | ); 58 | 59 | Icon.propTypes = { 60 | type: PropTypes.string.isRequired, 61 | children: PropTypes.element 62 | }; 63 | 64 | Icon.defaultProps = { 65 | children: null 66 | }; 67 | 68 | export default Icon; 69 | -------------------------------------------------------------------------------- /src/Icon/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import Icon from './index'; 4 | 5 | import { withInfo } from '@storybook/addon-info'; 6 | 7 | storiesOf('Icon', module) 8 | .add('with type', withInfo('with type')(() => ( 9 | 10 | ))) 11 | .add('with text', withInfo('with text')(() => ( 12 | 13 | Calendar 14 | 15 | ))); 16 | -------------------------------------------------------------------------------- /src/Icon/types.js: -------------------------------------------------------------------------------- 1 | export default { 2 | cursor: '', 3 | 4 | crosshair: '⌖', 5 | 6 | search: '🔎', 7 | 8 | zoomin: '', 9 | 10 | zoomout: '', 11 | 12 | view: '👀', 13 | 14 | attach: '📎', 15 | 16 | link: '🔗', 17 | 18 | move: '', 19 | 20 | write: '✎', 21 | 22 | writingdisabled: '', 23 | 24 | erase: '✐', 25 | 26 | compose: '📝', 27 | 28 | lock: '🔒', 29 | 30 | unlock: '🔓', 31 | 32 | key: '🔑', 33 | 34 | backspace: '⌫', 35 | 36 | ban: '🚫', 37 | 38 | trash: '', 39 | 40 | target: '◎', 41 | 42 | tag: '', 43 | 44 | bookmark: '🔖', 45 | 46 | flag: '⚑', 47 | 48 | like: '👍', 49 | 50 | dislike: '👎', 51 | 52 | heart: '♥', 53 | 54 | halfheart: '', 55 | 56 | star: '⋆', 57 | 58 | halfstar: '', 59 | 60 | sample: '', 61 | 62 | crop: '', 63 | 64 | layers: '', 65 | 66 | fill: '', 67 | 68 | stroke: '', 69 | 70 | phone: '📞', 71 | 72 | phonedisabled: '', 73 | 74 | rss: '', 75 | 76 | facetime: '', 77 | 78 | reply: '↩', 79 | 80 | send: '', 81 | 82 | mail: '✉', 83 | 84 | inbox: '📥', 85 | 86 | chat: '💬', 87 | 88 | ellipsischat: '', 89 | 90 | ellipsis: '…', 91 | 92 | user: '👤', 93 | 94 | femaleuser: '👧', 95 | 96 | users: '👥', 97 | 98 | cart: '', 99 | 100 | creditcard: '💳', 101 | 102 | dollarsign: '💲', 103 | 104 | barchart: '📊', 105 | 106 | piechart: '', 107 | 108 | box: '📦', 109 | 110 | home: '⌂', 111 | 112 | buildings: '🏢', 113 | 114 | warehouse: '', 115 | 116 | globe: '🌎', 117 | 118 | navigate: '', 119 | 120 | compass: '', 121 | 122 | signpost: '', 123 | 124 | map: '', 125 | 126 | location: '', 127 | 128 | pin: '📍', 129 | 130 | database: '', 131 | 132 | hdd: '', 133 | 134 | music: '♫', 135 | 136 | mic: '🎤', 137 | 138 | volume: '🔈', 139 | 140 | lowvolume: '🔉', 141 | 142 | highvolume: '🔊', 143 | 144 | airplay: '', 145 | 146 | camera: '📷', 147 | 148 | picture: '🌄', 149 | 150 | video: '📹', 151 | 152 | play: '▶', 153 | 154 | pause: '', 155 | 156 | stop: '■', 157 | 158 | record: '●', 159 | 160 | rewind: '⏪', 161 | 162 | fastforward: '⏩', 163 | 164 | skipback: '⏮', 165 | 166 | skipforward: '⏭', 167 | 168 | eject: '⏏', 169 | 170 | repeat: '🔁', 171 | 172 | replay: '↺', 173 | 174 | shuffle: '🔀', 175 | 176 | book: '📕', 177 | 178 | openbook: '📖', 179 | 180 | notebook: '📓', 181 | 182 | newspaper: '📰', 183 | 184 | grid: '', 185 | 186 | rows: '', 187 | 188 | columns: '', 189 | 190 | thumbnails: '', 191 | 192 | filter: '', 193 | 194 | desktop: '💻', 195 | 196 | laptop: '', 197 | 198 | tablet: '', 199 | 200 | cell: '📱', 201 | 202 | battery: '🔋', 203 | 204 | highbattery: '', 205 | 206 | mediumbattery: '', 207 | 208 | lowbattery: '', 209 | 210 | emptybattery: '', 211 | 212 | lightbulb: '💡', 213 | 214 | downloadcloud: '', 215 | 216 | download: '', 217 | 218 | uploadcloud: '', 219 | 220 | upload: '', 221 | 222 | fork: '', 223 | 224 | merge: '', 225 | 226 | transfer: '⇆', 227 | 228 | refresh: '↻', 229 | 230 | sync: '', 231 | 232 | loading: '', 233 | 234 | wifi: '', 235 | 236 | connection: '', 237 | 238 | file: '📄', 239 | 240 | folder: '📁', 241 | 242 | quote: '“', 243 | 244 | text: '', 245 | 246 | font: '', 247 | 248 | print: '⎙', 249 | 250 | fax: '📠', 251 | 252 | list: '', 253 | 254 | layout: '', 255 | 256 | action: '', 257 | 258 | redirect: '↪', 259 | 260 | expand: '⤢', 261 | 262 | contract: '', 263 | 264 | help: '❓', 265 | 266 | info: 'ℹ', 267 | 268 | alert: '⚠', 269 | 270 | caution: '⛔', 271 | 272 | logout: '', 273 | 274 | plus: '+', 275 | 276 | hyphen: '-', 277 | 278 | check: '✓', 279 | 280 | delete: '␡', 281 | 282 | settings: '⚙', 283 | 284 | dashboard: '', 285 | 286 | notifications: '🔔', 287 | 288 | notificationsdisabled: '🔕', 289 | 290 | clock: '⏲', 291 | 292 | stopwatch: '⏱', 293 | 294 | calendar: '📅', 295 | 296 | addcalendar: '', 297 | 298 | removecalendar: '', 299 | 300 | checkcalendar: '', 301 | 302 | deletecalendar: '', 303 | 304 | plane: '✈', 305 | 306 | briefcase: '💼', 307 | 308 | cloud: '☁', 309 | 310 | droplet: '💧', 311 | 312 | flask: '', 313 | 314 | up: '⬆', 315 | 316 | upright: '⬈', 317 | 318 | right: '➡', 319 | 320 | downright: '⬊', 321 | 322 | down: '⬇', 323 | 324 | downleft: '⬋', 325 | 326 | left: '⬅', 327 | 328 | upleft: '⬉', 329 | 330 | navigateup: '', 331 | 332 | navigateright: '▻', 333 | 334 | navigatedown: '', 335 | 336 | navigateleft: '◅', 337 | 338 | directup: '▴', 339 | 340 | directright: '▹', 341 | 342 | dropdown: '▾', 343 | 344 | directleft: '◃', 345 | 346 | retweet: '', 347 | 348 | /* Legacy classes */ 349 | volumelow: '🔉', 350 | 351 | volumehigh: '🔊', 352 | 353 | batteryhigh: '', 354 | 355 | batterymedium: '', 356 | 357 | batterylow: '', 358 | 359 | batteryempty: '', 360 | 361 | clouddownload: '', 362 | 363 | cloudupload: '', 364 | 365 | calendaradd: '', 366 | 367 | calendarremove: '', 368 | 369 | calendarcheck: '', 370 | 371 | calendardelete: '' 372 | }; 373 | -------------------------------------------------------------------------------- /src/Input/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | 4 | const StyledInput = styled.input` 5 | box-sizing: border-box; 6 | height: 100%; 7 | text-align: left; 8 | height: 40px; 9 | width: 90%; 10 | padding-right: 48px; 11 | background: #f1f1f1; 12 | padding: 10px 15px; 13 | padding-right: 60px; 14 | outline: none; 15 | border: none; 16 | font-size: 14px; 17 | color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 18 | &:focus { 19 | border-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 20 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 1px rgba(44, 164, 171, 0.52); 21 | } 22 | &[type=search] { 23 | padding: 10px 15px; 24 | height: 40px; 25 | -webkit-appearance: none; 26 | -moz-appearance: none; 27 | appearance: none; 28 | } 29 | `; 30 | 31 | const Input = ({ ...props }) => ( 32 | 33 | ); 34 | 35 | Input.defaultProps = { 36 | placeholder: 'type here ...' 37 | }; 38 | 39 | Input.propsTypes = { 40 | placeholder: PropTypes.string.isRequired 41 | }; 42 | 43 | export default Input; 44 | -------------------------------------------------------------------------------- /src/Input/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Input from './index'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Input', module) 9 | .add('search', withInfo('Basic usage of the button')(() => ( 10 | 11 | ))) 12 | .add('text', withInfo('text')(() => )); 13 | -------------------------------------------------------------------------------- /src/Jumbotron/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | import backgroundImage from '../images/background.jpg'; 4 | 5 | const Container = styled.div` 6 | text-align: center; 7 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 8 | background-size: cover; 9 | background-repeat: no-repeat; 10 | background-position: center; 11 | padding: 1rem 0rem; 12 | color: #fff; 13 | `; 14 | 15 | const Jumbotron = ({ children, className, style, ...props }) => ( 16 | 21 | {children} 22 | 23 | ); 24 | 25 | Jumbotron.defaultProps = { 26 | className: '', 27 | style: {} 28 | }; 29 | 30 | Jumbotron.propTypes = { 31 | children: PropTypes.oneOfType([ 32 | PropTypes.arrayOf(PropTypes.element), 33 | PropTypes.arrayOf(PropTypes.node), 34 | PropTypes.node, 35 | PropTypes.string 36 | ]).isRequired, 37 | className: PropTypes.string, 38 | style: PropTypes.object // eslint-disable-line 39 | }; 40 | 41 | export default Jumbotron; 42 | -------------------------------------------------------------------------------- /src/Jumbotron/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import Jumbotron from './index'; 4 | 5 | import { withInfo } from '@storybook/addon-info'; 6 | 7 | storiesOf('Jumbotron', module) 8 | .add('default', withInfo('default')(() => ( 9 | 10 | Hello world 11 | 12 | ))) 13 | .add('with props', withInfo('with props')(() => ( 14 | 15 | Hello world 16 | 17 | ))); 18 | -------------------------------------------------------------------------------- /src/Loader/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | import styled from 'styled-components'; 4 | 5 | const LoaderIcon = styled.div` 6 | position: fixed; 7 | margin: 60px auto; 8 | font-size: 10px; 9 | position: relative; 10 | text-indent: -9999em; 11 | border-top: 1.1em solid rgba(0, 90, 113, 0.2); 12 | border-right: 1.1em solid rgba(0, 90, 113, 0.2); 13 | border-bottom: 1.1em solid rgba(0, 90, 113, 0.2); 14 | border-left: 1.1em solid ${props => props.theme.brandPrimary || '#2CA4AB'}; 15 | -webkit-transform: translateZ(0); 16 | -ms-transform: translateZ(0); 17 | transform: translateZ(0); 18 | -webkit-animation: load8 1.1s infinite linear; 19 | animation: load8 1.1s infinite linear; 20 | z-index: 100; 21 | position: absolute; 22 | left: 45%; 23 | top: 30%; 24 | &, 25 | &:after { 26 | border-radius: 50%; 27 | width: 6em; 28 | height: 6em; 29 | position: fixed; 30 | } 31 | 32 | @-webkit-keyframes load8 { 33 | 0% { 34 | -webkit-transform: rotate(0deg); 35 | transform: roatate(0deg); 36 | } 37 | 100% { 38 | -webkit-transform: rotate(360deg); 39 | transform: rotate(360deg); 40 | } 41 | } 42 | @keyframes load8 { 43 | 0% { 44 | -webkit-transform: rotate(0deg); 45 | transform: rotate(0deg); 46 | } 47 | 100% { 48 | -webkit-transform: rotate(360deg); 49 | transform: rotate(360deg); 50 | } 51 | } 52 | 53 | ${props => 54 | (props.relative 55 | ? ` 56 | position: relative; 57 | top: initial; 58 | left: initial; 59 | ` 60 | : '')}; 61 | `; 62 | 63 | const Active = styled.div` 64 | opacity: 0.8; 65 | background-color: #ffffff; 66 | position: fixed; 67 | width: 100%; 68 | height: 100%; 69 | top: 0px; 70 | left: 0px; 71 | z-index: 99; 72 | `; 73 | 74 | const Loader = ({ children, isActive, className, relative, ...props }) => ( 75 |
76 | {isActive && !relative && } 77 | {isActive && ( 78 | 79 | Loading... 80 | 81 | )} 82 | {children} 83 |
84 | ); 85 | 86 | Loader.propTypes = { 87 | children: PropTypes.oneOfType([ 88 | PropTypes.arrayOf(PropTypes.element), 89 | PropTypes.arrayOf(PropTypes.node), 90 | PropTypes.node, 91 | PropTypes.string, 92 | ]).isRequired, 93 | isActive: PropTypes.bool.isRequired, 94 | relative: PropTypes.bool.isRequired, 95 | className: PropTypes.string, 96 | }; 97 | 98 | 99 | Loader.defaultProps = { 100 | isActive: false, 101 | relative: false, 102 | className: '', 103 | children: [], 104 | }; 105 | 106 | export default Loader; 107 | -------------------------------------------------------------------------------- /src/Loader/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import Loading from './index'; 4 | import Button from '../Button'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Loader', module) 9 | .add('isActive - true ', withInfo('isActive - true ')(() => ( ))) 10 | .add('isActive - false ', withInfo('isActive - false ')(() => ( ))) 11 | .add('isActive - relative ', withInfo('isActive - relative ')(() => ( 12 |
13 | 14 |
15 | ))); 16 | -------------------------------------------------------------------------------- /src/Menu/MenuItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import styled from 'styled-components'; 5 | import Icon from '../Icon'; 6 | 7 | const Item = styled.li` 8 | color: #777; 9 | list-style-type: none; 10 | 11 | ${props => (props.divider ? 'border-top: 1px solid #c4c4c4;' : '')}; 12 | `; 13 | 14 | const Menu = styled.div` 15 | height: ${props => (props.open ? 'auto' : 0)}; 16 | transition: 0.5s height; 17 | overflow: hidden; 18 | `; 19 | 20 | const StyledIcon = styled(Icon)` 21 | position: absolute; 22 | right: 15px; 23 | `; 24 | 25 | const StyledLink = styled.div` 26 | padding: 10px 15px; 27 | text-decoration: none; 28 | color: #777; 29 | display: block; 30 | cursor: pointer; 31 | position: relative; 32 | 33 | &:hover { 34 | background: #f5f5f5; 35 | color: #333; 36 | } 37 | `; 38 | 39 | class MenuItem extends Component { 40 | state = { 41 | menuOpen: false, 42 | }; 43 | 44 | handleClick = (event) => { 45 | const { onClick, menu } = this.props; 46 | 47 | if (menu) { 48 | this.setState({ menuOpen: !this.state.menuOpen }); 49 | } 50 | 51 | return onClick && onClick(event); 52 | }; 53 | 54 | render() { 55 | const { 56 | children, 57 | icon, 58 | href, 59 | className, 60 | divider, 61 | menu, 62 | component, // eslint-disable-line 63 | onClick, // eslint-disable-line 64 | ...props 65 | } = this.props; 66 | const Type = component ? StyledLink.withComponent(component) : StyledLink; 67 | 68 | return ( 69 | 70 | {children && ( 71 | 72 | {icon && {icon}} 73 | {children} 74 | {menu && } 75 | 76 | )} 77 | {menu && {menu}} 78 | 79 | ); 80 | } 81 | } 82 | 83 | MenuItem.propTypes = { 84 | children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]).isRequired, 85 | icon: PropTypes.element, 86 | href: PropTypes.string, 87 | className: PropTypes.string, 88 | divider: PropTypes.bool, 89 | menu: PropTypes.element, 90 | onClick: PropTypes.func, 91 | }; 92 | 93 | MenuItem.defaultProps = { 94 | icon: null, 95 | href: null, 96 | className: '', 97 | divider: false, 98 | menu: null, 99 | onClick: null, 100 | }; 101 | 102 | export default MenuItem; 103 | -------------------------------------------------------------------------------- /src/Menu/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | const List = styled.ul` 6 | padding-left: 0px; 7 | 8 | ${props => (props.bordered ? 'border: 1px solid #777;' : '')}; 9 | `; 10 | 11 | export { default as MenuItem } from './MenuItem.js'; 12 | 13 | const Menu = ({ children, bordered, className, ...props }) => ( 14 | 15 | {children} 16 | 17 | ); 18 | 19 | Menu.propTypes = { 20 | children: PropTypes.arrayOf(PropTypes.element), 21 | bordered: PropTypes.bool, 22 | className: PropTypes.string, 23 | }; 24 | 25 | Menu.defaultProps = { 26 | children: [], 27 | bordered: false, 28 | className: '', 29 | }; 30 | 31 | export default Menu; 32 | -------------------------------------------------------------------------------- /src/Menu/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Menu, { MenuItem } from './index'; 5 | import Icon from '../Icon'; 6 | import Checkbox from '../Checkbox'; 7 | import Radio from '../Radio'; 8 | 9 | import { withInfo } from '@storybook/addon-info'; 10 | 11 | storiesOf('Menu', module) 12 | .add('default', withInfo('Basic usage of the menu')(() => ( 13 | 14 | Item 1 15 | Item 2 16 | Item 3 17 | 18 | ))) 19 | .add('bordered', withInfo('bordered')(() => ( 20 | 21 | Item 1 22 | Item 2 23 | Item 3 24 | 25 | ))) 26 | .add('divider', withInfo('divider')(() => ( 27 | 28 | Item 1 29 | 30 | Item 2 31 | Item 3 32 | 33 | ))) 34 | .add('with icons', withInfo('with icons')(() => ( 35 | 36 | }>Item 1 37 | }>Item 2 38 | }>Item 3 39 | 40 | ))) 41 | .add('on click', withInfo('on click')(() => ( 42 | 43 | Item 1 44 | Item 2 45 | Item 3 46 | 47 | ))) 48 | .add('menu inside menu', withInfo('menu inside menu')(() => ( 49 | 50 | }>Item 1 51 | }>Item 2 52 | } 54 | menu={ 55 | 56 | }>Item 1 57 | }>Item 2 58 | }>Item 3 59 | 60 | } 61 | > 62 | Item 3 63 | 64 | 65 | ))) 66 | .add('checkboxes', withInfo('checkboxes')(() => ( 67 | 68 | 69 | 75 | translation 76 | 77 | 78 | 79 | 85 | translation 86 | 87 | 88 | 89 | 95 | translation 96 | 97 | 98 | 99 | ))) 100 | .add('radio', withInfo('radio')(() => ( 101 | 102 | {['translation', 'transliteration'].map(type => ( 103 | 104 | 105 | {type.toUpperCase()} 106 | 107 | 108 | ))} 109 | 110 | ))) 111 | .add( 112 | 'large', 113 | withInfo('Menu with all the checkboxes, radios and menu dropdowns')(() => ( 114 | 115 | }>Item 1 116 | }>Item 2 117 | } 119 | menu={ 120 | 121 | }>Item 1 122 | }>Item 2 123 | }>Item 3 124 | 125 | } 126 | > 127 | Item 3 128 | 129 | 130 | {['Translation', 'Transliteration'].map(type => ( 131 | 132 | 133 | {type} 134 | 135 | 136 | ))} 137 | 138 | 139 | 145 | translation 146 | 147 | 148 | 149 | 155 | translation 156 | 157 | 158 | 159 | 165 | translation 166 | 167 | 168 | 169 | )) 170 | ); 171 | -------------------------------------------------------------------------------- /src/Navbar/NavItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | const List = styled.ul` 6 | margin: 0; 7 | padding: 0; 8 | float: ${props => (props.right ? 'right' : 'left')}; 9 | 10 | & > li { 11 | display: inline-block; 12 | margin: 15px 10px; 13 | 14 | & > button { 15 | margin-top: -10px; 16 | } 17 | } 18 | `; 19 | 20 | const NavItem = ({ children, className, right }) => ( 21 | 22 | {children} 23 | 24 | ); 25 | 26 | NavItem.propTypes = { 27 | children: PropTypes.arrayOf(PropTypes.element), 28 | className: PropTypes.string, 29 | right: PropTypes.boolean, 30 | }; 31 | 32 | NavItem.defaultProps = { 33 | children: [], 34 | className: '', 35 | right: false, 36 | }; 37 | 38 | export default NavItem; 39 | -------------------------------------------------------------------------------- /src/Navbar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled, { css } from 'styled-components'; 4 | 5 | export { default as NavItem } from './NavItem'; 6 | 7 | const Fixed = css` 8 | position: Fixed; 9 | left: 0px; 10 | right: 0px; 11 | `; 12 | 13 | const Scrolled = css` 14 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 15 | 0 2px 4px -1px rgba(0, 0, 0, 0.2); 16 | `; 17 | 18 | const Nav = styled.nav` 19 | border-bottom: 1px solid rgba(0, 0, 0, 0.12); 20 | height: 50px; 21 | 22 | ${props => (props.fixed ? Fixed : '')} ${props => (props.scrolled ? Scrolled : '')}; 23 | `; 24 | 25 | const Container = styled.div` 26 | padding: 0 15px; 27 | `; 28 | 29 | class Navbar extends React.Component { 30 | static propTypes = { 31 | children: PropTypes.arrayOf(PropTypes.element), 32 | fixed: PropTypes.bool, 33 | className: PropTypes.string, 34 | }; 35 | 36 | static defaultProps = { 37 | fixed: false, 38 | children: [], 39 | className: '', 40 | }; 41 | 42 | state = { 43 | scrolled: false, 44 | }; 45 | 46 | componentDidMount() { 47 | window.addEventListener('scroll', this.handleNavbar, true); 48 | } 49 | 50 | componentWillUnmount() { 51 | window.removeEventListener('scroll', this.handleNavbar, true); 52 | } 53 | 54 | handleNavbar = () => { 55 | const { fixed } = this.props; 56 | const { scrolled } = this.state; 57 | 58 | if (window.pageYOffset > 50) { 59 | if (!scrolled && fixed) { 60 | this.setState({ scrolled: true }); 61 | } 62 | } else if (scrolled) { 63 | this.setState({ scrolled: false }); 64 | } 65 | 66 | return false; 67 | }; 68 | 69 | render() { 70 | const { children, fixed, className } = this.props; 71 | 72 | return ( 73 | 76 | ); 77 | } 78 | } 79 | 80 | export default Navbar; 81 | -------------------------------------------------------------------------------- /src/Navbar/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Navbar, { NavItem } from './index'; 5 | import Button from '../Button'; 6 | 7 | import { withInfo } from '@storybook/addon-info'; 8 | 9 | storiesOf('Navbar', module) 10 | .addDecorator(story => ( 11 |
12 | 20 | {story()} 21 |
22 | )) 23 | .add( 24 | 'items', 25 | withInfo('items')(() => ( 26 | 27 | 28 |
  • Something
  • 29 |
  • 30 | link 31 |
  • 32 |
  • 33 | 34 |
  • 35 |
    36 |
    37 | )), 38 | ) 39 | .add( 40 | 'fixed', 41 | withInfo('fixed')(() => ( 42 | 43 | 44 |
  • Something
  • 45 |
  • 46 | link 47 |
  • 48 |
    49 | 50 |
  • Something
  • 51 |
  • 52 | link 53 |
  • 54 |
  • 55 | 56 |
  • 57 |
    58 |
    59 | )), 60 | ) 61 | .add( 62 | 'left only', 63 | withInfo('left only')(() => ( 64 | 65 | 66 |
  • Something
  • 67 |
  • 68 | link 69 |
  • 70 |
    71 |
    72 | )), 73 | ) 74 | .add( 75 | 'right only', 76 | withInfo('right only')(() => ( 77 | 78 | 79 |
  • Something
  • 80 |
  • 81 | link 82 |
  • 83 |
  • 84 | 85 |
  • 86 |
    87 |
    88 | )), 89 | ) 90 | 91 | .add( 92 | 'With custom classNames', 93 | withInfo('With custom classNames')(() => ( 94 | 95 | 96 |
  • Something
  • 97 |
  • 98 | link 99 |
  • 100 |
    101 | 102 |
  • Something
  • 103 |
  • 104 | link 105 |
  • 106 |
  • 107 | 108 |
  • 109 |
    110 |
    111 | )), 112 | ); 113 | -------------------------------------------------------------------------------- /src/Popover/index.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import TetherComponent from 'react-tether'; 6 | import styled, { css } from 'styled-components'; 7 | 8 | const arrowSize = 5; 9 | const arrowSizePadding = 2; 10 | const transition = '0.3s'; 11 | 12 | const open = css` 13 | opacity: 1; 14 | margin-top: ${arrowSize}px; 15 | visibility: visible; 16 | `; 17 | 18 | const Content = styled.div` 19 | background: #fff; 20 | border: 1px solid #ccc; 21 | border-color: rgba(0, 0, 0, 0.2); 22 | color: #000; 23 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); 24 | // display: none; 25 | outline: none; 26 | border-radius: 2px; 27 | -webkit-user-select: text; 28 | padding: 10px 15px; 29 | max-width: 100%; 30 | min-width: 250px; 31 | position: relative; 32 | margin-top: ${arrowSize + 5}px; 33 | opacity: 0; 34 | visibility: hidden; 35 | transition: margin-top ${transition}, opacity ${transition}, visibility ${transition}; 36 | 37 | &:after { 38 | content: ''; 39 | position: absolute; 40 | top: ${arrowSize * -1}px; 41 | left: 50%; 42 | border-style: solid; 43 | border-width: 0 ${arrowSize}px ${arrowSize}px; 44 | border-color: #ffffff transparent; 45 | display: block; 46 | width: 0; 47 | z-index: 1; 48 | transform: translateX(-50%); 49 | } 50 | 51 | &:before { 52 | content: ''; 53 | position: absolute; 54 | top: ${arrowSize - 10 - arrowSizePadding}px; 55 | left: 50%; 56 | border-style: solid; 57 | border-width: 0 ${arrowSize + arrowSizePadding}px ${arrowSize + arrowSizePadding}px; 58 | border-color: rgba(0, 0, 0, 0.2) transparent; 59 | display: block; 60 | width: 0; 61 | z-index: 0; 62 | transform: translateX(-50%); 63 | } 64 | 65 | ${props => (props.open ? open : '')}; 66 | `; 67 | 68 | class Popover extends Component { 69 | state = { 70 | open: false, 71 | }; 72 | 73 | componentDidMount() { 74 | document.body.addEventListener('click', this.handleBodyClick.bind(this), true); 75 | } 76 | 77 | componentWillUnmount() { 78 | document.body.removeEventListener('click', this.handleBodyClick.bind(this), true); 79 | } 80 | 81 | handleBodyClick = (event) => { 82 | if ( 83 | !this.popover || 84 | !this.popover._targetNode || // eslint-disable-line 85 | !this.popover._elementParentNode // eslint-disable-line 86 | ) { 87 | return false; 88 | } 89 | 90 | const isSame = event.target === this.popover._targetNode; // eslint-disable-line 91 | const isWithinTrigger = this.popover._targetNode.contains(event.target); // eslint-disable-line 92 | // eslint-disable-next-line 93 | const isWithinPopover = this.popover._elementParentNode.contains(event.target); 94 | 95 | if (isSame || isWithinTrigger) { 96 | return this.setState({ open: !this.state.open }); 97 | } 98 | 99 | if (isWithinPopover && this.props.persist === 'self') { 100 | return false; 101 | } 102 | 103 | if (this.state.open) { 104 | return this.setState({ open: false }); 105 | } 106 | 107 | return false; 108 | }; 109 | 110 | render() { 111 | const { trigger, children, attachment, className } = this.props; 112 | 113 | return ( 114 | { 122 | this.popover = popover; 123 | }} 124 | > 125 | {trigger} 126 | 127 | {children} 128 | 129 | 130 | ); 131 | } 132 | } 133 | 134 | Popover.propTypes = { 135 | trigger: PropTypes.element.isRequired, 136 | attachment: PropTypes.string, 137 | className: PropTypes.string, 138 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.element), PropTypes.element]) 139 | .isRequired, 140 | persist: PropTypes.oneOf([null, 'self']), 141 | }; 142 | 143 | Popover.defaultProps = { 144 | trigger: Click, 145 | persist: null, 146 | className: '', 147 | attachment: 'top center', 148 | }; 149 | 150 | export default Popover; 151 | -------------------------------------------------------------------------------- /src/Popover/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import Popover from './index'; 4 | import Button from '../Button'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Popover', module) 9 | .addDecorator(story => ( 10 |
    11 | {story()} 12 |
    13 | )) 14 | .add('default', withInfo('Basic usage of the popover')(() => ( 15 | I am a trigger}> 16 |

    Text here

    17 |
    18 | ))) 19 | .add('persist self', withInfo('Basic usage of the popover')(() => ( 20 | I am a trigger}> 21 |

    Text here

    22 |
    23 | ))); 24 | -------------------------------------------------------------------------------- /src/Radio/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | 4 | const size = 20; 5 | 6 | const Label = styled.label` 7 | display: inline-block; 8 | padding-right: 20px; 9 | font-weight: 500; 10 | color: #777; 11 | line-height: 20px; 12 | cursor: pointer; 13 | display: inline-block; 14 | 15 | &:hover .inner { 16 | -webkit-transform: scale(0.5); 17 | -ms-transform: scale(0.5); 18 | transform: scale(0.5); 19 | opacity: 0.5; 20 | } 21 | 22 | & .input { 23 | width: 1px; 24 | height: 1px; 25 | opacity: 0; 26 | } 27 | 28 | & .input:checked + .outer .inner { 29 | -webkit-transform: scale(1); 30 | -ms-transform: scale(1); 31 | transform: scale(1); 32 | opacity: 1; 33 | } 34 | 35 | & .input:checked + .outer { 36 | border: 2px solid ${props => props.theme.brandPrimary || '#2CA4AB'}; 37 | } 38 | 39 | & .input:focus + .outer .inner { 40 | -webkit-transform: scale(1); 41 | -ms-transform: scale(1); 42 | transform: scale(1); 43 | opacity: 1; 44 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 45 | } 46 | 47 | & .outer { 48 | width: ${size}px; 49 | height: ${size}px; 50 | display: block; 51 | float: left; 52 | margin: 0px 5px 0px 0px; 53 | border: 2px solid ${props => props.theme.brandPrimary || '#2CA4AB'}; 54 | border-radius: 50%; 55 | background-color: #fff; 56 | } 57 | 58 | & .inner { 59 | -webkit-transition: all 0.25s ease-in-out; 60 | transition: all 0.25s ease-in-out; 61 | width: ${size / 2}px; 62 | height: ${size / 2}px; 63 | -webkit-transform: scale(0); 64 | -ms-transform: scale(0); 65 | transform: scale(0); 66 | display: block; 67 | margin: ${size / 4}px; 68 | border-radius: 50%; 69 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 70 | opacity: 0; 71 | } 72 | `; 73 | 74 | const Radio = ({ id, name, checked, handleChange, children }) => ( 75 | 89 | ); 90 | 91 | Radio.propTypes = { 92 | id: PropTypes.string.isRequired, 93 | name: PropTypes.string.isRequired, 94 | checked: PropTypes.bool.isRequired, 95 | handleChange: PropTypes.func.isRequired, 96 | children: PropTypes.element.isRequired, 97 | }; 98 | 99 | export default Radio; 100 | -------------------------------------------------------------------------------- /src/Radio/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Radio from './index'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Radio', module) 9 | .add('with text', withInfo('with text')(() => ( 10 |
    11 | {['translation', 'transliteration'].map(type => ( 12 | action(type)}> 13 | {type.toUpperCase()} 14 | 15 | ))} 16 |
    17 | ))) 18 | .add('default checked', withInfo('default checked')(() => ( 19 |
    20 | {['translation', 'transliteration'].map(type => ( 21 | action(type)} 27 | > 28 | {type.toUpperCase()} 29 | 30 | ))} 31 |
    32 | ))); 33 | -------------------------------------------------------------------------------- /src/Sites/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | import styled from 'styled-components'; 4 | import Icon from '../Icon'; 5 | import Popover from '../Popover'; 6 | 7 | const sites = [ 8 | { 9 | name: 'Quran', 10 | logo: '//assets-1f14.kxcdn.com/images/logo-lg.png', 11 | href: 'https://quran.com', 12 | }, 13 | { 14 | name: 'Audio', 15 | logo: 'https://raw.githubusercontent.com/quran/audio.quran.com/master/static/favicon.ico', 16 | href: 'https://quranicaudio.com', 17 | }, 18 | { 19 | name: 'Salah', 20 | logo: 'http://salah.com/images/favicon.png', 21 | href: 'http://salah.com', 22 | }, 23 | { 24 | name: 'Sunnah', 25 | logo: 'https://sunnah.com/favicon.ico', 26 | href: 'https://sunnah.com', 27 | }, 28 | ]; 29 | 30 | const Site = styled.div` 31 | width: 50%; 32 | display: inline-block; 33 | text-align: center; 34 | border: 1px solid transparent; 35 | border-radius: 2px; 36 | box-sizing: border-box; 37 | 38 | &:hover { 39 | border: 1px solid rgba(#777, 0.25); 40 | } 41 | `; 42 | 43 | const Link = styled.a` 44 | text-decoration: none; 45 | display: block; 46 | padding: 10px; 47 | color: #777; 48 | `; 49 | 50 | const Span = styled.span` 51 | height: 35px; 52 | width: 100%; 53 | display: block; 54 | margin-bottom: 5px; 55 | `; 56 | 57 | const Sites = ({ className, popoverClassName }) => ( 58 | 63 | 64 | 65 | } 66 | > 67 |
    68 | {sites.map(site => ( 69 | 70 | 71 | 77 | {site.name} 78 | 79 | 80 | ))} 81 |
    82 |
    83 | ); 84 | 85 | Sites.propTypes = { 86 | className: PropTypes.string, 87 | popoverClassName: PropTypes.string, 88 | }; 89 | 90 | Sites.defaultProps = { 91 | className: '', 92 | popoverClassName: '', 93 | }; 94 | 95 | export default Sites; 96 | -------------------------------------------------------------------------------- /src/Sites/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import Sites from './index'; 4 | 5 | import { withInfo } from '@storybook/addon-info'; 6 | 7 | storiesOf('Sites', module) 8 | .addDecorator(story => ( 9 |
    10 | {story()} 11 |
    12 | )) 13 | .add('default', withInfo('Basic usage of the sites')(() => ( 14 | 15 | ))); 16 | -------------------------------------------------------------------------------- /src/SurahTitle/Surahs/19.js: -------------------------------------------------------------------------------- 1 | 2 | import PropTypes from 'prop-types'; 3 | 4 | const Surah18 = props => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | Surah18.propTypes = { color: PropTypes.string.isRequired, className: PropTypes.string }; 29 | Surah18.defaultProps = { color: '#0000000', className: '' }; 30 | export default Surah18; 31 | -------------------------------------------------------------------------------- /src/SurahTitle/Surahs/20.js: -------------------------------------------------------------------------------- 1 | 2 | import PropTypes from 'prop-types'; 3 | 4 | const Surah19 = props => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | Surah19.propTypes = { color: PropTypes.string.isRequired, className: PropTypes.string }; 29 | Surah19.defaultProps = { color: '#0000000', className: '' }; 30 | export default Surah19; 31 | -------------------------------------------------------------------------------- /src/SurahTitle/Surahs/38.js: -------------------------------------------------------------------------------- 1 | 2 | import PropTypes from 'prop-types'; 3 | 4 | const Surah37 = props => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | Surah37.propTypes = { color: PropTypes.string.isRequired, className: PropTypes.string }; 28 | Surah37.defaultProps = { color: '#0000000', className: '' }; 29 | export default Surah37; 30 | -------------------------------------------------------------------------------- /src/SurahTitle/Surahs/50.js: -------------------------------------------------------------------------------- 1 | 2 | import PropTypes from 'prop-types'; 3 | 4 | const Surah49 = props => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | Surah49.propTypes = { color: PropTypes.string.isRequired, className: PropTypes.string }; 27 | Surah49.defaultProps = { color: '#0000000', className: '' }; 28 | export default Surah49; 29 | -------------------------------------------------------------------------------- /src/SurahTitle/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const SurahTitle = ({ chapterNumber, color, className, ...props }) => { 4 | const SurahSvg = require(`./Surahs/${chapterNumber}.js`).default; // eslint-disable-line 5 | return ; 6 | }; 7 | 8 | SurahTitle.defaultProps = { 9 | color: '#000000', 10 | className: '', 11 | chapterNumber: 1, 12 | }; 13 | 14 | SurahTitle.propTypes = { 15 | chapterNumber: PropTypes.number.isRequired, 16 | color: PropTypes.string, 17 | className: PropTypes.string, 18 | }; 19 | 20 | export default SurahTitle; 21 | -------------------------------------------------------------------------------- /src/SurahTitle/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import SurahTitle from './index'; 4 | 5 | import { withInfo } from '@storybook/addon-info'; 6 | 7 | storiesOf('SurahTitle', module) 8 | .addDecorator(story => ( 9 |
    10 | 19 | {story()} 20 |
    21 | )) 22 | .add('default', withInfo('default')(() => )) 23 | .add( 24 | 'color props', 25 | withInfo('color props')(() => ), 26 | ) 27 | .add( 28 | 'style props', 29 | withInfo('style props')(() => ( 30 | 35 | )), 36 | ) 37 | .add( 38 | 'style fill via css (classname)', 39 | withInfo('style props')(() => ( 40 | 46 | )), 47 | ) 48 | .add( 49 | 'height and width props', 50 | withInfo('height and width props')(() => ( 51 | 52 | )), 53 | ); 54 | -------------------------------------------------------------------------------- /src/Tabs/Tab.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const Tab = ({ title, id, children }) => ( 4 |
    {children}
    5 | ); 6 | 7 | Tab.propTypes = { 8 | id: PropTypes.string, 9 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, 10 | children: PropTypes.oneOfType([ 11 | PropTypes.arrayOf(PropTypes.element), 12 | PropTypes.arrayOf(PropTypes.node), 13 | PropTypes.node, 14 | PropTypes.string 15 | ]).isRequired 16 | }; 17 | 18 | Tab.defaultProps = { 19 | id: null 20 | }; 21 | 22 | export default Tab; 23 | -------------------------------------------------------------------------------- /src/Tabs/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | export { default as Tab } from './Tab'; 6 | 7 | const List = styled.ul` 8 | padding: 0; 9 | text-align: center; 10 | 11 | & > li { 12 | display: inline; 13 | 14 | & > a { 15 | display: inline-block; 16 | padding: 10px 15px; 17 | outline: none; 18 | cursor: pointer; 19 | box-sizing: border-box; 20 | color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 21 | font-size: 1.15rem; 22 | 23 | &:hover { 24 | background: #f1f1f1; 25 | } 26 | } 27 | } 28 | `; 29 | 30 | const Underline = styled.hr` 31 | border-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 32 | transition: 0.3s ease-in-out; 33 | margin-left: 0px; 34 | margin-top: 0px; 35 | `; 36 | 37 | class Tabs extends Component { 38 | static propTypes = { 39 | selected: PropTypes.number, 40 | className: PropTypes.string, 41 | onClick: PropTypes.func, // eslint-disable-line 42 | children: PropTypes.oneOfType([ 43 | PropTypes.arrayOf(PropTypes.element), 44 | PropTypes.arrayOf(PropTypes.node), 45 | PropTypes.node, 46 | PropTypes.string, 47 | ]).isRequired, 48 | }; 49 | 50 | static defaultProps = { 51 | className: '', 52 | selected: 0, 53 | }; 54 | 55 | state = { 56 | selected: this.props.selected, 57 | }; 58 | 59 | handleTabClick(selected) { 60 | if (this.props.onClick) { 61 | this.props.onClick(selected); 62 | } 63 | this.setState({ selected }); 64 | } 65 | 66 | render() { 67 | const { children, className } = this.props; 68 | const widthPercent = 1 / children.length * 100; // eslint-disable-line 69 | const width = `${widthPercent}%`; 70 | const marginLeft = `${this.state.selected * widthPercent}%`; 71 | 72 | return ( 73 |
    74 | 75 | {this.props.children.map((child, index) => ( 76 |
  • 77 | this.handleTabClick(index)} style={{ width }}> 78 | {child.props.title} 79 | 80 |
  • 81 | ))} 82 | 83 |
    84 | 85 |
    {children[this.state.selected]}
    86 |
    87 | ); 88 | } 89 | } 90 | 91 | export default Tabs; 92 | -------------------------------------------------------------------------------- /src/Tabs/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Tabs, { Tab } from './index'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Tabs', module) 9 | .add('default', withInfo('default')(() => ( 10 | 11 | Some info 1 12 | Some info 2 13 | Some info 3 14 | 15 | ))) 16 | .add('with onClick handler', withInfo('with onClick handler')(() => ( 17 | 18 | Some info 1 19 | Some info 2 20 | Some info 3 21 | 22 | ))) 23 | .add('selected props', withInfo('selected props')(() => ( 24 | 25 | Some info 1 26 | Some info 2 27 | Some info 3 28 | 29 | ))) 30 | .add('HTML tags in title', withInfo('HTML tags in title')(() => ( 31 | 32 | h2}>Some info 1 33 | small} id="small"> 34 | Some info 2 35 | 36 | Some info 3 37 | 38 | ))); 39 | -------------------------------------------------------------------------------- /src/Toggle/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled, { css } from 'styled-components'; 3 | 4 | const height = 25; 5 | const width = 50; 6 | 7 | const Switch = styled.div` 8 | display: inline-block; 9 | vertical-align: middle; 10 | `; 11 | 12 | const roundStyle = css` 13 | & + .label { 14 | padding: 2px; 15 | width: ${width}px; 16 | height: ${height}px; 17 | background-color: #dddddd; 18 | border-radius: ${height}px; 19 | } 20 | 21 | & + .label:before, 22 | & + .label:after { 23 | display: block; 24 | position: absolute; 25 | top: 1px; 26 | left: 1px; 27 | bottom: 1px; 28 | content: ""; 29 | } 30 | 31 | & + .label:before { 32 | right: 1px; 33 | background-color: #f1f1f1; 34 | border-radius: ${height}px; 35 | transition: background 0.4s; 36 | } 37 | 38 | & + .label:after { 39 | width: ${height - 2}px; 40 | height: ${height - 2}px; 41 | background-color: #fff; 42 | border-radius: ${height - 2}px; 43 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); 44 | transition: margin 0.4s; 45 | position: relative; 46 | top: 1px; 47 | } 48 | 49 | &:checked + .label:before { 50 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 51 | } 52 | 53 | &:checked + .label:after { 54 | margin-left: ${width / 2}px; 55 | } 56 | `; 57 | 58 | const flatStyle = css` 59 | & + .label { 60 | padding: 2px; 61 | width: ${width}px; 62 | height: ${height}px; 63 | background-color: #dddddd; 64 | border-radius: ${height}px; 65 | transition: background 0.4s; 66 | } 67 | & + .label:before, 68 | & + .label:after { 69 | display: block; 70 | position: absolute; 71 | content: ""; 72 | } 73 | & + .label:before { 74 | top: 2px; 75 | left: 2px; 76 | bottom: 2px; 77 | right: 2px; 78 | background-color: #fff; 79 | border-radius: ${height}px; 80 | transition: background 0.4s; 81 | } 82 | & + .label:after { 83 | top: 4px; 84 | left: 4px; 85 | bottom: 4px; 86 | width: ${height - 5}px; 87 | height: ${height - 5}px; 88 | background-color: #dddddd; 89 | border-radius: ${height - 5}px; 90 | transition: margin 0.4s, background 0.4s; 91 | } 92 | &:checked + .label { 93 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 94 | } 95 | &:checked + .label:after { 96 | margin-left: ${width / 2}px; 97 | background-color: ${props => props.theme.brandPrimary || '#2CA4AB'}; 98 | } 99 | `; 100 | 101 | const Toggle = styled.input` 102 | position: absolute; 103 | margin-left: -9999px; 104 | visibility: hidden; 105 | 106 | & + .label { 107 | display: block; 108 | position: relative; 109 | cursor: pointer; 110 | outline: none; 111 | user-select: none; 112 | margin-bottom: 0px; 113 | } 114 | 115 | ${props => (props.toggleStyle === 'flat' ? flatStyle : roundStyle)} 116 | `; 117 | 118 | const SwitchToggle = ({ id, flat, onToggle, ...otherProps }) => ( 119 | 120 | 127 | 129 | ); 130 | 131 | SwitchToggle.propTypes = { 132 | id: PropTypes.string, 133 | flat: PropTypes.bool, 134 | onToggle: PropTypes.func.isRequired 135 | }; 136 | 137 | SwitchToggle.defaultProps = { 138 | flat: false, 139 | id: '' 140 | }; 141 | 142 | export default SwitchToggle; 143 | -------------------------------------------------------------------------------- /src/Toggle/index.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import Toggle from './index'; 5 | 6 | import { withInfo } from '@storybook/addon-info'; 7 | 8 | storiesOf('Toggle', module) 9 | .add('basic', withInfo('basic')(() => )) 10 | .add('basic checked', withInfo('basic checked')(() => ( 11 | 12 | ))) 13 | .add('flat', withInfo('flat')(() => )) 14 | .add('flat checked', withInfo('flat checked')(() => ( 15 | 16 | ))); 17 | -------------------------------------------------------------------------------- /src/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quran/common-components/1cf5a5e95b7678093c8c75b23363afd5e5c54570/src/images/background.jpg -------------------------------------------------------------------------------- /utils/generateModulesList.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | const fs = require('fs'); 3 | 4 | fs.readdir('./lib', (err, files) => { 5 | if (err) return console.log(err); 6 | const filteredFolder = files.filter(item => new RegExp(/^(?!.*images|styles).*$/g).test(item)); 7 | const fileTemplate = folders => folders.map(folder => `exports.${folder} = require('./${folder}').default;`).join('\n'); 8 | fs.writeFile('./lib/index.js', fileTemplate(filteredFolder), error => error ? console.log(error) : console.log(`The file was saved! Components: ${filteredFolder.join(', ')}`)); 9 | }); 10 | 11 | /*eslint-enable */ 12 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/013.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/019.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/020.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/022.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/038.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/050.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /utils/surahTitleSvg/090.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /utils/test_helper.js: -------------------------------------------------------------------------------- 1 | import 'isomorphic-fetch'; 2 | 3 | import Enzyme from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | 6 | Enzyme.configure({ adapter: new Adapter() }); 7 | 8 | import { config } from '../package'; 9 | import jsdom from 'jsdom'; 10 | import chai from 'chai'; 11 | import sinon from 'sinon'; 12 | import sinonChai from 'sinon-chai'; 13 | import chaiEnzyme from 'chai-enzyme'; 14 | const doc = jsdom.jsdom('
    '); 15 | const win = doc.defaultView; 16 | 17 | chai.use(sinonChai); 18 | chai.use(chaiEnzyme()); 19 | chai.should(); 20 | global.expect = chai.expect; 21 | global.sinon = sinon.sandbox.create(); 22 | global.document = doc; 23 | global.window = win; 24 | global.log = console.log; 25 | Object.keys(window).forEach((key) => { 26 | if (!(key in global)) { 27 | global[key] = window[key]; 28 | } 29 | }); 30 | 31 | // afterEach('setup: restore sinon mocks', () => global.sinon.restore()); 32 | global.testAsyncExpectations = (done, testExpectations) => { 33 | setTimeout(() => { 34 | testExpectations(); 35 | done(); 36 | }, 0); 37 | }; 38 | --------------------------------------------------------------------------------