├── . browserslistrc
├── .babelrc
├── .gitignore
├── .npmignore
├── .npmrc
├── .stylelintrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── components
│ ├── CodeFormatter
│ │ ├── CodeFormatter.css
│ │ └── index.js
│ ├── HomePage
│ │ └── index.js
│ ├── Navigation
│ │ ├── Desktop.css
│ │ ├── Desktop.js
│ │ ├── Mobile.css
│ │ ├── Mobile.js
│ │ ├── Shared.css
│ │ ├── Shared.js
│ │ └── index.js
│ └── PageWrapper
│ │ ├── GitHub.svg
│ │ ├── PageWrapper.css
│ │ └── index.js
├── index.css
├── index.html
├── index.js
├── pages
│ ├── AppBar
│ │ └── index.js
│ ├── BottomNavigation
│ │ └── index.js
│ ├── Button
│ │ ├── Button.css
│ │ └── index.js
│ ├── Collapse
│ │ ├── Collapse.css
│ │ └── index.js
│ ├── Dialog
│ │ ├── Dialog.css
│ │ └── index.js
│ ├── Grid
│ │ └── index.js
│ ├── List
│ │ └── index.js
│ ├── Page.css
│ ├── Page.js
│ ├── Paper
│ │ └── index.js
│ ├── SelectField
│ │ └── index.js
│ ├── SnackBar
│ │ ├── SnackBar.css
│ │ └── index.js
│ ├── Switch
│ │ └── index.js
│ ├── Table
│ │ └── index.js
│ ├── Tabs
│ │ └── index.js
│ ├── TextField
│ │ └── index.js
│ └── Typography
│ │ └── index.js
└── utils
│ └── globals.js
├── karma.conf.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── AppBar
│ ├── AppBar.css
│ ├── AppBar.js
│ ├── AppBar.spec.js
│ └── index.js
├── BottomNavigation
│ ├── BottomNavigation.css
│ ├── BottomNavigation.js
│ ├── BottomNavigation.spec.js
│ ├── BottomNavigationItem.css
│ ├── BottomNavigationItem.js
│ ├── BottomNavigationItem.spec.js
│ └── index.js
├── Button
│ ├── Button.css
│ ├── Button.js
│ ├── Button.spec.js
│ └── index.js
├── Collapse
│ ├── Collapse.css
│ ├── Collapse.js
│ ├── Collapse.spec.js
│ └── index.js
├── Dialog
│ ├── Dialog.css
│ ├── Dialog.js
│ ├── Dialog.spec.js
│ ├── DialogInner.js
│ └── index.js
├── Grid
│ ├── Grid.css
│ ├── Grid.js
│ ├── Grid.spec.js
│ ├── GridItem.css
│ ├── GridItem.js
│ ├── GridItem.spec.js
│ └── index.js
├── List
│ ├── List.css
│ ├── List.js
│ ├── List.spec.js
│ ├── ListItem.css
│ ├── ListItem.js
│ ├── ListItem.spec.js
│ └── index.js
├── Paper
│ ├── Paper.css
│ ├── Paper.js
│ ├── Paper.spec.js
│ └── index.js
├── Ripple
│ ├── Ripple.css
│ ├── Ripple.js
│ ├── Ripple.spec.js
│ ├── RippleItem.css
│ ├── RippleItem.js
│ ├── RippleItem.spec.js
│ └── index.js
├── Scrollable
│ ├── Scrollable.css
│ ├── Scrollable.js
│ ├── Scrollable.spec.js
│ └── index.js
├── SelectField
│ ├── SelectField.css
│ ├── SelectField.js
│ ├── SelectField.spec.js
│ └── index.js
├── SnackBar
│ ├── SnackBar.css
│ ├── SnackBar.js
│ ├── SnackBar.spec.js
│ ├── SnackBarItem.js
│ └── index.js
├── SvgIcon
│ ├── SvgIcon.css
│ ├── SvgIcon.js
│ ├── SvgIcon.spec.js
│ └── index.js
├── Switch
│ ├── Switch.css
│ ├── Switch.js
│ ├── Switch.spec.js
│ └── index.js
├── Table
│ ├── Table.css
│ ├── Table.js
│ ├── Table.spec.js
│ ├── TableBody.js
│ ├── TableCell.css
│ ├── TableCell.js
│ ├── TableHead.js
│ ├── TableRow.js
│ └── index.js
├── Tabs
│ ├── ScrollbarSize.js
│ ├── ScrollbarSize.spec.js
│ ├── Tab.css
│ ├── Tab.js
│ ├── Tab.spec.js
│ ├── Tabs.css
│ ├── Tabs.js
│ ├── Tabs.spec.js
│ └── index.js
├── TextField
│ ├── TextField.css
│ ├── TextField.js
│ ├── TextField.spec.js
│ ├── TextFieldAnimations.css
│ └── index.js
├── Typography
│ ├── Typography.css
│ ├── Typography.js
│ ├── Typography.spec.js
│ └── index.js
├── index.js
├── utils
│ ├── DOMBodyRender.js
│ └── DOMBodyRender.spec.js
└── variables.js
├── test
├── context.html
├── ssr.js
├── test_index.js
└── utils.js
├── webpack.config.docs.js
└── webpack.config.ssr.js
/. browserslistrc:
--------------------------------------------------------------------------------
1 | last 4 versions
2 | Android >= 4.4
3 | Chrome >= 49
4 | Edge >= 12
5 | Firefox >= 51
6 | IE >= 11
7 | iOS >= 8.4
8 | Safari >= 8
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "commonjs": {
4 | "presets": [
5 | [
6 | "@babel/preset-env",
7 | {
8 | "modules": "commonjs",
9 | "targets": {
10 | "browsers": ["last 2 versions"]
11 | }
12 | }
13 | ]
14 | ],
15 | "plugins": [
16 | "babel-plugin-transform-react-remove-prop-types",
17 | [
18 | "babel-plugin-css-modules-transform",
19 | {
20 | "generateScopedName": "[name].[local]",
21 | "prepend": [ "./postcss.config.js" ],
22 | "extractCss": {
23 | "dir": "./dist/",
24 | "relativeRoot": "./src/",
25 | "filename": "[path]/[name].css"
26 | }
27 | }
28 | ]
29 | ]
30 | },
31 | "module": {
32 | "plugins": [
33 | "babel-plugin-transform-react-remove-prop-types",
34 | [
35 | "babel-plugin-css-modules-transform",
36 | {
37 | "generateScopedName": "[name].[local]",
38 | "prepend": [ "./postcss.config.js" ],
39 | "keepImport": true,
40 | "extractCss": {
41 | "dir": "./es/",
42 | "relativeRoot": "./src/",
43 | "filename": "[path]/[name].css"
44 | }
45 | }
46 | ]
47 | ]
48 | }
49 | },
50 | "plugins": [
51 | "@babel/plugin-proposal-class-properties",
52 | "@babel/plugin-proposal-object-rest-spread",
53 | "@babel/plugin-transform-runtime",
54 | [
55 | "babel-plugin-transform-define", {
56 | "__TEST__": false
57 | }
58 | ]
59 | ],
60 | "presets": [
61 | [
62 | "@babel/preset-env",
63 | {
64 | "modules": false,
65 | "targets": {
66 | "browsers": ["last 2 versions"]
67 | }
68 | }
69 | ],
70 | "@babel/preset-react"
71 | ]
72 | }
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .coverage
2 | .tmp
3 | dist
4 | es
5 | node_modules
6 | .coveralls.yml
7 | npm-debug.log
8 |
9 | # Documentation-generated files
10 | /index.html
11 | /main.js
12 | /main.js.map
13 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 | !.babelrc
3 | !dist/
4 | !es/
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 | save-exact=true
3 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard",
3 | "rules": {
4 | "declaration-block-no-redundant-longhand-properties": null
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "stable"
4 | before_script:
5 | - npm run lint
6 | - npm run build
7 | install:
8 | - npm install
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.0.6](https://github.com/collegepulse/material-react-components/compare/v2.0.5...v2.0.6) (2019-09-03)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * Docs updates ([#21](https://github.com/collegepulse/material-react-components/issues/21)) ([c1439d6](https://github.com/collegepulse/material-react-components/commit/c1439d6))
7 |
8 |
9 |
10 | ## v2.0.5
11 |
12 | - [Tabs] Implementation detail changes to reduce bundle size
13 |
14 | ## v2.0.4
15 |
16 | - [Switch] [TextField] enhancements to support Preact
17 |
18 | ## v2.0.3
19 |
20 | - [Dialog] fix for better support for Preact
21 |
22 | ## v2.0.2
23 |
24 | - Upgrade to @babel/runtime@7.0.0
25 |
26 | ## v2.0.1
27 |
28 | - Exclude require statements to CSS files in CommonJS build
29 |
30 | ## v2.0.0
31 |
32 | ### API Changes
33 |
34 | - `Switch` and `TextField` no longer autogenerate an aria-labelledby attribute for their input fields. They can be supplied via the labelId prop. This change was needed to fix server-side rendering hydration errors on the client. uuid was also removed from dependencies, so expect a slightly smaller bundle size.
35 |
36 | ### Build Changes
37 |
38 | For versions `1.2.3` (inclusive) and prior, the distributions were incorrect.
39 |
40 | - The commonjs `dist/index.js` file had module ES module import/export syntax.
41 | - The commonjs source files had their css inlined (poor for performance and causes bundle size bloat).
42 | - The module build couldn't be tree-shaken since `{sideEffects: false}` was not set in package.json.
43 | - Both builds missed other opportunities for reducing bundle size, like removing prop types.
44 |
45 | ## v1.2.3
46 |
47 | - remove `deep-assign` from dependencies
48 | - remove `keycode` from dependencies
49 |
50 | ## v1.2.2
51 |
52 | No changes.
53 |
54 | ## v1.2.1
55 |
56 | - Upgrade from babel 6 to 7
57 | - switch linter from eslint to xo
58 | - upgrade to webpack 4
59 | - move babel dependencies to devDependencies (where applicable)
60 |
61 | ## v1.2.0
62 |
63 | - `SelectField` component implementation
64 |
65 | ## v1.1.0
66 |
67 | - Added `Table`, partial implementation (https://material.io/guidelines/components/data-tables.html)
68 | - Upgraded some dependencies
69 |
70 | ## 1.0.0
71 |
72 | 🎉 1.0.0 release 🎉
73 |
74 | - **Support for React 16.** Cleaned up warnings that were present upon upgrade.
75 | - **Removed undocumented API's.** Some components accepted an undocumented domRef property; these have been removed. Out of an abundance of caution, a major release bump-- upgrading should be relatively effortless, however).
76 |
77 | ## 0.0.5
78 |
79 | - [SnackBar] fix centering on IE 11 and Safari
80 |
81 | ## 0.0.4
82 |
83 | - [GridItem] fix issue where child DOM elements could overflow outside the grid
84 |
85 | ## 0.0.3
86 |
87 | - [Tabs] a11y enhancements, fix UI bug where tabs had incorrect height
88 |
89 | ## v0.0.2-0
90 |
91 | ## v0.0.1-0
92 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 CollegePulse
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ### ✨[Demo Site](https://collegepulse.github.io/material-react-components/): live component demos, code samples & more. ✨
3 | material-react-components
4 | =========================
5 |
6 | [![npm package][npm-image]][npm-url]
7 | [![Build Status][travis-image]][travis-url]
8 | [![Coverage Status][coveralls-image]][coveralls-url]
9 | [![Dependencies Status][david-image]][david-url]
10 |
11 | This is a collection of React components that implement Google's [material design specification](https://material.io/guidelines). The goals of this project include:
12 |
13 | - Minimal to zero configuration to consume components through carefully-crafted, lightweight implementations.
14 | - Be fit for enterprise consumption: components are to be performant, accessible, unit tested, cross-browser supported, support server-side rendering, etc.
15 |
16 |
17 | Examples
18 | --------
19 |
20 | See our [material-react-components-examples](https://github.com/collegepulse/material-react-components-examples) repo for examples of small projects that demonstrate how to import the ES5 or ES6 components with Webpack.
21 |
22 | Acknowlegements
23 | ---------------
24 |
25 | [ ](https://www.browserstack.com/)
26 |
27 | [BrowserStack](https://www.browserstack.com/) has provided us with the tools and infrastructure necessary to build a high quality component library by testing our components on an array of browsers. Thank you.
28 |
29 | [material-ui](https://github.com/callemall/material-ui) has been an influence in some of our component implementations. We are appreciative of the effort and contributions happening over there.
30 |
31 | [npm-image]:https://img.shields.io/npm/v/material-react-components.svg
32 | [npm-url]:https://www.npmjs.com/package/material-react-components
33 | [travis-image]:https://travis-ci.org/collegepulse/material-react-components.svg?branch=master
34 | [travis-url]:https://travis-ci.org/collegepulse/material-react-components
35 | [coveralls-image]:https://coveralls.io/repos/github/collegepulse/material-react-components/badge.svg?branch=master
36 | [coveralls-url]:https://coveralls.io/github/collegepulse/material-react-components?branch=master
37 | [david-image]:https://david-dm.org/collegepulse/material-react-components/status.svg
38 | [david-url]:https://david-dm.org/collegepulse/material-react-components
39 |
--------------------------------------------------------------------------------
/docs/components/CodeFormatter/CodeFormatter.css:
--------------------------------------------------------------------------------
1 | .code {
2 | font-family: monospace;
3 | white-space: pre;
4 | overflow-x: scroll;
5 | }
6 |
7 | :global(.token) {
8 | background: transparent !important;
9 | }
10 |
--------------------------------------------------------------------------------
/docs/components/CodeFormatter/index.js:
--------------------------------------------------------------------------------
1 | import lightTheme from '!raw-loader!prismjs/themes/prism.css'; // eslint-disable-line
2 | import prism from 'prismjs';
3 | import 'prismjs/components/prism-markup';
4 | import 'prismjs/components/prism-clike';
5 | import 'prismjs/components/prism-javascript';
6 | import 'prismjs/components/prism-jsx';
7 | import PropTypes from 'prop-types';
8 | import React from 'react';
9 | import Scrollable from '../../../src/Scrollable';
10 | import Styles from './CodeFormatter.css';
11 |
12 | const styleNode = window.document.createElement('style');
13 | window.document.head.appendChild(styleNode);
14 | styleNode.textContent = lightTheme;
15 |
16 | /* Removes the fewest number of spaces present in front of every line. */
17 | function formatCode(str) {
18 | if (!str) {
19 | return '';
20 | }
21 | const lines = str.split('\n');
22 | let fewestStartingSpaces = Infinity;
23 | lines.map((line) => {
24 | if (line) {
25 | fewestStartingSpaces = Math.min(fewestStartingSpaces, line.search(/\S/));
26 | }
27 | });
28 | return lines.reduce((acc, val) => (
29 | `${acc}${val.substring(fewestStartingSpaces)}\n`
30 | ), '');
31 | }
32 |
33 | class CodeFormatter extends React.Component {
34 | render() {
35 | const {code, ...other} = this.props;
36 | const formattedCode = formatCode(code);
37 | const highlightedCode = prism.highlight(formattedCode, prism.languages.jsx);
38 | return (
39 |
40 |
45 |
46 | );
47 | }
48 | }
49 |
50 | CodeFormatter.defaultProps = {
51 | code: null
52 | };
53 |
54 | CodeFormatter.propTypes = {
55 | code: PropTypes.string
56 | };
57 |
58 | export default CodeFormatter;
59 |
--------------------------------------------------------------------------------
/docs/components/HomePage/index.js:
--------------------------------------------------------------------------------
1 | import CodeFormatter from '../CodeFormatter';
2 | import React from 'react';
3 | import Typography from '../../../src/Typography';
4 |
5 | function Base() {
6 | return (
7 |
8 |
12 | Installation
13 |
14 |
15 |
19 | Configuration
20 |
21 |
22 |
23 | Two methods are available for using the library:
24 | {' ES6 (recommended) or ES5. See the '}
25 |
26 | example projects
27 |
28 | {' that demonstrate both.'}
29 |
30 |
34 | ES6 Configuration (recommended)
35 |
36 | Add the following loaders to your Webpack configuration:
37 |
58 |
{'If using the material-design-icons SVG icons, or any components containing SVG\'s (e.g., SelectField), include the following loader:'}
59 |
75 |
79 | ES5 Configuration
80 |
81 |
Follow the SVG instructions in the ES6 Configuration section, if applicable.
82 |
83 | );
84 | }
85 |
86 | export default Base;
87 |
--------------------------------------------------------------------------------
/docs/components/Navigation/Desktop.css:
--------------------------------------------------------------------------------
1 | .root {
2 | display: none;
3 | flex: 0 0 auto;
4 | width: 250px;
5 | }
6 |
7 | @media (min-width: $screenMd) {
8 | .root {
9 | display: inherit;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/docs/components/Navigation/Desktop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Scrollable from '../../../src/Scrollable';
3 | import Shared from './Shared';
4 | import Styles from './Desktop.css';
5 |
6 | function Desktop() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default Desktop;
17 |
--------------------------------------------------------------------------------
/docs/components/Navigation/Mobile.css:
--------------------------------------------------------------------------------
1 | .root {
2 | position: fixed;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | z-index: 2500;
8 | }
9 |
10 | .overlay {
11 | position: absolute;
12 | top: 0;
13 | left: 0;
14 | right: 0;
15 | bottom: 0;
16 | background-color: $black54;
17 | animation-duration: 350ms;
18 | animation-fill-mode: forwards;
19 | animation-name: fade-in;
20 | }
21 |
22 | @keyframes fade-in {
23 | 0% {
24 | opacity: 0;
25 | }
26 |
27 | 100% {
28 | opacity: 1;
29 | }
30 | }
31 |
32 | @keyframes fade-out {
33 | 0% {
34 | opacity: 1;
35 | }
36 |
37 | 100% {
38 | opacity: 0;
39 | }
40 | }
41 |
42 | .content {
43 | transition: all 250ms;
44 | position: fixed;
45 | animation-duration: 350ms;
46 | animation-fill-mode: forwards;
47 | animation-name: slide-in;
48 | }
49 |
50 | @keyframes slide-in {
51 | 0% {
52 | transform: translateX(-250px);
53 | }
54 |
55 | 100% {
56 | transform: translateX(0);
57 | }
58 | }
59 |
60 | @keyframes slide-out {
61 | 0% {
62 | transform: translateX(0);
63 | }
64 |
65 | 100% {
66 | transform: translateX(-250px);
67 | }
68 | }
--------------------------------------------------------------------------------
/docs/components/Navigation/Mobile.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import Shared from './Shared';
4 | import Styles from './Mobile.css';
5 | import TransitionGroup from 'react-transition-group/TransitionGroup';
6 |
7 | function FirstChild(props) {
8 | const childrenArray = React.Children.toArray(props.children);
9 | return childrenArray[0] || null;
10 | }
11 |
12 | function Mobile({onClose, open}) {
13 | return (
14 |
15 | {open && }
16 |
17 | );
18 | }
19 |
20 | Mobile.defaultProps = {
21 | open: false
22 | };
23 |
24 | Mobile.propTypes = {
25 | onClose: PropTypes.func.isRequired,
26 | open: PropTypes.bool
27 | };
28 |
29 | class MobileInner extends React.Component {
30 | componentWillLeave(cb) {
31 | this.content.style.animationName = Styles['slide-out'];
32 | this.overlay.style.animationName = Styles['fade-out'];
33 | setTimeout(() => {
34 | cb();
35 | }, 350);
36 | }
37 |
38 | render() {
39 | const {onClose} = this.props;
40 | return (
41 |
42 |
(this.overlay = c)}
46 | />
47 |
(this.content = c)}
50 | >
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | MobileInner.defaultProps = {
59 | onClose: () => {}
60 | };
61 |
62 | MobileInner.propTypes = {
63 | onClose: PropTypes.func
64 | };
65 |
66 | export default Mobile;
67 |
--------------------------------------------------------------------------------
/docs/components/Navigation/Shared.css:
--------------------------------------------------------------------------------
1 | .root {
2 | border-right: 1px solid #ddd;
3 | flex: 1 0 auto;
4 | height: 100vh;
5 | position: fixed;
6 | width: 250px;
7 | box-sizing: border-box;
8 | overflow-y: auto;
9 | zindex: 1;
10 | }
11 |
12 | .title {
13 | padding: 15px;
14 | }
15 |
16 | .subtitle {
17 | margin-top: 5px;
18 | }
19 |
20 | .inactive .primary {
21 | color: $black54;
22 | }
23 |
24 | .active .primary {
25 | color: $black;
26 | }
27 |
--------------------------------------------------------------------------------
/docs/components/Navigation/Shared.js:
--------------------------------------------------------------------------------
1 | import {Link, NavLink} from 'react-router-dom';
2 | import List, {ListItem} from '../../../src/List';
3 | import {makeURL} from '../../utils/globals';
4 | import Paper from '../../../src/Paper';
5 | import PropTypes from 'prop-types';
6 | import React from 'react';
7 | import Scrollable from '../../../src/Scrollable';
8 | import Styles from './Shared.css';
9 | import Typography from '../../../src/Typography';
10 |
11 | const items = [
12 | {
13 | label: 'AppBar',
14 | url: makeURL('/AppBar')
15 | },
16 | {
17 | label: 'BottomNavigation',
18 | url: makeURL('/BottomNavigation')
19 | },
20 | {
21 | label: 'Button',
22 | url: makeURL('/Button')
23 | },
24 | {
25 | label: 'Collapse',
26 | url: makeURL('/Collapse')
27 | },
28 | {
29 | label: 'Dialog',
30 | url: makeURL('/Dialog')
31 | },
32 | {
33 | label: 'Grid',
34 | url: makeURL('/Grid')
35 | },
36 | {
37 | label: 'List',
38 | url: makeURL('/List')
39 | },
40 | {
41 | label: 'Paper',
42 | url: makeURL('/Paper')
43 | },
44 | {
45 | label: 'SelectField',
46 | url: makeURL('/SelectField')
47 | },
48 | {
49 | label: 'SnackBar',
50 | url: makeURL('/SnackBar')
51 | },
52 | {
53 | label: 'Switch',
54 | url: makeURL('/Switch')
55 | },
56 | {
57 | label: 'Table',
58 | url: makeURL('/Table')
59 | },
60 | {
61 | label: 'Tabs',
62 | url: makeURL('/Tabs')
63 | },
64 | {
65 | label: 'TextField',
66 | url: makeURL('/TextField')
67 | },
68 | {
69 | label: 'Typography',
70 | url: makeURL('/Typography')
71 | }
72 | ];
73 |
74 | function Shared({onClick}) {
75 | return (
76 |
77 |
78 |
79 | material-react-components
80 | By CollegePulse
81 |
82 |
83 | {items.map(item => (
84 |
95 | {item.label}
96 |
97 | )}
98 | />
99 | ))}
100 |
101 |
102 |
103 | );
104 | }
105 |
106 | Shared.defaultProps = {
107 | onClick: () => {}
108 | };
109 |
110 | Shared.propTypes = {
111 | onClick: PropTypes.func
112 | };
113 |
114 | export default Shared;
115 |
116 |
--------------------------------------------------------------------------------
/docs/components/Navigation/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './Desktop';
2 |
--------------------------------------------------------------------------------
/docs/components/PageWrapper/GitHub.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/components/PageWrapper/PageWrapper.css:
--------------------------------------------------------------------------------
1 | .rootInner {
2 | width: 100%;
3 | overflow-y: auto;
4 | height: 100%;
5 | position: absolute;
6 | }
7 |
8 | @media (min-width: $screenMd) {
9 | .rootInner {
10 | width: calc(100vw - 250px);
11 | left: 250px;
12 | }
13 | }
14 |
15 | .appBar {
16 | position: fixed;
17 | z-index: 1;
18 | }
19 |
20 | @media (min-width: $screenMd) {
21 | .appBar {
22 | position: relative;
23 | }
24 | }
25 |
26 | .iconWrapper {
27 | display: inherit;
28 | }
29 |
30 | .iconLink {
31 | display: flex;
32 | }
33 |
34 | @media (min-width: $screenMd) {
35 | .iconWrapper {
36 | display: none;
37 | }
38 | }
39 |
40 | .icon {
41 | fill: $white;
42 | }
43 |
44 | .content {
45 | margin: 100px 20px 20px;
46 | }
47 |
48 | @media (min-width: $screenMd) {
49 | .content {
50 | margin: 50px;
51 | }
52 | }
--------------------------------------------------------------------------------
/docs/components/PageWrapper/index.js:
--------------------------------------------------------------------------------
1 | import AppBar from '../../../src/AppBar';
2 | import AppBarDocs from '../../pages/AppBar';
3 | import BottomNavigation from '../../pages/BottomNavigation';
4 | import Button from '../../pages/Button';
5 | import Collapse from '../../pages/Collapse';
6 | import Dialog from '../../pages/Dialog';
7 | import GitHub from './GitHub.svg';
8 | import Grid from '../../pages/Grid';
9 | import HomePage from '../HomePage';
10 | import List from '../../pages/List';
11 | import Mobile from '../Navigation/Mobile';
12 | import Menu from 'material-design-icons/navigation/svg/production/ic_menu_24px.svg';
13 | import {makeURL} from '../../utils/globals';
14 | import Paper from '../../pages/Paper';
15 | import React from 'react';
16 | import {Route} from 'react-router-dom';
17 | import SelectField from '../../pages/SelectField';
18 | import SnackBar from '../../pages/SnackBar';
19 | import Styles from './PageWrapper.css';
20 | import Switch from '../../pages/Switch';
21 | import SvgIcon from '../../../src/SvgIcon';
22 | import Table from '../../pages/Table';
23 | import Tabs from '../../pages/Tabs';
24 | import TextField from '../../pages/TextField';
25 | import TypographyDocs from '../../pages/Typography';
26 |
27 | class PageWrapper extends React.Component {
28 | constructor(props) {
29 | super(props);
30 | this.onClick = this.onClick.bind(this);
31 | this.state = {
32 | open: false
33 | };
34 | }
35 |
36 | onClick() {
37 | this.setState({
38 | open: !this.state.open
39 | });
40 | }
41 |
42 | render() {
43 | const pageName = window.location.pathname.split('/').pop() || 'material-react-components';
44 | return (
45 |
46 |
50 |
56 |
57 | }
58 | secondary={
59 |
64 |
69 |
70 | }
71 | style={{width: '100%', color: '#fff'}}
72 | >
73 | {`<${pageName} />`}
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | );
96 | }
97 | }
98 |
99 | export default PageWrapper;
100 |
--------------------------------------------------------------------------------
/docs/index.css:
--------------------------------------------------------------------------------
1 | .index {
2 | display: flex;
3 | flex-direction: row;
4 | height: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | material-react-components
8 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | import {BrowserRouter} from 'react-router-dom';
2 | import Navigation from './components/Navigation';
3 | import PageWrapper from './components/PageWrapper';
4 | import React from 'react';
5 | import {render} from 'react-dom';
6 | import Styles from './index.css';
7 | import 'babel-polyfill';
8 |
9 | const app = document.createElement('div');
10 | app.style.height = '100%';
11 | document.body.appendChild(app);
12 |
13 | function Index() {
14 | return (
15 |
16 |
20 |
21 | );
22 | }
23 |
24 | render( , app);
25 |
--------------------------------------------------------------------------------
/docs/pages/AppBar/index.js:
--------------------------------------------------------------------------------
1 | import AppBar from '../../../src/AppBar';
2 | import Colors from '../../../src/variables';
3 | import CodeFormatter from '../../components/CodeFormatter';
4 | import Menu from 'material-design-icons/navigation/svg/production/ic_menu_24px.svg';
5 | import Page from '../Page';
6 | import React from 'react';
7 | import Search from 'material-design-icons/action/svg/production/ic_search_24px.svg';
8 | import SvgIcon from '../../../src/SvgIcon';
9 | import Switch from '../../../src/Switch';
10 | import TextField from '../../../src/TextField';
11 |
12 | class AppBarDocs extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.onControlPanel = this.onControlPanel.bind(this);
16 | this.state = {
17 | backgroundColor: Colors.$primary,
18 | children: 'AppBar',
19 | elevation: '2',
20 | style: '{"fill": "#FFF", "color": "#FFF"}',
21 | primary: true,
22 | secondary: true
23 | };
24 | }
25 |
26 | onControlPanel(key, value) {
27 | this.setState({[key]: value});
28 | }
29 |
30 | render() {
31 | const {backgroundColor, children, elevation, primary, secondary, style} = this.state;
32 | let elevationNum;
33 | let styleObj;
34 | try {
35 | elevationNum = Number(this.state.elevation);
36 | styleObj = JSON.parse(style);
37 | } catch (e) {
38 | styleObj = {};
39 | }
40 | return (
41 | }
49 | secondary={secondary && }
50 | >
51 | {children}
52 |
53 | }
54 | buildYourOwnCode={
55 | ' : null}}
67 | secondary={${secondary ? ' ' : null}}
68 | >
69 | ${children}
70 | `
71 | }
72 | />
73 | }
74 | buildYourOwnControlPanel={[
75 | (this.onControlPanel('children', e.target.value))}
77 | label="children"
78 | value={children}
79 | />,
80 | (this.onControlPanel('elevation', e.target.value))}
83 | label="elevation"
84 | value={elevation}
85 | />,
86 | (this.onControlPanel('style', e.target.value))}
88 | label="Style"
89 | value={style}
90 | />,
91 | (this.onControlPanel('primary', e.target.checked))}
93 | checked={primary}
94 | label="primary"
95 | />,
96 | (this.onControlPanel('secondary', e.target.checked))}
98 | checked={secondary}
99 | label="secondary"
100 | />
101 | ]}
102 | examples={
103 |
104 |
105 | AppBar
106 |
107 |
108 | }
109 | />
110 | );
111 | }
112 | }
113 |
114 | export default AppBarDocs;
115 |
--------------------------------------------------------------------------------
/docs/pages/BottomNavigation/index.js:
--------------------------------------------------------------------------------
1 | import BottomNavigation, {BottomNavigationItem} from '../../../src/BottomNavigation';
2 | import Page from '../Page';
3 | import React from 'react';
4 | import TextField from '../../../src/TextField';
5 |
6 | class BottomNavigationDocs extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.onControlPanel = this.onControlPanel.bind(this);
10 | this.state = {
11 | label: 'label'
12 | };
13 | }
14 |
15 | onControlPanel(key, value) {
16 | this.setState({[key]: value});
17 | }
18 |
19 | render() {
20 | const {label} = this.state;
21 | return (
22 |
26 | {[1, 2, 3].map(key => (
27 | {label}
28 | ))}
29 |
30 | }
31 | buildYourOwnControlPanel={[
32 | (this.onControlPanel('label', e.target.value))}
34 | label="label"
35 | value={label}
36 | />
37 | ]}
38 | examples={
39 |
40 |
41 | Foo
42 | Bar
43 | Baz
44 |
45 |
46 | }
47 | />
48 | );
49 | }
50 |
51 | }
52 |
53 | export default BottomNavigationDocs;
54 |
--------------------------------------------------------------------------------
/docs/pages/Button/Button.css:
--------------------------------------------------------------------------------
1 | .flex {
2 | display: flex;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/Button/index.js:
--------------------------------------------------------------------------------
1 | import Add from 'material-design-icons/content/svg/production/ic_add_24px.svg';
2 | import Button from '../../../src/Button';
3 | import Edit from 'material-design-icons/editor/svg/production/ic_mode_edit_24px.svg';
4 | import Page from '../Page';
5 | import React from 'react';
6 | import Styles from './Button.css';
7 | import Switch from '../../../src/Switch';
8 | import TextField from '../../../src/TextField';
9 | import Variables from '../../../src/variables';
10 |
11 | class ButtonDocs extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.onControlPanel = this.onControlPanel.bind(this);
15 | this.state = {
16 | children: 'children',
17 | textColor: '#FFF',
18 | buttonColor: Variables.$primary,
19 | fab: false,
20 | fill: '#FFF',
21 | focusRippleDisabled: false
22 | };
23 | }
24 |
25 | onControlPanel(key, value) {
26 | this.setState({[key]: value});
27 | }
28 |
29 | render() {
30 | const {children, textColor, buttonColor, fill, fab, focusRippleDisabled} = this.state;
31 | return (
32 |
41 | {fab ? : children}
42 |
43 | }
44 | buildYourOwnControlPanel={[
45 | (this.onControlPanel('children', e.target.value))}
47 | label="children"
48 | value={children}
49 | helperText="For demo, hidden when fab is enabled"
50 | />,
51 | (this.onControlPanel('textColor', e.target.value))}
53 | label="textColor"
54 | value={textColor}
55 | helperText="Button text color"
56 | />,
57 | (this.onControlPanel('buttonColor', e.target.value))}
59 | label="buttonColor"
60 | value={buttonColor}
61 | helperText="Button background color"
62 | />,
63 | (this.onControlPanel('fill', e.target.value))}
65 | label="fill"
66 | value={fill}
67 | helperText="SVG icon color (applicable when fab mode is enabled)"
68 | />,
69 | (this.onControlPanel('fab', e.target.checked))}
71 | checked={fab}
72 | label="fab"
73 | />,
74 | (this.onControlPanel('focusRippleDisabled', e.target.checked))}
76 | checked={focusRippleDisabled}
77 | label="focusRippleDisabled"
78 | />
79 | ]}
80 | examples={
81 |
82 |
83 | Default
84 | Custom Text Color
85 | Custom Button Color
86 | Custom Button Color
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | }
98 | />
99 | );
100 | }
101 | }
102 |
103 | export default ButtonDocs;
104 |
--------------------------------------------------------------------------------
/docs/pages/Collapse/Collapse.css:
--------------------------------------------------------------------------------
1 | .collapse {
2 | width: 100%;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/Collapse/index.js:
--------------------------------------------------------------------------------
1 | import Collapse from '../../../src/Collapse';
2 | import Page from '../Page';
3 | import Paper from '../../../src/Paper';
4 | import React from 'react';
5 | import Styles from './Collapse.css';
6 | import Switch from '../../../src/Switch';
7 | import TextField from '../../../src/TextField';
8 |
9 | class CollapseDocs extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.onControlPanel = this.onControlPanel.bind(this);
13 | this.state = {
14 | collapseBaseline: false,
15 | collapsedOpenOnInitialRender: true,
16 | open: true,
17 | children: 'The\nCollapse\nComponent\nInner\nText'
18 | };
19 | }
20 |
21 | onControlPanel(key, value) {
22 | if (value) {
23 | return this.setState({[key]: value});
24 | }
25 | return this.setState({[key]: !this.state[key]});
26 | }
27 |
28 | render() {
29 | return (
30 |
34 |
35 | {this.state.children}
36 |
37 |
38 | }
39 | buildYourOwnControlPanel={[
40 | (this.onControlPanel('open', e.target.checked))}
42 | checked={this.state.open}
43 | label="open"
44 | />,
45 | (this.onControlPanel('children', e.target.value))}
47 | label="children"
48 | value={this.state.children}
49 | multiline
50 | helperText="Wrapped in a element with white-space: pre-wrap; and 10px padding."
51 | />
52 | ]}
53 | examples={
54 |
55 |
(this.onControlPanel('collapseBaseline'))} label={'Initially Closed'} />
56 |
57 |
58 | This
59 | is
60 | really
61 | long
62 | content
63 | that
64 | should
65 | animate.
66 |
67 |
68 | (this.onControlPanel('collapsedOpenOnInitialRender'))} label={'Initially Opened'} />
69 |
70 |
71 | This
72 | is
73 | really
74 | long
75 | content
76 | that
77 | should
78 | animate.
79 |
80 |
81 |
82 | }
83 | />
84 | );
85 | }
86 | }
87 |
88 | export default CollapseDocs;
89 |
--------------------------------------------------------------------------------
/docs/pages/Dialog/Dialog.css:
--------------------------------------------------------------------------------
1 | .flex {
2 | display: flex;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/Dialog/index.js:
--------------------------------------------------------------------------------
1 | import Button from '../../../src/Button';
2 | import Dialog from '../../../src/Dialog';
3 | import Page from '../Page';
4 | import React from 'react';
5 | import Styles from './Dialog.css';
6 | import TextField from '../../../src/TextField';
7 |
8 | class DialogDocs extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.onControlPanel = this.onControlPanel.bind(this);
12 | this.state = {
13 | buildYourOwnDialogOpen: false,
14 | title: 'Title',
15 | description: 'Description',
16 | dialog1Open: false
17 | };
18 | }
19 |
20 | onControlPanel(key, value) {
21 | if (value) {
22 | return this.setState({[key]: value});
23 | }
24 | return this.setState({[key]: !this.state[key]});
25 | }
26 |
27 |
28 | render() {
29 | return (
30 |
34 | (this.onControlPanel('buildYourOwnDialogOpen'))}>Open Dialog
35 | (this.onControlPanel('buildYourOwnDialogOpen'))}
38 | title={this.state.title}
39 | description={this.state.description}
40 | actions={[
41 | (this.onControlPanel('buildYourOwnDialogOpen'))}>Disagree ,
42 | (this.onControlPanel('buildYourOwnDialogOpen'))}>Agree
43 | ]}
44 | />
45 |
46 | }
47 | buildYourOwnControlPanel={[
48 | (this.onControlPanel('title', e.target.value))}
50 | label="title"
51 | value={this.state.title}
52 | />,
53 | (this.onControlPanel('description', e.target.value))}
55 | label="description"
56 | value={this.state.description}
57 | />
58 | ]}
59 | examples={
60 |
61 | (this.onControlPanel('dialog1Open'))}>Open Dialog
62 | (this.onControlPanel('dialog1Open'))}
65 | title={'Use Google\'s location service?'}
66 | description={'Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.'}
67 | actions={[
68 | (this.onControlPanel('dialog1Open'))}>Disagree ,
69 | (this.onControlPanel('dialog1Open'))}>Agree
70 | ]}
71 | />
72 |
73 | }
74 | />
75 | );
76 | }
77 | }
78 |
79 | export default DialogDocs;
80 |
--------------------------------------------------------------------------------
/docs/pages/Grid/index.js:
--------------------------------------------------------------------------------
1 | import Grid, {GridItem} from '../../../src/Grid';
2 | import Page from '../Page';
3 | import Paper from '../../../src/Paper';
4 | import React from 'react';
5 | import Typography from '../../../src/Typography';
6 |
7 | class GridDocs extends React.Component {
8 | render() {
9 | return (
10 |
14 |
15 | Full-width
16 | grids use fluid columns.
17 |
18 |
19 | {[1, 2, 3, 4, 5, 6, 7, 8].map(item => (
20 |
21 |
31 | xs=12, sm=6, md=4, lg=3
32 |
33 |
34 | ))}
35 |
36 |
37 | Centered
38 | grids use fixed columns.
39 |
40 |
41 | {[1, 2, 3, 4, 5, 6, 7].map((item) => {
42 | let xs;
43 | if (item === 1) {
44 | xs = 12;
45 | } else if (item === 2 || item === 3) {
46 | xs = 6;
47 | } else {
48 | xs = 3;
49 | }
50 | return (
51 |
52 |
62 | {`xs=${xs}`}
63 |
64 |
65 | );
66 | })}
67 |
68 |
69 | }
70 | />
71 | );
72 | }
73 | }
74 |
75 | export default GridDocs;
76 |
--------------------------------------------------------------------------------
/docs/pages/List/index.js:
--------------------------------------------------------------------------------
1 | import Folder from 'material-design-icons/file/svg/design/ic_folder_24px.svg';
2 | import Grid, {GridItem} from '../../../src/Grid';
3 | import List, {ListItem} from '../../../src/List';
4 | import MoreVert from 'material-design-icons/navigation/svg/production/ic_more_vert_24px.svg';
5 | import Page from '../Page';
6 | import React from 'react';
7 | import Switch from '../../../src/Switch';
8 | import SvgIcon from '../../../src/SvgIcon';
9 | import TextField from '../../../src/TextField';
10 |
11 | class ListDocs extends React.Component {
12 |
13 | constructor(props) {
14 | super(props);
15 | this.onControlPanel = this.onControlPanel.bind(this);
16 | this.state = {
17 | primary: 'Primary',
18 | secondary: 'Secondary',
19 | avatar: true,
20 | action: true,
21 | style: '{"width": "300px"}',
22 | arrowNavigation: false
23 | };
24 | }
25 |
26 | onControlPanel(key, value) {
27 | this.setState({[key]: value});
28 | }
29 |
30 | render() {
31 | const {primary, secondary, avatar, action, style, arrowNavigation} = this.state;
32 | let styleObj = {};
33 | try {
34 | styleObj = JSON.parse(style);
35 | } catch (e) { }
36 | return (
37 |
41 |
42 | {[1, 2, 3].map(key => (
43 | }
48 | action={action && }
49 | />
50 | ))}
51 |
52 |
53 | }
54 | buildYourOwnControlPanel={[
55 | (this.onControlPanel('primary', e.target.value))}
57 | label="primary"
58 | value={primary}
59 | helperText=" "
60 | />,
61 | (this.onControlPanel('secondary', e.target.value))}
63 | label="secondary"
64 | value={secondary}
65 | helperText=" "
66 | />,
67 | (this.onControlPanel('style', e.target.value))}
69 | label="style"
70 | value={style}
71 | helperText="JSON will be converted to a style object"
72 | />,
73 | (this.onControlPanel('avatar', e.target.checked))}
75 | checked={avatar}
76 | label="avatar"
77 | />,
78 | (this.onControlPanel('action', e.target.checked))}
80 | checked={action}
81 | label="action"
82 | />,
83 | (this.onControlPanel('arrowNavigation', e.target.checked))}
85 | checked={arrowNavigation}
86 | label="arrowNavigation"
87 | />
88 | ]}
89 | examples={
90 |
91 |
92 |
93 | {[1, 2, 3].map(key => (
94 |
95 | ))}
96 |
97 |
98 |
99 |
100 | {[1, 2, 3].map(key => (
101 | }
105 | />
106 | ))}
107 |
108 |
109 |
110 |
111 | {[1, 2, 3].map(key => (
112 | }
115 | primary={'Primary'}
116 | secondary={'Secondary'}
117 | />
118 | ))}
119 |
120 |
121 |
122 |
123 | {[1, 2, 3].map(key => (
124 | }
127 | primary={'Primary'}
128 | secondary={'Secondary'}
129 | action={ }
130 | />
131 | ))}
132 |
133 |
134 |
135 | }
136 | />
137 | );
138 | }
139 | }
140 |
141 | export default ListDocs;
142 |
--------------------------------------------------------------------------------
/docs/pages/Page.css:
--------------------------------------------------------------------------------
1 | .buildYourOwn {
2 | padding: 20px;
3 | background-color: rgba(0, 0, 0, 0.025);
4 | display: flex;
5 | justify-content: center;
6 | max-width: 100%;
7 | }
8 |
9 | @media (min-width: $screenMd) {
10 | padding: 40px 100px 40px;
11 | }
12 |
13 | .buildYourOwnCode,
14 | .buildYourOwnControlPanel {
15 | padding: 0 20px 20px;
16 | background-color: rgba(0, 0, 0, 0.025);
17 | }
18 |
--------------------------------------------------------------------------------
/docs/pages/Page.js:
--------------------------------------------------------------------------------
1 | import Grid, {GridItem} from '../../src/Grid';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Page.css';
5 | import Typography from '../../src/Typography';
6 |
7 | function DocPage({
8 | componentName,
9 | description,
10 | buildYourOwn,
11 | buildYourOwnCode,
12 | buildYourOwnControlPanel,
13 | examples
14 | }) {
15 | return (
16 |
17 |
21 | {componentName}
22 |
23 |
24 | {description}
25 |
26 | {buildYourOwn && (
27 |
31 | Build your own
32 |
33 | )}
34 | {buildYourOwn && (
35 |
36 | {buildYourOwn}
37 |
38 | )}
39 | {buildYourOwnCode && (
40 |
41 | {buildYourOwnCode}
42 |
43 | )}
44 | {buildYourOwnControlPanel && (
45 |
46 |
47 | {/* eslint-disable react/no-array-index-key */}
48 | {buildYourOwnControlPanel.map((item, index) => {
49 | const grid = {};
50 | if (buildYourOwnControlPanel.length >= 2) {
51 | grid.sm = 6;
52 | }
53 | if (buildYourOwnControlPanel.length >= 3) {
54 | grid.md = 4;
55 | }
56 | return (
57 |
66 | {item}
67 |
68 | );
69 | })}
70 | {/* eslint-enable react/no-array-index-key */}
71 |
72 |
73 | )}
74 | {examples && (
75 |
79 | Examples
80 |
81 | )}
82 | {examples}
83 |
84 | );
85 | }
86 |
87 | DocPage.defaultProps = {
88 | componentName: null,
89 | description: null,
90 | buildYourOwn: null,
91 | buildYourOwnCode: null,
92 | buildYourOwnControlPanel: null,
93 | examples: null
94 | };
95 |
96 | DocPage.propTypes = {
97 | componentName: PropTypes.node,
98 | description: PropTypes.node,
99 | buildYourOwn: PropTypes.node,
100 | buildYourOwnCode: PropTypes.node,
101 | buildYourOwnControlPanel: PropTypes.arrayOf(PropTypes.node),
102 | examples: PropTypes.node
103 | };
104 |
105 | export default DocPage;
106 |
--------------------------------------------------------------------------------
/docs/pages/Paper/index.js:
--------------------------------------------------------------------------------
1 | import Grid, {GridItem} from '../../../src/Grid';
2 | import Page from '../Page';
3 | import Paper from '../../../src/Paper';
4 | import React from 'react';
5 | import TextField from '../../../src/TextField';
6 |
7 | class PaperDocs extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.onControlPanel = this.onControlPanel.bind(this);
12 | this.state = {
13 | children: 'Hello World',
14 | elevation: '10',
15 | style: '{"padding": "20px", "width": "300px"}'
16 | };
17 | }
18 |
19 | onControlPanel(key, value) {
20 | this.setState({[key]: value});
21 | }
22 |
23 | render() {
24 | let elevation;
25 | let style;
26 | try {
27 | elevation = Number(this.state.elevation);
28 | style = JSON.parse(this.state.style);
29 | } catch (e) {
30 | style = {};
31 | }
32 | return (
33 |
37 |
38 | {this.state.children}
39 |
40 |
41 | }
42 | buildYourOwnControlPanel={[
43 | (this.onControlPanel('elevation', e.target.value))}
46 | label="Elevation"
47 | value={this.state.elevation}
48 | helperText="Pick a value between 1 and 25"
49 | />,
50 | (this.onControlPanel('children', e.target.value))}
52 | label="Children"
53 | value={this.state.children}
54 | helperText="For demonstration purposes, limited to plaintext"
55 | />,
56 | (this.onControlPanel('style', e.target.value))}
58 | label="Style"
59 | value={this.state.style}
60 | helperText="JSON will be converted to a style object"
61 | />
62 | ]}
63 | examples={
64 |
65 | {[1, 2, 3].map((value) => {
66 | let elevationDemo;
67 | if (value === 1) {
68 | elevationDemo = 2;
69 | } else if (value === 2) {
70 | elevationDemo = 12;
71 | } else {
72 | elevationDemo = 25;
73 | }
74 | return (
75 |
76 |
80 | Paper with elevation of {elevationDemo}.
81 |
82 |
83 | );
84 | })}
85 |
86 | }
87 | />
88 | );
89 | }
90 | }
91 |
92 | export default PaperDocs;
93 |
--------------------------------------------------------------------------------
/docs/pages/SelectField/index.js:
--------------------------------------------------------------------------------
1 | import Grid, {GridItem} from '../../../src/Grid';
2 | import Page from '../Page';
3 | import React from 'react';
4 | import SelectField from '../../../src/SelectField';
5 | import Switch from '../../../src/Switch';
6 | import TextField from '../../../src/TextField';
7 |
8 | class SelectFieldDocs extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.onControlPanel = this.onControlPanel.bind(this);
12 | this.state = {
13 | disabled: false,
14 | errorColor: '',
15 | helperText: 'Pick your favorite color',
16 | id: 'id',
17 | label: 'Color',
18 | value: '',
19 | style: '{}',
20 | };
21 | }
22 |
23 | onControlPanel(key, value) {
24 | this.setState({[key]: value});
25 | }
26 |
27 | render() {
28 | const {disabled, errorColor, helperText, id, label, value, style} = this.state;
29 | let styleObj = {};
30 | try {
31 | styleObj = JSON.parse(style);
32 | } catch (e) { }
33 |
34 | const selectOptions = [
35 | 'red',
36 | 'orange',
37 | 'yellow',
38 | 'green',
39 | 'blue',
40 | 'indigo',
41 | 'violet'
42 | ];
43 | return (
44 |
48 |
49 | {
58 | this.setState({value: e.target.value})
59 | }}
60 | >
61 |
62 | {selectOptions.map(value => (
63 |
67 | {value.charAt(0).toUpperCase() + value.substr(1)}
68 |
69 | ))}
70 |
71 |
72 |
73 | }
74 | buildYourOwnControlPanel={[
75 | (this.onControlPanel('errorColor', e.target.value))}
77 | label="errorColor"
78 | helperText="If present, sets aria-invalid to true"
79 | value={errorColor}
80 | />,
81 | (this.onControlPanel('helperText', e.target.value))}
83 | label="helperText"
84 | helperText="Overflows onto a second line"
85 | value={helperText}
86 | />,
87 | (this.onControlPanel('id', e.target.value))}
89 | label="id"
90 | helperText="Binds label and select elements for a11y"
91 | value={id}
92 | />,
93 | (this.onControlPanel('label', e.target.value))}
95 | label="label"
96 | helperText="Truncated to a single line"
97 | value={label}
98 | />,
99 | (this.onControlPanel('value', e.target.value))}
101 | label="value"
102 | helperText="Value of selected option"
103 | value={value}
104 | />,
105 | (this.onControlPanel('style', e.target.value))}
107 | label="style"
108 | helperText="Applied to the select element"
109 | value={style}
110 | />,
111 | (this.onControlPanel('disabled', e.target.checked))}
113 | checked={disabled}
114 | label="disabled"
115 | />
116 | ]}
117 | />
118 | );
119 | }
120 | }
121 |
122 | export default SelectFieldDocs;
123 |
--------------------------------------------------------------------------------
/docs/pages/SnackBar/SnackBar.css:
--------------------------------------------------------------------------------
1 | .flex {
2 | display: flex;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/SnackBar/index.js:
--------------------------------------------------------------------------------
1 | import Button from '../../../src/Button';
2 | import CodeFormatter from '../../components/CodeFormatter';
3 | import Page from '../Page';
4 | import React from 'react';
5 | import SnackBar, {SnackBarItem} from '../../../src/SnackBar';
6 | import Styles from './SnackBar.css';
7 | import TextField from '../../../src/TextField';
8 | import Variables from '../../../src/variables';
9 |
10 | class SnackBarDocs extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | this.addSnackBarItem = this.addSnackBarItem.bind(this);
14 | this.state = {
15 | message: 'Message sent',
16 | actionText: 'undo'
17 | };
18 | }
19 |
20 | onControlPanel(key, value) {
21 | this.setState({[key]: value});
22 | }
23 |
24 | addSnackBarItem({addTwo}) {
25 | const snackbaritem = (
26 | {this.state.actionText}}
29 | />
30 | );
31 | this.snackbar.queue(snackbaritem);
32 | if (addTwo) {
33 | this.snackbar.queue(snackbaritem);
34 | }
35 | }
36 |
37 | render() {
38 | return (
39 |
43 | Compared to our other APIs, SnackBar is unique in that is uses an imperative API to schedule showing a SnackBar.
44 | This API is necessary since only one SnackBar can be displayed at a time .
45 | Snackbar's are managed like a first-in, first-out queue. Once a SnackBarItem is dismissed, the next scheduled one will show.
46 |
47 | }
48 | buildYourOwn={
49 |
50 | (this.snackbar = c)} />
51 |
56 | Queue 1 SnackBarItem
57 |
58 | (this.addSnackBarItem({addTwo: true}))}
62 | >
63 | Queue 2 SnackBarItem
64 |
65 |
66 | }
67 | buildYourOwnCode={
68 | (this.snackbar = c)} />
74 |
75 | this.snackbar.queue(
76 | ${this.state.actionText}}
79 | />
80 | );`
81 | }
82 | />
83 | }
84 | buildYourOwnControlPanel={[
85 | (this.onControlPanel('message', e.target.value))}
87 | label="message"
88 | value={this.state.message}
89 | />,
90 | (this.onControlPanel('actionText', e.target.value))}
92 | label="actionText"
93 | value={this.state.actionText}
94 | />
95 | ]}
96 | />
97 | );
98 | }
99 | }
100 |
101 | export default SnackBarDocs;
102 |
--------------------------------------------------------------------------------
/docs/pages/Switch/index.js:
--------------------------------------------------------------------------------
1 | import Colors from '../../../src/variables';
2 | import Grid, {GridItem} from '../../../src/Grid';
3 | import Page from '../Page';
4 | import React from 'react';
5 | import Switch from '../../../src/Switch';
6 | import TextField from '../../../src/TextField';
7 |
8 | class SwitchDocs extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.onControlPanel = this.onControlPanel.bind(this);
12 | this.state = {
13 | checked: true,
14 | disabled: false,
15 | label: 'Switch Label',
16 | labelId: 'foobar',
17 | primaryColor: Colors.$primary,
18 | switch1: false,
19 | switch2: true,
20 | switch3: true,
21 | switch4: false,
22 | switch5: true
23 | };
24 | }
25 |
26 | onControlPanel(key, value) {
27 | this.setState({[key]: value});
28 | }
29 |
30 | render() {
31 | const {checked, disabled, label, labelId, primaryColor} = this.state;
32 | return (
33 |
37 | (this.onControlPanel('checked', e.target.checked))}
39 | disabled={disabled}
40 | label={label}
41 | labelId={labelId}
42 | checked={checked}
43 | primaryColor={primaryColor}
44 | style={{justifyContent: 'center'}}
45 | />
46 |
47 | }
48 | buildYourOwnControlPanel={[
49 | (this.onControlPanel('checked', e.target.checked))}
51 | checked={checked}
52 | label="checked"
53 | />,
54 | (this.onControlPanel('disabled', e.target.checked))}
56 | checked={disabled}
57 | label="disabled"
58 | />,
59 | (this.onControlPanel('label', e.target.value))}
61 | label="label"
62 | value={label}
63 | />,
64 | (this.onControlPanel('labelId', e.target.value))}
66 | label="labelId"
67 | value={labelId}
68 | />,
69 | (this.onControlPanel('primaryColor', e.target.value))}
71 | label="primaryColor"
72 | value={primaryColor}
73 | />
74 | ]}
75 | examples={
76 |
77 |
78 | (this.onControlPanel('switch1', e.target.checked))}
80 | checked={this.state.switch1}
81 | label={'Switch #1'}
82 | />
83 |
84 |
85 | (this.onControlPanel('switch2', e.target.checked))}
87 | checked={this.state.switch2}
88 | label={'Switch #2'}
89 | />
90 |
91 |
92 | (this.onControlPanel('switch3', e.target.checked))}
94 | checked={this.state.switch3}
95 | label={'Custom Color'}
96 | primaryColor={Colors.$orange700}
97 | />
98 |
99 |
100 | (this.onControlPanel('switch4', e.target.checked))}
102 | checked={this.state.switch4}
103 | label={'Disabled off'}
104 | disabled
105 | />
106 |
107 |
108 | (this.onControlPanel('switch5', e.target.checked))}
110 | checked={this.state.switch5}
111 | label={'Disabled on'}
112 | disabled
113 | />
114 |
115 |
116 | }
117 | />
118 | );
119 | }
120 | }
121 |
122 | export default SwitchDocs;
123 |
--------------------------------------------------------------------------------
/docs/pages/Table/index.js:
--------------------------------------------------------------------------------
1 | import Grid, {GridItem} from '../../../src/Grid';
2 | import Page from '../Page';
3 | import Paper from '../../../src/Paper';
4 | import React from 'react';
5 | import Table, {TableBody, TableCell, TableHead, TableRow} from '../../../src/Table';
6 | import Typography from '../../../src/Typography';
7 |
8 | class TabsDocs extends React.Component {
9 | render() {
10 | return (
11 |
15 |
16 |
17 | Simple table.
18 |
19 |
20 |
21 |
22 |
23 | Name
24 | Age
25 | Street Address
26 | ZIP Code
27 | State
28 |
29 |
30 |
31 |
32 | Johnathan Doe
33 | 25
34 | 700 1st Ave
35 | 90210
36 | CA
37 |
38 |
39 | Jane Doe
40 | 23
41 | 15 Spruce St
42 | 92101
43 | CA
44 |
45 |
46 |
47 |
48 |
49 |
50 | }
51 | />
52 | );
53 | }
54 | }
55 |
56 | export default TabsDocs;
57 |
--------------------------------------------------------------------------------
/docs/pages/Typography/index.js:
--------------------------------------------------------------------------------
1 | import Page from '../Page';
2 | import React from 'react';
3 | import Scrollable from '../../../src/Scrollable';
4 | import SelectField from '../../../src/SelectField';
5 | import TextField from '../../../src/TextField';
6 | import Typography from '../../../src/Typography';
7 |
8 | class TypographyDocs extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.onControlPanel = this.onControlPanel.bind(this);
12 | this.state = {
13 | component: 'div',
14 | type: 'body1',
15 | children: 'Hello World'
16 | };
17 | }
18 |
19 | onControlPanel(key, value) {
20 | this.setState({[key]: value});
21 | }
22 |
23 | render() {
24 | const {component, type, children} = this.state;
25 | return (
26 |
33 | {children}
34 |
35 | }
36 | buildYourOwnControlPanel={[
37 | (this.onControlPanel('component', e.target.value))}
39 | label="component"
40 | value={component}
41 | />,
42 | (this.onControlPanel('type', e.target.value))}
46 | >
47 | display4
48 | display3
49 | display2
50 | display1
51 | headline
52 | title
53 | subheading
54 | body2
55 | body1
56 | caption
57 | button
58 | ,
59 | (this.onControlPanel('children', e.target.value))}
61 | label="children"
62 | value={children}
63 | />
64 | ]}
65 | examples={
66 |
67 |
68 | display4
69 | display3
70 | display2
71 | display1
72 | headline
73 | title
74 | subheading
75 | body2
76 | body1
77 | caption
78 | button
79 |
80 |
81 | }
82 | />
83 | );
84 | }
85 | }
86 |
87 | export default TypographyDocs;
88 |
--------------------------------------------------------------------------------
/docs/utils/globals.js:
--------------------------------------------------------------------------------
1 | const globals = {
2 | BASE_URL: '/material-react-components'
3 | };
4 |
5 | export default globals;
6 |
7 | export function makeURL(path) {
8 | if (!path) {
9 | return globals.BASE_URL;
10 | }
11 | return globals.BASE_URL + path;
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "material-react-components",
3 | "version": "2.0.6",
4 | "description": "A collection of React components that implement Google's material design specification.",
5 | "homepage": "https://collegepulse.app/material-react-components",
6 | "main": "dist/index.js",
7 | "module": "es/index.js",
8 | "jsnext:main": "es/index.js",
9 | "sideEffects": false,
10 | "keywords": [
11 | "react",
12 | "react-component",
13 | "material design",
14 | "material-ui"
15 | ],
16 | "scripts": {
17 | "build": "npm-run-all build:*",
18 | "build:commonjs": "BABEL_ENV=commonjs babel src --out-dir dist --ignore '**/*.spec.js'",
19 | "build:docs": "NODE_ENV=production webpack --config webpack.config.docs.js --env.docs",
20 | "build:module": "BABEL_ENV=module babel src --out-dir es --ignore '**/*.spec.js'",
21 | "build:ssr": "webpack --config webpack.config.ssr.js",
22 | "coverage:open": "open .coverage/lcov-report/index.html",
23 | "lint": "npm run lint:css && npm run lint:js",
24 | "lint:css": "stylelint src/**/*.css",
25 | "lint:js": "xo src/**/*.js",
26 | "start": "webpack-dev-server --config webpack.config.docs.js --env.dev --content-base docs",
27 | "test": "npm run test:unit && npm run build:ssr && npm run test:ssr",
28 | "test:ssr": "mocha .tmp/main.js",
29 | "test:unit": "karma start",
30 | "test:unit:chrome": "SOURCEMAPS=false karma start --browsers Chrome --no-single-run",
31 | "test:unit:safari": "SOURCEMAPS=false karma start --browsers Safari --no-single-run",
32 | "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
33 | },
34 | "author": "Peter Mikitsh",
35 | "license": "MIT",
36 | "peerDependencies": {
37 | "react": "^15.5.4 || ^16.0.0",
38 | "react-dom": "^15.5.4 || ^16.0.0"
39 | },
40 | "dependencies": {
41 | "@babel/runtime": "^7.5.5",
42 | "classnames": "2.2.6",
43 | "debounce": "1.2.0",
44 | "material-design-icons": "3.0.1",
45 | "react-event-listener": "^0.6.6",
46 | "react-transition-group": "1.2.1",
47 | "tinycolor2": "1.4.1"
48 | },
49 | "devDependencies": {
50 | "@babel/cli": "^7.5.5",
51 | "@babel/core": "^7.5.5",
52 | "@babel/plugin-proposal-class-properties": "^7.5.5",
53 | "@babel/plugin-proposal-object-rest-spread": "7.5.5",
54 | "@babel/plugin-transform-runtime": "^7.5.5",
55 | "@babel/preset-env": "^7.5.5",
56 | "@babel/preset-react": "7.0.0",
57 | "autoprefixer": "9.6.1",
58 | "babel-core": "7.0.0-bridge.0",
59 | "babel-eslint": "9.0.0",
60 | "babel-loader": "8.0.6",
61 | "babel-plugin-css-modules-transform": "1.6.2",
62 | "babel-plugin-istanbul": "5.2.0",
63 | "babel-plugin-transform-define": "1.3.1",
64 | "babel-plugin-transform-react-remove-prop-types": "0.4.18",
65 | "babel-polyfill": "6.26.0",
66 | "case-sensitive-paths-webpack-plugin": "2.2.0",
67 | "conventional-changelog": "3.1.10",
68 | "core-js": "2.5.7",
69 | "css-loader": "1.0.0",
70 | "enzyme": "3.6.0",
71 | "enzyme-adapter-react-16": "1.5.0",
72 | "eslint-config-xo": "0.25.0",
73 | "eslint-config-xo-react": "0.17.0",
74 | "eslint-plugin-react": "7.14.3",
75 | "html-webpack-plugin": "3.2.0",
76 | "isomorphic-style-loader": "4.0.0",
77 | "karma": "3.0.0",
78 | "karma-browserstack-launcher": "1.5.1",
79 | "karma-chrome-launcher": "2.2.0",
80 | "karma-coverage": "1.1.2",
81 | "karma-coveralls": "2.1.0",
82 | "karma-mocha": "1.3.0",
83 | "karma-mocha-reporter": "2.2.5",
84 | "karma-safari-launcher": "1.0.0",
85 | "karma-sourcemap-loader": "0.3.7",
86 | "karma-webpack": "3.0.5",
87 | "keycode": "2.2.0",
88 | "lodash": "4.17.15",
89 | "mocha": "5.2.0",
90 | "npm-run-all": "4.1.5",
91 | "postcss-cli": "6.1.3",
92 | "postcss-loader": "3.0.0",
93 | "postcss-simple-vars": "5.0.2",
94 | "prismjs": "1.17.1",
95 | "prop-types": "15.7.2",
96 | "raw-loader": "0.5.1",
97 | "react": "16.5.2",
98 | "react-dom": "16.5.2",
99 | "react-hot-loader": "4.12.12",
100 | "react-router-dom": "4.3.1",
101 | "react-svg-loader": "2.1.0",
102 | "react-test-renderer": "16.9.0",
103 | "sinon": "6.3.4",
104 | "style-loader": "0.23.0",
105 | "stylelint": "9.5.0",
106 | "stylelint-config-standard": "18.3.0",
107 | "webpack": "4.39.3",
108 | "webpack-cli": "3.3.7",
109 | "webpack-dev-server": "3.8.0",
110 | "webpack-node-externals": "1.7.2",
111 | "xo": "0.24.0"
112 | },
113 | "xo": {
114 | "envs": [
115 | "browser",
116 | "mocha"
117 | ],
118 | "extends": [
119 | "xo",
120 | "xo-react/space"
121 | ],
122 | "globals": [
123 | "__TEST__"
124 | ],
125 | "parser": "babel-eslint",
126 | "rules": {
127 | "react/jsx-sort-props": false,
128 | "react/no-deprecated": false,
129 | "react/no-find-dom-node": false,
130 | "unicorn/filename-case": 0,
131 | "unicorn/prefer-node-append": 0,
132 | "unicorn/prefer-query-selector": 0
133 | },
134 | "settings": {
135 | "react": {
136 | "version": "16.0"
137 | }
138 | },
139 | "space": true
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const cssvariables = require('./src/variables');
2 |
3 | module.exports = () => ({
4 | plugins: [
5 | require('postcss-simple-vars')({
6 | variables: () => (
7 | cssvariables
8 | )
9 | }),
10 | require('autoprefixer')()
11 | ]
12 | });
13 |
--------------------------------------------------------------------------------
/src/AppBar/AppBar.css:
--------------------------------------------------------------------------------
1 | .root {
2 | background-color: $primary;
3 | height: 64px;
4 | max-height: 64px;
5 | overflow: hidden;
6 | line-height: 64px;
7 | box-sizing: border-box;
8 | width: 100%;
9 | display: flex;
10 | }
11 |
12 | .header {
13 | color: #fff;
14 | line-height: 64px;
15 | flex: 0 1 auto;
16 | overflow: hidden;
17 | white-space: nowrap;
18 | text-overflow: ellipsis;
19 | }
20 |
21 | .header.headerColor {
22 | color: inherit;
23 | }
24 |
25 | .primary,
26 | .secondary {
27 | height: 64px;
28 | display: flex;
29 | align-items: center;
30 | }
31 |
32 | .primary,
33 | .headerNoPrimary {
34 | padding-left: 16px;
35 | }
36 |
37 | .secondary {
38 | margin-left: auto;
39 | padding-right: 16px;
40 | }
41 |
42 | @media (min-width: 600px) {
43 | .headerNoPrimary {
44 | padding-right: 16px;
45 | }
46 |
47 | .secondary {
48 | padding-left: 16px;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/AppBar/AppBar.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Paper from '../Paper';
5 | import Typography from '../Typography';
6 | import Variables from '../variables';
7 | import Styles from './AppBar.css';
8 |
9 | function AppBar({
10 | backgroundColor,
11 | className,
12 | elevation,
13 | children,
14 | primary,
15 | secondary,
16 | ...other
17 | }) {
18 | return (
19 |
25 | {primary && (
26 |
27 | {primary}
28 |
29 | )}
30 |
41 | {children}
42 |
43 | {secondary && (
44 |
45 | {secondary}
46 |
47 | )}
48 |
49 | );
50 | }
51 |
52 | AppBar.defaultProps = {
53 | backgroundColor: Variables.$primary,
54 | className: '',
55 | children: null,
56 | elevation: 4,
57 | primary: null,
58 | secondary: null
59 | };
60 |
61 | AppBar.propTypes = {
62 | backgroundColor: PropTypes.string,
63 | className: PropTypes.string,
64 | children: PropTypes.node,
65 | elevation: PropTypes.number,
66 | primary: PropTypes.node,
67 | secondary: PropTypes.node
68 | };
69 |
70 | export default AppBar;
71 |
--------------------------------------------------------------------------------
/src/AppBar/AppBar.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow, createMount, createTest} from '../../test/utils';
6 | import AppBar from './AppBar';
7 |
8 | describe('Appbar', () => {
9 | let shallow;
10 | let mount;
11 |
12 | beforeEach(() => {
13 | shallow = createShallow();
14 | mount = createMount();
15 | });
16 |
17 | afterEach(() => {
18 | shallow.cleanUp();
19 | mount.cleanUp();
20 | });
21 |
22 | it('should shallow render', () => {
23 | const wrapper = shallow( );
24 | assert(wrapper);
25 | });
26 |
27 | it('should deep render primary', createTest(() => {
28 | const wrapper = mount( );
29 | assert(wrapper);
30 | }));
31 |
32 | it('should deep render children', createTest(() => {
33 | const wrapper = mount(Header );
34 | assert(wrapper);
35 | }));
36 | });
37 |
--------------------------------------------------------------------------------
/src/AppBar/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './AppBar';
2 |
--------------------------------------------------------------------------------
/src/BottomNavigation/BottomNavigation.css:
--------------------------------------------------------------------------------
1 | .root {
2 | height: 56px;
3 | display: flex;
4 | background-color: #fff;
5 | width: 100%;
6 | }
7 |
--------------------------------------------------------------------------------
/src/BottomNavigation/BottomNavigation.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import Styles from './BottomNavigation.css';
4 |
5 | function BottomNavigation({children, ...other}) {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | BottomNavigation.defaultProps = {
14 | children: null
15 | };
16 |
17 | BottomNavigation.propTypes = {
18 | children: PropTypes.node
19 | };
20 |
21 | export default BottomNavigation;
22 |
--------------------------------------------------------------------------------
/src/BottomNavigation/BottomNavigation.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow} from '../../test/utils';
6 | import BottomNavigation from './BottomNavigation';
7 |
8 | describe('BottomNavigation', () => {
9 | let shallow;
10 |
11 | beforeEach(() => {
12 | shallow = createShallow();
13 | });
14 |
15 | afterEach(() => {
16 | shallow.cleanUp();
17 | });
18 |
19 | it('should shallow render', () => {
20 | const wrapper = shallow( );
21 | assert(wrapper);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/BottomNavigation/BottomNavigationItem.css:
--------------------------------------------------------------------------------
1 | .root {
2 | flex: 1;
3 | border-radius: 0;
4 | justify-content: center;
5 | margin: 0;
6 | }
7 |
--------------------------------------------------------------------------------
/src/BottomNavigation/BottomNavigationItem.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Button from '../Button';
5 | import Styles from './BottomNavigationItem.css';
6 |
7 | function BottomNavigationItem({children, className, ...other}) {
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | }
14 |
15 | BottomNavigationItem.defaultProps = {
16 | children: null,
17 | className: null
18 | };
19 |
20 | BottomNavigationItem.propTypes = {
21 | children: PropTypes.node,
22 | className: PropTypes.string
23 | };
24 |
25 | export default BottomNavigationItem;
26 |
--------------------------------------------------------------------------------
/src/BottomNavigation/BottomNavigationItem.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow} from '../../test/utils';
6 | import BottomNavigationItem from './BottomNavigationItem';
7 |
8 | describe('BottomNavigationItem', () => {
9 | let shallow;
10 |
11 | beforeEach(() => {
12 | shallow = createShallow();
13 | });
14 |
15 | afterEach(() => {
16 | shallow.cleanUp();
17 | });
18 |
19 | it('should shallow render', () => {
20 | const wrapper = shallow( );
21 | assert(wrapper);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/BottomNavigation/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './BottomNavigation';
2 | export {default as BottomNavigationItem} from './BottomNavigationItem';
3 |
--------------------------------------------------------------------------------
/src/Button/Button.css:
--------------------------------------------------------------------------------
1 | .root {
2 | align-items: center;
3 | position: relative;
4 | display: inline-flex;
5 | cursor: pointer;
6 | min-height: 36px;
7 | min-width: 88px;
8 | padding: 0 16px;
9 | justify-content: center;
10 | transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
11 | border: 0;
12 | background: none;
13 | outline: none;
14 | text-transform: uppercase;
15 | font-family: inherit;
16 | font-weight: 500;
17 | font-size: 14px;
18 | margin: 8px;
19 | color: inherit;
20 | border-radius: 2px;
21 | user-select: none;
22 | text-decoration: none;
23 | box-sizing: border-box;
24 | -webkit-tap-highlight-color: transparent;
25 | }
26 |
27 | .root.hasBackground {
28 | box-shadow: $elevation2;
29 | }
30 |
31 | .root.hasBackground:active {
32 | box-shadow: $elevation5;
33 | }
34 |
35 | .root::-moz-focus-inner {
36 | border: 0;
37 | }
38 |
39 | .fab {
40 | height: 56px;
41 | width: 56px;
42 | min-height: 56px;
43 | min-width: 56px;
44 | border-radius: 50%;
45 | padding: 0;
46 | box-shadow: $elevation5;
47 | display: inline-flex;
48 | align-items: center;
49 | justify-content: center;
50 | }
51 |
52 | .fab:active {
53 | box-shadow: $elevation10;
54 | }
55 |
56 | .label {
57 | align-items: inherit;
58 | display: inherit;
59 | pointer-events: none;
60 | height: 100%;
61 | overflow: hidden;
62 | text-overflow: ellipsis;
63 | white-space: nowrap;
64 | width: 100%;
65 | justify-content: inherit;
66 | }
67 |
68 | .fab > .label {
69 | justify-content: center;
70 | width: 100%;
71 | }
72 |
73 | .isNotFab {
74 | padding-top: 11px;
75 | padding-bottom: 11px;
76 | }
77 |
78 | .isIcon {
79 | border-radius: 50%;
80 | }
81 |
82 | .lightText {
83 | color: $white;
84 | }
85 |
86 | .darkText {
87 | color: $black87;
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/src/Button/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Button';
2 |
--------------------------------------------------------------------------------
/src/Collapse/Collapse.css:
--------------------------------------------------------------------------------
1 | .root {
2 | overflow: hidden;
3 | transition: height 350ms ease;
4 | }
5 |
--------------------------------------------------------------------------------
/src/Collapse/Collapse.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Collapse.css';
5 |
6 | class Collapse extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | height: props.open ? null : 0
11 | };
12 | }
13 |
14 | componentDidMount() {
15 | if (this.props.open) {
16 | this.setState({
17 | height: `${this.rootInner.offsetHeight}px`
18 | });
19 | }
20 | }
21 |
22 | componentWillReceiveProps({open}) {
23 | if (!this.props.open && open) {
24 | this.setState({
25 | height: `${this.rootInner.offsetHeight}px`
26 | });
27 | } else if (this.props.open && !open) {
28 | this.setState({
29 | height: '0'
30 | });
31 | }
32 | }
33 |
34 | registerRoot = c => {
35 | this.root = c;
36 | }
37 |
38 | registerInner = c => {
39 | this.rootInner = c;
40 | }
41 |
42 | render() {
43 | const {
44 | open, children, className, ...other
45 | } = this.props;
46 | return (
47 |
56 |
59 | {children}
60 |
61 |
62 | );
63 | }
64 | }
65 |
66 | Collapse.defaultProps = {
67 | children: null,
68 | className: null,
69 | open: false
70 | };
71 |
72 | Collapse.propTypes = {
73 | children: PropTypes.node,
74 | className: PropTypes.string,
75 | open: PropTypes.bool
76 | };
77 |
78 | export default Collapse;
79 |
--------------------------------------------------------------------------------
/src/Collapse/Collapse.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow, createMount} from '../../test/utils';
6 | import variables from '../variables';
7 | import Collapse from './Collapse';
8 |
9 | const longDiv = (
10 |
11 | Foo
12 | {' '}
13 |
14 | Bar
15 | {' '}
16 |
17 | Foo
18 | {' '}
19 |
20 | Bar
21 | {' '}
22 |
23 | Foo
24 | {' '}
25 |
26 | Bar
27 | {' '}
28 |
29 | Foo
30 | {' '}
31 |
32 | Bar
33 | {' '}
34 |
35 |
36 | );
37 |
38 | describe('Collapse', () => {
39 | let shallow;
40 | let mount;
41 |
42 | beforeEach(() => {
43 | shallow = createShallow();
44 | mount = createMount();
45 | });
46 |
47 | afterEach(() => {
48 | shallow.cleanUp();
49 | mount.cleanUp();
50 | });
51 |
52 | it('should shallow render', () => {
53 | const wrapper = shallow( );
54 | assert(wrapper);
55 | });
56 |
57 | it('should deep render', () => {
58 | const wrapper = mount( );
59 | assert(wrapper);
60 | });
61 |
62 | it('should not animate on initial render if open is set to true', done => {
63 | const wrapper = mount({longDiv} );
64 | const initialHeight = wrapper.getDOMNode().style.height;
65 | assert(parseInt(initialHeight, 10) > '0');
66 | setTimeout(() => {
67 | const heightSometimeLater = wrapper.getDOMNode().style.height;
68 | assert(initialHeight === heightSometimeLater);
69 | done();
70 | }, 1000);
71 | });
72 |
73 | it('should animate when open changes from false to true', done => {
74 | const wrapper = mount({longDiv} );
75 | const initialHeight = wrapper.getDOMNode().style.height;
76 | wrapper.setProps({open: true});
77 | setTimeout(() => {
78 | const heightSometimeLater = wrapper.getDOMNode().style.height;
79 | assert(parseInt(initialHeight, 10) < parseInt(heightSometimeLater, 10));
80 | done();
81 | }, 1000);
82 | });
83 |
84 | it('should animate when open changes from true to false', done => {
85 | const wrapper = mount({longDiv} );
86 | const initialHeight = wrapper.getDOMNode().style.height;
87 | setTimeout(() => {
88 | wrapper.setProps({open: false});
89 | }, 100);
90 | setTimeout(() => {
91 | const heightSometimeLater = wrapper.getDOMNode().style.height;
92 | assert(parseInt(initialHeight, 10) > parseInt(heightSometimeLater, 10));
93 | done();
94 | }, 1000);
95 | });
96 | });
97 |
--------------------------------------------------------------------------------
/src/Collapse/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Collapse';
2 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.css:
--------------------------------------------------------------------------------
1 | .root {
2 | animation-duration: 225ms;
3 | animation-timing-function: $rippleEaseOutFunction;
4 | animation-fill-mode: forwards;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | position: fixed;
9 | top: 0;
10 | bottom: 0;
11 | left: 0;
12 | right: 0;
13 | z-index: 2500;
14 | pointer-events: none;
15 | opacity: 0;
16 | animation-name: fadeout;
17 | }
18 |
19 | .root.open {
20 | pointer-events: auto;
21 | animation-name: fadein;
22 | }
23 |
24 | .backdrop {
25 | justify-content: center;
26 | position: fixed;
27 | height: 100%;
28 | width: 100%;
29 | top: 0;
30 | left: 0;
31 | right: 0;
32 | bottom: 0;
33 | background-color: rgba(0, 0, 0, 0.52);
34 | -webkit-tap-highlight-color: transparent;
35 | }
36 |
37 | .dialog {
38 | width: 75%;
39 | max-width: 600px;
40 | max-height: 90vh;
41 | pointer-events: auto;
42 | z-index: 2500;
43 | position: relative;
44 | }
45 |
46 | .title {
47 | font-size: 21px;
48 | font-weight: 500;
49 | line-height: 1;
50 | padding: 24px;
51 | color: rgba(0, 0, 0, 0.87);
52 | }
53 |
54 | .description {
55 | font-size: 16px;
56 | line-height: 24px;
57 | color: rgba(0, 0, 0, 0.54);
58 | padding: 0 24px 24px;
59 | -webkit-font-smoothing: antialiased;
60 | }
61 |
62 | .actions {
63 | display: flex;
64 | justify-content: flex-end;
65 | margin: 8px 4px;
66 | }
67 |
68 | @keyframes fadein {
69 | 0% { opacity: 0; }
70 | 100% { opacity: 1; }
71 | }
72 |
73 | @keyframes fadeout {
74 | 0% { opacity: 1; }
75 | 100% { opacity: 0; }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import TransitionGroup from 'react-transition-group/TransitionGroup';
4 | import DialogInner from './DialogInner';
5 |
6 | function FirstChild(props) {
7 | const childrenArray = React.Children.toArray(props.children);
8 | return childrenArray[0] || null;
9 | }
10 |
11 | class Dialog extends React.Component {
12 | register = c => {
13 | this.inner = c;
14 | }
15 |
16 | render() {
17 | return (
18 |
19 | {this.props.open && }
20 |
21 | );
22 | }
23 | }
24 |
25 | Dialog.defaultProps = {
26 | open: false
27 | };
28 |
29 | Dialog.propTypes = {
30 | open: PropTypes.bool
31 | };
32 |
33 | export default Dialog;
34 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import keycode from 'keycode';
5 | import React from 'react';
6 | import {createShallow, createMount, createTest} from '../../test/utils';
7 | import Button from '../Button';
8 | import Dialog from './Dialog';
9 | import Styles from './Dialog.css';
10 |
11 | describe('Dialog', () => {
12 | let shallow;
13 | let mount;
14 |
15 | beforeEach(() => {
16 | shallow = createShallow();
17 | mount = createMount();
18 | });
19 |
20 | afterEach(() => {
21 | shallow.cleanUp();
22 | mount.cleanUp();
23 | });
24 |
25 | it('should shallow render', () => {
26 | const wrapper = shallow( );
27 | assert(wrapper);
28 | });
29 |
30 | it('should mount', () => {
31 | const wrapper = mount( );
32 | assert(wrapper);
33 | });
34 |
35 | it('should mount in an open state', () => {
36 | const wrapper = mount( );
37 | assert(wrapper);
38 | });
39 |
40 | it('should mount in an open state, then close', createTest(() => {
41 | const wrapper = mount( );
42 | wrapper.setProps({open: false});
43 | setTimeout(() => {
44 | assert(wrapper);
45 | }, 500);
46 | }));
47 |
48 | it('should mount in an open state, then close', createTest(() => {
49 | const wrapper = mount( {
54 | wrapper.setProps({open: false});
55 | }}
56 | actions={[
57 | Foo ,
58 | Bar
59 | ]}
60 | />);
61 | assert(wrapper.find(`.${Styles.root}`).length === 1);
62 | setTimeout(() => {
63 | wrapper.find(`.${Styles.root}`).simulate('keydown', {keyCode: keycode('esc')});
64 | }, 500);
65 | setTimeout(() => {
66 | assert(wrapper.html() === null);
67 | }, 1000);
68 | }, 2000));
69 |
70 | it('should focus the first action on tab key press when last button has current focus', createTest(() => {
71 | const wrapper = mount( {
76 | wrapper.setProps({open: false});
77 | }}
78 | actions={[
79 | Foo ,
80 | Bar
81 | ]}
82 | />);
83 | wrapper.find('button').last().instance().focus();
84 |
85 | setTimeout(() => {
86 | wrapper.find(`.${Styles.root}`).simulate('keydown', {keyCode: keycode('tab')});
87 | }, 500);
88 |
89 | setTimeout(() => {
90 | assert(document.activeElement === wrapper.find('button').first().instance());
91 | }, 750);
92 | }));
93 |
94 | it('should focus the last action on shift+tab key press when first action has current focus', createTest(() => {
95 | const wrapper = mount(Foo,
101 | Bar
102 | ]}
103 | />);
104 | wrapper.find('button').first().instance().focus();
105 |
106 | setTimeout(() => {
107 | wrapper.find(`.${Styles.root}`).simulate('keydown', {keyCode: keycode('tab'), shiftKey: true});
108 | }, 500);
109 |
110 | setTimeout(() => {
111 | assert(document.activeElement === wrapper.find('button').last().instance());
112 | }, 750);
113 | }));
114 | });
115 |
--------------------------------------------------------------------------------
/src/Dialog/DialogInner.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Paper from '../Paper';
5 | import Styles from './Dialog.css';
6 |
7 | function ontouchmove() {
8 | return true;
9 | }
10 |
11 | class DialogInner extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.onBackwardTab = this.onBackwardTab.bind(this);
15 | this.onForwardTab = this.onForwardTab.bind(this);
16 | this.onKeyDown = this.onKeyDown.bind(this);
17 | }
18 |
19 | componentDidMount() {
20 | document.body.style.overflow = 'hidden';
21 | window.addEventListener('keydown', this.onKeyDown);
22 | this.lastFocusBeforeDialog = document.activeElement;
23 | this.root.focus();
24 |
25 | this.lastOnTouchMove = document.ontouchmove || ontouchmove;
26 | document.ontouchmove = e => {
27 | e.preventDefault();
28 | };
29 | }
30 |
31 | componentWillUnmount() {
32 | document.ontouchmove = this.lastOnTouchMove;
33 | }
34 |
35 | onKeyDown(e) {
36 | if (this.props.open) {
37 | const {keyCode} = e;
38 | const isTab = (keyCode === 9);
39 | const isEsc = (keyCode === 27);
40 |
41 | if (isTab) {
42 | if (this.actions.children.length <= 1) {
43 | e.preventDefault();
44 | } else if (e.shiftKey) {
45 | this.onBackwardTab(e);
46 | } else {
47 | this.onForwardTab(e);
48 | }
49 | }
50 | if (isEsc) {
51 | this.props.onClose();
52 | }
53 | }
54 | }
55 |
56 | onForwardTab(e) {
57 | if (document.activeElement === this.actions.lastChild) {
58 | e.preventDefault();
59 | this.actions.firstChild.focus();
60 | }
61 | }
62 |
63 | onBackwardTab(e) {
64 | if (document.activeElement === this.actions.firstChild) {
65 | e.preventDefault();
66 | this.actions.lastChild.focus();
67 | }
68 | }
69 |
70 | componentWillLeave(callback) {
71 | this.root.style.animationName = Styles.fadeout;
72 | window.removeEventListener('keydown', this.onKeyDown);
73 | if (this.lastFocusBeforeDialog.focus) {
74 | this.lastFocusBeforeDialog.focus();
75 | }
76 | setTimeout(() => {
77 | this.root.style.animationName = '';
78 | document.body.style.overflow = '';
79 | callback();
80 | }, 350);
81 | }
82 |
83 | registerRoot = c => {
84 | this.root = c;
85 | }
86 |
87 | registerActions = c => {
88 | this.actions = c;
89 | }
90 |
91 | render() {
92 | const {
93 | actions, className, description, onClose, open, title, ...other
94 | } = this.props;
95 | return (
96 |
104 |
108 |
112 |
113 | {title}
114 |
115 |
116 | {description}
117 |
118 |
122 | {actions}
123 |
124 |
125 |
126 | );
127 | }
128 | }
129 |
130 | DialogInner.defaultProps = {
131 | actions: [],
132 | className: null,
133 | description: null,
134 | onClose: () => {},
135 | open: false,
136 | title: null
137 | };
138 |
139 | DialogInner.propTypes = {
140 | actions: PropTypes.arrayOf(PropTypes.node),
141 | className: PropTypes.string,
142 | description: PropTypes.node,
143 | onClose: PropTypes.func,
144 | open: PropTypes.bool,
145 | title: PropTypes.node
146 | };
147 |
148 | export default DialogInner;
149 |
--------------------------------------------------------------------------------
/src/Dialog/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Dialog';
2 |
--------------------------------------------------------------------------------
/src/Grid/Grid.css:
--------------------------------------------------------------------------------
1 | .root {
2 | width: 100%;
3 | }
4 |
5 | .margin-0 {
6 | margin: 0;
7 | }
8 |
9 | .margin-8 {
10 | margin: auto 8px;
11 | max-width: calc(100% - 16px);
12 | }
13 |
14 | .margin-16 {
15 | margin: auto 16px;
16 | max-width: calc(100% - 32px);
17 | }
18 |
19 | .margin-24 {
20 | margin: auto 24px;
21 | max-width: calc(100% - 48px);
22 | }
23 |
24 | .margin-40 {
25 | margin: auto 40px;
26 | max-width: calc(100% - 80px);
27 | }
28 |
29 | .gutter {
30 | display: flex;
31 | flex-wrap: wrap;
32 | }
33 |
34 | .gutter-8 {
35 | width: calc(100% + 8px);
36 | margin: auto -4px;
37 | }
38 |
39 | .gutter-8 > .gutterChild {
40 | padding: 4px;
41 | }
42 |
43 | .gutter-16 {
44 | width: calc(100% + 16px);
45 | margin: auto -8px;
46 | }
47 |
48 | .gutter-16 > .gutterChild {
49 | padding: 8px;
50 | }
51 |
52 | .gutter-24 {
53 | width: calc(100% + 24px);
54 | margin: auto -12px;
55 | }
56 |
57 | .gutter-24 > .gutterChild {
58 | padding: 12px;
59 | }
60 |
61 | .gutter-40 {
62 | width: calc(100% + 40px);
63 | margin: auto -20px;
64 | }
65 |
66 | .gutter-40 > .gutterChild {
67 | padding: 20px;
68 | }
69 |
--------------------------------------------------------------------------------
/src/Grid/Grid.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Grid.css';
5 |
6 | class Grid extends React.Component {
7 | render() {
8 | const {
9 | children, className, margin, gutter, ...other
10 | } = this.props;
11 |
12 | const rootClasses = makeClass(
13 | Styles.root,
14 | Styles[`margin-${margin}`],
15 | className
16 | );
17 |
18 | const gutterClasses = makeClass(
19 | Styles.gutter,
20 | Styles[`gutter-${gutter}`]
21 | );
22 |
23 | return (
24 |
25 |
26 | {children}
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | Grid.defaultProps = {
34 | children: null,
35 | className: null,
36 | gutter: 0,
37 | margin: 0
38 | };
39 |
40 | Grid.propTypes = {
41 | children: PropTypes.node,
42 | className: PropTypes.string,
43 | gutter: PropTypes.oneOf([0, 8, 16, 24, 40]),
44 | margin: PropTypes.oneOf([0, 8, 16, 24, 40])
45 | };
46 |
47 | export default Grid;
48 |
--------------------------------------------------------------------------------
/src/Grid/Grid.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createMount, createTest} from '../../test/utils';
6 | import variables from '../variables';
7 | import Grid from './Grid';
8 | import GridItem from './GridItem';
9 |
10 | describe('Grid', () => {
11 | let mount;
12 |
13 | beforeEach(() => {
14 | mount = createMount();
15 | });
16 |
17 | afterEach(() => {
18 | mount.cleanUp();
19 | });
20 |
21 | it('should render a Grid', createTest(() => {
22 | const component = (
23 |
24 | {[1, 2, 3, 4, 5, 6, 7, 8].map(item => (
25 |
26 |
36 | xs=12, sm=6, md=4, lg=3
37 |
38 |
39 | ))}
40 |
41 | );
42 | const wrapper = mount(component);
43 | assert(wrapper);
44 | }));
45 | });
46 |
--------------------------------------------------------------------------------
/src/Grid/GridItem.css:
--------------------------------------------------------------------------------
1 | .root {
2 | box-sizing: border-box;
3 | }
4 |
5 | .xs-1 {
6 | flex-basis: 8.33333%;
7 | max-width: 8.33333%;
8 | }
9 |
10 | .xs-2 {
11 | flex-basis: 16.66666%;
12 | max-width: 16.66666%;
13 | }
14 |
15 | .xs-3 {
16 | flex-basis: 25%;
17 | max-width: 25%;
18 | }
19 |
20 | .xs-4 {
21 | flex-basis: 33.33333%;
22 | max-width: 33.33333%;
23 | }
24 |
25 | .xs-5 {
26 | flex-basis: 41.66666%;
27 | max-width: 41.66666%;
28 | }
29 |
30 | .xs-6 {
31 | flex-basis: 50%;
32 | max-width: 50%;
33 | }
34 |
35 | .xs-7 {
36 | flex-basis: 58.33333%;
37 | max-width: 58.33333%;
38 | }
39 |
40 | .xs-8 {
41 | flex-basis: 66.66666%;
42 | max-width: 66.66666%;
43 | }
44 |
45 | .xs-9 {
46 | flex-basis: 75%;
47 | max-width: 75%;
48 | }
49 |
50 | .xs-10 {
51 | flex-basis: 83.33333%;
52 | max-width: 83.33333%;
53 | }
54 |
55 | .xs-11 {
56 | flex-basis: 91.66666%;
57 | max-width: 91.66666%;
58 | }
59 |
60 | .xs-12 {
61 | flex-basis: 100%;
62 | max-width: 100%;
63 | }
64 |
65 | @media (min-width: $screenSm) {
66 | .sm-1 {
67 | flex-basis: 8.33333%;
68 | max-width: 8.33333%;
69 | }
70 |
71 | .sm-2 {
72 | flex-basis: 16.66666%;
73 | max-width: 16.66666%;
74 | }
75 |
76 | .sm-3 {
77 | flex-basis: 25%;
78 | max-width: 25%;
79 | }
80 |
81 | .sm-4 {
82 | flex-basis: 33.33333%;
83 | max-width: 33.33333%;
84 | }
85 |
86 | .sm-5 {
87 | flex-basis: 41.66666%;
88 | max-width: 41.66666%;
89 | }
90 |
91 | .sm-6 {
92 | flex-basis: 50%;
93 | max-width: 50%;
94 | }
95 |
96 | .sm-7 {
97 | flex-basis: 58.33333%;
98 | max-width: 58.33333%;
99 | }
100 |
101 | .sm-8 {
102 | flex-basis: 66.66666%;
103 | max-width: 66.66666%;
104 | }
105 |
106 | .sm-9 {
107 | flex-basis: 75%;
108 | max-width: 75%;
109 | }
110 |
111 | .sm-10 {
112 | flex-basis: 83.33333%;
113 | max-width: 83.33333%;
114 | }
115 |
116 | .sm-11 {
117 | flex-basis: 91.66666%;
118 | max-width: 91.66666%;
119 | }
120 |
121 | .sm-12 {
122 | flex-basis: 100%;
123 | max-width: 100%;
124 | }
125 | }
126 |
127 | @media (min-width: $screenMd) {
128 | .md-1 {
129 | flex-basis: 8.33333%;
130 | max-width: 8.33333%;
131 | }
132 |
133 | .md-2 {
134 | flex-basis: 16.66666%;
135 | max-width: 16.66666%;
136 | }
137 |
138 | .md-3 {
139 | flex-basis: 25%;
140 | max-width: 25%;
141 | }
142 |
143 | .md-4 {
144 | flex-basis: 33.33333%;
145 | max-width: 33.33333%;
146 | }
147 |
148 | .md-5 {
149 | flex-basis: 41.66666%;
150 | max-width: 41.66666%;
151 | }
152 |
153 | .md-6 {
154 | flex-basis: 50%;
155 | max-width: 50%;
156 | }
157 |
158 | .md-7 {
159 | flex-basis: 58.33333%;
160 | max-width: 58.33333%;
161 | }
162 |
163 | .md-8 {
164 | flex-basis: 66.66666%;
165 | max-width: 66.66666%;
166 | }
167 |
168 | .md-9 {
169 | flex-basis: 75%;
170 | max-width: 75%;
171 | }
172 |
173 | .md-10 {
174 | flex-basis: 83.33333%;
175 | max-width: 83.33333%;
176 | }
177 |
178 | .md-11 {
179 | flex-basis: 91.66666%;
180 | max-width: 91.66666%;
181 | }
182 |
183 | .md-12 {
184 | flex-basis: 100%;
185 | max-width: 100%;
186 | }
187 | }
188 |
189 | @media (min-width: $screenLg) {
190 | .lg-1 {
191 | flex-basis: 8.33333%;
192 | max-width: 8.33333%;
193 | }
194 |
195 | .lg-2 {
196 | flex-basis: 16.66666%;
197 | max-width: 16.66666%;
198 | }
199 |
200 | .lg-3 {
201 | flex-basis: 25%;
202 | max-width: 25%;
203 | }
204 |
205 | .lg-4 {
206 | flex-basis: 33.33333%;
207 | max-width: 33.33333%;
208 | }
209 |
210 | .lg-5 {
211 | flex-basis: 41.66666%;
212 | max-width: 41.66666%;
213 | }
214 |
215 | .lg-6 {
216 | flex-basis: 50%;
217 | max-width: 50%;
218 | }
219 |
220 | .lg-7 {
221 | flex-basis: 58.33333%;
222 | max-width: 58.33333%;
223 | }
224 |
225 | .lg-8 {
226 | flex-basis: 66.66666%;
227 | max-width: 66.66666%;
228 | }
229 |
230 | .lg-9 {
231 | flex-basis: 75%;
232 | max-width: 75%;
233 | }
234 |
235 | .lg-10 {
236 | flex-basis: 83.33333%;
237 | max-width: 83.33333%;
238 | }
239 |
240 | .lg-11 {
241 | flex-basis: 91.66666%;
242 | max-width: 91.66666%;
243 | }
244 |
245 | .lg-12 {
246 | flex-basis: 100%;
247 | max-width: 100%;
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/src/Grid/GridItem.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import GridStyles from './Grid.css';
5 | import Styles from './GridItem.css';
6 |
7 | class GridItem extends React.Component {
8 | render() {
9 | const {
10 | children, className, xs, sm, md, lg, ...other
11 | } = this.props;
12 | const classes = makeClass(
13 | Styles.root,
14 | GridStyles.gutterChild,
15 | Styles[`xs-${xs}`],
16 | Styles[`sm-${sm}`],
17 | Styles[`md-${md}`],
18 | Styles[`lg-${lg}`]
19 | );
20 | return (
21 |
22 | {children}
23 |
24 | );
25 | }
26 | }
27 |
28 | GridItem.defaultProps = {
29 | children: null,
30 | className: null,
31 | xs: 12,
32 | sm: 0,
33 | md: 0,
34 | lg: 0
35 | };
36 |
37 | GridItem.propTypes = {
38 | children: PropTypes.node,
39 | className: PropTypes.string,
40 | xs: PropTypes.number,
41 | sm: PropTypes.number,
42 | md: PropTypes.number,
43 | lg: PropTypes.number
44 | };
45 |
46 | export default GridItem;
47 |
--------------------------------------------------------------------------------
/src/Grid/GridItem.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createMount, createTest} from '../../test/utils';
6 | import variables from '../variables';
7 | import GridItem from './GridItem';
8 |
9 | describe('GridItem', () => {
10 | let mount;
11 |
12 | beforeEach(() => {
13 | mount = createMount();
14 | });
15 |
16 | afterEach(() => {
17 | mount.cleanUp();
18 | });
19 |
20 | it('should render a GridItem', createTest(() => {
21 | const component = (
22 |
23 |
33 | xs=12
34 |
35 |
36 | );
37 | const wrapper = mount(component);
38 | assert(wrapper);
39 | }));
40 | });
41 |
--------------------------------------------------------------------------------
/src/Grid/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Grid';
2 | export {default as GridItem} from './GridItem';
3 |
--------------------------------------------------------------------------------
/src/List/List.css:
--------------------------------------------------------------------------------
1 | .root {
2 | background-color: white;
3 | padding-top: 8px;
4 | padding-bottom: 8px;
5 | }
6 |
--------------------------------------------------------------------------------
/src/List/List.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './List.css';
5 |
6 | class List extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.onKeyDown = this.onKeyDown.bind(this);
10 | }
11 |
12 | onKeyDown(e, ...args) {
13 | const {keyCode} = e;
14 | const isDown = (keyCode === 40);
15 | const isUp = (keyCode === 38);
16 |
17 | if (isDown || isUp) {
18 | e.preventDefault();
19 | const nodeOfInterest = isUp ? 'previousElementSibling' : 'nextElementSibling';
20 | const nextListItem = e.target.parentElement;
21 | if (nextListItem && nextListItem[nodeOfInterest]) {
22 | nextListItem[nodeOfInterest].firstChild.focus();
23 | }
24 | }
25 | this.props.onKeyDown(e, ...args);
26 | }
27 |
28 | registerRoot = c => {
29 | this.root = c;
30 | }
31 |
32 | render() {
33 | const {
34 | arrowNavigation, children, className, onKeyDown, ...other
35 | } = this.props;
36 | return (
37 |
38 | {React.Children.map(children, child => {
39 | const props = {
40 | buttonProps: {...child.props.buttonProps, focusRippleDisabled: true}
41 | };
42 | if (arrowNavigation) {
43 | props.onKeyDown = this.onKeyDown;
44 | }
45 | return React.cloneElement(child, props);
46 | })}
47 |
48 | );
49 | }
50 | }
51 |
52 | List.defaultProps = {
53 | arrowNavigation: false,
54 | children: null,
55 | className: null,
56 | onKeyDown: () => {},
57 | style: {}
58 | };
59 |
60 | List.propTypes = {
61 | arrowNavigation: PropTypes.bool,
62 | children: PropTypes.node,
63 | className: PropTypes.string,
64 | onKeyDown: PropTypes.func,
65 | style: PropTypes.object
66 | };
67 |
68 | export default List;
69 |
--------------------------------------------------------------------------------
/src/List/List.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import keycode from 'keycode';
5 | import React from 'react';
6 | import {createShallow, createMount, createTest} from '../../test/utils';
7 | import List from './List';
8 | import ListItem from './ListItem';
9 |
10 | describe('List', () => {
11 | let shallow;
12 | let mount;
13 |
14 | beforeEach(() => {
15 | shallow = createShallow();
16 | mount = createMount();
17 | });
18 |
19 | afterEach(() => {
20 | shallow.cleanUp();
21 | mount.cleanUp();
22 | });
23 |
24 | it('should shallow render', () => {
25 | const wrapper = shallow(
);
26 | assert(wrapper);
27 | });
28 |
29 | it('should deep render', () => {
30 | const wrapper = mount(
);
31 | assert(wrapper);
32 | });
33 |
34 | it('should support arrow key navigation with arrowNavigation prop', createTest(() => {
35 | const component = (
36 |
37 |
38 |
39 |
40 | );
41 | const wrapper = mount(component);
42 |
43 | wrapper.find('#first button').instance().focus();
44 |
45 | setTimeout(() => {
46 | wrapper.find('#first button').simulate('keyDown', {keyCode: keycode('down')});
47 | assert(document.activeElement === wrapper.find('#second button').instance());
48 | }, 250);
49 |
50 | setTimeout(() => {
51 | wrapper.find('#second button').simulate('keyDown', {keyCode: keycode('up')});
52 | assert(document.activeElement === wrapper.find('#first button').instance());
53 | }, 750);
54 | }));
55 | });
56 |
--------------------------------------------------------------------------------
/src/List/ListItem.css:
--------------------------------------------------------------------------------
1 | .root {
2 | position: relative;
3 | }
4 |
5 | .button {
6 | align-items: center;
7 | border-radius: 0;
8 | display: flex;
9 | flex-direction: row;
10 | margin: 0;
11 | padding: 12px 16px;
12 | text-align: left;
13 | text-transform: none;
14 | width: 100%;
15 | }
16 |
17 | .button:focus,
18 | .button:hover {
19 | background-color: rgba(0, 0, 0, 0.12);
20 | }
21 |
22 | .avatar {
23 | height: 40px;
24 | width: 40px;
25 | align-items: center;
26 | justify-content: center;
27 | display: flex;
28 | fill: rgba(0, 0, 0, 0.54);
29 | }
30 |
31 | .text {
32 | display: flex;
33 | align-items: flex-start;
34 | flex-direction: column;
35 | justify-content: center;
36 | text-transform: none;
37 | width: inherit;
38 | }
39 |
40 | .textWithAvatarOrAction {
41 | max-width: calc(100% - 100px);
42 | overflow: hidden;
43 | padding: 0 16px;
44 | }
45 |
46 | .action {
47 | align-items: center;
48 | display: flex;
49 | fill: rgba(0, 0, 0, 0.54);
50 | height: 100%;
51 | position: absolute;
52 | right: 10px;
53 | top: 0;
54 | }
55 |
56 | .primary.primaryText,
57 | .secondary.secondaryText {
58 | text-overflow: ellipsis;
59 | overflow: hidden;
60 | max-width: 100%;
61 | width: 100%;
62 | }
63 |
64 | .primary.primaryText {
65 | color: #000;
66 | }
67 |
68 | .secondary.secondaryText {
69 | color: $black54;
70 | }
71 |
--------------------------------------------------------------------------------
/src/List/ListItem.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Button from '../Button';
5 | import Typography from '../Typography';
6 | import Styles from './ListItem.css';
7 |
8 | class ListItem extends React.Component {
9 | registerButton = c => {
10 | this.Button = c;
11 | }
12 |
13 | render() {
14 | const {
15 | action, buttonProps, avatar, className, primary, secondary, ...other
16 | } = this.props;
17 | return (
18 |
19 |
24 | {avatar && (
25 |
26 | {React.cloneElement(avatar, {focusable: 'false'})}
27 |
28 | )}
29 |
34 | {primary && (
35 |
39 | {primary}
40 |
41 | )}
42 | {secondary && (
43 |
47 | {secondary}
48 |
49 | )}
50 |
51 |
52 | {action && (
53 |
54 | {action}
55 |
56 | )}
57 |
58 | );
59 | }
60 | }
61 |
62 | ListItem.defaultProps = {
63 | action: null,
64 | avatar: null,
65 | buttonProps: {},
66 | className: null,
67 | primary: null,
68 | secondary: null
69 | };
70 |
71 | ListItem.propTypes = {
72 | action: PropTypes.node,
73 | avatar: PropTypes.node,
74 | buttonProps: PropTypes.object,
75 | className: PropTypes.string,
76 | primary: PropTypes.node,
77 | secondary: PropTypes.node
78 | };
79 |
80 | export default ListItem;
81 |
--------------------------------------------------------------------------------
/src/List/ListItem.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import Folder from 'material-design-icons/file/svg/design/ic_folder_24px.svg';
5 | import MoreVert from 'material-design-icons/navigation/svg/production/ic_more_vert_24px.svg';
6 | import React from 'react';
7 | import {createShallow, createMount, createTest} from '../../test/utils';
8 | import ListItem from './ListItem';
9 | import Styles from './ListItem.css';
10 |
11 | describe('ListItem', () => {
12 | let shallow;
13 | let mount;
14 |
15 | beforeEach(() => {
16 | shallow = createShallow();
17 | mount = createMount();
18 | });
19 |
20 | afterEach(() => {
21 | shallow.cleanUp();
22 | mount.cleanUp();
23 | });
24 |
25 | it('should shallow render', () => {
26 | const wrapper = shallow( );
27 | assert(wrapper);
28 | });
29 |
30 | it('should deep render', () => {
31 | const wrapper = mount( );
32 | assert(wrapper);
33 | });
34 |
35 | it('should render with just a primary label', createTest(() => {
36 | const wrapper = mount( );
39 | assert(wrapper.find(`.${Styles.text}`).length === 1);
40 | }));
41 |
42 | it('should render with a primary and secondary label', createTest(() => {
43 | const wrapper = mount( );
47 | assert(wrapper.find(`.${Styles.text}`).length === 1);
48 | }));
49 |
50 | it('should render with an avatar, primary + secondary label', createTest(() => {
51 | const wrapper = mount(}
53 | primary="primary"
54 | secondary="secondary"
55 | />);
56 | assert(wrapper.find(`.${Styles.avatar}`).length === 1);
57 | }));
58 |
59 | it('should render with an avatar, primary + secondary labels, + an action', createTest(() => {
60 | const wrapper = mount(}
62 | primary="primary"
63 | secondary="secondary"
64 | action={ }
65 | />);
66 | assert(wrapper.find(`.${Styles.action}`).length === 1);
67 | }));
68 | });
69 |
--------------------------------------------------------------------------------
/src/List/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './List';
2 | export {default as ListItem} from './ListItem';
3 |
--------------------------------------------------------------------------------
/src/Paper/Paper.css:
--------------------------------------------------------------------------------
1 | .root {
2 | background-color: #fff;
3 | }
4 |
5 | .elevation1 { box-shadow: $elevation1; }
6 | .elevation2 { box-shadow: $elevation2; }
7 | .elevation3 { box-shadow: $elevation3; }
8 | .elevation4 { box-shadow: $elevation4; }
9 | .elevation5 { box-shadow: $elevation5; }
10 | .elevation6 { box-shadow: $elevation6; }
11 | .elevation7 { box-shadow: $elevation7; }
12 | .elevation8 { box-shadow: $elevation8; }
13 | .elevation9 { box-shadow: $elevation9; }
14 | .elevation10 { box-shadow: $elevation10; }
15 | .elevation11 { box-shadow: $elevation11; }
16 | .elevation12 { box-shadow: $elevation12; }
17 | .elevation13 { box-shadow: $elevation13; }
18 | .elevation14 { box-shadow: $elevation14; }
19 | .elevation15 { box-shadow: $elevation15; }
20 | .elevation16 { box-shadow: $elevation16; }
21 | .elevation17 { box-shadow: $elevation17; }
22 | .elevation18 { box-shadow: $elevation18; }
23 | .elevation19 { box-shadow: $elevation19; }
24 | .elevation20 { box-shadow: $elevation20; }
25 | .elevation21 { box-shadow: $elevation21; }
26 | .elevation22 { box-shadow: $elevation22; }
27 | .elevation23 { box-shadow: $elevation23; }
28 | .elevation24 { box-shadow: $elevation24; }
29 | .elevation25 { box-shadow: $elevation25; }
30 |
--------------------------------------------------------------------------------
/src/Paper/Paper.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Paper.css';
5 |
6 | class Paper extends React.Component {
7 | render() {
8 | const {
9 | backgroundColor, children, className, elevation, style, ...other
10 | } = this.props;
11 | return (
12 |
17 | {children}
18 |
19 | );
20 | }
21 | }
22 |
23 | Paper.defaultProps = {
24 | backgroundColor: '#fff',
25 | children: null,
26 | className: '',
27 | elevation: 1,
28 | style: {}
29 | };
30 |
31 | Paper.propTypes = {
32 | backgroundColor: PropTypes.string,
33 | children: PropTypes.node,
34 | className: PropTypes.string,
35 | elevation: PropTypes.number,
36 | style: PropTypes.object
37 | };
38 |
39 | export default Paper;
40 |
--------------------------------------------------------------------------------
/src/Paper/Paper.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow, createMount, createTest} from '../../test/utils';
6 | import Paper from './Paper';
7 |
8 | describe('Paper', () => {
9 | let shallow;
10 | let mount;
11 |
12 | beforeEach(() => {
13 | shallow = createShallow();
14 | mount = createMount();
15 | });
16 |
17 | afterEach(() => {
18 | shallow.cleanUp();
19 | mount.cleanUp();
20 | });
21 |
22 | it('should shallow render', () => {
23 | const wrapper = shallow( );
24 | assert(wrapper);
25 | });
26 |
27 | it('should deep render', createTest(() => {
28 | mount(Paper!!! );
29 | }));
30 | });
31 |
--------------------------------------------------------------------------------
/src/Paper/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Paper';
2 |
--------------------------------------------------------------------------------
/src/Ripple/Ripple.css:
--------------------------------------------------------------------------------
1 | .root {
2 | border-radius: inherit;
3 | display: block;
4 | height: 100%;
5 | width: 100%;
6 | left: 0;
7 | top: 0;
8 | pointer-events: none;
9 | position: absolute;
10 | overflow: hidden;
11 | transition: all 250ms;
12 | z-index: 0;
13 | }
14 |
--------------------------------------------------------------------------------
/src/Ripple/Ripple.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import ReactTransitionGroup from 'react-transition-group/TransitionGroup';
4 | import RippleItem from './RippleItem';
5 | import Styles from './Ripple.css';
6 |
7 | /* Usage note: the parent DOM element must have relative positioning. */
8 | class Ripple extends React.Component {
9 | static defaultProps = {
10 | color: 'rgba(0, 0, 0, 0.87)'
11 | };
12 |
13 | static propTypes = {
14 | color: PropTypes.string
15 | };
16 |
17 | constructor(props) {
18 | super(props);
19 | this.add = this.add.bind(this);
20 | this.remove = this.remove.bind(this);
21 | this.ignoringMouseDown = false;
22 | this.state = {
23 | nextKey: 0,
24 | ripples: []
25 | };
26 | }
27 |
28 | shouldComponentUpdate(nextProps, nextState) {
29 | return (
30 | this.props.color !== nextProps.color ||
31 | this.state.ripples.length !== nextState.ripples.length ||
32 | this.state.nextKey !== nextState.nextKey
33 | );
34 | }
35 |
36 | add(e, options, cb = () => {}) {
37 | if (e.type === 'mousedown' && this.ignoringMouseDown) {
38 | this.ignoringMouseDown = false;
39 | return;
40 | }
41 |
42 | if (e.type === 'touchstart') {
43 | this.ignoringMouseDown = true;
44 | }
45 |
46 | const centered = options && options.centered ? options.centered : false;
47 | const pulsate = options && options.pulsate ? options.pulsate : false;
48 | const {
49 | left, top, bottom, right, height, width
50 | } = e.target.getBoundingClientRect();
51 |
52 | const props = {};
53 |
54 | if (centered) {
55 | props.rippleX = width / 2;
56 | props.rippleY = height / 2;
57 | } else {
58 | const clientX = e.clientX ? e.clientX : e.touches[0].clientX;
59 | const clientY = e.clientY ? e.clientY : e.touches[0].clientY;
60 | props.rippleX = clientX - left;
61 | props.rippleY = clientY - top;
62 | }
63 |
64 | const rippleSize = centered ?
65 | Math.sqrt((((2 * (width ** 2)) + (height ** 2))) / 3) :
66 | Math.sqrt(((right - left) ** 2) + ((bottom - top) ** 2)) * 2;
67 |
68 | let {ripples} = this.state;
69 |
70 | ripples = [...ripples, (
71 |
78 | )];
79 |
80 | this.setState(prevState => ({
81 | nextKey: prevState.nextKey + 1,
82 | ripples
83 | }), cb);
84 | }
85 |
86 | remove(e, opts, cb = () => {}) {
87 | const {ripples} = this.state;
88 | this.setState({
89 | ripples: opts && opts.removeAll ? [] : ripples.slice(1)
90 | }, cb);
91 | }
92 |
93 | render() {
94 | return (
95 |
101 | {this.state.ripples}
102 |
103 | );
104 | }
105 | }
106 |
107 | export default Ripple;
108 |
--------------------------------------------------------------------------------
/src/Ripple/Ripple.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow} from '../../test/utils';
6 | import Ripple from './Ripple';
7 |
8 | describe('Ripple', () => {
9 | let shallow;
10 |
11 | beforeEach(() => {
12 | shallow = createShallow();
13 | });
14 |
15 | afterEach(() => {
16 | shallow.cleanUp();
17 | });
18 |
19 | it('should shallow render', () => {
20 | const wrapper = shallow( );
21 | assert(wrapper);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/Ripple/RippleItem.css:
--------------------------------------------------------------------------------
1 | .container {
2 | opacity: 1;
3 | position: absolute;
4 | left: 0;
5 | }
6 |
7 | .containerLeaving {
8 | animation-name: exit;
9 | max-height: 100%;
10 | max-width: 100%;
11 | animation-duration: 550ms;
12 | animation-timing-function: $rippleEaseOutFunction;
13 | animation-fill-mode: forwards;
14 | }
15 |
16 | .containerPulsating {
17 | position: absolute;
18 | left: 0;
19 | top: 0;
20 | display: block;
21 | width: 100%;
22 | height: 100%;
23 | animation-name: pulsate;
24 | animation-duration: 1500ms;
25 | animation-timing-function: $rippleEaseOutFunction;
26 | animation-iteration-count: infinite;
27 | }
28 |
29 | .ripple {
30 | width: 50px;
31 | height: 50px;
32 | left: 0;
33 | top: 0;
34 | position: absolute;
35 | border-radius: 50%;
36 | background-color: rgba(153, 153, 153, 0.6);
37 | }
38 |
39 | .rippleVisible {
40 | opacity: 0.3;
41 | animation-name: enter;
42 | animation-duration: 550ms;
43 | animation-timing-function: $rippleEaseOutFunction;
44 | animation-fill-mode: forwards;
45 | }
46 |
47 | .rippleFast {
48 | animation-duration: 200ms;
49 | }
50 |
51 | @keyframes enter {
52 | 0% { transform: scale(0); }
53 | 100% { transform: scale(1); }
54 | }
55 |
56 | @keyframes exit {
57 | 0% { opacity: 1; }
58 | 100% { opacity: 0; }
59 | }
60 |
61 | @keyframes pulsate {
62 | 0% { transform: scale(1); }
63 | 50% { transform: scale(0.9); }
64 | 100% { transform: scale(1); }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Ripple/RippleItem.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './RippleItem.css';
5 |
6 | class RippleItem extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.rippleStyles = this.rippleStyles.bind(this);
10 | this.state = {
11 | rippleVisible: false,
12 | rippleLeaving: false
13 | };
14 | }
15 |
16 | componentWillUnmount() {
17 | clearTimeout(this.leaveTimer);
18 | }
19 |
20 | componentWillLeave(cb) {
21 | this.stop(() => {
22 | this.leaveTimer = setTimeout(() => {
23 | cb();
24 | }, 550);
25 | });
26 | }
27 |
28 | componentWillEnter(cb) {
29 | this.start(cb);
30 | }
31 |
32 | start(cb) {
33 | this.setState({
34 | rippleVisible: true
35 | }, cb);
36 | }
37 |
38 | stop(cb) {
39 | this.setState({
40 | rippleLeaving: true
41 | }, cb);
42 | }
43 |
44 | rippleStyles() {
45 | const {
46 | color, rippleSize, rippleX, rippleY
47 | } = this.props;
48 | const rippleStyles = {
49 | backgroundColor: color,
50 | width: rippleSize,
51 | height: rippleSize,
52 | top: -(rippleSize / 2) + rippleY,
53 | left: -(rippleSize / 2) + rippleX
54 | };
55 | return rippleStyles;
56 | }
57 |
58 | render() {
59 | const {pulsate} = this.props;
60 | const {rippleLeaving, rippleVisible} = this.state;
61 |
62 | const containerClasses = makeClass(Styles.container, {
63 | [Styles.containerLeaving]: rippleLeaving,
64 | [Styles.containerPulsating]: pulsate && !rippleLeaving
65 | });
66 |
67 | const rippleClasses = makeClass(Styles.ripple, {
68 | [Styles.rippleVisible]: rippleVisible,
69 | [Styles.rippleFast]: pulsate
70 | });
71 |
72 | return (
73 |
74 |
75 |
76 | );
77 | }
78 | }
79 |
80 | RippleItem.defaultProps = {
81 | color: 'rgba(0, 0, 0, 0.87)',
82 | rippleX: 0,
83 | rippleY: 0,
84 | rippleSize: 0,
85 | pulsate: false
86 | };
87 |
88 | RippleItem.propTypes = {
89 | color: PropTypes.string,
90 | rippleX: PropTypes.number,
91 | rippleY: PropTypes.number,
92 | rippleSize: PropTypes.number,
93 | pulsate: PropTypes.bool
94 | };
95 |
96 | export default RippleItem;
97 |
--------------------------------------------------------------------------------
/src/Ripple/RippleItem.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow} from '../../test/utils';
6 | import RippleItem from './RippleItem';
7 |
8 | describe('RippleItem', () => {
9 | let shallow;
10 |
11 | beforeEach(() => {
12 | shallow = createShallow();
13 | });
14 |
15 | afterEach(() => {
16 | shallow.cleanUp();
17 | });
18 |
19 | it('should shallow render', () => {
20 | const wrapper = shallow( );
21 | assert(wrapper);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/Ripple/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Ripple';
2 | export {default as RippleItem} from './RippleItem';
3 |
--------------------------------------------------------------------------------
/src/Scrollable/Scrollable.css:
--------------------------------------------------------------------------------
1 | .root::-webkit-scrollbar {
2 | height: 4px;
3 | width: 4px;
4 | }
5 |
6 | .root::-webkit-scrollbar-thumb {
7 | border-radius: 0;
8 | background-color: #dcdcdc;
9 | box-shadow: none;
10 | }
11 |
--------------------------------------------------------------------------------
/src/Scrollable/Scrollable.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Scrollable.css';
5 |
6 | class Scrollable extends React.Component {
7 | render() {
8 | const firstChild = React.Children.only(this.props.children);
9 | return React.cloneElement(firstChild, {
10 | className: makeClass(Styles.root, firstChild.props.className)
11 | });
12 | }
13 | }
14 |
15 | Scrollable.defaultProps = {
16 | children: null
17 | };
18 |
19 | Scrollable.propTypes = {
20 | children: PropTypes.node
21 | };
22 |
23 | export default Scrollable;
24 |
--------------------------------------------------------------------------------
/src/Scrollable/Scrollable.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createMount, createTest} from '../../test/utils';
6 | import Scrollable from './Scrollable';
7 |
8 | describe('Scrollable', () => {
9 | let mount;
10 |
11 | beforeEach(() => {
12 | mount = createMount();
13 | });
14 |
15 | afterEach(() => {
16 | mount.cleanUp();
17 | });
18 |
19 | it('should render a custom scrollbar on WebKit browsers', createTest(() => {
20 | const component = (
21 |
22 |
30 | Line 1
31 | {'\n'}
32 | Line 2
33 | {'\n'}
34 | Line 3
35 | {'\n'}
36 | Line 4
37 | {'\n'}
38 | Line 5
39 | {'\n'}
40 | Line 6
41 | {'\n'}
42 | Line 7
43 | {'\n'}
44 | Line 8
45 | {'\n'}
46 | Line 9
47 | {'\n'}
48 | Line 10
49 | {'\n'}
50 | Line 11
51 | {'\n'}
52 | Line 12
53 | {'\n'}
54 | Line 13
55 | {'\n'}
56 | Line 14
57 | {'\n'}
58 | Line 15 should cause a horizontal scrollbar to be present
59 |
60 |
61 | );
62 | const wrapper = mount(component);
63 | setTimeout(() => {
64 | wrapper.getDOMNode().scrollTop = 200;
65 | }, 500);
66 | assert(wrapper);
67 | }));
68 | });
69 |
--------------------------------------------------------------------------------
/src/Scrollable/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Scrollable';
2 |
--------------------------------------------------------------------------------
/src/SelectField/SelectField.css:
--------------------------------------------------------------------------------
1 | .root {
2 | position: relative;
3 | width: 100%;
4 | flex: 1;
5 | -webkit-tap-highlight-color: transparent;
6 | }
7 |
8 | .icon {
9 | position: absolute;
10 | right: 0;
11 | top: 40px;
12 | pointer-events: none;
13 | }
14 |
15 | .helperText {
16 | padding-top: 8px;
17 | }
18 |
19 | .label {
20 | color: rgba(0, 0, 0, 0.54);
21 | left: 0;
22 | line-height: 30px;
23 | max-height: 30px;
24 | overflow: hidden;
25 | pointer-events: none;
26 | position: absolute;
27 | right: 30px;
28 | text-overflow: ellipsis;
29 | top: 35px;
30 | transform-origin: left top 0;
31 | transition-duration: 250ms;
32 | }
33 |
34 | .label.hasValue {
35 | transform: $textFieldLabelFloatTransform;
36 | }
37 |
38 | .inkbar {
39 | border-bottom-width: 1px;
40 | border-bottom-color: rgba(0, 0, 0, 0.42);
41 | border-bottom-style: solid;
42 | box-sizing: border-box;
43 | height: 2px;
44 | }
45 |
46 | .select {
47 | appearance: none;
48 | background: transparent;
49 | border: none;
50 | font-family: inherit;
51 | width: 100%;
52 | font-size: 16px;
53 | padding-top: 8px;
54 | padding-bottom: 8px;
55 | padding-right: 30px;
56 | margin-top: 32px;
57 | }
58 |
59 | select::-ms-expand {
60 | display: none;
61 | }
62 |
63 | select:-moz-focusring {
64 | color: transparent;
65 | text-shadow: 0 0 0 #000;
66 | }
67 |
68 | select:focus::-ms-value {
69 | background-color: transparent;
70 | color: #000;
71 | }
72 |
73 | .select:focus {
74 | outline: none;
75 | }
76 |
77 | .select:disabled {
78 | color: rgba(0, 0, 0, 0.42);
79 | cursor: not-allowed;
80 | }
81 |
82 | .select:disabled ~ .inkbar {
83 | border-bottom-style: dashed;
84 | }
85 |
86 | .select:focus:not(:disabled) ~ .inkbar {
87 | animation-name: stretch;
88 | animation-duration: 250ms;
89 | animation-timing-function: $easeOutFunction;
90 | animation-fill-mode: forwards;
91 | border-bottom-width: 2px;
92 | border-bottom-color: $primary;
93 | max-width: 70%;
94 | }
95 |
96 | .select:hover:not(:disabled) ~ .inkbar {
97 | border-bottom-width: 2px;
98 | }
99 |
100 | @keyframes stretch {
101 | 0% {
102 | margin-left: 15%;
103 | max-width: 70%;
104 | }
105 |
106 | 100% {
107 | max-width: 100%;
108 | margin-left: 0%;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/SelectField/SelectField.js:
--------------------------------------------------------------------------------
1 | import DropDownIcon from 'material-design-icons/navigation/svg/production/ic_arrow_drop_down_24px.svg';
2 | import makeClass from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import React from 'react';
5 | import Typography from '../Typography';
6 | import Styles from './SelectField.css';
7 |
8 | class SelectField extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.onChange = this.onChange.bind(this);
12 | }
13 |
14 | onChange(...args) {
15 | this.props.onChange(...args);
16 | }
17 |
18 | render() {
19 | const {
20 | children,
21 | disabled,
22 | errorColor,
23 | helperText,
24 | id,
25 | label,
26 | value,
27 | ...other
28 | } = this.props;
29 | return (
30 |
31 |
38 | {label}
39 |
40 |
49 | {children}
50 |
51 |
55 |
59 |
64 | {helperText}
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | SelectField.defaultProps = {
72 | children: null,
73 | disabled: false,
74 | errorColor: null,
75 | helperText: null,
76 | id: null,
77 | label: null,
78 | onChange: () => {},
79 | value: {}
80 | };
81 |
82 | SelectField.propTypes = {
83 | children: PropTypes.node,
84 | disabled: PropTypes.bool,
85 | errorColor: PropTypes.string,
86 | helperText: PropTypes.string,
87 | id: PropTypes.string,
88 | label: PropTypes.string,
89 | onChange: PropTypes.func,
90 | value: PropTypes.any
91 | };
92 |
93 | export default SelectField;
94 |
--------------------------------------------------------------------------------
/src/SelectField/SelectField.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow} from '../../test/utils';
6 | import SelectField from './SelectField';
7 |
8 | describe('SelectField', () => {
9 | let shallow;
10 |
11 | beforeEach(() => {
12 | shallow = createShallow();
13 | });
14 |
15 | afterEach(() => {
16 | shallow.cleanUp();
17 | });
18 |
19 | it('should shallow render', () => {
20 | const wrapper = shallow( );
21 | assert(wrapper);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/SelectField/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './SelectField';
2 |
--------------------------------------------------------------------------------
/src/SnackBar/SnackBar.css:
--------------------------------------------------------------------------------
1 | .root {
2 | text-align: center;
3 | }
4 |
5 | .overlay {
6 | position: fixed;
7 | top: 0;
8 | left: 0;
9 | right: 0;
10 | bottom: 0;
11 | -webkit-tap-highlight-color: transparent;
12 | }
13 |
14 | .snackbar {
15 | background-color: #323232;
16 | bottom: 0;
17 | transform: translateY(48px);
18 | z-index: 1;
19 | color: white;
20 | width: 100%;
21 | height: 48px;
22 | box-sizing: border-box;
23 | display: inline-flex;
24 | justify-content: space-between;
25 | animation-name: show;
26 | animation-duration: 550ms;
27 | animation-fill-mode: forwards;
28 | }
29 |
30 | @media (min-width: $screenSm) {
31 | .snackbar {
32 | width: auto;
33 | max-width: 550px;
34 | }
35 | }
36 |
37 | .snackbarWrapper {
38 | bottom: 0;
39 | display: flex;
40 | justify-content: center;
41 | position: fixed;
42 | width: 100%;
43 | }
44 |
45 | @keyframes show {
46 | 0% {
47 | transform: translateY(48px);
48 | }
49 |
50 | 100% {
51 | transform: translateY(0);
52 | }
53 | }
54 |
55 | @keyframes hide {
56 | 0% {
57 | transform: translateY(0);
58 | }
59 |
60 | 100% {
61 | transform: translateY(48px);
62 | }
63 | }
64 |
65 | .message {
66 | padding: 14px 24px;
67 | color: $white;
68 | white-space: nowrap;
69 | overflow: hidden;
70 | text-overflow: ellipsis;
71 | }
72 |
73 | .button {
74 | margin: 0;
75 | }
76 |
77 | .delay {
78 | animation-delay: 550ms;
79 | }
80 |
--------------------------------------------------------------------------------
/src/SnackBar/SnackBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTransitionGroup from 'react-transition-group/TransitionGroup';
3 |
4 | class SnackBar extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.queue = this.queue.bind(this);
8 | this.dequeue = this.dequeue.bind(this);
9 | this.handleTransition = this.handleTransition.bind(this);
10 | this.state = {
11 | delay: false,
12 | index: 0,
13 | SnackBarItems: [],
14 | transitioning: false
15 | };
16 | }
17 |
18 | shouldComponentUpdate(nextProps, nextState) {
19 | return this.state.transitioning !== nextState.transitioning ||
20 | nextState.SnackBarItems.length === 1 ||
21 | (this.state.SnackBarItems.length - 1 === nextState.SnackBarItems.length);
22 | }
23 |
24 | componentWillUnmount() {
25 | clearTimeout(this.timeout);
26 | }
27 |
28 | queue(SnackBarItem) {
29 | this.timeout = setTimeout(() => {
30 | this.setState(prevState => {
31 | const newElement = React.cloneElement(SnackBarItem, {
32 | key: prevState.index
33 | });
34 |
35 | return {
36 | delay: prevState.SnackBarItems.length !== 0,
37 | index: prevState.index + 1,
38 | SnackBarItems: [
39 | ...prevState.SnackBarItems,
40 | newElement
41 | ]
42 | };
43 | });
44 | });
45 | }
46 |
47 | dequeue() {
48 | this.setState(prevState => {
49 | const newitems = prevState.SnackBarItems.slice(1);
50 | return {
51 | delay: !newitems.length !== 0,
52 | SnackBarItems: newitems
53 | };
54 | });
55 | }
56 |
57 | handleTransition() {
58 | this.setState(prevState => ({
59 | transitioning: !prevState.transitioning
60 | }));
61 | }
62 |
63 | render() {
64 | const item = this.state.SnackBarItems[0];
65 | return (
66 |
69 | {item && (
70 | React.cloneElement(item, {
71 | onClose: this.dequeue,
72 | delay: this.state.delay,
73 | handleTransition: this.handleTransition,
74 | transitioning: this.state.transitioning
75 | })
76 | )}
77 |
78 | );
79 | }
80 | }
81 |
82 | export default SnackBar;
83 |
--------------------------------------------------------------------------------
/src/SnackBar/SnackBar.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import Button from '../Button';
6 | import {createShallow, createMount, createTest} from '../../test/utils';
7 | import Styles from './SnackBar.css';
8 | import SnackBar, {SnackBarItem} from '.';
9 |
10 | describe('Snackbar', () => {
11 | let shallow;
12 | let mount;
13 |
14 | beforeEach(() => {
15 | shallow = createShallow();
16 | mount = createMount();
17 | });
18 |
19 | afterEach(() => {
20 | shallow.cleanUp();
21 | mount.cleanUp();
22 | });
23 |
24 | it('should shallow render a SnackBar', () => {
25 | const wrapper = shallow( );
26 | assert(wrapper);
27 | });
28 |
29 | it('should deep render a SnackBar', () => {
30 | const wrapper = mount( );
31 | assert(wrapper);
32 | });
33 |
34 | it('should render a snackbar with a single snackbaritem', createTest(() => {
35 | const snackbaritem = (
36 | Test}
39 | />
40 | );
41 | const wrapper = mount( );
42 | wrapper.find('SnackBar').instance().queue(snackbaritem);
43 | setTimeout(() => {
44 | assert(document.body.innerHTML.indexOf('Hello World') > -1);
45 | }, 550);
46 | }));
47 |
48 | it('should queue multiple SnackBarItem', createTest(() => {
49 | const wrapper = mount( );
50 | const first = (
51 | {}}
58 | >
59 | First
60 |
61 | )}
62 | />
63 | );
64 | const second = (
65 | {}}
71 | >
72 | Second
73 |
74 | )}
75 | />
76 | );
77 | wrapper.find('SnackBar').instance().queue(first);
78 | wrapper.find('SnackBar').instance().queue(second);
79 |
80 | setTimeout(() => {
81 | assert(wrapper.state('SnackBarItems').length === 2);
82 | document.getElementById('firstBtn').click();
83 | }, 500);
84 |
85 | setTimeout(() => {
86 | assert(wrapper.state('SnackBarItems').length === 1);
87 | }, 1000);
88 |
89 | setTimeout(() => {
90 | document.getElementsByClassName(Styles.overlay)[0].click();
91 | }, 1500);
92 |
93 | setTimeout(() => {
94 | assert(wrapper.state('SnackBarItems').length === 0);
95 | }, 1750);
96 | }, 2000));
97 | });
98 |
--------------------------------------------------------------------------------
/src/SnackBar/SnackBarItem.js:
--------------------------------------------------------------------------------
1 | import {findDOMNode} from 'react-dom';
2 | import makeClass from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import React from 'react';
5 | import DOMBodyRender from '../utils/DOMBodyRender';
6 | import Typography from '../Typography';
7 | import Styles from './SnackBar.css';
8 |
9 | class SnackBarItem extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.handleOverlayClick = this.handleOverlayClick.bind(this);
13 | this.makeAction = this.makeAction.bind(this);
14 | }
15 |
16 | componentDidMount() {
17 | if (this.action) {
18 | findDOMNode(this.action).focus();
19 | }
20 | }
21 |
22 | componentWillUnmount() {
23 | clearTimeout(this.timeout);
24 | }
25 |
26 | componentWillEnter(cb) {
27 | this.timeout = setTimeout(() => {
28 | cb();
29 | }, 550);
30 | }
31 |
32 | componentWillLeave(cb) {
33 | this.props.handleTransition();
34 | this.snackbar.style.animationDelay = '0ms';
35 | this.snackbar.style.animationName = Styles.hide;
36 | this.timeout = setTimeout(() => {
37 | cb();
38 | this.props.handleTransition();
39 | }, 550);
40 | }
41 |
42 | handleOverlayClick() {
43 | if (!this.props.transitioning) {
44 | this.props.onClose();
45 | }
46 | }
47 |
48 | makeAction() {
49 | const {action} = this.props;
50 | return React.cloneElement(action, {
51 | className: makeClass(
52 | action.props.className,
53 | Styles.button
54 | ),
55 | onClick: (...args) => {
56 | this.handleOverlayClick();
57 | if (action.props.onClick) {
58 | action.props.onClick(args);
59 | }
60 | },
61 | ref: this.registerAction
62 | });
63 | }
64 |
65 | registerAction = c => {
66 | this.action = c;
67 | }
68 |
69 | registerSnackbar = c => {
70 | this.snackbar = c;
71 | }
72 |
73 | render() {
74 | const {action, delay, message} = this.props;
75 | return (
76 |
77 |
78 |
82 |
83 |
90 |
91 | {message}
92 |
93 | {action && this.makeAction()}
94 |
95 |
96 |
97 |
98 | );
99 | }
100 | }
101 |
102 | SnackBarItem.defaultProps = {
103 | action: null,
104 | delay: false,
105 | handleTransition: () => {},
106 | message: null,
107 | onClose: () => {},
108 | transitioning: false
109 | };
110 |
111 | SnackBarItem.propTypes = {
112 | action: PropTypes.node,
113 | delay: PropTypes.bool,
114 | handleTransition: PropTypes.func,
115 | message: PropTypes.string,
116 | onClose: PropTypes.func,
117 | transitioning: PropTypes.bool
118 | };
119 |
120 | export default SnackBarItem;
121 |
--------------------------------------------------------------------------------
/src/SnackBar/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './SnackBar';
2 | export {default as SnackBarItem} from './SnackBarItem';
3 |
--------------------------------------------------------------------------------
/src/SvgIcon/SvgIcon.css:
--------------------------------------------------------------------------------
1 | .root {
2 | padding: 0;
3 | height: 48px;
4 | width: 48px;
5 | justify-content: center;
6 | background: none;
7 | border: none;
8 | outline: none;
9 | position: relative;
10 | min-width: 0;
11 | }
12 |
--------------------------------------------------------------------------------
/src/SvgIcon/SvgIcon.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Button from '../Button';
5 | import Styles from './SvgIcon.css';
6 |
7 | /* This wrapper is a necessary abstraction since IE11 makes
8 | * SVG's tab key focusable, even when tabindex = -1.
9 | */
10 | class SvgIcon extends React.Component {
11 | render() {
12 | const {buttonProps, component, ...other} = this.props;
13 | const Component = component;
14 | return (
15 |
20 |
21 |
22 | );
23 | }
24 | }
25 |
26 | SvgIcon.defaultProps = {
27 | buttonProps: {
28 | className: null
29 | }
30 | };
31 |
32 | SvgIcon.propTypes = {
33 | buttonProps: PropTypes.object,
34 | component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired
35 | };
36 |
37 | export default SvgIcon;
38 |
--------------------------------------------------------------------------------
/src/SvgIcon/SvgIcon.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import Add from 'material-design-icons/content/svg/production/ic_add_24px.svg';
5 | import React from 'react';
6 | import {createMount, createTest} from '../../test/utils';
7 | import SvgIcon from './SvgIcon';
8 |
9 | describe('SvgIcon', () => {
10 | let mount;
11 |
12 | beforeEach(() => {
13 | mount = createMount();
14 | });
15 |
16 | afterEach(() => {
17 | mount.cleanUp();
18 | });
19 |
20 | it('should accept an SVG as a component', createTest(() => {
21 | const wrapper = mount();
22 | assert(wrapper.find(Add).length === 1);
23 | assert(wrapper.find(Add).props().focusable === 'false');
24 | }));
25 | });
26 |
--------------------------------------------------------------------------------
/src/SvgIcon/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './SvgIcon';
2 |
--------------------------------------------------------------------------------
/src/Switch/Switch.css:
--------------------------------------------------------------------------------
1 | .root {
2 | cursor: pointer;
3 | display: flex;
4 | -webkit-tap-highlight-color: transparent;
5 | }
6 |
7 | .disabled {
8 | cursor: not-allowed;
9 | }
10 |
11 | .switchWrapper {
12 | height: 64px;
13 | width: 64px;
14 | position: relative;
15 | display: flex;
16 | }
17 |
18 | .input {
19 | position: absolute;
20 | height: 100%;
21 | width: 100%;
22 | border: 0;
23 | background: transparent;
24 | -webkit-appearance: none;
25 | outline: none;
26 | margin: 0;
27 | z-index: 10;
28 | clip: rect(1px, 1px, 1px, 1px);
29 | }
30 |
31 | .input:disabled {
32 | cursor: not-allowed;
33 | }
34 |
35 | .track {
36 | border-radius: 7px;
37 | width: 34px;
38 | height: 14px;
39 | background-color: #000;
40 | opacity: 0.5;
41 | margin: auto;
42 | position: relative;
43 | }
44 |
45 | .thumb {
46 | width: 20px;
47 | height: 20px;
48 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12);
49 | border-radius: 50%;
50 | background-color: #fff;
51 | position: absolute;
52 | top: 22px;
53 | left: 10px;
54 | transition: all 250ms $easeOutFunction;
55 | }
56 |
57 | .input:checked ~ .thumb {
58 | background-color: $primary;
59 | transform: translate(25px, 0);
60 | }
61 |
62 | .input:checked ~ .track {
63 | background-color: $primary;
64 | }
65 |
66 | .input:disabled ~ .thumb,
67 | .input:disabled ~ .track {
68 | background-color: #bdbdbd;
69 | }
70 |
71 | .input:focus ~ .thumb.thumbKeyboardFocus::before {
72 | background-color: #000;
73 | position: absolute;
74 | height: 100%;
75 | width: 100%;
76 | border-radius: 50%;
77 | opacity: 0.15;
78 | transform: scale(2);
79 | content: ' ';
80 | }
81 |
82 | .input:not(:checked) ~ .thumb {
83 | background-color: white;
84 | }
85 |
86 | .label {
87 | line-height: 64px;
88 | pointer-events: none;
89 | user-select: none;
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/src/Switch/Switch.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Switch.css';
5 |
6 | class Switch extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.onBlur = this.onBlur.bind(this);
10 | this.onMouseUp = this.onMouseUp.bind(this);
11 | this.onKeyUp = this.onKeyUp.bind(this);
12 | this.getTrackColor = this.getTrackColor.bind(this);
13 | this.getThumbColor = this.getThumbColor.bind(this);
14 | this.state = {
15 | keyboardFocused: false
16 | };
17 | }
18 |
19 | onBlur() {
20 | this.setState({
21 | keyboardFocused: false
22 | });
23 | }
24 |
25 | onMouseUp() {
26 | this.input.click();
27 | }
28 |
29 | onKeyUp({keyCode}) {
30 | const isTab = (keyCode === 9);
31 | const isSpace = (keyCode === 32);
32 |
33 | this.setState({
34 | keyboardFocused: (isTab || isSpace)
35 | });
36 | }
37 |
38 | registerInput = c => {
39 | this.input = c;
40 | }
41 |
42 | getTrackColor = () => {
43 | const {checked, disabled, primaryColor} = this.props;
44 | if (disabled) {
45 | return '#bdbdbd';
46 | }
47 | if (checked) {
48 | return primaryColor || '#2196f3';
49 | }
50 | return '#000';
51 | }
52 |
53 | getThumbColor = () => {
54 | const {checked, disabled, primaryColor} = this.props;
55 | if (disabled && checked) {
56 | return '#bdbdbd';
57 | }
58 | if (checked) {
59 | return primaryColor || '#2196f3';
60 | }
61 | return '#FFF';
62 | }
63 |
64 | render() {
65 | const {
66 | checked,
67 | disabled,
68 | label,
69 | labelId,
70 | onChange,
71 | primaryColor,
72 | style,
73 | ...props
74 | } = this.props;
75 | const {keyboardFocused} = this.state;
76 | return (
77 |
110 | );
111 | }
112 | }
113 |
114 | Switch.defaultProps = {
115 | checked: false,
116 | disabled: false,
117 | label: null,
118 | labelId: null,
119 | primaryColor: null,
120 | style: {}
121 | };
122 |
123 | Switch.propTypes = {
124 | checked: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
125 | disabled: PropTypes.bool,
126 | label: PropTypes.node,
127 | labelId: PropTypes.string,
128 | onChange: PropTypes.func.isRequired,
129 | primaryColor: PropTypes.string,
130 | style: PropTypes.object
131 | };
132 |
133 | export default Switch;
134 |
--------------------------------------------------------------------------------
/src/Switch/Switch.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import keycode from 'keycode';
5 | import noop from 'lodash';
6 | import React from 'react';
7 | import {createShallow, createMount, createTest} from '../../test/utils';
8 | import Styles from './Switch.css';
9 | import Switch from './Switch';
10 |
11 | describe('Switch', () => {
12 | let shallow;
13 | let mount;
14 |
15 | beforeEach(() => {
16 | shallow = createShallow();
17 | mount = createMount();
18 | });
19 |
20 | afterEach(() => {
21 | shallow.cleanUp();
22 | mount.cleanUp();
23 | });
24 |
25 | it('should shallow render', () => {
26 | const wrapper = shallow();
27 | assert(wrapper);
28 | });
29 |
30 | it('should add aria attribute', () => {
31 | const labelId = 'foo-bar-baz';
32 | const wrapper = mount();
33 | assert(wrapper.find('input').getDOMNode().getAttribute('aria-labelledby') === labelId);
34 | assert(wrapper.find('label').getDOMNode().getAttribute('id') === labelId);
35 | });
36 |
37 | it('should animate when checked', createTest(() => {
38 | const wrapper = mount();
39 | setTimeout(() => {
40 | wrapper.setProps({checked: true});
41 | }, 500);
42 | }));
43 |
44 | it('should set keyboardFocused state to true on tab press', createTest(() => {
45 | const wrapper = mount();
46 | wrapper.simulate('mouseup');
47 | wrapper.find('input').simulate('keyUp', {keyCode: keycode('tab')});
48 | assert(wrapper.state('keyboardFocused'));
49 | }));
50 |
51 | it('should set keyboardFocused state to true on space press', createTest(() => {
52 | const wrapper = mount();
53 | wrapper.simulate('mouseup');
54 | wrapper.find('input').simulate('keyUp', {keyCode: keycode('space')});
55 | assert(wrapper.state('keyboardFocused'));
56 | }));
57 |
58 | it('should set keyboardFocused state to false when blurred', createTest(() => {
59 | const wrapper = mount();
60 | wrapper.find('input').simulate('focus');
61 | wrapper.find('input').simulate('blur');
62 | assert(!wrapper.state('keyboardFocused'));
63 | }));
64 |
65 | it('should use custom color when checked', createTest(() => {
66 | const wrapper = mount();
67 | assert(wrapper.find(`.${Styles.track}`).getDOMNode().style.backgroundColor === 'rgb(255, 255, 255)');
68 | }));
69 | });
70 |
--------------------------------------------------------------------------------
/src/Switch/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Switch';
2 |
--------------------------------------------------------------------------------
/src/Table/Table.css:
--------------------------------------------------------------------------------
1 | .container {
2 | overflow-x: auto;
3 | }
4 |
5 | .table {
6 | border-spacing: 0;
7 | border-collapse: collapse;
8 | overflow: hidden;
9 | width: 100%;
10 | }
11 |
--------------------------------------------------------------------------------
/src/Table/Table.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Table.css';
5 |
6 | class Table extends React.Component {
7 | render() {
8 | const {children, className, ...other} = this.props;
9 | return (
10 |
15 | );
16 | }
17 | }
18 |
19 | Table.propTypes = {
20 | children: PropTypes.node,
21 | className: PropTypes.string
22 | };
23 |
24 | Table.defaultProps = {
25 | children: null,
26 | className: ''
27 | };
28 |
29 | export default Table;
30 |
--------------------------------------------------------------------------------
/src/Table/Table.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createMount} from '../../test/utils';
6 | import Table, {
7 | TableBody, TableCell, TableHead, TableRow
8 | } from '.';
9 |
10 | describe('Tabs', () => {
11 | let mount;
12 |
13 | beforeEach(() => {
14 | mount = createMount();
15 | });
16 |
17 | afterEach(() => {
18 | mount.cleanUp();
19 | });
20 |
21 | it('should deep render', () => {
22 | const component = (
23 |
24 |
25 |
26 | Name
27 | Age
28 | Street Address
29 | ZIP Code
30 | State
31 |
32 |
33 |
34 |
35 | Johnathan Doe
36 | 25
37 | 700 1st Ave
38 | 90210
39 | CA
40 |
41 |
42 | Jane Doe
43 | 23
44 | 15 Spruce St
45 | 92101
46 | CA
47 |
48 |
49 |
50 | );
51 | const wrapper = mount(component);
52 | assert(wrapper);
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/src/Table/TableBody.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | class TableBody extends React.Component {
5 | render() {
6 | const {children, ...other} = this.props;
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | }
13 | }
14 |
15 | TableBody.propTypes = {
16 | children: PropTypes.node
17 | };
18 |
19 | TableBody.defaultProps = {
20 | children: null
21 | };
22 |
23 | export default TableBody;
24 |
--------------------------------------------------------------------------------
/src/Table/TableCell.css:
--------------------------------------------------------------------------------
1 | .cell {
2 | border-bottom: 1px solid rgb(235, 235, 235);
3 | color: $black87;
4 | font-size: 13px;
5 | height: 48px;
6 | padding-left: 24px;
7 | padding-right: 56px;
8 | white-space: nowrap;
9 | }
10 |
11 | .cell:first-child {
12 | text-align: left;
13 | }
14 |
15 | .cell:not(:first-child) {
16 | text-align: right;
17 | }
18 |
19 | .head {
20 | color: $black54;
21 | font-size: 12px;
22 | font-weight: 500;
23 | height: 56px;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Table/TableCell.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './TableCell.css';
5 |
6 | class TableCell extends React.Component {
7 | render() {
8 | const {
9 | children,
10 | className,
11 | head,
12 | ...other
13 | } = this.props;
14 | const Component = head ? 'th' : 'td';
15 | const classnames = makeClass(Styles.cell, {
16 | [Styles.head]: head
17 | }, className);
18 |
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | }
25 | }
26 |
27 | TableCell.propTypes = {
28 | children: PropTypes.node,
29 | className: PropTypes.string,
30 | head: PropTypes.bool
31 | };
32 |
33 | TableCell.defaultProps = {
34 | children: null,
35 | className: '',
36 | head: false
37 | };
38 |
39 | export default TableCell;
40 |
--------------------------------------------------------------------------------
/src/Table/TableHead.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | class TableHead extends React.Component {
5 | renderChildren() {
6 | const {children} = this.props;
7 | return React.Children.map(children, child => (
8 | React.cloneElement(child, {head: true})
9 | ));
10 | }
11 |
12 | render() {
13 | const {children, ...other} = this.props;
14 | return (
15 |
16 | {this.renderChildren()}
17 |
18 | );
19 | }
20 | }
21 |
22 | TableHead.propTypes = {
23 | children: PropTypes.node
24 | };
25 |
26 | TableHead.defaultProps = {
27 | children: null
28 | };
29 |
30 | export default TableHead;
31 |
--------------------------------------------------------------------------------
/src/Table/TableRow.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | class TableRow extends React.Component {
5 | renderChildren() {
6 | const {children, head} = this.props;
7 | return React.Children.map(children, child => (
8 | React.cloneElement(child, {head})
9 | ));
10 | }
11 |
12 | render() {
13 | const {children, head, ...other} = this.props;
14 | return (
15 |
16 | {this.renderChildren()}
17 |
18 | );
19 | }
20 | }
21 |
22 | TableRow.propTypes = {
23 | children: PropTypes.node,
24 | head: PropTypes.bool
25 | };
26 |
27 | TableRow.defaultProps = {
28 | children: null,
29 | head: false
30 | };
31 |
32 | export default TableRow;
33 |
--------------------------------------------------------------------------------
/src/Table/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Table';
2 | export {default as TableBody} from './TableBody';
3 | export {default as TableCell} from './TableCell';
4 | export {default as TableHead} from './TableHead';
5 | export {default as TableRow} from './TableRow';
6 |
--------------------------------------------------------------------------------
/src/Tabs/ScrollbarSize.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import EventListener from 'react-event-listener';
4 | import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb.
5 |
6 | const styles = {
7 | width: 100,
8 | height: 100,
9 | position: 'absolute',
10 | top: -10000,
11 | overflow: 'scroll',
12 | msOverflowStyle: 'scrollbar'
13 | };
14 |
15 | /**
16 | * @ignore - internal component.
17 | * The component is originates from https://github.com/STORIS/react-scrollbar-size.
18 | * It has been moved into the core in order to minimize the bundle size.
19 | */
20 | class ScrollbarSize extends React.Component {
21 | handleResize = debounce(() => {
22 | const {onChange} = this.props;
23 |
24 | const prevHeight = this.scrollbarHeight;
25 | const prevWidth = this.scrollbarWidth;
26 | this.setMeasurements();
27 | if (prevHeight !== this.scrollbarHeight || prevWidth !== this.scrollbarWidth) {
28 | onChange({scrollbarHeight: this.scrollbarHeight, scrollbarWidth: this.scrollbarWidth});
29 | }
30 | }, 166); // Corresponds to 10 frames at 60 Hz.
31 |
32 | componentDidMount() {
33 | this.setMeasurements();
34 | this.props.onLoad({
35 | scrollbarHeight: this.scrollbarHeight,
36 | scrollbarWidth: this.scrollbarWidth
37 | });
38 | }
39 |
40 | componentWillUnmount() {
41 | this.handleResize.clear();
42 | }
43 |
44 | setMeasurements = () => {
45 | const {nodeRef} = this;
46 |
47 | if (!nodeRef) {
48 | return;
49 | }
50 |
51 | this.scrollbarHeight = nodeRef.offsetHeight - nodeRef.clientHeight;
52 | this.scrollbarWidth = nodeRef.offsetWidth - nodeRef.clientWidth;
53 | };
54 |
55 | render() {
56 | const {onChange} = this.props;
57 |
58 | return (
59 |
60 | {onChange ?
: null}
61 | {
64 | this.nodeRef = ref;
65 | }}
66 | />
67 |
68 | );
69 | }
70 | }
71 |
72 | ScrollbarSize.propTypes = {
73 | onChange: PropTypes.func.isRequired,
74 | onLoad: PropTypes.func.isRequired
75 | };
76 |
77 | export default ScrollbarSize;
78 |
--------------------------------------------------------------------------------
/src/Tabs/ScrollbarSize.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import EventListener from 'react-event-listener';
3 | import React from 'react';
4 | import {spy, useFakeTimers} from 'sinon';
5 | import {createMount, createShallow} from '../../test/utils';
6 | import ScrollbarSize from './ScrollbarSize';
7 |
8 | describe(' ', () => {
9 | const defaultProps = {
10 | onLoad: () => {},
11 | onChange: () => {}
12 | };
13 | let clock;
14 | let mount;
15 | let shallow;
16 |
17 | before(() => {
18 | clock = useFakeTimers();
19 | });
20 |
21 | beforeEach(() => {
22 | mount = createMount();
23 | shallow = createShallow();
24 | });
25 |
26 | afterEach(() => {
27 | mount.cleanUp();
28 | shallow.cleanUp();
29 | });
30 |
31 | after(() => {
32 | clock.restore();
33 | });
34 |
35 | describe('prop: onLoad', () => {
36 | it('should not call on initial load', () => {
37 | const onLoad = spy();
38 | mount( );
39 | assert(onLoad.callCount === 0);
40 | });
41 |
42 | it('should call on initial load', () => {
43 | const onLoad = spy();
44 | mount();
45 | assert(onLoad.callCount === 1);
46 | });
47 | });
48 |
49 | describe('prop: onChange', () => {
50 | let onChange;
51 | let wrapper;
52 |
53 | beforeEach(() => {
54 | onChange = spy();
55 | wrapper = shallow();
56 | const instance = wrapper.instance();
57 | instance.nodeRef = {
58 | offsetHeight: 17,
59 | clientHeight: 0,
60 | offsetWidth: 17,
61 | clientWidth: 0
62 | };
63 | });
64 |
65 | it('should call on first resize event', () => {
66 | wrapper.find(EventListener).simulate('resize');
67 | clock.tick(166);
68 | assert(onChange.callCount === 1);
69 | assert(onChange.calledWith({scrollbarHeight: 17, scrollbarWidth: 17}) === true);
70 | });
71 |
72 | it('should not call on second resize event', () => {
73 | wrapper.find(EventListener).simulate('resize');
74 | clock.tick(166);
75 | assert(onChange.callCount === 1);
76 | });
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/src/Tabs/Tab.css:
--------------------------------------------------------------------------------
1 | .tab {
2 | display: inline-block;
3 | }
4 |
5 | .tabFixed {
6 | flex: 1;
7 | }
8 |
9 | .button {
10 | border-radius: 0;
11 | margin: 0;
12 | padding: 0;
13 | box-shadow: none;
14 | }
15 |
16 | .label {
17 | padding: 15px 16px;
18 | }
19 |
20 | .indicator {
21 | position: absolute;
22 | bottom: 0;
23 | left: 0;
24 | right: 0;
25 | height: 2px;
26 | background-color: $accent;
27 | margin-top: -2px;
28 | }
29 |
--------------------------------------------------------------------------------
/src/Tabs/Tab.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Button from '../Button';
5 | import Variables from '../variables';
6 | import Styles from './Tab.css';
7 |
8 | class Tab extends React.Component {
9 | renderIndicator = () => (
10 |
15 | )
16 |
17 | render() {
18 | const {
19 | label, selected, type, index, indexChanged,
20 | indicatorColor, ...other
21 | } = this.props;
22 | const showInitialIndicator = selected && !indexChanged;
23 | const isFixed = type === 'fixed';
24 |
25 | return (
26 |
34 |
35 | {label}
36 |
37 | {showInitialIndicator && this.renderIndicator()}
38 |
39 | );
40 | }
41 | }
42 |
43 | Tab.defaultProps = {
44 | index: null,
45 | indexChanged: true,
46 | indicatorColor: Variables.$accent,
47 | label: null,
48 | selected: false,
49 | type: 'fixed'
50 | };
51 |
52 | Tab.propTypes = {
53 | index: PropTypes.number,
54 | indexChanged: PropTypes.bool,
55 | indicatorColor: PropTypes.string,
56 | label: PropTypes.string,
57 | selected: PropTypes.bool,
58 | type: PropTypes.oneOf(['fixed', 'scrollable', 'centered'])
59 | };
60 |
61 | export default Tab;
62 |
--------------------------------------------------------------------------------
/src/Tabs/Tab.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow, createMount} from '../../test/utils';
6 | import Tab from './Tab';
7 |
8 | describe('Tab', () => {
9 | let shallow;
10 | let mount;
11 |
12 | beforeEach(() => {
13 | shallow = createShallow();
14 | mount = createMount();
15 | });
16 |
17 | afterEach(() => {
18 | shallow.cleanUp();
19 | mount.cleanUp();
20 | });
21 |
22 | it('should shallow render', () => {
23 | const wrapper = shallow( );
24 | assert(wrapper);
25 | });
26 |
27 | it('should deep render', () => {
28 | const wrapper = mount( );
29 | assert(wrapper);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/Tabs/Tabs.css:
--------------------------------------------------------------------------------
1 | .tabs {
2 | overflow: hidden;
3 | white-space: nowrap;
4 | width: 100%;
5 | }
6 |
7 | .tabsInner {
8 | overflow-x: scroll;
9 | overflow-y: hidden;
10 | padding: 0;
11 | }
12 |
13 | .tabList {
14 | margin: 0;
15 | padding: 0;
16 | }
17 |
18 | .tabListFixed {
19 | display: flex;
20 | }
21 |
22 | .indicator {
23 | transition: all 250ms ease;
24 | height: 2px;
25 | margin-top: -2px;
26 | }
27 |
--------------------------------------------------------------------------------
/src/Tabs/Tabs.js:
--------------------------------------------------------------------------------
1 | import {findDOMNode} from 'react-dom';
2 | import makeClass from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import React from 'react';
5 | import Variables from '../variables';
6 | import ScrollbarSize from './ScrollbarSize';
7 | import Styles from './Tabs.css';
8 |
9 | class Tabs extends React.Component {
10 | state = {
11 | indicatorLeft: 0,
12 | indicatorWidth: 0,
13 | scrollbarHeight: 0,
14 | indexChanged: false
15 | };
16 |
17 | componentDidMount() {
18 | this.setIndicatorStyles();
19 | window.addEventListener('resize', this.setIndicatorStyles);
20 | }
21 |
22 | componentWillReceiveProps(nextProps) {
23 | if (this.props.index !== nextProps.index) {
24 | this.setState({indexChanged: true});
25 | }
26 | }
27 |
28 | componentDidUpdate({index, type}) {
29 | if (
30 | this.props.index !== index ||
31 | this.props.type !== type
32 | ) {
33 | this.setIndicatorStyles();
34 | }
35 | }
36 |
37 | componentWillUnmount() {
38 | window.removeEventListener('resize', this.setIndicatorStyles);
39 | clearTimeout(this.timeout);
40 | }
41 |
42 | onClick = (e, index) => {
43 | this.props.onChange(e, index);
44 | }
45 |
46 | getMetadata = nextIndex => {
47 | const meta = {};
48 | if (this.tabsInner) {
49 | meta.tabsMeta = this.tabsInner.getBoundingClientRect();
50 | meta.tabsMeta.scrollLeft = this.tabsInner.scrollLeft;
51 | }
52 | const index = typeof nextIndex === 'number' ? nextIndex : this.props.index;
53 | const currentTab = findDOMNode(this.tabs[index]);
54 | if (currentTab) {
55 | meta.tabMeta = currentTab.getBoundingClientRect();
56 | }
57 | return meta;
58 | }
59 |
60 | setIndicatorStyles = nextIndex => {
61 | const {tabsMeta, tabMeta} = this.getMetadata(nextIndex);
62 | const indicatorLeft = `${tabMeta.left + (tabsMeta.scrollLeft - tabsMeta.left)}px`;
63 | const indicatorWidth = `${tabMeta.width}px`;
64 | if (
65 | this.state.indicatorLeft !== indicatorLeft ||
66 | this.state.indicatorWidth !== indicatorWidth
67 | ) {
68 | this.setState({
69 | indicatorLeft,
70 | indicatorWidth
71 | });
72 | }
73 | }
74 |
75 | tabs = {};
76 |
77 | scrollbarSizeLoad = ({scrollbarHeight}) => {
78 | this.setState({scrollbarHeight});
79 | }
80 |
81 | scrollbarSizeChange = scrollbarHeight => {
82 | this.setState({scrollbarHeight});
83 | }
84 |
85 | registerTab = c => {
86 | if (c) {
87 | this.tabs[c.props.index] = c;
88 | }
89 | }
90 |
91 | registerTabsInner = c => {
92 | this.tabsInner = c;
93 | }
94 |
95 | renderTabs = () => {
96 | const {
97 | children, indicatorColor, textColor, type
98 | } = this.props;
99 | const {indexChanged} = this.state;
100 | return React.Children.map(children, (tab, i) => (
101 | React.cloneElement(tab, {
102 | index: i,
103 | indexChanged,
104 | indicatorColor,
105 | onClick: e => (this.onClick(e, i)),
106 | ref: this.registerTab,
107 | selected: i === this.props.index,
108 | style: {
109 | boxShadow: 'none'
110 | },
111 | textColor,
112 | type
113 | })
114 | ));
115 | }
116 |
117 | render() {
118 | const {
119 | barColor, className, style, type,
120 | indicatorColor, textColor, index, ...other
121 | } = this.props;
122 | const isFixed = type === 'fixed';
123 | const isCentered = type === 'centered';
124 | const {scrollbarHeight} = this.state;
125 | return (
126 |
133 |
141 |
147 | {this.renderTabs()}
148 |
149 | {this.state.indexChanged && (
150 |
159 | )}
160 |
161 |
162 |
166 |
167 |
168 | );
169 | }
170 | }
171 |
172 | Tabs.defaultProps = {
173 | barColor: Variables.$primary,
174 | children: null,
175 | className: null,
176 | index: 0,
177 | indicatorColor: Variables.$accent,
178 | onChange: () => {},
179 | style: {},
180 | textColor: '#fff',
181 | type: 'fixed'
182 | };
183 |
184 | Tabs.propTypes = {
185 | barColor: PropTypes.string,
186 | children: PropTypes.node,
187 | className: PropTypes.string,
188 | index: PropTypes.number,
189 | indicatorColor: PropTypes.string,
190 | onChange: PropTypes.func,
191 | style: PropTypes.object,
192 | textColor: PropTypes.string,
193 | type: PropTypes.oneOf(['fixed', 'scrollable', 'centered'])
194 | };
195 |
196 | export default Tabs;
197 |
--------------------------------------------------------------------------------
/src/Tabs/Tabs.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import sinon from 'sinon';
6 | import {createMount, createTest} from '../../test/utils';
7 | import Styles from './Tabs.css';
8 | import Tabs, {Tab} from '.';
9 |
10 | describe('Tabs', () => {
11 | let mount;
12 |
13 | beforeEach(() => {
14 | mount = createMount();
15 | });
16 |
17 | afterEach(() => {
18 | mount.cleanUp();
19 | });
20 |
21 | it('should deep render', () => {
22 | const component = (
23 |
24 |
25 |
26 | );
27 | const wrapper = mount(component);
28 | assert(wrapper);
29 | });
30 |
31 | it('should call onChange function with new index when a tab is clicked', createTest(() => {
32 | const onChange = sinon.spy();
33 | const component = (
34 |
35 |
36 |
37 |
38 |
39 | );
40 | const wrapper = mount(component);
41 | assert(wrapper);
42 | const newIndex = 2;
43 | wrapper.find('button').at(newIndex).simulate('click');
44 | assert(onChange.calledOnce);
45 | // Second parameter should be index
46 | assert(onChange.args[0][1] === newIndex);
47 | }));
48 |
49 | it('should repaint the indicator when the index changes', createTest(() => {
50 | const component = (
51 | {}}>
52 |
53 |
54 |
55 |
56 | );
57 | const wrapper = mount(component);
58 |
59 | const numberPattern = /\d+/g;
60 | wrapper.setProps({index: 2});
61 |
62 | setTimeout(() => {
63 | const node = wrapper.find(`.${Styles.indicator}`).getDOMNode();
64 | const newTransform = node.style.transform.match(numberPattern)[0];
65 | assert(newTransform !== 0);
66 | }, 100);
67 | }));
68 |
69 | it('should repaint the indicator on window resize', createTest(() => {
70 | const component = (
71 | {}}>
72 |
73 |
74 |
75 |
76 | );
77 | const wrapper = mount(component);
78 |
79 | const numberPattern = /\d+/g;
80 | wrapper.setProps({index: 2});
81 |
82 | setTimeout(() => {
83 | wrapper.getDOMNode().style.width = '200px';
84 | }, 400);
85 |
86 | setTimeout(() => {
87 | const event = document.createEvent('UIEvents');
88 | event.initUIEvent('resize', true, false, window, 0);
89 | window.dispatchEvent(event);
90 | }, 700);
91 |
92 | setTimeout(() => {
93 | const node = wrapper.find(`.${Styles.indicator}`).getDOMNode();
94 | const endLeftOffset = node.style.transform.match(numberPattern)[0];
95 | assert(parseInt(endLeftOffset, 10) !== 0);
96 | }, 900);
97 | }));
98 | });
99 |
--------------------------------------------------------------------------------
/src/Tabs/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Tabs';
2 | export {default as Tab} from './Tab';
3 |
--------------------------------------------------------------------------------
/src/TextField/TextField.css:
--------------------------------------------------------------------------------
1 | .root {
2 | display: inline-block;
3 | position: relative;
4 | width: 100%;
5 | }
6 |
7 | .input {
8 | background-color: transparent;
9 | border: 0;
10 | outline: 0;
11 | margin-top: 32px;
12 | display: block;
13 | width: 100%;
14 | padding: 8px 0;
15 | font-size: 16px;
16 | font-family: inherit;
17 | -webkit-tap-highlight-color: transparent;
18 | }
19 |
20 | .input:disabled {
21 | cursor: not-allowed;
22 | }
23 |
24 | .input::placeholder {
25 | color: rgba(0, 0, 0, 0.42);
26 | }
27 |
28 | .inkbar {
29 | border-bottom-width: 1px;
30 | border-bottom-color: rgba(0, 0, 0, 0.42);
31 | border-bottom-style: solid;
32 | box-sizing: border-box;
33 | height: 2px;
34 | animation-duration: 250ms;
35 | animation-timing-function: easeOutFunction;
36 | animation-fill-mode: forwards;
37 | }
38 |
39 | .input:disabled ~ .inkbar {
40 | border-bottom-style: dashed;
41 | }
42 |
43 | .input:focus ~ .inkbar {
44 | animation-name: stretch;
45 | border-bottom-color: $primary;
46 | max-width: 70%;
47 | }
48 |
49 | .input:focus ~ .inkbar,
50 | .input:hover:not(:disabled) ~ .inkbar {
51 | border-bottom-width: 2px;
52 | }
53 |
54 | @keyframes stretch {
55 | 0% {
56 | margin-left: 15%;
57 | max-width: 70%;
58 | }
59 |
60 | 100% {
61 | max-width: 100%;
62 | margin-left: 0%;
63 | }
64 | }
65 |
66 | .label {
67 | color: rgba(0, 0, 0, 0.54);
68 | position: absolute;
69 | top: 40px;
70 | pointer-events: none;
71 | animation-duration: 250ms;
72 | animation-timing-function: $easeOutFunction;
73 | font-size: 16px;
74 | transform-origin: left top 0;
75 | animation-fill-mode: forwards;
76 | }
77 |
78 | .input.hasPlaceholder + .label,
79 | .input.hasValue + .label,
80 | .input:focus + .label {
81 | transform: $textFieldLabelFloatTransform;
82 | }
83 |
84 | .input:focus + .label {
85 | color: $primary;
86 | }
87 |
88 | .helper {
89 | padding-top: 8px;
90 | font-size: 12px;
91 | line-height: 12px;
92 | color: rgba(0, 0, 0, 0.54);
93 | }
94 |
95 | .shadow {
96 | height: auto;
97 | overflow: hidden;
98 | padding: 0;
99 | margin: 0;
100 | border: 0;
101 | box-sizing: border-box;
102 | visibility: hidden;
103 | pointer-events: none;
104 | margin-top: 40px;
105 | margin-bottom: -40px;
106 | position: absolute;
107 | resize: none;
108 | font-family: inherit;
109 | font-size: inherit;
110 | line-height: 20px;
111 | width: 100%;
112 | }
113 |
114 | .textarea {
115 | height: auto;
116 | resize: none;
117 | overflow-y: hidden;
118 | transition: all 0.3s $easeOutFunction;
119 | box-sizing: border-box;
120 | padding: 0;
121 | margin: 0;
122 | margin-top: 40px;
123 | position: relative;
124 | margin-bottom: -20px;
125 | line-height: 20px;
126 | }
127 |
--------------------------------------------------------------------------------
/src/TextField/TextField.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import {noop} from 'lodash';
5 | import React from 'react';
6 | import {createShallow, createMount, createTest} from '../../test/utils';
7 | import Styles from './TextField.css';
8 | import TextField from './TextField';
9 | import TextFieldAnimations from './TextFieldAnimations.css';
10 |
11 | const shortText = 'Some text';
12 | const longText = 'A really,\n really,\n really,\n really,\n really long string.';
13 |
14 | describe('TextField', () => {
15 | let shallow;
16 | let mount;
17 |
18 | beforeEach(() => {
19 | shallow = createShallow();
20 | mount = createMount();
21 | });
22 |
23 | afterEach(() => {
24 | shallow.cleanUp();
25 | mount.cleanUp();
26 | });
27 |
28 | it('should shallow render', () => {
29 | const wrapper = shallow( );
34 | assert(wrapper);
35 | });
36 |
37 | it('should add aria attribute', createTest(() => {
38 | const labelId = 'foo-bar';
39 | const wrapper = mount( );
45 | assert(wrapper.find('input').getDOMNode().getAttribute('aria-labelledby') === labelId);
46 | assert(wrapper.find('label').getDOMNode().getAttribute('id') === labelId);
47 | }));
48 |
49 | it('should render a TextArea if multiline', createTest(() => {
50 | const wrapper = mount( );
56 | assert(wrapper.find('textarea').length === 2);
57 | }));
58 |
59 | it('should set the interal state to focused on focus', createTest(() => {
60 | const wrapper = mount( );
66 | wrapper.find('input').simulate('focus');
67 | assert(wrapper.state('focused'));
68 | assert(wrapper.find('label').getDOMNode().style.animationName !== TextFieldAnimations.float);
69 | }));
70 |
71 | it('should add the float animation on focus when there is no placeholder and value is empty', createTest(() => {
72 | const wrapper = mount( );
77 | wrapper.find('input').simulate('focus');
78 | assert(wrapper.find('label').getDOMNode().style.animationName === TextFieldAnimations.float);
79 | }));
80 |
81 | it('should set the interal state to not focused on blur', createTest(() => {
82 | const wrapper = mount( );
88 | wrapper.find('input').simulate('blur');
89 | assert(!wrapper.state('focused'));
90 | assert(wrapper.find('label').getDOMNode().style.animationName !== TextFieldAnimations.sink);
91 | }));
92 |
93 | it('should add the sink animation class on blur when there is no placeholder and value is empty', createTest(() => {
94 | const wrapper = mount( );
99 |
100 | setTimeout(() => {
101 | wrapper.find('input').simulate('blur');
102 | }, 250);
103 |
104 | setTimeout(() => {
105 | assert(!wrapper.state('focused'));
106 | assert(wrapper.find('label').getDOMNode().style.animationName === TextFieldAnimations.sink);
107 | }, 500);
108 | }));
109 |
110 | it('should increase the height of the textarea when there is more text', createTest(() => {
111 | const wrapper = mount( );
118 | const textarea = wrapper.find(`.${Styles.textarea}`);
119 | const beginningHeight = parseInt(textarea.getDOMNode().style.height, 10);
120 | setTimeout(() => {
121 | wrapper.setProps({value: longText});
122 | textarea.simulate('change', {target: {value: longText}});
123 | }, 500);
124 |
125 | setTimeout(() => {
126 | const height = parseInt(textarea.getDOMNode().style.height, 10);
127 | assert(beginningHeight < height);
128 | }, 750);
129 | }));
130 |
131 | it('should decrease the height of the textarea when there is less text', createTest(() => {
132 | const wrapper = mount( );
139 | const textarea = wrapper.find(`.${Styles.textarea}`);
140 | const heightWithLongText = parseInt(textarea.getDOMNode().style.height, 10);
141 | wrapper.setProps({value: shortText});
142 | textarea.simulate('change', {target: {value: shortText}});
143 | const heightWithShortText = parseInt(textarea.getDOMNode().style.height, 10);
144 | assert(heightWithLongText > heightWithShortText);
145 | }));
146 | });
147 |
--------------------------------------------------------------------------------
/src/TextField/TextFieldAnimations.css:
--------------------------------------------------------------------------------
1 | @keyframes sink {
2 | 0% { transform: $textFieldLabelFloatTransform; }
3 | 100% { transform: scale(1) translate(0, 0); }
4 | }
5 |
6 | @keyframes float {
7 | 0% { transform: scale(1) translate(0, 0); }
8 | 100% { transform: $textFieldLabelFloatTransform; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/TextField/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './TextField';
2 |
--------------------------------------------------------------------------------
/src/Typography/Typography.css:
--------------------------------------------------------------------------------
1 | /* The Material spec describes tracking/kerning in units of thousandths of an em.
2 | *
3 | * For more information on converting tracking/kerning to letter-spacing, see:
4 | * https://stackoverflow.com/questions/2760784/how-to-calculate-css-letter-spacing-v-s-tracking-in-typography
5 | */
6 |
7 | .display4 {
8 | font-size: 112px;
9 | font-weight: $typographyWeightLight;
10 | color: $black54;
11 | letter-spacing: -0.01em;
12 | }
13 |
14 | .display3 {
15 | font-size: 56px;
16 | font-weight: $typographyWeightRegular;
17 | line-height: 1.35;
18 | color: $black54;
19 | letter-spacing: -0.005em;
20 | }
21 |
22 | .display2 {
23 | font-size: 45px;
24 | font-weight: $typographyWeightRegular;
25 | line-height: 48px;
26 | color: $black54;
27 | }
28 |
29 | .display1 {
30 | font-size: 34px;
31 | font-weight: $typographyWeightRegular;
32 | line-height: 40px;
33 | color: $black54;
34 | }
35 |
36 | .headline {
37 | font-size: 24px;
38 | font-weight: $typographyWeightRegular;
39 | line-height: 32px;
40 | color: $black87;
41 | }
42 |
43 | .title {
44 | font-size: 21px;
45 | font-weight: $typographyWeightMedium;
46 | color: $black87;
47 | letter-spacing: 0.005em;
48 | }
49 |
50 | .subheading {
51 | font-size: 16px;
52 | font-weight: $typographyWeightRegular;
53 | line-height: 24px;
54 | color: $black87;
55 | letter-spacing: 0.01em;
56 | }
57 |
58 | .body2 {
59 | font-size: 14px;
60 | font-weight: $typographyWeightMedium;
61 | line-height: 24px;
62 | color: $black87;
63 | letter-spacing: 0.01em;
64 | }
65 |
66 | .body1 {
67 | font-size: 14px;
68 | font-weight: $typographyWeightRegular;
69 | line-height: 20px;
70 | color: $black87;
71 | letter-spacing: 0.01em;
72 | }
73 |
74 | .caption {
75 | font-size: 12px;
76 | font-weight: $typographyWeightRegular;
77 | line-height: 1;
78 | color: $black54;
79 | letter-spacing: 0.02em;
80 | }
81 |
82 | .button {
83 | text-transform: uppercase;
84 | font-weight: $typographyWeightMedium;
85 | color: $black87;
86 | letter-spacing: 0.01em;
87 | }
88 |
--------------------------------------------------------------------------------
/src/Typography/Typography.js:
--------------------------------------------------------------------------------
1 | import makeClass from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import Styles from './Typography.css';
5 |
6 | function Typography({
7 | children, className, component, type, ...other
8 | }) {
9 | const Component = component;
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
17 | Typography.defaultProps = {
18 | children: null,
19 | className: null,
20 | component: 'div',
21 | type: 'body1'
22 | };
23 |
24 | Typography.propTypes = {
25 | children: PropTypes.node,
26 | className: PropTypes.string,
27 | component: PropTypes.node,
28 | type: PropTypes.oneOf([
29 | 'display4',
30 | 'display3',
31 | 'display2',
32 | 'display1',
33 | 'headline',
34 | 'title',
35 | 'subheading',
36 | 'body2',
37 | 'body1',
38 | 'caption',
39 | 'button'
40 | ])
41 | };
42 |
43 | export default Typography;
44 |
--------------------------------------------------------------------------------
/src/Typography/Typography.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createShallow} from '../../test/utils';
6 | import Styles from './Typography.css';
7 | import Typography from './Typography';
8 |
9 | describe('Typography', () => {
10 | let shallow;
11 |
12 | beforeEach(() => {
13 | shallow = createShallow();
14 | });
15 |
16 | afterEach(() => {
17 | shallow.cleanUp();
18 | });
19 |
20 | it('should shallow render', () => {
21 | const wrapper = shallow( );
22 | assert(wrapper);
23 | });
24 |
25 | it('should use custom component', () => {
26 | const wrapper = shallow( );
27 | assert(wrapper.find('span').length === 1);
28 | });
29 |
30 | it('should apply custom typestyle class name', () => {
31 | const wrapper = shallow( );
32 | assert(wrapper.find(`.${Styles.display4}`).length === 1);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/Typography/index.js:
--------------------------------------------------------------------------------
1 | export {default} from './Typography';
2 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export {default as AppBar} from './AppBar';
2 | export {default as BottomNavigation, BottomNavigationItem} from './BottomNavigation';
3 | export {default as Button} from './Button';
4 | export {default as Collapse} from './Collapse';
5 | export {default as Dialog} from './Dialog';
6 | export {default as Grid, GridItem} from './Grid';
7 | export {default as List, ListItem} from './List';
8 | export {default as Paper} from './Paper';
9 | export {default as Ripple, RippleItem} from './Ripple';
10 | export {default as Scrollable} from './Scrollable';
11 | export {default as SnackBar, SnackBarItem} from './SnackBar';
12 | export {default as SvgIcon} from './SvgIcon';
13 | export {default as Switch} from './Switch';
14 | export {default as Table, TableBody, TableCell, TableHead, TableRow} from './Table';
15 | export {default as Tabs, Tab} from './Tabs';
16 | export {default as TextField} from './TextField';
17 | export {default as Typography} from './Typography';
18 |
--------------------------------------------------------------------------------
/src/utils/DOMBodyRender.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {render, unmountComponentAtNode} from 'react-dom';
4 |
5 | class DOMBodyRender extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.node = null;
9 | this.renderNode = this.renderNode.bind(this);
10 | this.unrenderNode = this.unrenderNode.bind(this);
11 | }
12 |
13 | componentDidMount() {
14 | this.renderNode();
15 | }
16 |
17 | componentDidUpdate() {
18 | this.renderNode();
19 | }
20 |
21 | componentWillUnmount() {
22 | this.unrenderNode();
23 | }
24 |
25 | unrenderNode() {
26 | if (!this.node) {
27 | return;
28 | }
29 | unmountComponentAtNode(this.node);
30 | if (document && document.body) {
31 | document.body.removeChild(this.node);
32 | }
33 | }
34 |
35 | renderNode() {
36 | if (!this.node) {
37 | this.node = document.createElement('div');
38 | if (document && document.body) {
39 | document.body.appendChild(this.node);
40 | }
41 | }
42 | render(this.props.children, this.node);
43 | }
44 |
45 | render() {
46 | return null;
47 | }
48 | }
49 |
50 | DOMBodyRender.defaultProps = {
51 | children: null
52 | };
53 |
54 | DOMBodyRender.propTypes = {
55 | children: PropTypes.node
56 | };
57 |
58 | export default DOMBodyRender;
59 |
--------------------------------------------------------------------------------
/src/utils/DOMBodyRender.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import React from 'react';
5 | import {createMount} from '../../test/utils';
6 | import DOMBodyRender from './DOMBodyRender';
7 |
8 | describe('DOMBodyRender', () => {
9 | let mount;
10 |
11 | beforeEach(() => {
12 | mount = createMount();
13 | });
14 |
15 | afterEach(() => {
16 | mount.cleanUp();
17 | });
18 |
19 | it('should deep render', () => {
20 | const text = 'Foo bar';
21 | const component = (
22 |
23 | {text}
24 |
25 | );
26 | const wrapper = mount(component);
27 | const {node} = wrapper.instance();
28 | assert(node.innerHTML.indexOf(text) > -1);
29 | });
30 |
31 | it('should update DOM when children change', () => {
32 | const oldText = 'Hello, World!';
33 | const component = (
34 |
35 | {oldText}
36 |
37 | );
38 | const wrapper = mount(component);
39 | const {node} = wrapper.instance();
40 | assert(node.innerHTML.indexOf(oldText) > -1);
41 | const newText = 'New content';
42 | wrapper.setProps({children: {newText}
});
43 | assert(node.innerHTML.indexOf(oldText) === -1);
44 | assert(node.innerHTML.indexOf(newText) > -1);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/variables.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | $elevation1: 'none',
3 | $elevation2: '0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12)',
4 | $elevation3: '0 1px 5px 0 rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12)',
5 | $elevation4: '0 1px 8px 0 rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.12)',
6 | $elevation5: '0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)',
7 | $elevation6: '0 3px 5px -1px rgba(0,0,0,.2),0 5px 8px 0 rgba(0,0,0,.14),0 1px 14px 0 rgba(0,0,0,.12)',
8 | $elevation7: '0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)',
9 | $elevation8: '0 4px 5px -2px rgba(0,0,0,.2),0 7px 10px 1px rgba(0,0,0,.14),0 2px 16px 1px rgba(0,0,0,.12)',
10 | $elevation9: '0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12)',
11 | $elevation10: '0 5px 6px -3px rgba(0,0,0,.2),0 9px 12px 1px rgba(0,0,0,.14),0 3px 16px 2px rgba(0,0,0,.12)',
12 | $elevation11: '0 6px 6px -3px rgba(0,0,0,.2),0 10px 14px 1px rgba(0,0,0,.14),0 4px 18px 3px rgba(0,0,0,.12)',
13 | $elevation12: '0 6px 7px -4px rgba(0,0,0,.2),0 11px 15px 1px rgba(0,0,0,.14),0 4px 20px 3px rgba(0,0,0,.12)',
14 | $elevation13: '0 7px 8px -4px rgba(0,0,0,.2),0 12px 17px 2px rgba(0,0,0,.14),0 5px 22px 4px rgba(0,0,0,.12)',
15 | $elevation14: '0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12)',
16 | $elevation15: '0 7px 9px -4px rgba(0,0,0,.2),0 14px 21px 2px rgba(0,0,0,.14),0 5px 26px 4px rgba(0,0,0,.12)',
17 | $elevation16: '0 8px 9px -5px rgba(0,0,0,.2),0 15px 22px 2px rgba(0,0,0,.14),0 6px 28px 5px rgba(0,0,0,.12)',
18 | $elevation17: '0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)',
19 | $elevation18: '0 8px 11px -5px rgba(0,0,0,.2),0 17px 26px 2px rgba(0,0,0,.14),0 6px 32px 5px rgba(0,0,0,.12)',
20 | $elevation19: '0 9px 11px -5px rgba(0,0,0,.2),0 18px 28px 2px rgba(0,0,0,.14),0 7px 34px 6px rgba(0,0,0,.12)',
21 | $elevation20: '0 9px 12px -6px rgba(0,0,0,.2),0 19px 29px 2px rgba(0,0,0,.14),0 7px 36px 6px rgba(0,0,0,.12)',
22 | $elevation21: '0 10px 13px -6px rgba(0,0,0,.2),0 20px 31px 3px rgba(0,0,0,.14),0 8px 38px 7px rgba(0,0,0,.12)',
23 | $elevation22: '0 10px 13px -6px rgba(0,0,0,.2),0 21px 33px 3px rgba(0,0,0,.14),0 8px 40px 7px rgba(0,0,0,.12)',
24 | $elevation23: '0 10px 14px -6px rgba(0,0,0,.2),0 22px 35px 3px rgba(0,0,0,.14),0 8px 42px 7px rgba(0,0,0,.12)',
25 | $elevation24: '0 11px 14px -7px rgba(0,0,0,.2),0 23px 36px 3px rgba(0,0,0,.14),0 9px 44px 8px rgba(0,0,0,.12)',
26 | $elevation25: '0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12)',
27 | $easeOutFunction: 'cubic-bezier(.23,1,.32,1)',
28 | $rippleEaseOutFunction: 'cubic-bezier(.4,0,.2,1)',
29 | $primary: '#2196f3',
30 | $accent: '#ff4081',
31 | $borderColor: '#e0e0e0',
32 | $red500: '#f44336',
33 | $orange700: '#f57c00',
34 | $black: '#000',
35 | $black54: 'rgba(0,0,0,.54)',
36 | $black87: 'rgba(0,0,0,.87)',
37 | $white: '#fff',
38 | $textFieldLabelFloatTransform: 'scale(.75) translate(0,-25px)',
39 | $typographyWeightLight: '300',
40 | $typographyWeightRegular: '400',
41 | $typographyWeightMedium: '500',
42 | $screenSm: '600px',
43 | $screenMd: '960px',
44 | $screenLg: '1280px',
45 | $screenXl: '1920px'
46 | };
47 |
--------------------------------------------------------------------------------
/test/context.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
24 |
25 |
32 |
33 | %SCRIPTS%
34 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/ssr.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | import assert from 'assert';
4 | import Edit from 'material-design-icons/editor/svg/production/ic_mode_edit_24px.svg';
5 | import React from 'react';
6 | import ReactDOMServer from 'react-dom/server';
7 | import SvgIcon from '../src/SvgIcon';
8 |
9 | /*
10 | * These lightweight tests are an assurance that
11 | * components render in server-side environments
12 | */
13 | describe('Server Side Rendering', () => {
14 | it('should render SVG', () => {
15 | const render = ReactDOMServer.renderToString(
16 |
17 | );
18 | assert(render.indexOf('svg') > -1);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/test/test_index.js:
--------------------------------------------------------------------------------
1 | /* eslint-env es5 */
2 |
3 | // Require all modules ending in ".spec.js" from the
4 | // 'src' directory and all subdirectories
5 |
6 | var testsContext = require.context('../src', true, /\.spec\.js$/);
7 | testsContext.keys().forEach(testsContext);
8 |
9 | // Hide karma elements added to DOM
10 | parent.document.getElementById('banner').style.display = 'none';
11 | parent.document.getElementById('browsers').style.display = 'none';
12 |
13 | var configure = require('enzyme').configure;
14 | var Adapter = require('enzyme-adapter-react-16');
15 |
16 | configure({adapter: new Adapter()});
17 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | import {mount, shallow} from 'enzyme';
2 | import {unmountComponentAtNode} from 'react-dom';
3 |
4 | export function createWrapper(fn) {
5 | const attachTo = window.document.createElement('div');
6 | const firstChild = window.document.body.firstChild;
7 |
8 | window.document.body.insertBefore(attachTo, firstChild);
9 |
10 | const wrapper = function enzymeWrapper(node) {
11 | return fn(node, {attachTo});
12 | };
13 |
14 | wrapper.cleanUp = () => {
15 | unmountComponentAtNode(attachTo);
16 | attachTo.parentNode.removeChild(attachTo);
17 | };
18 |
19 | return wrapper;
20 | }
21 |
22 | export function createMount() {
23 | return createWrapper(mount);
24 | }
25 |
26 | export function createShallow() {
27 | return createWrapper(shallow);
28 | }
29 |
30 | /* Slow tests down a little so we can view
31 | * the visual renders in BrowserStack
32 | */
33 | export function createTest(test, timeout) {
34 | return function (done) {
35 | test.bind(this)();
36 | setTimeout(() => {
37 | done();
38 | }, timeout || 1000);
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/webpack.config.docs.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
3 | const webpack = require('webpack');
4 |
5 | function entry(env) {
6 | let entries = [];
7 | if (env.dev) {
8 | entries = [
9 | 'react-hot-loader/patch',
10 | 'webpack-dev-server/client?http://localhost:8080',
11 | 'webpack/hot/only-dev-server'
12 | ];
13 | }
14 | entries.push('./docs/index');
15 | return entries;
16 | }
17 |
18 | function getPlugins(env) {
19 | let plugins = [
20 | new CaseSensitivePathsPlugin()
21 | ];
22 | if (env.dev) {
23 | plugins = [
24 | new webpack.HotModuleReplacementPlugin(),
25 | new webpack.DefinePlugin({
26 | __TEST__: false
27 | })
28 | ];
29 | }
30 | if (env.docs) {
31 | plugins = [
32 | new webpack.DefinePlugin({
33 | __TEST__: false,
34 | 'process.env': {
35 | NODE_ENV: JSON.stringify('production')
36 | }
37 | })
38 | ];
39 | }
40 | return plugins;
41 | }
42 |
43 | module.exports = function config(env = {}) {
44 | return {
45 | devServer: {
46 | historyApiFallback: true,
47 | disableHostCheck: true,
48 | host: '0.0.0.0',
49 | hot: true,
50 | publicPath: '/material-react-components'
51 | },
52 | devtool: env.docs ? 'cheap-module-source-map' : 'eval-source-map',
53 | entry: entry(env),
54 | output: {
55 | path: path.resolve(__dirname, env.docs ? '.' : './dist'),
56 | publicPath: '/',
57 | filename: '[name].js'
58 | },
59 | mode: 'development',
60 | module: {
61 | rules: [
62 | {
63 | test: /\.css$/,
64 | exclude: /node_modules/,
65 | use: [
66 | 'style-loader',
67 | 'css-loader?modules&localIdentName=[name].[local]',
68 | 'postcss-loader'
69 | ]
70 | },
71 | {
72 | test: /\.js$/,
73 | exclude: /node_modules/,
74 | loader: 'babel-loader'
75 | },
76 | {
77 | test: /\.svg$/,
78 | use: [
79 | {
80 | loader: 'babel-loader',
81 | options: {
82 | presets: [
83 | [
84 | '@babel/preset-env',
85 | {
86 | modules: false,
87 | targets: {
88 | browsers: ['last 2 versions']
89 | }
90 | }
91 | ]
92 | ]
93 | }
94 | },
95 | 'react-svg-loader'
96 | ]
97 | }
98 | ]
99 | },
100 | plugins: getPlugins(env)
101 | };
102 | };
103 |
--------------------------------------------------------------------------------
/webpack.config.ssr.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | devtool: 'eval',
5 | entry: [
6 | './test/ssr.js'
7 | ],
8 | output: {
9 | path: path.resolve(__dirname, './.tmp'),
10 | publicPath: '/',
11 | filename: '[name].js'
12 | },
13 | mode: 'development',
14 | module: {
15 | rules: [
16 | {
17 | test: /\.css$/,
18 | exclude: /node_modules/,
19 | use: [
20 | 'isomorphic-style-loader',
21 | 'css-loader?modules&localIdentName=[name].[local]',
22 | 'postcss-loader'
23 | ]
24 | },
25 | {
26 | test: /\.js$/,
27 | exclude: /node_modules/,
28 | use: [
29 | 'babel-loader'
30 | ]
31 | },
32 | {
33 | test: /\.svg$/,
34 | use: [
35 | 'babel-loader',
36 | 'react-svg-loader'
37 | ]
38 | }
39 | ]
40 | },
41 | target: 'node'
42 | };
43 |
--------------------------------------------------------------------------------