├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .istanbul.yml
├── .npmignore
├── LICENSE
├── README.md
├── bin
└── guard.js
├── circle.yml
├── examples
├── async-example
│ ├── .babelrc
│ ├── .eslintrc
│ ├── LICENSE
│ ├── index.css
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── server.js
│ └── webpack.config.babel.js
├── basic-example
│ ├── .babelrc
│ ├── .eslintrc
│ ├── index.css
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── server.js
│ └── webpack.config.babel.js
├── dynamic-fields
│ ├── .babelrc
│ ├── .eslintrc
│ ├── index.css
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── server.js
│ └── webpack.config.babel.js
├── foundation.css
└── mation-example
│ ├── .babelrc
│ ├── .eslintrc
│ ├── index.css
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── server.js
│ └── webpack.config.babel.js
├── package.json
├── src
├── createValidate.js
├── disabledFormSubmit.js
├── feedbackFormSubmit.js
├── form.js
├── from.js
├── getValue.js
├── index.js
└── resetFormButton.js
├── test
├── .eslintrc.json
├── creatValidate-test.js
├── disabledFormSubmit-test.js
├── feedbackFormSubmit-test.js
├── form-combination-test.js
├── form-test.js
├── from-test.js
├── getValue-test.js
├── mocha.opts
├── resetFormButton-test.js
└── test_setup.js
└── webpack.config.babel.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-decorators-legacy"],
4 | "env": {
5 | "test": {
6 | "plugins": ["rewire"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "react/forbid-prop-types": 0,
10 | "no-unused-vars": [1, { "varsIgnorePattern": "ignored" }],
11 | "react/jsx-filename-extension": 0
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [options]
8 | suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
9 | esproposal.class_instance_fields=enable
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | lib
4 | dist
5 | npm-debug.log
6 | coverage
7 |
--------------------------------------------------------------------------------
/.istanbul.yml:
--------------------------------------------------------------------------------
1 | instrumentation:
2 | root: src
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | doc
2 | src
3 | examples
4 | .DS_Store
5 | .eslintrc
6 | .gitignore
7 | .npmignore
8 | webpack.config.js
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Adam Nalisnick
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 | [](https://circleci.com/gh/theadam/react-inform)
2 | # react-inform
3 |
4 | Forms are not currently fun to work with in React. There are a lot of form libraries out there, but a lot of them have issues making them a pain to work with. These issues include:
5 |
6 | * You have to use provided input / form components rather than whatever components you want.
7 | * The provided inputs can have bugs and inconsistencies with the built-in components.
8 | * The forms cannot be controlled from outside of the library's components and user input.
9 | * You are forced into using refs to call methods on components.
10 | * Validations are not straightforward, and you cannot validate across fields (like having two different inputs that should have the same value).
11 |
12 | `react-inform` is a form library for React that avoids all of these issues.
13 |
14 | ## Demos
15 |
16 | [Simple Form](http://theadam.github.io/react-inform/examples/basic-example/)
17 |
18 | [Simple Form With Animations Using mation](http://theadam.github.io/react-inform/examples/mation-example/)
19 |
20 | [Form with Async validation](http://theadam.github.io/react-inform/examples/async-example/)
21 |
22 | [Dynamic Fields](http://theadam.github.io/react-inform/examples/dynamic-fields/)
23 |
24 | [Integration with react-intl v2](https://jsfiddle.net/theadam/d0hypvtz/21/)
25 |
26 | ## Installation
27 |
28 | `npm install --save react-inform`
29 |
30 | ## Guide
31 |
32 | Creating a [simple validating form](https://jsfiddle.net/theadam/Lc3nkx7g/5/embedded/result%2Cjs%2Ccss%2Chtml%2Cresources/) is easy with `react-inform`.
33 |
34 | `react-inform` provides a simple decorator. To that decorator you provide a list of fields in the form, and an optional validate function. The `validate` function takes in an object where the keys are fieldnames and the values are the values of the fields, and it should return an object where the keys are fieldnames and the values are error strings.
35 |
36 | We can configure a simple form that has the fields `username`, `password`, and `confirmPassword`. This form will just validate that the `username` and `password` exist and that `confirmPassword` matches the password. First just create the fields and validate function. There is a helper function to aid in creating these validate functions, but for now, we will write one out by hand to get the hang of it.
37 |
38 | ```jsx
39 | const fields = ['username', 'password', 'confirmPassword'];
40 |
41 | const validate = values => {
42 | const { username, password, confirmPassword } = values;
43 | const errors = {};
44 |
45 | if (!username) errors.username = 'Username is required!';
46 | if (!password) errors.password = 'Password is required!';
47 | if (confirmPassword !== password) errors.confirmPassword = 'Passwords must match!';
48 |
49 | return errors;
50 | }
51 | ```
52 |
53 | Now that you have the fields and validate function, you can just use the form decorator:
54 |
55 | ```jsx
56 | import { form } from 'react-inform';
57 |
58 | @form({
59 | fields,
60 | validate
61 | })
62 | class MyForm extends Component {
63 | ...
64 | ```
65 |
66 | Or you can use form as a function.
67 |
68 | ```jsx
69 | class MyForm extends Component {
70 | ...
71 | }
72 |
73 | MyForm = form({
74 | fields,
75 | validate
76 | })(MyForm);
77 | ```
78 |
79 | The form function wraps your react component passing a `form` and `fields` property. The `fields` property can be "plugged into" regular inputs in your render function. The fields also willl have errors if your validate function returned any!
80 |
81 | ```jsx
82 |
83 | {username.error}
84 |
85 | {password.error}
86 |
87 | {confirmPassword.error}
88 | ```
89 |
90 | Simple! Your form now validates, and keeps track of its state without all the boilerplate! The complete working example can be seen [here!](https://jsfiddle.net/theadam/Lc3nkx7g/5/embedded/result%2Cjs%2Ccss%2Chtml%2Cresources/)
91 |
92 | ## Api
93 |
94 | ### form({ fields, [validate] })
95 |
96 | Creates a function used to wrap a react component. Accepts an object that contains the keys `fields` and optionally `validate`. `fields` is an array of fieldnames. `validate` is a function used to validate the field values.
97 |
98 | The validate function should accept a map of fieldnames to values, and return a map of fieldnames to error strings (or a Promise that resolves to a map of fieldnames to error strings).
99 |
100 | #### Accepted properties
101 |
102 | | Property | Description |
103 | |---------|----------|
104 | | `value`| to control the data in the form from the parent of a form. |
105 | | `onChange`| to react to changes to the form in the parent of a form. |
106 | | `onValidate`| to react to changes in the validation state of the form. The callback will be passed a boolean that is `true` when the form becomes valid. |
107 | | `fields`| Can be used instead of the `field` key in the decorator to control the list of fields in the form |
108 | | `validate`| Can be used instead of the `validate` key in the decorator to control the validate function |
109 | | `touched`| An object to control the `touched` state of each field. The objects shape is `{ [fieldName]: [boolean], ... }` |
110 | | `onTouch`| An event fired anytime the touch state of any field in the form changes. The handler receives the entire touched state of the form, with the same shape as the value for the `touched` prop |
111 |
112 | #### Properties passed to wrapped components
113 |
114 | ##### form
115 |
116 | Form contains some utility functions to affect the wrapping form.
117 |
118 | | Function | Description |
119 | |----------|-------|
120 | | `isValid()`| returns true if all of the fields are valid |
121 | | `forceValidate()`| Causes all of the fields to get passed their error properties by setting every field's touched state to true. Usually errors are only passed after the field has been "touched" (after onBlur). |
122 | | `values()`| returns the current value of all the form fields as a map. |
123 | | `onValues(values)`| forcefully sets all of the values in the form to the passed values. |
124 | | `touch(fields)`| sets the touched state to true for all of the fields named in the passed array arg |
125 | | `untouch(fields)`| sets the touched state to false for all of the fields named in the passed array arg |
126 | | `resetTouched()`| sets the touched state for all fields to false |
127 |
128 | ##### fields
129 |
130 | `fields` is a map of the fields you passed in. Each field has a value, onChange, and onBlur property so that they can be passed into regular input components and have them be controlled with `react-inform`. If there is an error message on a field, the field will also have an error property.
131 |
132 | All of the props that should be passed to your rendered input component (value, onChange, and onBlur) are also available using the `props` property. For example:
133 |
134 | ```jsx
135 | const { fieldName } = this.props.fields;
136 | ...
137 |
138 | ```
139 |
140 | This keeps react from complaining about unknown props being passed to input components. See [this link](https://gist.github.com/jimfb/d99e0678e9da715ccf6454961ef04d1b) for more details.
141 |
142 | ### createValidate(ruleMap)
143 |
144 | A helper to create `validate` functions from maps of rules. Rule functions can return Promises that resolve to booleans to support async validations.
145 |
146 | This is an example rule map that ensures that username exists, password exists, and confirmPassword matches password. Notice the keys to the rules are the error messages that will appear when the field is invalid.
147 |
148 | ```jsx
149 | const exists = v => Boolean(v);
150 |
151 | const ruleMap = {
152 | username: {
153 | 'Username must exist': exists
154 | },
155 | password: {
156 | 'Password must exist': exists
157 | },
158 | confirmPassword: {
159 | 'Passwords must match': (value, values) => value === values.password
160 | }
161 | };
162 | ```
163 |
164 | ### from(rulesMap)
165 |
166 | Creates an object that can be passed directly to the form function / decorator using a ruleMap. All fields must be represented in the ruleMap. Fields without validations should have empty objects as their values in the rule map.
167 |
168 | `@form(from(rulesMap))`
169 |
170 | ### ResetFormButton
171 |
172 | A Component which can be used inside a React Component wrapped with `react-inform`. When clicked it will reset the form so that it contains no values.
173 |
174 | Accepts a `resetTouched` boolean prop. When set to true, clicking the reset button will also reset all the fields' touched states.
175 |
176 | ### DisabledFormSubmit
177 |
178 | A Component which can be used inside a React Component wrapped with `react-inform`. It will remain disabled until all of the fields in the form are valid. Once its enabled, clicking it will submit the form.
179 |
180 | ### FeedbackFormSubmit
181 |
182 | A Component which can be used inside a React Component wrapped with `react-inform`. When clicked, if the form has invalid fields, it will force those fields' errors to be passed as props. If the form is valid, it will submit the form.
183 |
184 | ## License
185 |
186 | MIT
187 |
--------------------------------------------------------------------------------
/bin/guard.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 | import chokidar from 'chokidar';
3 | import childProcess from 'child_process';
4 | import fs from 'fs';
5 |
6 | let proc;
7 |
8 | function toTest(path) {
9 | if (path.startsWith('test') && path.endsWith('test.js')) {
10 | return path;
11 | }
12 | if (!path.startsWith('src')) return undefined;
13 |
14 | const testPath = `test/${path.replace(/^src\//, '').replace(/\.js/, '-test.js')}`;
15 | return fs.existsSync(testPath) ? testPath : undefined;
16 | }
17 |
18 | chokidar.watch(['src', 'test']).on('change', path => {
19 | const testPath = toTest(path);
20 | if (!testPath) return;
21 | console.log(`Running Test file ${testPath}`);
22 | if (proc) proc.kill('SIGINT');
23 |
24 | proc = childProcess.spawn('./node_modules/.bin/_mocha', [testPath], { stdio: 'inherit' });
25 | proc.on('error', err => console.log(err));
26 | });
27 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 6.3.0
4 |
--------------------------------------------------------------------------------
/examples/async-example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-decorators-legacy"]
4 | }
5 |
--------------------------------------------------------------------------------
/examples/async-example/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "import/no-unresolved": 0,
10 | "react/prop-types": 0,
11 | "react/no-multi-comp": 0
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/async-example/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Adam Nalisnick
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 |
--------------------------------------------------------------------------------
/examples/async-example/index.css:
--------------------------------------------------------------------------------
1 | #container {
2 | margin-top: 40px;
3 | }
4 |
5 | .ui-hint {
6 | color: #DD0000;
7 | display: block;
8 | font-size: 1em;
9 | margin-top: -15px;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/async-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | react-inform-async-example
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/async-example/index.js:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import '../foundation.css';
3 |
4 | import React, { Component } from 'react';
5 | import { render } from 'react-dom';
6 | import alertify from 'alertify.js';
7 |
8 | import {
9 | form,
10 | from,
11 | FeedbackFormSubmit,
12 | } from 'react-inform';
13 |
14 | alertify.logPosition('top right');
15 |
16 | function LabeledInput({ text, error, id, props }) {
17 | return (
18 |
19 | {text}
20 |
21 | {error}
22 |
23 | );
24 | }
25 |
26 | function validateUsernameAsync(username) {
27 | return Promise.resolve().then(() => username.length > 3);
28 | }
29 |
30 | @form(from({
31 | username: {
32 | 'Username is required': Boolean,
33 | 'Username must longer than 3 characters (async)': validateUsernameAsync,
34 | },
35 | password: {
36 | 'Password is required': Boolean,
37 | },
38 | }))
39 | class MyForm extends Component {
40 | handleSubmit(e) {
41 | e.preventDefault();
42 | alertify.success('Yay, it submitted');
43 | }
44 |
45 | render() {
46 | const { username, password } = this.props.fields;
47 |
48 | return (
49 |
58 | );
59 | }
60 | }
61 |
62 | function App() {
63 | return (
64 |
70 | );
71 | }
72 |
73 | render( , document.getElementById('container'));
74 |
--------------------------------------------------------------------------------
/examples/async-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inform-async-example-example",
3 | "version": "1.0.0",
4 | "description": "async-example",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "babel-node server.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/theadam/react-inform.git"
12 | },
13 | "license": "MIT",
14 | "devDependencies": {
15 | "babel-cli": "^6.11.4",
16 | "babel-core": "^6.11.4",
17 | "babel-eslint": "^6.1.2",
18 | "babel-loader": "^6.2.4",
19 | "babel-plugin-rewire": "^1.0.0-rc-4",
20 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
21 | "babel-preset-es2015": "^6.9.0",
22 | "babel-preset-react": "^6.11.1",
23 | "babel-preset-stage-0": "^6.5.0",
24 | "css-loader": "^0.23.1",
25 | "eslint": "^3.1.1",
26 | "eslint-config-airbnb": "^9.0.1",
27 | "eslint-plugin-import": "^1.11.1",
28 | "eslint-plugin-jsx-a11y": "^2.0.1",
29 | "eslint-plugin-react": "^5.2.2",
30 | "extract-text-webpack-plugin": "^1.0.1",
31 | "react": "^15.2.1",
32 | "style-loader": "^0.13.0",
33 | "webpack": "^1.13.1",
34 | "webpack-dev-server": "^1.14.1"
35 | },
36 | "dependencies": {
37 | "alertify.js": "^1.0.10",
38 | "react": "^15.0.2",
39 | "react-dom": "^15.0.2"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/async-example/server.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import WebpackDevServer from 'webpack-dev-server';
3 | import config from './webpack.config.babel';
4 |
5 | config.entry.push('webpack-dev-server/client?http://localhost:3000');
6 | config.entry.push('webpack/hot/only-dev-server');
7 |
8 | config.plugins.push(new webpack.HotModuleReplacementPlugin());
9 |
10 | new WebpackDevServer(webpack(config), {
11 | publicPath: config.output.publicPath,
12 | hot: true,
13 | historyApiFallback: true,
14 | stats: {
15 | colors: true,
16 | },
17 | }).listen(3000, 'localhost', err => {
18 | if (err) {
19 | // eslint-disable-next-line no-console
20 | console.log(err);
21 | }
22 |
23 | // eslint-disable-next-line no-console
24 | console.log('Listening at localhost:3000');
25 | });
26 |
--------------------------------------------------------------------------------
/examples/async-example/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 |
4 | module.exports = {
5 | devtool: 'eval-source-map',
6 | entry: [
7 | './index',
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | publicPath: '/dist',
13 | },
14 | plugins: [
15 | new webpack.NoErrorsPlugin(),
16 | ],
17 | resolve: {
18 | alias: {
19 | 'react-inform': path.join(__dirname, '..', '..', 'src'),
20 | },
21 | extensions: ['', '.js'],
22 | },
23 | module: {
24 | loaders: [{
25 | test: /\.js$/,
26 | loaders: ['babel'],
27 | exclude: /node_modules/,
28 | include: __dirname,
29 | }, {
30 | test: /\.js$/,
31 | loaders: ['babel'],
32 | include: path.join(__dirname, '..', '..', 'src'),
33 | },
34 | {
35 | test: /\.css$/,
36 | loader: 'style-loader!css-loader',
37 | }],
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/examples/basic-example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-decorators-legacy"]
4 | }
5 |
--------------------------------------------------------------------------------
/examples/basic-example/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "import/no-unresolved": 0,
10 | "react/prop-types": 0,
11 | "react/no-multi-comp": 0
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/basic-example/index.css:
--------------------------------------------------------------------------------
1 | #container {
2 | margin-top: 40px;
3 | }
4 |
5 | .ui-hint {
6 | color: #DD0000;
7 | display: block;
8 | font-size: 1em;
9 | margin-top: -15px;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/basic-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | react-inform-basic-example
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/basic-example/index.js:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import '../foundation.css';
3 |
4 | import React, { Component } from 'react';
5 | import TextArea from 'react-textarea-autosize';
6 | import { render } from 'react-dom';
7 | import alertify from 'alertify.js';
8 | import { isEmail } from 'string-validator';
9 | import {
10 | form,
11 | from,
12 | DisabledFormSubmit,
13 | FeedbackFormSubmit,
14 | ResetFormButton,
15 | } from 'react-inform';
16 |
17 | alertify.logPosition('top right');
18 |
19 | function LabeledInput({ text, error, id, props }) {
20 | return (
21 |
22 | {text}
23 |
24 | {error}
25 |
26 | );
27 | }
28 |
29 | const isRequired = value => value;
30 |
31 | const rulesMap = {
32 | checkbox: {
33 | 'Must be checked': v => v,
34 | },
35 | username: {
36 | 'Username is required': isRequired,
37 | },
38 | email: {
39 | 'Email is required': isRequired,
40 | 'Must be a valid email': isEmail(),
41 | },
42 | password: {
43 | 'Password is required': isRequired,
44 | },
45 | confirmPassword: {
46 | 'Passwords must match': (value, { password }) => value === password,
47 | },
48 | };
49 |
50 | @form(from(rulesMap))
51 | class MyForm extends Component {
52 | handleSubmit(e) {
53 | e.preventDefault();
54 | alertify.success('Awesome, it submitted!');
55 | }
56 |
57 | render() {
58 | const { username, email, password, confirmPassword, checkbox } = this.props.fields;
59 | return (
60 |
83 | );
84 | }
85 | }
86 |
87 | class App extends Component {
88 | state= {
89 | value: {
90 | checkbox: true,
91 | username: 'test user',
92 | email: 'badEmail',
93 | },
94 | };
95 |
96 | setValue(value) {
97 | this.setState({ value, typedValue: undefined });
98 | }
99 |
100 | handleChange(e) {
101 | const { value } = e.target;
102 | this.setState({ typedValue: value });
103 |
104 | try {
105 | const objValue = JSON.parse(value);
106 | this.setState({ value: objValue });
107 | } catch (error) {
108 | // do nothing
109 | }
110 | }
111 |
112 | render() {
113 | const { value, typedValue } = this.state;
114 | return (
115 |
116 |
117 | this.setValue(v)} value={value} />
118 |
119 |
Try editing this
120 |
126 | );
127 | }
128 | }
129 |
130 | render( , document.getElementById('container'));
131 |
--------------------------------------------------------------------------------
/examples/basic-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inform-basic-example-example",
3 | "version": "1.0.0",
4 | "description": "basic-example",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "babel-node server.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/theadam/react-inform.git"
12 | },
13 | "license": "MIT",
14 | "devDependencies": {
15 | "babel-cli": "^6.11.4",
16 | "babel-core": "^6.11.4",
17 | "babel-eslint": "^6.1.2",
18 | "babel-loader": "^6.2.4",
19 | "babel-plugin-rewire": "^1.0.0-rc-4",
20 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
21 | "babel-preset-es2015": "^6.9.0",
22 | "babel-preset-react": "^6.11.1",
23 | "babel-preset-stage-0": "^6.5.0",
24 | "css-loader": "^0.23.1",
25 | "eslint": "^3.1.1",
26 | "eslint-config-airbnb": "^9.0.1",
27 | "eslint-plugin-import": "^1.11.1",
28 | "eslint-plugin-jsx-a11y": "^2.0.1",
29 | "eslint-plugin-react": "^5.2.2",
30 | "extract-text-webpack-plugin": "^1.0.1",
31 | "react": "^15.2.1",
32 | "style-loader": "^0.13.0",
33 | "webpack": "^1.13.1",
34 | "webpack-dev-server": "^1.14.1"
35 | },
36 | "dependencies": {
37 | "alertify.js": "^1.0.9",
38 | "react": "^15.0.0",
39 | "react-dom": "^15.0.0",
40 | "react-textarea-autosize": "^4.0.5",
41 | "string-validator": "^1.0.5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/basic-example/server.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import WebpackDevServer from 'webpack-dev-server';
3 | import config from './webpack.config.babel';
4 |
5 | config.entry.push('webpack-dev-server/client?http://localhost:3000');
6 | config.entry.push('webpack/hot/only-dev-server');
7 |
8 | config.plugins.push(new webpack.HotModuleReplacementPlugin());
9 |
10 | new WebpackDevServer(webpack(config), {
11 | publicPath: config.output.publicPath,
12 | hot: true,
13 | historyApiFallback: true,
14 | stats: {
15 | colors: true,
16 | },
17 | }).listen(3000, 'localhost', err => {
18 | if (err) {
19 | // eslint-disable-next-line no-console
20 | console.log(err);
21 | }
22 |
23 | // eslint-disable-next-line no-console
24 | console.log('Listening at localhost:3000');
25 | });
26 |
--------------------------------------------------------------------------------
/examples/basic-example/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 |
4 | module.exports = {
5 | devtool: 'eval-source-map',
6 | entry: [
7 | './index',
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | publicPath: '/dist',
13 | },
14 | plugins: [
15 | new webpack.NoErrorsPlugin(),
16 | ],
17 | resolve: {
18 | alias: {
19 | 'react-inform': path.join(__dirname, '..', '..', 'src'),
20 | },
21 | extensions: ['', '.js'],
22 | },
23 | module: {
24 | loaders: [{
25 | test: /\.js$/,
26 | loaders: ['babel'],
27 | exclude: /node_modules/,
28 | include: __dirname,
29 | }, {
30 | test: /\.js$/,
31 | loaders: ['babel'],
32 | include: path.join(__dirname, '..', '..', 'src'),
33 | },
34 | {
35 | test: /\.css$/,
36 | loader: 'style-loader!css-loader',
37 | }],
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-decorators-legacy"]
4 | }
5 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "import/no-unresolved": 0,
10 | "react/prop-types": 0,
11 | "react/no-multi-comp": 0
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/index.css:
--------------------------------------------------------------------------------
1 | #container {
2 | margin-top: 40px;
3 | }
4 |
5 | .ui-hint {
6 | color: #DD0000;
7 | display: block;
8 | font-size: 1em;
9 | margin-top: -15px;
10 | }
11 |
12 | fields {
13 | position: relative;
14 | }
15 |
16 | .fields input {
17 | width: 25%;
18 | display: inline-block;
19 | }
20 |
21 | .fields input:nth-child(2) {
22 | width: 75%;
23 | display: inline-block;
24 | }
25 |
26 | .fields .button {
27 | position: absolute;
28 | border-bottom: 0;
29 | }
30 |
31 | pre {
32 | position: absolute;
33 | top: 20%;
34 | left: 110%;
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | react-inform-dynamic-fields-example
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/index.js:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import '../foundation.css';
3 |
4 | import React, { Component } from 'react';
5 | import { render } from 'react-dom';
6 | import alertify from 'alertify.js';
7 | import {
8 | form,
9 | DisabledFormSubmit,
10 | FeedbackFormSubmit,
11 | ResetFormButton,
12 | } from 'react-inform';
13 |
14 | alertify.logPosition('top right');
15 |
16 | function LabeledInput({ text, error, id, props }) {
17 | return (
18 |
19 | {text}
20 |
21 | {error}
22 |
23 | );
24 | }
25 |
26 | @form()
27 | class MyForm extends Component {
28 | handleSubmit(e) {
29 | e.preventDefault();
30 | alertify.success('Awesome, it submitted!');
31 | }
32 |
33 | render() {
34 | const { fields: fieldMap } = this.props;
35 | const fields = Object.keys(fieldMap);
36 | return (
37 |
54 | );
55 | }
56 | }
57 |
58 | class App extends Component {
59 | state= {
60 | descriptors: [{
61 | fieldName: 'Email',
62 | validate: "x => !!x && !!x.match(/[^@]+@[^@]+\\.[^@]+/) || 'Must be a valid email'",
63 | }, {
64 | fieldName: 'Password',
65 | validate: "x => !!x || 'Password is required'",
66 | }, {
67 | fieldName: 'Confirm Password',
68 | validate: "(x, values) => x === values.Password || 'Passwords must match'",
69 | }],
70 | };
71 |
72 | setPruned(value) {
73 | this.setState({ value: this.pruned(value) });
74 | }
75 |
76 | pruned = (value = this.state.value) =>
77 | this.fields().reduce((acc, field) => (
78 | value.hasOwnProperty(field) ? { ...acc, [field]: value[field] } : acc
79 | ), {});
80 |
81 | convertDescriptor = ({ fieldName, validate }) => {
82 | const result = this.tryEval(validate);
83 | if (result instanceof Function) {
84 | return {
85 | fieldName,
86 | validate: result,
87 | };
88 | }
89 | return {
90 | fieldName,
91 | validate: () => undefined,
92 | };
93 | }
94 |
95 | makeValidate = () => {
96 | const descriptors = this.state.descriptors.map(this.convertDescriptor);
97 |
98 | return values => {
99 | const errors = {};
100 | descriptors.forEach(({ fieldName, validate }) => {
101 | if (!fieldName) return;
102 | try {
103 | const result = validate(values[fieldName], values);
104 | if (result === true) return;
105 | if (result) errors[fieldName] = result;
106 | } catch (e) {
107 | errors[fieldName] = e.toString();
108 | }
109 | });
110 | return errors;
111 | };
112 | }
113 |
114 | fields() {
115 | const { descriptors } = this.state;
116 | return descriptors.filter(({ fieldName }) => !!fieldName).map(d => d.fieldName);
117 | }
118 |
119 | tryEval(code) {
120 | try {
121 | return eval(code);
122 | } catch (e) {
123 | return undefined;
124 | }
125 | }
126 |
127 | handleRemove = i => {
128 | const { descriptors } = this.state;
129 | this.setState({
130 | descriptors: descriptors.filter((d, idx) => idx !== i),
131 | }, () => this.setPruned());
132 | }
133 |
134 | handleAdd = () => {
135 | const { descriptors } = this.state;
136 | this.setState({
137 | descriptors: descriptors.concat({
138 | fieldName: '',
139 | validate: 'x => undefined',
140 | }),
141 | });
142 | }
143 |
144 | handleChange = e => {
145 | const { descriptors } = this.state;
146 | const index = parseInt(e.target.getAttribute('data-index'), 10);
147 | const type = e.target.getAttribute('data-type');
148 | this.setState({
149 | descriptors: descriptors.map((des, i) => {
150 | if (i !== index) return des;
151 | return {
152 | ...des,
153 | [type]: e.target.value,
154 | };
155 | }),
156 | });
157 | }
158 |
159 | render() {
160 | const { descriptors } = this.state;
161 |
162 | return (
163 |
164 |
165 |
this.setPruned(value)}
167 | value={this.state.value}
168 | fields={this.fields()}
169 | validate={this.makeValidate()}
170 | />
171 | {JSON.stringify(this.state.value || {}, 2, 2)}
172 |
173 |
202 |
+
203 |
204 | );
205 | }
206 | }
207 |
208 | render( , document.getElementById('container'));
209 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inform-dynamic-fields-example-example",
3 | "version": "1.0.0",
4 | "description": "basic-example",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "babel-node server.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/theadam/react-inform.git"
12 | },
13 | "license": "MIT",
14 | "devDependencies": {
15 | "babel-cli": "^6.11.4",
16 | "babel-core": "^6.11.4",
17 | "babel-eslint": "^6.1.2",
18 | "babel-loader": "^6.2.4",
19 | "babel-plugin-rewire": "^1.0.0-rc-4",
20 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
21 | "babel-preset-es2015": "^6.9.0",
22 | "babel-preset-react": "^6.11.1",
23 | "babel-preset-stage-0": "^6.5.0",
24 | "css-loader": "^0.23.1",
25 | "eslint": "^3.1.1",
26 | "eslint-config-airbnb": "^9.0.1",
27 | "eslint-plugin-import": "^1.11.1",
28 | "eslint-plugin-jsx-a11y": "^2.0.1",
29 | "eslint-plugin-react": "^5.2.2",
30 | "extract-text-webpack-plugin": "^1.0.1",
31 | "react": "^15.2.1",
32 | "style-loader": "^0.13.0",
33 | "webpack": "^1.13.1",
34 | "webpack-dev-server": "^1.14.1"
35 | },
36 | "dependencies": {
37 | "alertify.js": "^1.0.9",
38 | "react": "^15.0.0",
39 | "react-dom": "^15.0.0",
40 | "react-textarea-autosize": "^3.3.0",
41 | "string-validator": "^1.0.5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/server.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import WebpackDevServer from 'webpack-dev-server';
3 | import config from './webpack.config.babel';
4 |
5 | config.entry.push('webpack-dev-server/client?http://localhost:3000');
6 | config.entry.push('webpack/hot/only-dev-server');
7 |
8 | config.plugins.push(new webpack.HotModuleReplacementPlugin());
9 |
10 | new WebpackDevServer(webpack(config), {
11 | publicPath: config.output.publicPath,
12 | hot: true,
13 | historyApiFallback: true,
14 | stats: {
15 | colors: true,
16 | },
17 | }).listen(3000, 'localhost', err => {
18 | if (err) {
19 | // eslint-disable-next-line no-console
20 | console.log(err);
21 | }
22 |
23 | // eslint-disable-next-line no-console
24 | console.log('Listening at localhost:3000');
25 | });
26 |
--------------------------------------------------------------------------------
/examples/dynamic-fields/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 |
4 | module.exports = {
5 | devtool: 'eval-source-map',
6 | entry: [
7 | './index',
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | publicPath: '/dist',
13 | },
14 | plugins: [
15 | new webpack.NoErrorsPlugin(),
16 | ],
17 | resolve: {
18 | alias: {
19 | 'react-inform': path.join(__dirname, '..', '..', 'src'),
20 | },
21 | extensions: ['', '.js'],
22 | },
23 | module: {
24 | loaders: [{
25 | test: /\.js$/,
26 | loaders: ['babel'],
27 | exclude: /node_modules/,
28 | include: __dirname,
29 | }, {
30 | test: /\.js$/,
31 | loaders: ['babel'],
32 | include: path.join(__dirname, '..', '..', 'src'),
33 | },
34 | {
35 | test: /\.css$/,
36 | loader: 'style-loader!css-loader',
37 | }],
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/examples/foundation.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | /**
3 | * Foundation for Sites by ZURB
4 | * Version 6.2.0
5 | * foundation.zurb.com
6 | * Licensed under MIT Open Source
7 | */
8 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
9 | /**
10 | * 1. Set default font family to sans-serif.
11 | * 2. Prevent iOS and IE text size adjust after device orientation change,
12 | * without disabling user zoom.
13 | */
14 | html {
15 | font-family: sans-serif;
16 | /* 1 */
17 | -ms-text-size-adjust: 100%;
18 | /* 2 */
19 | -webkit-text-size-adjust: 100%;
20 | /* 2 */ }
21 |
22 | /**
23 | * Remove default margin.
24 | */
25 | body {
26 | margin: 0; }
27 |
28 | /* HTML5 display definitions
29 | ========================================================================== */
30 | /**
31 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
32 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
33 | * and Firefox.
34 | * Correct `block` display not defined for `main` in IE 11.
35 | */
36 | article,
37 | aside,
38 | details,
39 | figcaption,
40 | figure,
41 | footer,
42 | header,
43 | hgroup,
44 | main,
45 | menu,
46 | nav,
47 | section,
48 | summary {
49 | display: block; }
50 |
51 | /**
52 | * 1. Correct `inline-block` display not defined in IE 8/9.
53 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
54 | */
55 | audio,
56 | canvas,
57 | progress,
58 | video {
59 | display: inline-block;
60 | /* 1 */
61 | vertical-align: baseline;
62 | /* 2 */ }
63 |
64 | /**
65 | * Prevent modern browsers from displaying `audio` without controls.
66 | * Remove excess height in iOS 5 devices.
67 | */
68 | audio:not([controls]) {
69 | display: none;
70 | height: 0; }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | */
76 | [hidden],
77 | template {
78 | display: none; }
79 |
80 | /* Links
81 | ========================================================================== */
82 | /**
83 | * Remove the gray background color from active links in IE 10.
84 | */
85 | a {
86 | background-color: transparent; }
87 |
88 | /**
89 | * Improve readability of focused elements when they are also in an
90 | * active/hover state.
91 | */
92 | a:active,
93 | a:hover {
94 | outline: 0; }
95 |
96 | /* Text-level semantics
97 | ========================================================================== */
98 | /**
99 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
100 | */
101 | abbr[title] {
102 | border-bottom: 1px dotted; }
103 |
104 | /**
105 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
106 | */
107 | b,
108 | strong {
109 | font-weight: bold; }
110 |
111 | /**
112 | * Address styling not present in Safari and Chrome.
113 | */
114 | dfn {
115 | font-style: italic; }
116 |
117 | /**
118 | * Address variable `h1` font-size and margin within `section` and `article`
119 | * contexts in Firefox 4+, Safari, and Chrome.
120 | */
121 | h1 {
122 | font-size: 2em;
123 | margin: 0.67em 0; }
124 |
125 | /**
126 | * Address styling not present in IE 8/9.
127 | */
128 | mark {
129 | background: #ff0;
130 | color: #000; }
131 |
132 | /**
133 | * Address inconsistent and variable font size in all browsers.
134 | */
135 | small {
136 | font-size: 80%; }
137 |
138 | /**
139 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
140 | */
141 | sub,
142 | sup {
143 | font-size: 75%;
144 | line-height: 0;
145 | position: relative;
146 | vertical-align: baseline; }
147 |
148 | sup {
149 | top: -0.5em; }
150 |
151 | sub {
152 | bottom: -0.25em; }
153 |
154 | /* Embedded content
155 | ========================================================================== */
156 | /**
157 | * Remove border when inside `a` element in IE 8/9/10.
158 | */
159 | img {
160 | border: 0; }
161 |
162 | /**
163 | * Correct overflow not hidden in IE 9/10/11.
164 | */
165 | svg:not(:root) {
166 | overflow: hidden; }
167 |
168 | /* Grouping content
169 | ========================================================================== */
170 | /**
171 | * Address margin not present in IE 8/9 and Safari.
172 | */
173 | figure {
174 | margin: 1em 40px; }
175 |
176 | /**
177 | * Address differences between Firefox and other browsers.
178 | */
179 | hr {
180 | box-sizing: content-box;
181 | height: 0; }
182 |
183 | /**
184 | * Contain overflow in all browsers.
185 | */
186 | pre {
187 | overflow: auto; }
188 |
189 | /**
190 | * Address odd `em`-unit font size rendering in all browsers.
191 | */
192 | code,
193 | kbd,
194 | pre,
195 | samp {
196 | font-family: monospace, monospace;
197 | font-size: 1em; }
198 |
199 | /* Forms
200 | ========================================================================== */
201 | /**
202 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
203 | * styling of `select`, unless a `border` property is set.
204 | */
205 | /**
206 | * 1. Correct color not being inherited.
207 | * Known issue: affects color of disabled elements.
208 | * 2. Correct font properties not being inherited.
209 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
210 | */
211 | button,
212 | input,
213 | optgroup,
214 | select,
215 | textarea {
216 | color: inherit;
217 | /* 1 */
218 | font: inherit;
219 | /* 2 */
220 | margin: 0;
221 | /* 3 */ }
222 |
223 | /**
224 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
225 | */
226 | button {
227 | overflow: visible; }
228 |
229 | /**
230 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
231 | * All other form control elements do not inherit `text-transform` values.
232 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
233 | * Correct `select` style inheritance in Firefox.
234 | */
235 | button,
236 | select {
237 | text-transform: none; }
238 |
239 | /**
240 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
241 | * and `video` controls.
242 | * 2. Correct inability to style clickable `input` types in iOS.
243 | * 3. Improve usability and consistency of cursor style between image-type
244 | * `input` and others.
245 | */
246 | button,
247 | html input[type="button"],
248 | input[type="reset"],
249 | input[type="submit"] {
250 | -webkit-appearance: button;
251 | /* 2 */
252 | cursor: pointer;
253 | /* 3 */ }
254 |
255 | /**
256 | * Re-set default cursor for disabled elements.
257 | */
258 | button[disabled],
259 | html input[disabled] {
260 | cursor: default; }
261 |
262 | /**
263 | * Remove inner padding and border in Firefox 4+.
264 | */
265 | button::-moz-focus-inner,
266 | input::-moz-focus-inner {
267 | border: 0;
268 | padding: 0; }
269 |
270 | /**
271 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
272 | * the UA stylesheet.
273 | */
274 | input {
275 | line-height: normal; }
276 |
277 | /**
278 | * It's recommended that you don't attempt to style these elements.
279 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
280 | *
281 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
282 | * 2. Remove excess padding in IE 8/9/10.
283 | */
284 | input[type="checkbox"],
285 | input[type="radio"] {
286 | box-sizing: border-box;
287 | /* 1 */
288 | padding: 0;
289 | /* 2 */ }
290 |
291 | /**
292 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
293 | * `font-size` values of the `input`, it causes the cursor style of the
294 | * decrement button to change from `default` to `text`.
295 | */
296 | input[type="number"]::-webkit-inner-spin-button,
297 | input[type="number"]::-webkit-outer-spin-button {
298 | height: auto; }
299 |
300 | /**
301 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
302 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
303 | */
304 | input[type="search"] {
305 | -webkit-appearance: textfield;
306 | /* 1 */
307 | box-sizing: content-box;
308 | /* 2 */ }
309 |
310 | /**
311 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
312 | * Safari (but not Chrome) clips the cancel button when the search input has
313 | * padding (and `textfield` appearance).
314 | */
315 | input[type="search"]::-webkit-search-cancel-button,
316 | input[type="search"]::-webkit-search-decoration {
317 | -webkit-appearance: none; }
318 |
319 | /**
320 | * Define consistent border, margin, and padding.
321 | * [NOTE] We don't enable this ruleset in Foundation, because we want the element to have plain styling.
322 | */
323 | /* fieldset {
324 | border: 1px solid #c0c0c0;
325 | margin: 0 2px;
326 | padding: 0.35em 0.625em 0.75em;
327 | } */
328 | /**
329 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
330 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
331 | */
332 | legend {
333 | border: 0;
334 | /* 1 */
335 | padding: 0;
336 | /* 2 */ }
337 |
338 | /**
339 | * Remove default vertical scrollbar in IE 8/9/10/11.
340 | */
341 | textarea {
342 | overflow: auto; }
343 |
344 | /**
345 | * Don't inherit the `font-weight` (applied by a rule above).
346 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
347 | */
348 | optgroup {
349 | font-weight: bold; }
350 |
351 | /* Tables
352 | ========================================================================== */
353 | /**
354 | * Remove most spacing between table cells.
355 | */
356 | table {
357 | border-collapse: collapse;
358 | border-spacing: 0; }
359 |
360 | td,
361 | th {
362 | padding: 0; }
363 |
364 | .foundation-mq {
365 | font-family: "small=0em&medium=40em&large=64em&xlarge=75em&xxlarge=90em"; }
366 |
367 | html {
368 | font-size: 100%;
369 | box-sizing: border-box; }
370 |
371 | *,
372 | *:before,
373 | *:after {
374 | box-sizing: inherit; }
375 |
376 | body {
377 | padding: 0;
378 | margin: 0;
379 | font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
380 | font-weight: normal;
381 | line-height: 1.5;
382 | color: #0a0a0a;
383 | background: #fefefe;
384 | -webkit-font-smoothing: antialiased;
385 | -moz-osx-font-smoothing: grayscale; }
386 |
387 | img {
388 | max-width: 100%;
389 | height: auto;
390 | -ms-interpolation-mode: bicubic;
391 | display: inline-block;
392 | vertical-align: middle; }
393 |
394 | textarea {
395 | height: auto;
396 | min-height: 50px;
397 | border-radius: 0; }
398 |
399 | select {
400 | width: 100%;
401 | border-radius: 0; }
402 |
403 | #map_canvas img,
404 | #map_canvas embed,
405 | #map_canvas object,
406 | .map_canvas img,
407 | .map_canvas embed,
408 | .map_canvas object,
409 | .mqa-display img,
410 | .mqa-display embed,
411 | .mqa-display object {
412 | max-width: none !important; }
413 |
414 | button {
415 | -webkit-appearance: none;
416 | -moz-appearance: none;
417 | background: transparent;
418 | padding: 0;
419 | border: 0;
420 | border-radius: 0;
421 | line-height: 1; }
422 |
423 | .is-visible {
424 | display: block !important; }
425 |
426 | .is-hidden {
427 | display: none !important; }
428 |
429 | .row {
430 | max-width: 75rem;
431 | margin-left: auto;
432 | margin-right: auto; }
433 | .row::before, .row::after {
434 | content: ' ';
435 | display: table;
436 | -webkit-flex-basis: 0;
437 | -ms-flex-preferred-size: 0;
438 | flex-basis: 0;
439 | -webkit-order: 1;
440 | -ms-flex-order: 1;
441 | order: 1; }
442 | .row::after {
443 | clear: both; }
444 | .row.collapse > .column, .row.collapse > .columns {
445 | padding-left: 0;
446 | padding-right: 0; }
447 | .row .row {
448 | margin-left: -0.625rem;
449 | margin-right: -0.625rem; }
450 | @media screen and (min-width: 40em) {
451 | .row .row {
452 | margin-left: -0.9375rem;
453 | margin-right: -0.9375rem; } }
454 | .row .row.collapse {
455 | margin-left: 0;
456 | margin-right: 0; }
457 | .row.expanded {
458 | max-width: none; }
459 | .row.expanded .row {
460 | margin-left: auto;
461 | margin-right: auto; }
462 |
463 | .column, .columns {
464 | width: 100%;
465 | float: left;
466 | padding-left: 0.625rem;
467 | padding-right: 0.625rem; }
468 | @media screen and (min-width: 40em) {
469 | .column, .columns {
470 | padding-left: 0.9375rem;
471 | padding-right: 0.9375rem; } }
472 | .column:last-child:not(:first-child), .columns:last-child:not(:first-child) {
473 | float: right; }
474 | .column.end:last-child:last-child, .end.columns:last-child:last-child {
475 | float: left; }
476 |
477 | .column.row.row, .row.row.columns {
478 | float: none; }
479 | .row .column.row.row, .row .row.row.columns {
480 | padding-left: 0;
481 | padding-right: 0;
482 | margin-left: 0;
483 | margin-right: 0; }
484 |
485 | .small-1 {
486 | width: 8.33333%; }
487 |
488 | .small-push-1 {
489 | position: relative;
490 | left: 8.33333%; }
491 |
492 | .small-pull-1 {
493 | position: relative;
494 | left: -8.33333%; }
495 |
496 | .small-offset-0 {
497 | margin-left: 0%; }
498 |
499 | .small-2 {
500 | width: 16.66667%; }
501 |
502 | .small-push-2 {
503 | position: relative;
504 | left: 16.66667%; }
505 |
506 | .small-pull-2 {
507 | position: relative;
508 | left: -16.66667%; }
509 |
510 | .small-offset-1 {
511 | margin-left: 8.33333%; }
512 |
513 | .small-3 {
514 | width: 25%; }
515 |
516 | .small-push-3 {
517 | position: relative;
518 | left: 25%; }
519 |
520 | .small-pull-3 {
521 | position: relative;
522 | left: -25%; }
523 |
524 | .small-offset-2 {
525 | margin-left: 16.66667%; }
526 |
527 | .small-4 {
528 | width: 33.33333%; }
529 |
530 | .small-push-4 {
531 | position: relative;
532 | left: 33.33333%; }
533 |
534 | .small-pull-4 {
535 | position: relative;
536 | left: -33.33333%; }
537 |
538 | .small-offset-3 {
539 | margin-left: 25%; }
540 |
541 | .small-5 {
542 | width: 41.66667%; }
543 |
544 | .small-push-5 {
545 | position: relative;
546 | left: 41.66667%; }
547 |
548 | .small-pull-5 {
549 | position: relative;
550 | left: -41.66667%; }
551 |
552 | .small-offset-4 {
553 | margin-left: 33.33333%; }
554 |
555 | .small-6 {
556 | width: 50%; }
557 |
558 | .small-push-6 {
559 | position: relative;
560 | left: 50%; }
561 |
562 | .small-pull-6 {
563 | position: relative;
564 | left: -50%; }
565 |
566 | .small-offset-5 {
567 | margin-left: 41.66667%; }
568 |
569 | .small-7 {
570 | width: 58.33333%; }
571 |
572 | .small-push-7 {
573 | position: relative;
574 | left: 58.33333%; }
575 |
576 | .small-pull-7 {
577 | position: relative;
578 | left: -58.33333%; }
579 |
580 | .small-offset-6 {
581 | margin-left: 50%; }
582 |
583 | .small-8 {
584 | width: 66.66667%; }
585 |
586 | .small-push-8 {
587 | position: relative;
588 | left: 66.66667%; }
589 |
590 | .small-pull-8 {
591 | position: relative;
592 | left: -66.66667%; }
593 |
594 | .small-offset-7 {
595 | margin-left: 58.33333%; }
596 |
597 | .small-9 {
598 | width: 75%; }
599 |
600 | .small-push-9 {
601 | position: relative;
602 | left: 75%; }
603 |
604 | .small-pull-9 {
605 | position: relative;
606 | left: -75%; }
607 |
608 | .small-offset-8 {
609 | margin-left: 66.66667%; }
610 |
611 | .small-10 {
612 | width: 83.33333%; }
613 |
614 | .small-push-10 {
615 | position: relative;
616 | left: 83.33333%; }
617 |
618 | .small-pull-10 {
619 | position: relative;
620 | left: -83.33333%; }
621 |
622 | .small-offset-9 {
623 | margin-left: 75%; }
624 |
625 | .small-11 {
626 | width: 91.66667%; }
627 |
628 | .small-push-11 {
629 | position: relative;
630 | left: 91.66667%; }
631 |
632 | .small-pull-11 {
633 | position: relative;
634 | left: -91.66667%; }
635 |
636 | .small-offset-10 {
637 | margin-left: 83.33333%; }
638 |
639 | .small-12 {
640 | width: 100%; }
641 |
642 | .small-offset-11 {
643 | margin-left: 91.66667%; }
644 |
645 | .small-up-1 > .column, .small-up-1 > .columns {
646 | width: 100%;
647 | float: left; }
648 | .small-up-1 > .column:nth-of-type(1n), .small-up-1 > .columns:nth-of-type(1n) {
649 | clear: none; }
650 | .small-up-1 > .column:nth-of-type(1n+1), .small-up-1 > .columns:nth-of-type(1n+1) {
651 | clear: both; }
652 | .small-up-1 > .column:last-child, .small-up-1 > .columns:last-child {
653 | float: left; }
654 |
655 | .small-up-2 > .column, .small-up-2 > .columns {
656 | width: 50%;
657 | float: left; }
658 | .small-up-2 > .column:nth-of-type(1n), .small-up-2 > .columns:nth-of-type(1n) {
659 | clear: none; }
660 | .small-up-2 > .column:nth-of-type(2n+1), .small-up-2 > .columns:nth-of-type(2n+1) {
661 | clear: both; }
662 | .small-up-2 > .column:last-child, .small-up-2 > .columns:last-child {
663 | float: left; }
664 |
665 | .small-up-3 > .column, .small-up-3 > .columns {
666 | width: 33.33333%;
667 | float: left; }
668 | .small-up-3 > .column:nth-of-type(1n), .small-up-3 > .columns:nth-of-type(1n) {
669 | clear: none; }
670 | .small-up-3 > .column:nth-of-type(3n+1), .small-up-3 > .columns:nth-of-type(3n+1) {
671 | clear: both; }
672 | .small-up-3 > .column:last-child, .small-up-3 > .columns:last-child {
673 | float: left; }
674 |
675 | .small-up-4 > .column, .small-up-4 > .columns {
676 | width: 25%;
677 | float: left; }
678 | .small-up-4 > .column:nth-of-type(1n), .small-up-4 > .columns:nth-of-type(1n) {
679 | clear: none; }
680 | .small-up-4 > .column:nth-of-type(4n+1), .small-up-4 > .columns:nth-of-type(4n+1) {
681 | clear: both; }
682 | .small-up-4 > .column:last-child, .small-up-4 > .columns:last-child {
683 | float: left; }
684 |
685 | .small-up-5 > .column, .small-up-5 > .columns {
686 | width: 20%;
687 | float: left; }
688 | .small-up-5 > .column:nth-of-type(1n), .small-up-5 > .columns:nth-of-type(1n) {
689 | clear: none; }
690 | .small-up-5 > .column:nth-of-type(5n+1), .small-up-5 > .columns:nth-of-type(5n+1) {
691 | clear: both; }
692 | .small-up-5 > .column:last-child, .small-up-5 > .columns:last-child {
693 | float: left; }
694 |
695 | .small-up-6 > .column, .small-up-6 > .columns {
696 | width: 16.66667%;
697 | float: left; }
698 | .small-up-6 > .column:nth-of-type(1n), .small-up-6 > .columns:nth-of-type(1n) {
699 | clear: none; }
700 | .small-up-6 > .column:nth-of-type(6n+1), .small-up-6 > .columns:nth-of-type(6n+1) {
701 | clear: both; }
702 | .small-up-6 > .column:last-child, .small-up-6 > .columns:last-child {
703 | float: left; }
704 |
705 | .small-up-7 > .column, .small-up-7 > .columns {
706 | width: 14.28571%;
707 | float: left; }
708 | .small-up-7 > .column:nth-of-type(1n), .small-up-7 > .columns:nth-of-type(1n) {
709 | clear: none; }
710 | .small-up-7 > .column:nth-of-type(7n+1), .small-up-7 > .columns:nth-of-type(7n+1) {
711 | clear: both; }
712 | .small-up-7 > .column:last-child, .small-up-7 > .columns:last-child {
713 | float: left; }
714 |
715 | .small-up-8 > .column, .small-up-8 > .columns {
716 | width: 12.5%;
717 | float: left; }
718 | .small-up-8 > .column:nth-of-type(1n), .small-up-8 > .columns:nth-of-type(1n) {
719 | clear: none; }
720 | .small-up-8 > .column:nth-of-type(8n+1), .small-up-8 > .columns:nth-of-type(8n+1) {
721 | clear: both; }
722 | .small-up-8 > .column:last-child, .small-up-8 > .columns:last-child {
723 | float: left; }
724 |
725 | .small-collapse > .column, .small-collapse > .columns {
726 | padding-left: 0;
727 | padding-right: 0; }
728 |
729 | .small-collapse .row {
730 | margin-left: 0;
731 | margin-right: 0; }
732 |
733 | .small-uncollapse > .column, .small-uncollapse > .columns {
734 | padding-left: 0.625rem;
735 | padding-right: 0.625rem; }
736 |
737 | .small-centered {
738 | float: none;
739 | margin-left: auto;
740 | margin-right: auto; }
741 |
742 | .small-uncentered,
743 | .small-push-0,
744 | .small-pull-0 {
745 | position: static;
746 | margin-left: 0;
747 | margin-right: 0;
748 | float: left; }
749 |
750 | @media screen and (min-width: 40em) {
751 | .medium-1 {
752 | width: 8.33333%; }
753 | .medium-push-1 {
754 | position: relative;
755 | left: 8.33333%; }
756 | .medium-pull-1 {
757 | position: relative;
758 | left: -8.33333%; }
759 | .medium-offset-0 {
760 | margin-left: 0%; }
761 | .medium-2 {
762 | width: 16.66667%; }
763 | .medium-push-2 {
764 | position: relative;
765 | left: 16.66667%; }
766 | .medium-pull-2 {
767 | position: relative;
768 | left: -16.66667%; }
769 | .medium-offset-1 {
770 | margin-left: 8.33333%; }
771 | .medium-3 {
772 | width: 25%; }
773 | .medium-push-3 {
774 | position: relative;
775 | left: 25%; }
776 | .medium-pull-3 {
777 | position: relative;
778 | left: -25%; }
779 | .medium-offset-2 {
780 | margin-left: 16.66667%; }
781 | .medium-4 {
782 | width: 33.33333%; }
783 | .medium-push-4 {
784 | position: relative;
785 | left: 33.33333%; }
786 | .medium-pull-4 {
787 | position: relative;
788 | left: -33.33333%; }
789 | .medium-offset-3 {
790 | margin-left: 25%; }
791 | .medium-5 {
792 | width: 41.66667%; }
793 | .medium-push-5 {
794 | position: relative;
795 | left: 41.66667%; }
796 | .medium-pull-5 {
797 | position: relative;
798 | left: -41.66667%; }
799 | .medium-offset-4 {
800 | margin-left: 33.33333%; }
801 | .medium-6 {
802 | width: 50%; }
803 | .medium-push-6 {
804 | position: relative;
805 | left: 50%; }
806 | .medium-pull-6 {
807 | position: relative;
808 | left: -50%; }
809 | .medium-offset-5 {
810 | margin-left: 41.66667%; }
811 | .medium-7 {
812 | width: 58.33333%; }
813 | .medium-push-7 {
814 | position: relative;
815 | left: 58.33333%; }
816 | .medium-pull-7 {
817 | position: relative;
818 | left: -58.33333%; }
819 | .medium-offset-6 {
820 | margin-left: 50%; }
821 | .medium-8 {
822 | width: 66.66667%; }
823 | .medium-push-8 {
824 | position: relative;
825 | left: 66.66667%; }
826 | .medium-pull-8 {
827 | position: relative;
828 | left: -66.66667%; }
829 | .medium-offset-7 {
830 | margin-left: 58.33333%; }
831 | .medium-9 {
832 | width: 75%; }
833 | .medium-push-9 {
834 | position: relative;
835 | left: 75%; }
836 | .medium-pull-9 {
837 | position: relative;
838 | left: -75%; }
839 | .medium-offset-8 {
840 | margin-left: 66.66667%; }
841 | .medium-10 {
842 | width: 83.33333%; }
843 | .medium-push-10 {
844 | position: relative;
845 | left: 83.33333%; }
846 | .medium-pull-10 {
847 | position: relative;
848 | left: -83.33333%; }
849 | .medium-offset-9 {
850 | margin-left: 75%; }
851 | .medium-11 {
852 | width: 91.66667%; }
853 | .medium-push-11 {
854 | position: relative;
855 | left: 91.66667%; }
856 | .medium-pull-11 {
857 | position: relative;
858 | left: -91.66667%; }
859 | .medium-offset-10 {
860 | margin-left: 83.33333%; }
861 | .medium-12 {
862 | width: 100%; }
863 | .medium-offset-11 {
864 | margin-left: 91.66667%; }
865 | .medium-up-1 > .column, .medium-up-1 > .columns {
866 | width: 100%;
867 | float: left; }
868 | .medium-up-1 > .column:nth-of-type(1n), .medium-up-1 > .columns:nth-of-type(1n) {
869 | clear: none; }
870 | .medium-up-1 > .column:nth-of-type(1n+1), .medium-up-1 > .columns:nth-of-type(1n+1) {
871 | clear: both; }
872 | .medium-up-1 > .column:last-child, .medium-up-1 > .columns:last-child {
873 | float: left; }
874 | .medium-up-2 > .column, .medium-up-2 > .columns {
875 | width: 50%;
876 | float: left; }
877 | .medium-up-2 > .column:nth-of-type(1n), .medium-up-2 > .columns:nth-of-type(1n) {
878 | clear: none; }
879 | .medium-up-2 > .column:nth-of-type(2n+1), .medium-up-2 > .columns:nth-of-type(2n+1) {
880 | clear: both; }
881 | .medium-up-2 > .column:last-child, .medium-up-2 > .columns:last-child {
882 | float: left; }
883 | .medium-up-3 > .column, .medium-up-3 > .columns {
884 | width: 33.33333%;
885 | float: left; }
886 | .medium-up-3 > .column:nth-of-type(1n), .medium-up-3 > .columns:nth-of-type(1n) {
887 | clear: none; }
888 | .medium-up-3 > .column:nth-of-type(3n+1), .medium-up-3 > .columns:nth-of-type(3n+1) {
889 | clear: both; }
890 | .medium-up-3 > .column:last-child, .medium-up-3 > .columns:last-child {
891 | float: left; }
892 | .medium-up-4 > .column, .medium-up-4 > .columns {
893 | width: 25%;
894 | float: left; }
895 | .medium-up-4 > .column:nth-of-type(1n), .medium-up-4 > .columns:nth-of-type(1n) {
896 | clear: none; }
897 | .medium-up-4 > .column:nth-of-type(4n+1), .medium-up-4 > .columns:nth-of-type(4n+1) {
898 | clear: both; }
899 | .medium-up-4 > .column:last-child, .medium-up-4 > .columns:last-child {
900 | float: left; }
901 | .medium-up-5 > .column, .medium-up-5 > .columns {
902 | width: 20%;
903 | float: left; }
904 | .medium-up-5 > .column:nth-of-type(1n), .medium-up-5 > .columns:nth-of-type(1n) {
905 | clear: none; }
906 | .medium-up-5 > .column:nth-of-type(5n+1), .medium-up-5 > .columns:nth-of-type(5n+1) {
907 | clear: both; }
908 | .medium-up-5 > .column:last-child, .medium-up-5 > .columns:last-child {
909 | float: left; }
910 | .medium-up-6 > .column, .medium-up-6 > .columns {
911 | width: 16.66667%;
912 | float: left; }
913 | .medium-up-6 > .column:nth-of-type(1n), .medium-up-6 > .columns:nth-of-type(1n) {
914 | clear: none; }
915 | .medium-up-6 > .column:nth-of-type(6n+1), .medium-up-6 > .columns:nth-of-type(6n+1) {
916 | clear: both; }
917 | .medium-up-6 > .column:last-child, .medium-up-6 > .columns:last-child {
918 | float: left; }
919 | .medium-up-7 > .column, .medium-up-7 > .columns {
920 | width: 14.28571%;
921 | float: left; }
922 | .medium-up-7 > .column:nth-of-type(1n), .medium-up-7 > .columns:nth-of-type(1n) {
923 | clear: none; }
924 | .medium-up-7 > .column:nth-of-type(7n+1), .medium-up-7 > .columns:nth-of-type(7n+1) {
925 | clear: both; }
926 | .medium-up-7 > .column:last-child, .medium-up-7 > .columns:last-child {
927 | float: left; }
928 | .medium-up-8 > .column, .medium-up-8 > .columns {
929 | width: 12.5%;
930 | float: left; }
931 | .medium-up-8 > .column:nth-of-type(1n), .medium-up-8 > .columns:nth-of-type(1n) {
932 | clear: none; }
933 | .medium-up-8 > .column:nth-of-type(8n+1), .medium-up-8 > .columns:nth-of-type(8n+1) {
934 | clear: both; }
935 | .medium-up-8 > .column:last-child, .medium-up-8 > .columns:last-child {
936 | float: left; }
937 | .medium-collapse > .column, .medium-collapse > .columns {
938 | padding-left: 0;
939 | padding-right: 0; }
940 | .medium-collapse .row {
941 | margin-left: 0;
942 | margin-right: 0; }
943 | .medium-uncollapse > .column, .medium-uncollapse > .columns {
944 | padding-left: 0.9375rem;
945 | padding-right: 0.9375rem; }
946 | .medium-centered {
947 | float: none;
948 | margin-left: auto;
949 | margin-right: auto; }
950 | .medium-uncentered,
951 | .medium-push-0,
952 | .medium-pull-0 {
953 | position: static;
954 | margin-left: 0;
955 | margin-right: 0;
956 | float: left; } }
957 |
958 | @media screen and (min-width: 64em) {
959 | .large-1 {
960 | width: 8.33333%; }
961 | .large-push-1 {
962 | position: relative;
963 | left: 8.33333%; }
964 | .large-pull-1 {
965 | position: relative;
966 | left: -8.33333%; }
967 | .large-offset-0 {
968 | margin-left: 0%; }
969 | .large-2 {
970 | width: 16.66667%; }
971 | .large-push-2 {
972 | position: relative;
973 | left: 16.66667%; }
974 | .large-pull-2 {
975 | position: relative;
976 | left: -16.66667%; }
977 | .large-offset-1 {
978 | margin-left: 8.33333%; }
979 | .large-3 {
980 | width: 25%; }
981 | .large-push-3 {
982 | position: relative;
983 | left: 25%; }
984 | .large-pull-3 {
985 | position: relative;
986 | left: -25%; }
987 | .large-offset-2 {
988 | margin-left: 16.66667%; }
989 | .large-4 {
990 | width: 33.33333%; }
991 | .large-push-4 {
992 | position: relative;
993 | left: 33.33333%; }
994 | .large-pull-4 {
995 | position: relative;
996 | left: -33.33333%; }
997 | .large-offset-3 {
998 | margin-left: 25%; }
999 | .large-5 {
1000 | width: 41.66667%; }
1001 | .large-push-5 {
1002 | position: relative;
1003 | left: 41.66667%; }
1004 | .large-pull-5 {
1005 | position: relative;
1006 | left: -41.66667%; }
1007 | .large-offset-4 {
1008 | margin-left: 33.33333%; }
1009 | .large-6 {
1010 | width: 50%; }
1011 | .large-push-6 {
1012 | position: relative;
1013 | left: 50%; }
1014 | .large-pull-6 {
1015 | position: relative;
1016 | left: -50%; }
1017 | .large-offset-5 {
1018 | margin-left: 41.66667%; }
1019 | .large-7 {
1020 | width: 58.33333%; }
1021 | .large-push-7 {
1022 | position: relative;
1023 | left: 58.33333%; }
1024 | .large-pull-7 {
1025 | position: relative;
1026 | left: -58.33333%; }
1027 | .large-offset-6 {
1028 | margin-left: 50%; }
1029 | .large-8 {
1030 | width: 66.66667%; }
1031 | .large-push-8 {
1032 | position: relative;
1033 | left: 66.66667%; }
1034 | .large-pull-8 {
1035 | position: relative;
1036 | left: -66.66667%; }
1037 | .large-offset-7 {
1038 | margin-left: 58.33333%; }
1039 | .large-9 {
1040 | width: 75%; }
1041 | .large-push-9 {
1042 | position: relative;
1043 | left: 75%; }
1044 | .large-pull-9 {
1045 | position: relative;
1046 | left: -75%; }
1047 | .large-offset-8 {
1048 | margin-left: 66.66667%; }
1049 | .large-10 {
1050 | width: 83.33333%; }
1051 | .large-push-10 {
1052 | position: relative;
1053 | left: 83.33333%; }
1054 | .large-pull-10 {
1055 | position: relative;
1056 | left: -83.33333%; }
1057 | .large-offset-9 {
1058 | margin-left: 75%; }
1059 | .large-11 {
1060 | width: 91.66667%; }
1061 | .large-push-11 {
1062 | position: relative;
1063 | left: 91.66667%; }
1064 | .large-pull-11 {
1065 | position: relative;
1066 | left: -91.66667%; }
1067 | .large-offset-10 {
1068 | margin-left: 83.33333%; }
1069 | .large-12 {
1070 | width: 100%; }
1071 | .large-offset-11 {
1072 | margin-left: 91.66667%; }
1073 | .large-up-1 > .column, .large-up-1 > .columns {
1074 | width: 100%;
1075 | float: left; }
1076 | .large-up-1 > .column:nth-of-type(1n), .large-up-1 > .columns:nth-of-type(1n) {
1077 | clear: none; }
1078 | .large-up-1 > .column:nth-of-type(1n+1), .large-up-1 > .columns:nth-of-type(1n+1) {
1079 | clear: both; }
1080 | .large-up-1 > .column:last-child, .large-up-1 > .columns:last-child {
1081 | float: left; }
1082 | .large-up-2 > .column, .large-up-2 > .columns {
1083 | width: 50%;
1084 | float: left; }
1085 | .large-up-2 > .column:nth-of-type(1n), .large-up-2 > .columns:nth-of-type(1n) {
1086 | clear: none; }
1087 | .large-up-2 > .column:nth-of-type(2n+1), .large-up-2 > .columns:nth-of-type(2n+1) {
1088 | clear: both; }
1089 | .large-up-2 > .column:last-child, .large-up-2 > .columns:last-child {
1090 | float: left; }
1091 | .large-up-3 > .column, .large-up-3 > .columns {
1092 | width: 33.33333%;
1093 | float: left; }
1094 | .large-up-3 > .column:nth-of-type(1n), .large-up-3 > .columns:nth-of-type(1n) {
1095 | clear: none; }
1096 | .large-up-3 > .column:nth-of-type(3n+1), .large-up-3 > .columns:nth-of-type(3n+1) {
1097 | clear: both; }
1098 | .large-up-3 > .column:last-child, .large-up-3 > .columns:last-child {
1099 | float: left; }
1100 | .large-up-4 > .column, .large-up-4 > .columns {
1101 | width: 25%;
1102 | float: left; }
1103 | .large-up-4 > .column:nth-of-type(1n), .large-up-4 > .columns:nth-of-type(1n) {
1104 | clear: none; }
1105 | .large-up-4 > .column:nth-of-type(4n+1), .large-up-4 > .columns:nth-of-type(4n+1) {
1106 | clear: both; }
1107 | .large-up-4 > .column:last-child, .large-up-4 > .columns:last-child {
1108 | float: left; }
1109 | .large-up-5 > .column, .large-up-5 > .columns {
1110 | width: 20%;
1111 | float: left; }
1112 | .large-up-5 > .column:nth-of-type(1n), .large-up-5 > .columns:nth-of-type(1n) {
1113 | clear: none; }
1114 | .large-up-5 > .column:nth-of-type(5n+1), .large-up-5 > .columns:nth-of-type(5n+1) {
1115 | clear: both; }
1116 | .large-up-5 > .column:last-child, .large-up-5 > .columns:last-child {
1117 | float: left; }
1118 | .large-up-6 > .column, .large-up-6 > .columns {
1119 | width: 16.66667%;
1120 | float: left; }
1121 | .large-up-6 > .column:nth-of-type(1n), .large-up-6 > .columns:nth-of-type(1n) {
1122 | clear: none; }
1123 | .large-up-6 > .column:nth-of-type(6n+1), .large-up-6 > .columns:nth-of-type(6n+1) {
1124 | clear: both; }
1125 | .large-up-6 > .column:last-child, .large-up-6 > .columns:last-child {
1126 | float: left; }
1127 | .large-up-7 > .column, .large-up-7 > .columns {
1128 | width: 14.28571%;
1129 | float: left; }
1130 | .large-up-7 > .column:nth-of-type(1n), .large-up-7 > .columns:nth-of-type(1n) {
1131 | clear: none; }
1132 | .large-up-7 > .column:nth-of-type(7n+1), .large-up-7 > .columns:nth-of-type(7n+1) {
1133 | clear: both; }
1134 | .large-up-7 > .column:last-child, .large-up-7 > .columns:last-child {
1135 | float: left; }
1136 | .large-up-8 > .column, .large-up-8 > .columns {
1137 | width: 12.5%;
1138 | float: left; }
1139 | .large-up-8 > .column:nth-of-type(1n), .large-up-8 > .columns:nth-of-type(1n) {
1140 | clear: none; }
1141 | .large-up-8 > .column:nth-of-type(8n+1), .large-up-8 > .columns:nth-of-type(8n+1) {
1142 | clear: both; }
1143 | .large-up-8 > .column:last-child, .large-up-8 > .columns:last-child {
1144 | float: left; }
1145 | .large-collapse > .column, .large-collapse > .columns {
1146 | padding-left: 0;
1147 | padding-right: 0; }
1148 | .large-collapse .row {
1149 | margin-left: 0;
1150 | margin-right: 0; }
1151 | .large-uncollapse > .column, .large-uncollapse > .columns {
1152 | padding-left: 0.9375rem;
1153 | padding-right: 0.9375rem; }
1154 | .large-centered {
1155 | float: none;
1156 | margin-left: auto;
1157 | margin-right: auto; }
1158 | .large-uncentered,
1159 | .large-push-0,
1160 | .large-pull-0 {
1161 | position: static;
1162 | margin-left: 0;
1163 | margin-right: 0;
1164 | float: left; } }
1165 |
1166 | div,
1167 | dl,
1168 | dt,
1169 | dd,
1170 | ul,
1171 | ol,
1172 | li,
1173 | h1,
1174 | h2,
1175 | h3,
1176 | h4,
1177 | h5,
1178 | h6,
1179 | pre,
1180 | form,
1181 | p,
1182 | blockquote,
1183 | th,
1184 | td {
1185 | margin: 0;
1186 | padding: 0; }
1187 |
1188 | p {
1189 | font-size: inherit;
1190 | line-height: 1.6;
1191 | margin-bottom: 1rem;
1192 | text-rendering: optimizeLegibility; }
1193 |
1194 | em,
1195 | i {
1196 | font-style: italic;
1197 | line-height: inherit; }
1198 |
1199 | strong,
1200 | b {
1201 | font-weight: bold;
1202 | line-height: inherit; }
1203 |
1204 | small {
1205 | font-size: 80%;
1206 | line-height: inherit; }
1207 |
1208 | h1,
1209 | h2,
1210 | h3,
1211 | h4,
1212 | h5,
1213 | h6 {
1214 | font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
1215 | font-weight: normal;
1216 | font-style: normal;
1217 | color: inherit;
1218 | text-rendering: optimizeLegibility;
1219 | margin-top: 0;
1220 | margin-bottom: 0.5rem;
1221 | line-height: 1.4; }
1222 | h1 small,
1223 | h2 small,
1224 | h3 small,
1225 | h4 small,
1226 | h5 small,
1227 | h6 small {
1228 | color: #cacaca;
1229 | line-height: 0; }
1230 |
1231 | h1 {
1232 | font-size: 1.5rem; }
1233 |
1234 | h2 {
1235 | font-size: 1.25rem; }
1236 |
1237 | h3 {
1238 | font-size: 1.1875rem; }
1239 |
1240 | h4 {
1241 | font-size: 1.125rem; }
1242 |
1243 | h5 {
1244 | font-size: 1.0625rem; }
1245 |
1246 | h6 {
1247 | font-size: 1rem; }
1248 |
1249 | @media screen and (min-width: 40em) {
1250 | h1 {
1251 | font-size: 3rem; }
1252 | h2 {
1253 | font-size: 2.5rem; }
1254 | h3 {
1255 | font-size: 1.9375rem; }
1256 | h4 {
1257 | font-size: 1.5625rem; }
1258 | h5 {
1259 | font-size: 1.25rem; }
1260 | h6 {
1261 | font-size: 1rem; } }
1262 |
1263 | a {
1264 | color: #2199e8;
1265 | text-decoration: none;
1266 | line-height: inherit;
1267 | cursor: pointer; }
1268 | a:hover, a:focus {
1269 | color: #1585cf; }
1270 | a img {
1271 | border: 0; }
1272 |
1273 | hr {
1274 | max-width: 75rem;
1275 | height: 0;
1276 | border-right: 0;
1277 | border-top: 0;
1278 | border-bottom: 1px solid #cacaca;
1279 | border-left: 0;
1280 | margin: 1.25rem auto;
1281 | clear: both; }
1282 |
1283 | ul,
1284 | ol,
1285 | dl {
1286 | line-height: 1.6;
1287 | margin-bottom: 1rem;
1288 | list-style-position: outside; }
1289 |
1290 | li {
1291 | font-size: inherit; }
1292 |
1293 | ul {
1294 | list-style-type: disc;
1295 | margin-left: 1.25rem; }
1296 |
1297 | ol {
1298 | margin-left: 1.25rem; }
1299 |
1300 | ul ul, ol ul, ul ol, ol ol {
1301 | margin-left: 1.25rem;
1302 | margin-bottom: 0; }
1303 |
1304 | dl {
1305 | margin-bottom: 1rem; }
1306 | dl dt {
1307 | margin-bottom: 0.3rem;
1308 | font-weight: bold; }
1309 |
1310 | blockquote {
1311 | margin: 0 0 1rem;
1312 | padding: 0.5625rem 1.25rem 0 1.1875rem;
1313 | border-left: 1px solid #cacaca; }
1314 | blockquote, blockquote p {
1315 | line-height: 1.6;
1316 | color: #8a8a8a; }
1317 |
1318 | cite {
1319 | display: block;
1320 | font-size: 0.8125rem;
1321 | color: #8a8a8a; }
1322 | cite:before {
1323 | content: '\2014 \0020'; }
1324 |
1325 | abbr {
1326 | color: #0a0a0a;
1327 | cursor: help;
1328 | border-bottom: 1px dotted #0a0a0a; }
1329 |
1330 | code {
1331 | font-family: Consolas, "Liberation Mono", Courier, monospace;
1332 | font-weight: normal;
1333 | color: #0a0a0a;
1334 | background-color: #e6e6e6;
1335 | border: 1px solid #cacaca;
1336 | padding: 0.125rem 0.3125rem 0.0625rem; }
1337 |
1338 | kbd {
1339 | padding: 0.125rem 0.25rem 0;
1340 | margin: 0;
1341 | background-color: #e6e6e6;
1342 | color: #0a0a0a;
1343 | font-family: Consolas, "Liberation Mono", Courier, monospace; }
1344 |
1345 | .subheader {
1346 | margin-top: 0.2rem;
1347 | margin-bottom: 0.5rem;
1348 | font-weight: normal;
1349 | line-height: 1.4;
1350 | color: #8a8a8a; }
1351 |
1352 | .lead {
1353 | font-size: 125%;
1354 | line-height: 1.6; }
1355 |
1356 | .stat {
1357 | font-size: 2.5rem;
1358 | line-height: 1; }
1359 | p + .stat {
1360 | margin-top: -1rem; }
1361 |
1362 | .no-bullet {
1363 | margin-left: 0;
1364 | list-style: none; }
1365 |
1366 | .text-left {
1367 | text-align: left; }
1368 |
1369 | .text-right {
1370 | text-align: right; }
1371 |
1372 | .text-center {
1373 | text-align: center; }
1374 |
1375 | .text-justify {
1376 | text-align: justify; }
1377 |
1378 | @media screen and (min-width: 40em) {
1379 | .medium-text-left {
1380 | text-align: left; }
1381 | .medium-text-right {
1382 | text-align: right; }
1383 | .medium-text-center {
1384 | text-align: center; }
1385 | .medium-text-justify {
1386 | text-align: justify; } }
1387 |
1388 | @media screen and (min-width: 64em) {
1389 | .large-text-left {
1390 | text-align: left; }
1391 | .large-text-right {
1392 | text-align: right; }
1393 | .large-text-center {
1394 | text-align: center; }
1395 | .large-text-justify {
1396 | text-align: justify; } }
1397 |
1398 | .show-for-print {
1399 | display: none !important; }
1400 |
1401 | @media print {
1402 | * {
1403 | background: transparent !important;
1404 | color: black !important;
1405 | box-shadow: none !important;
1406 | text-shadow: none !important; }
1407 | .show-for-print {
1408 | display: block !important; }
1409 | .hide-for-print {
1410 | display: none !important; }
1411 | table.show-for-print {
1412 | display: table !important; }
1413 | thead.show-for-print {
1414 | display: table-header-group !important; }
1415 | tbody.show-for-print {
1416 | display: table-row-group !important; }
1417 | tr.show-for-print {
1418 | display: table-row !important; }
1419 | td.show-for-print {
1420 | display: table-cell !important; }
1421 | th.show-for-print {
1422 | display: table-cell !important; }
1423 | a,
1424 | a:visited {
1425 | text-decoration: underline; }
1426 | a[href]:after {
1427 | content: " (" attr(href) ")"; }
1428 | .ir a:after,
1429 | a[href^='javascript:']:after,
1430 | a[href^='#']:after {
1431 | content: ''; }
1432 | abbr[title]:after {
1433 | content: " (" attr(title) ")"; }
1434 | pre,
1435 | blockquote {
1436 | border: 1px solid #8a8a8a;
1437 | page-break-inside: avoid; }
1438 | thead {
1439 | display: table-header-group; }
1440 | tr,
1441 | img {
1442 | page-break-inside: avoid; }
1443 | img {
1444 | max-width: 100% !important; }
1445 | @page {
1446 | margin: 0.5cm; }
1447 | p,
1448 | h2,
1449 | h3 {
1450 | orphans: 3;
1451 | widows: 3; }
1452 | h2,
1453 | h3 {
1454 | page-break-after: avoid; } }
1455 |
1456 | [type='text'], [type='password'], [type='date'], [type='datetime'], [type='datetime-local'], [type='month'], [type='week'], [type='email'], [type='number'], [type='search'], [type='tel'], [type='time'], [type='url'], [type='color'],
1457 | textarea {
1458 | display: block;
1459 | box-sizing: border-box;
1460 | width: 100%;
1461 | height: 2.4375rem;
1462 | padding: 0.5rem;
1463 | border: 1px solid #cacaca;
1464 | margin: 0 0 1rem;
1465 | font-family: inherit;
1466 | font-size: 1rem;
1467 | color: #0a0a0a;
1468 | background-color: #fefefe;
1469 | box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
1470 | border-radius: 0;
1471 | transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
1472 | -webkit-appearance: none;
1473 | -moz-appearance: none; }
1474 | [type='text']:focus, [type='password']:focus, [type='date']:focus, [type='datetime']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='week']:focus, [type='email']:focus, [type='number']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='url']:focus, [type='color']:focus,
1475 | textarea:focus {
1476 | border: 1px solid #8a8a8a;
1477 | background-color: #fefefe;
1478 | outline: none;
1479 | box-shadow: 0 0 5px #cacaca;
1480 | transition: box-shadow 0.5s, border-color 0.25s ease-in-out; }
1481 |
1482 | textarea {
1483 | max-width: 100%; }
1484 | textarea[rows] {
1485 | height: auto; }
1486 |
1487 | input::-webkit-input-placeholder,
1488 | textarea::-webkit-input-placeholder {
1489 | color: #cacaca; }
1490 |
1491 | input::-moz-placeholder,
1492 | textarea::-moz-placeholder {
1493 | color: #cacaca; }
1494 |
1495 | input:-ms-input-placeholder,
1496 | textarea:-ms-input-placeholder {
1497 | color: #cacaca; }
1498 |
1499 | input::placeholder,
1500 | textarea::placeholder {
1501 | color: #cacaca; }
1502 |
1503 | input:disabled, input[readonly],
1504 | textarea:disabled,
1505 | textarea[readonly] {
1506 | background-color: #e6e6e6;
1507 | cursor: default; }
1508 |
1509 | [type='submit'],
1510 | [type='button'] {
1511 | border-radius: 0;
1512 | -webkit-appearance: none;
1513 | -moz-appearance: none; }
1514 |
1515 | input[type='search'] {
1516 | box-sizing: border-box; }
1517 |
1518 | [type='file'],
1519 | [type='checkbox'],
1520 | [type='radio'] {
1521 | margin: 0 0 1rem; }
1522 |
1523 | [type='checkbox'] + label,
1524 | [type='radio'] + label {
1525 | display: inline-block;
1526 | margin-left: 0.5rem;
1527 | margin-right: 1rem;
1528 | margin-bottom: 0;
1529 | vertical-align: baseline; }
1530 |
1531 | label > [type='checkbox'],
1532 | label > [type='radio'] {
1533 | margin-right: 0.5rem; }
1534 |
1535 | [type='file'] {
1536 | width: 100%; }
1537 |
1538 | label {
1539 | display: block;
1540 | margin: 0;
1541 | font-size: 0.875rem;
1542 | font-weight: normal;
1543 | line-height: 1.8;
1544 | color: #0a0a0a; }
1545 | label.middle {
1546 | margin: 0 0 1rem;
1547 | padding: 0.5625rem 0; }
1548 |
1549 | .help-text {
1550 | margin-top: -0.5rem;
1551 | font-size: 0.8125rem;
1552 | font-style: italic;
1553 | color: #0a0a0a; }
1554 |
1555 | .input-group {
1556 | display: table;
1557 | width: 100%;
1558 | margin-bottom: 1rem; }
1559 | .input-group > :first-child {
1560 | border-radius: 0 0 0 0; }
1561 | .input-group > :last-child > * {
1562 | border-radius: 0 0 0 0; }
1563 |
1564 | .input-group-label, .input-group-field, .input-group-button {
1565 | margin: 0;
1566 | display: table-cell;
1567 | vertical-align: middle; }
1568 |
1569 | .input-group-label {
1570 | text-align: center;
1571 | padding: 0 1rem;
1572 | background: #e6e6e6;
1573 | color: #0a0a0a;
1574 | border: 1px solid #cacaca;
1575 | white-space: nowrap;
1576 | width: 1%;
1577 | height: 100%; }
1578 | .input-group-label:first-child {
1579 | border-right: 0; }
1580 | .input-group-label:last-child {
1581 | border-left: 0; }
1582 |
1583 | .input-group-field {
1584 | border-radius: 0;
1585 | height: 2.5rem; }
1586 |
1587 | .input-group-button {
1588 | padding-top: 0;
1589 | padding-bottom: 0;
1590 | text-align: center;
1591 | height: 100%;
1592 | width: 1%; }
1593 | .input-group-button a,
1594 | .input-group-button input,
1595 | .input-group-button button {
1596 | margin: 0; }
1597 |
1598 | .input-group .input-group-button {
1599 | display: table-cell; }
1600 |
1601 | fieldset {
1602 | border: 0;
1603 | padding: 0;
1604 | margin: 0; }
1605 |
1606 | legend {
1607 | margin-bottom: 0.5rem;
1608 | max-width: 100%; }
1609 |
1610 | .fieldset {
1611 | border: 1px solid #cacaca;
1612 | padding: 1.25rem;
1613 | margin: 1.125rem 0; }
1614 | .fieldset legend {
1615 | background: #fefefe;
1616 | padding: 0 0.1875rem;
1617 | margin: 0;
1618 | margin-left: -0.1875rem; }
1619 |
1620 | select {
1621 | height: 2.4375rem;
1622 | padding: 0.5rem;
1623 | border: 1px solid #cacaca;
1624 | margin: 0 0 1rem;
1625 | font-size: 1rem;
1626 | font-family: inherit;
1627 | line-height: normal;
1628 | color: #0a0a0a;
1629 | background-color: #fefefe;
1630 | border-radius: 0;
1631 | -webkit-appearance: none;
1632 | -moz-appearance: none;
1633 | background-image: url('data:image/svg+xml;utf8, ');
1634 | background-size: 9px 6px;
1635 | background-position: right center;
1636 | background-origin: content-box;
1637 | background-repeat: no-repeat; }
1638 | @media screen and (min-width: 0\0) {
1639 | select {
1640 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg=="); } }
1641 | select:disabled {
1642 | background-color: #e6e6e6;
1643 | cursor: default; }
1644 | select::-ms-expand {
1645 | display: none; }
1646 | select[multiple] {
1647 | height: auto; }
1648 |
1649 | .is-invalid-input:not(:focus) {
1650 | background-color: rgba(236, 88, 64, 0.1);
1651 | border-color: #ec5840; }
1652 |
1653 | .is-invalid-label {
1654 | color: #ec5840; }
1655 |
1656 | .form-error {
1657 | display: none;
1658 | margin-top: -0.5rem;
1659 | margin-bottom: 1rem;
1660 | font-size: 0.75rem;
1661 | font-weight: bold;
1662 | color: #ec5840; }
1663 | .form-error.is-visible {
1664 | display: block; }
1665 |
1666 | .button {
1667 | display: inline-block;
1668 | text-align: center;
1669 | line-height: 1;
1670 | cursor: pointer;
1671 | -webkit-appearance: none;
1672 | transition: background-color 0.25s ease-out, color 0.25s ease-out;
1673 | vertical-align: middle;
1674 | border: 1px solid transparent;
1675 | border-radius: 0;
1676 | padding: 0.85em 1em;
1677 | margin: 0 0 1rem 0;
1678 | font-size: 0.9rem;
1679 | background-color: #2199e8;
1680 | color: #fefefe; }
1681 | [data-whatinput='mouse'] .button {
1682 | outline: 0; }
1683 | .button:hover, .button:focus {
1684 | background-color: #1583cc;
1685 | color: #fefefe; }
1686 | .button.tiny {
1687 | font-size: 0.6rem; }
1688 | .button.small {
1689 | font-size: 0.75rem; }
1690 | .button.large {
1691 | font-size: 1.25rem; }
1692 | .button.expanded {
1693 | display: block;
1694 | width: 100%;
1695 | margin-left: 0;
1696 | margin-right: 0; }
1697 | .button.primary {
1698 | background-color: #2199e8;
1699 | color: #fefefe; }
1700 | .button.primary:hover, .button.primary:focus {
1701 | background-color: #147cc0;
1702 | color: #fefefe; }
1703 | .button.secondary {
1704 | background-color: #777;
1705 | color: #fefefe; }
1706 | .button.secondary:hover, .button.secondary:focus {
1707 | background-color: #5f5f5f;
1708 | color: #fefefe; }
1709 | .button.success {
1710 | background-color: #3adb76;
1711 | color: #fefefe; }
1712 | .button.success:hover, .button.success:focus {
1713 | background-color: #22bb5b;
1714 | color: #fefefe; }
1715 | .button.warning {
1716 | background-color: #ffae00;
1717 | color: #fefefe; }
1718 | .button.warning:hover, .button.warning:focus {
1719 | background-color: #cc8b00;
1720 | color: #fefefe; }
1721 | .button.alert {
1722 | background-color: #ec5840;
1723 | color: #fefefe; }
1724 | .button.alert:hover, .button.alert:focus {
1725 | background-color: #da3116;
1726 | color: #fefefe; }
1727 | .button.hollow {
1728 | border: 1px solid #2199e8;
1729 | color: #2199e8; }
1730 | .button.hollow, .button.hollow:hover, .button.hollow:focus {
1731 | background-color: transparent; }
1732 | .button.hollow:hover, .button.hollow:focus {
1733 | border-color: #0c4d78;
1734 | color: #0c4d78; }
1735 | .button.hollow.primary {
1736 | border: 1px solid #2199e8;
1737 | color: #2199e8; }
1738 | .button.hollow.primary:hover, .button.hollow.primary:focus {
1739 | border-color: #0c4d78;
1740 | color: #0c4d78; }
1741 | .button.hollow.secondary {
1742 | border: 1px solid #777;
1743 | color: #777; }
1744 | .button.hollow.secondary:hover, .button.hollow.secondary:focus {
1745 | border-color: #3c3c3c;
1746 | color: #3c3c3c; }
1747 | .button.hollow.success {
1748 | border: 1px solid #3adb76;
1749 | color: #3adb76; }
1750 | .button.hollow.success:hover, .button.hollow.success:focus {
1751 | border-color: #157539;
1752 | color: #157539; }
1753 | .button.hollow.warning {
1754 | border: 1px solid #ffae00;
1755 | color: #ffae00; }
1756 | .button.hollow.warning:hover, .button.hollow.warning:focus {
1757 | border-color: #805700;
1758 | color: #805700; }
1759 | .button.hollow.alert {
1760 | border: 1px solid #ec5840;
1761 | color: #ec5840; }
1762 | .button.hollow.alert:hover, .button.hollow.alert:focus {
1763 | border-color: #881f0e;
1764 | color: #881f0e; }
1765 | .button.disabled, .button[disabled] {
1766 | opacity: 0.25;
1767 | cursor: not-allowed;
1768 | pointer-events: none; }
1769 | .button.dropdown::after {
1770 | content: '';
1771 | display: block;
1772 | width: 0;
1773 | height: 0;
1774 | border: inset 0.4em;
1775 | border-color: #fefefe transparent transparent;
1776 | border-top-style: solid;
1777 | border-bottom-width: 0;
1778 | position: relative;
1779 | top: 0.4em;
1780 | float: right;
1781 | margin-left: 1em;
1782 | display: inline-block; }
1783 | .button.arrow-only::after {
1784 | margin-left: 0;
1785 | float: none;
1786 | top: -0.1em; }
1787 |
1788 | .accordion {
1789 | list-style-type: none;
1790 | background: #fefefe;
1791 | border: 1px solid #e6e6e6;
1792 | border-bottom: 0;
1793 | border-radius: 0;
1794 | margin-left: 0; }
1795 |
1796 | .accordion-title {
1797 | display: block;
1798 | padding: 1.25rem 1rem;
1799 | line-height: 1;
1800 | font-size: 0.75rem;
1801 | color: #2199e8;
1802 | position: relative;
1803 | border-bottom: 1px solid #e6e6e6; }
1804 | .accordion-title:hover, .accordion-title:focus {
1805 | background-color: #e6e6e6; }
1806 | .accordion-title::before {
1807 | content: '+';
1808 | position: absolute;
1809 | right: 1rem;
1810 | top: 50%;
1811 | margin-top: -0.5rem; }
1812 | .is-active > .accordion-title::before {
1813 | content: '–'; }
1814 |
1815 | .accordion-content {
1816 | padding: 1rem;
1817 | display: none;
1818 | border-bottom: 1px solid #e6e6e6;
1819 | background-color: #fefefe; }
1820 |
1821 | .is-accordion-submenu-parent > a {
1822 | position: relative; }
1823 | .is-accordion-submenu-parent > a::after {
1824 | content: '';
1825 | display: block;
1826 | width: 0;
1827 | height: 0;
1828 | border: inset 6px;
1829 | border-color: #2199e8 transparent transparent;
1830 | border-top-style: solid;
1831 | border-bottom-width: 0;
1832 | position: absolute;
1833 | top: 50%;
1834 | margin-top: -4px;
1835 | right: 1rem; }
1836 |
1837 | .is-accordion-submenu-parent[aria-expanded='true'] > a::after {
1838 | -webkit-transform-origin: 50% 50%;
1839 | -ms-transform-origin: 50% 50%;
1840 | transform-origin: 50% 50%;
1841 | -webkit-transform: scaleY(-1);
1842 | -ms-transform: scaleY(-1);
1843 | transform: scaleY(-1); }
1844 |
1845 | .badge {
1846 | display: inline-block;
1847 | padding: 0.3em;
1848 | min-width: 2.1em;
1849 | font-size: 0.6rem;
1850 | text-align: center;
1851 | border-radius: 50%;
1852 | background: #2199e8;
1853 | color: #fefefe; }
1854 | .badge.secondary {
1855 | background: #777;
1856 | color: #fefefe; }
1857 | .badge.success {
1858 | background: #3adb76;
1859 | color: #fefefe; }
1860 | .badge.warning {
1861 | background: #ffae00;
1862 | color: #fefefe; }
1863 | .badge.alert {
1864 | background: #ec5840;
1865 | color: #fefefe; }
1866 |
1867 | .breadcrumbs {
1868 | list-style: none;
1869 | margin: 0 0 1rem 0; }
1870 | .breadcrumbs::before, .breadcrumbs::after {
1871 | content: ' ';
1872 | display: table;
1873 | -webkit-flex-basis: 0;
1874 | -ms-flex-preferred-size: 0;
1875 | flex-basis: 0;
1876 | -webkit-order: 1;
1877 | -ms-flex-order: 1;
1878 | order: 1; }
1879 | .breadcrumbs::after {
1880 | clear: both; }
1881 | .breadcrumbs li {
1882 | float: left;
1883 | color: #0a0a0a;
1884 | font-size: 0.6875rem;
1885 | cursor: default;
1886 | text-transform: uppercase; }
1887 | .breadcrumbs li:not(:last-child)::after {
1888 | color: #cacaca;
1889 | content: "/";
1890 | margin: 0 0.75rem;
1891 | position: relative;
1892 | top: 1px;
1893 | opacity: 1; }
1894 | .breadcrumbs a {
1895 | color: #2199e8; }
1896 | .breadcrumbs a:hover {
1897 | text-decoration: underline; }
1898 | .breadcrumbs .disabled {
1899 | color: #cacaca; }
1900 |
1901 | .button-group {
1902 | margin-bottom: 1rem;
1903 | font-size: 0; }
1904 | .button-group::before, .button-group::after {
1905 | content: ' ';
1906 | display: table;
1907 | -webkit-flex-basis: 0;
1908 | -ms-flex-preferred-size: 0;
1909 | flex-basis: 0;
1910 | -webkit-order: 1;
1911 | -ms-flex-order: 1;
1912 | order: 1; }
1913 | .button-group::after {
1914 | clear: both; }
1915 | .button-group .button {
1916 | margin: 0;
1917 | font-size: 0.9rem;
1918 | float: left; }
1919 | .button-group .button:not(:last-child) {
1920 | border-right: 1px solid #fefefe; }
1921 | .button-group.tiny .button {
1922 | font-size: 0.6rem; }
1923 | .button-group.small .button {
1924 | font-size: 0.75rem; }
1925 | .button-group.large .button {
1926 | font-size: 1.25rem; }
1927 | .button-group.expanded {
1928 | margin-right: -1px; }
1929 | .button-group.expanded::before, .button-group.expanded::after {
1930 | display: none; }
1931 | .button-group.expanded .button:first-child:nth-last-child(2), .button-group.expanded .button:first-child:nth-last-child(2):first-child:nth-last-child(2) ~ .button {
1932 | display: inline-block;
1933 | width: calc(50% - 1px);
1934 | margin-right: 1px; }
1935 | .button-group.expanded .button:first-child:nth-last-child(3), .button-group.expanded .button:first-child:nth-last-child(3):first-child:nth-last-child(3) ~ .button {
1936 | display: inline-block;
1937 | width: calc(33.33333% - 1px);
1938 | margin-right: 1px; }
1939 | .button-group.expanded .button:first-child:nth-last-child(4), .button-group.expanded .button:first-child:nth-last-child(4):first-child:nth-last-child(4) ~ .button {
1940 | display: inline-block;
1941 | width: calc(25% - 1px);
1942 | margin-right: 1px; }
1943 | .button-group.expanded .button:first-child:nth-last-child(5), .button-group.expanded .button:first-child:nth-last-child(5):first-child:nth-last-child(5) ~ .button {
1944 | display: inline-block;
1945 | width: calc(20% - 1px);
1946 | margin-right: 1px; }
1947 | .button-group.expanded .button:first-child:nth-last-child(6), .button-group.expanded .button:first-child:nth-last-child(6):first-child:nth-last-child(6) ~ .button {
1948 | display: inline-block;
1949 | width: calc(16.66667% - 1px);
1950 | margin-right: 1px; }
1951 | .button-group.primary .button {
1952 | background-color: #2199e8;
1953 | color: #fefefe; }
1954 | .button-group.primary .button:hover, .button-group.primary .button:focus {
1955 | background-color: #147cc0;
1956 | color: #fefefe; }
1957 | .button-group.secondary .button {
1958 | background-color: #777;
1959 | color: #fefefe; }
1960 | .button-group.secondary .button:hover, .button-group.secondary .button:focus {
1961 | background-color: #5f5f5f;
1962 | color: #fefefe; }
1963 | .button-group.success .button {
1964 | background-color: #3adb76;
1965 | color: #fefefe; }
1966 | .button-group.success .button:hover, .button-group.success .button:focus {
1967 | background-color: #22bb5b;
1968 | color: #fefefe; }
1969 | .button-group.warning .button {
1970 | background-color: #ffae00;
1971 | color: #fefefe; }
1972 | .button-group.warning .button:hover, .button-group.warning .button:focus {
1973 | background-color: #cc8b00;
1974 | color: #fefefe; }
1975 | .button-group.alert .button {
1976 | background-color: #ec5840;
1977 | color: #fefefe; }
1978 | .button-group.alert .button:hover, .button-group.alert .button:focus {
1979 | background-color: #da3116;
1980 | color: #fefefe; }
1981 | .button-group.stacked .button, .button-group.stacked-for-small .button, .button-group.stacked-for-medium .button {
1982 | width: 100%; }
1983 | .button-group.stacked .button:not(:last-child), .button-group.stacked-for-small .button:not(:last-child), .button-group.stacked-for-medium .button:not(:last-child) {
1984 | margin-right: 0; }
1985 | @media screen and (min-width: 40em) {
1986 | .button-group.stacked-for-small .button {
1987 | width: auto; }
1988 | .button-group.stacked-for-small .button:not(:last-child) {
1989 | margin-right: 1px; } }
1990 | @media screen and (min-width: 64em) {
1991 | .button-group.stacked-for-medium .button {
1992 | width: auto; }
1993 | .button-group.stacked-for-medium .button:not(:last-child) {
1994 | margin-right: 1px; } }
1995 | @media screen and (max-width: 39.9375em) {
1996 | .button-group.stacked-for-small.expanded {
1997 | display: block; }
1998 | .button-group.stacked-for-small.expanded .button {
1999 | display: block;
2000 | margin-right: 0; } }
2001 |
2002 | .callout {
2003 | margin: 0 0 1rem 0;
2004 | padding: 1rem;
2005 | border: 1px solid rgba(10, 10, 10, 0.25);
2006 | border-radius: 0;
2007 | position: relative;
2008 | color: #0a0a0a;
2009 | background-color: white; }
2010 | .callout > :first-child {
2011 | margin-top: 0; }
2012 | .callout > :last-child {
2013 | margin-bottom: 0; }
2014 | .callout.primary {
2015 | background-color: #def0fc; }
2016 | .callout.secondary {
2017 | background-color: #ebebeb; }
2018 | .callout.success {
2019 | background-color: #e1faea; }
2020 | .callout.warning {
2021 | background-color: #fff3d9; }
2022 | .callout.alert {
2023 | background-color: #fce6e2; }
2024 | .callout.small {
2025 | padding-top: 0.5rem;
2026 | padding-right: 0.5rem;
2027 | padding-bottom: 0.5rem;
2028 | padding-left: 0.5rem; }
2029 | .callout.large {
2030 | padding-top: 3rem;
2031 | padding-right: 3rem;
2032 | padding-bottom: 3rem;
2033 | padding-left: 3rem; }
2034 |
2035 | .close-button {
2036 | position: absolute;
2037 | color: #8a8a8a;
2038 | right: 1rem;
2039 | top: 0.5rem;
2040 | font-size: 2em;
2041 | line-height: 1;
2042 | cursor: pointer; }
2043 | [data-whatinput='mouse'] .close-button {
2044 | outline: 0; }
2045 | .close-button:hover, .close-button:focus {
2046 | color: #0a0a0a; }
2047 |
2048 | .menu {
2049 | margin: 0;
2050 | list-style-type: none; }
2051 | .menu > li {
2052 | display: table-cell;
2053 | vertical-align: middle; }
2054 | [data-whatinput='mouse'] .menu > li {
2055 | outline: 0; }
2056 | .menu > li > a {
2057 | display: block;
2058 | padding: 0.7rem 1rem;
2059 | line-height: 1; }
2060 | .menu input,
2061 | .menu a,
2062 | .menu button {
2063 | margin-bottom: 0; }
2064 | .menu > li > a img,
2065 | .menu > li > a i {
2066 | vertical-align: middle; }
2067 | .menu > li > a i + span,
2068 | .menu > li > a img + span {
2069 | vertical-align: middle; }
2070 | .menu > li > a img,
2071 | .menu > li > a i {
2072 | margin-right: 0.25rem;
2073 | display: inline-block; }
2074 | .menu > li {
2075 | display: table-cell; }
2076 | .menu.vertical > li {
2077 | display: block; }
2078 | @media screen and (min-width: 40em) {
2079 | .menu.medium-horizontal > li {
2080 | display: table-cell; }
2081 | .menu.medium-vertical > li {
2082 | display: block; } }
2083 | @media screen and (min-width: 64em) {
2084 | .menu.large-horizontal > li {
2085 | display: table-cell; }
2086 | .menu.large-vertical > li {
2087 | display: block; } }
2088 | .menu.simple li {
2089 | line-height: 1;
2090 | display: inline-block;
2091 | margin-right: 1rem; }
2092 | .menu.simple a {
2093 | padding: 0; }
2094 | .menu.align-right::before, .menu.align-right::after {
2095 | content: ' ';
2096 | display: table;
2097 | -webkit-flex-basis: 0;
2098 | -ms-flex-preferred-size: 0;
2099 | flex-basis: 0;
2100 | -webkit-order: 1;
2101 | -ms-flex-order: 1;
2102 | order: 1; }
2103 | .menu.align-right::after {
2104 | clear: both; }
2105 | .menu.align-right > li {
2106 | float: right; }
2107 | .menu.expanded {
2108 | width: 100%;
2109 | display: table;
2110 | table-layout: fixed; }
2111 | .menu.expanded > li:first-child:last-child {
2112 | width: 100%; }
2113 | .menu.icon-top > li > a {
2114 | text-align: center; }
2115 | .menu.icon-top > li > a img,
2116 | .menu.icon-top > li > a i {
2117 | display: block;
2118 | margin: 0 auto 0.25rem; }
2119 | .menu.nested {
2120 | margin-left: 1rem; }
2121 | .menu .active > a {
2122 | color: #fefefe;
2123 | background: #2199e8; }
2124 |
2125 | .menu-text {
2126 | font-weight: bold;
2127 | color: inherit;
2128 | line-height: 1;
2129 | padding-top: 0;
2130 | padding-bottom: 0;
2131 | padding: 0.7rem 1rem; }
2132 |
2133 | .menu-centered {
2134 | text-align: center; }
2135 | .menu-centered > .menu {
2136 | display: inline-block; }
2137 |
2138 | .no-js [data-responsive-menu] ul {
2139 | display: none; }
2140 |
2141 | .menu-icon {
2142 | position: relative;
2143 | display: inline-block;
2144 | vertical-align: middle;
2145 | cursor: pointer;
2146 | width: 20px;
2147 | height: 16px; }
2148 | .menu-icon::after {
2149 | content: '';
2150 | position: absolute;
2151 | display: block;
2152 | width: 100%;
2153 | height: 2px;
2154 | background: #fefefe;
2155 | top: 0;
2156 | left: 0;
2157 | box-shadow: 0 7px 0 #fefefe, 0 14px 0 #fefefe; }
2158 | .menu-icon:hover::after {
2159 | background: #cacaca;
2160 | box-shadow: 0 7px 0 #cacaca, 0 14px 0 #cacaca; }
2161 |
2162 | .menu-icon.dark {
2163 | position: relative;
2164 | display: inline-block;
2165 | vertical-align: middle;
2166 | cursor: pointer;
2167 | width: 20px;
2168 | height: 16px; }
2169 | .menu-icon.dark::after {
2170 | content: '';
2171 | position: absolute;
2172 | display: block;
2173 | width: 100%;
2174 | height: 2px;
2175 | background: #0a0a0a;
2176 | top: 0;
2177 | left: 0;
2178 | box-shadow: 0 7px 0 #0a0a0a, 0 14px 0 #0a0a0a; }
2179 | .menu-icon.dark:hover::after {
2180 | background: #8a8a8a;
2181 | box-shadow: 0 7px 0 #8a8a8a, 0 14px 0 #8a8a8a; }
2182 |
2183 | .is-drilldown {
2184 | position: relative;
2185 | overflow: hidden; }
2186 | .is-drilldown li {
2187 | display: block !important; }
2188 |
2189 | .is-drilldown-submenu {
2190 | position: absolute;
2191 | top: 0;
2192 | left: 100%;
2193 | z-index: -1;
2194 | height: 100%;
2195 | width: 100%;
2196 | background: #fefefe;
2197 | transition: -webkit-transform 0.15s linear;
2198 | transition: transform 0.15s linear; }
2199 | .is-drilldown-submenu.is-active {
2200 | z-index: 1;
2201 | display: block;
2202 | -webkit-transform: translateX(-100%);
2203 | -ms-transform: translateX(-100%);
2204 | transform: translateX(-100%); }
2205 | .is-drilldown-submenu.is-closing {
2206 | -webkit-transform: translateX(100%);
2207 | -ms-transform: translateX(100%);
2208 | transform: translateX(100%); }
2209 |
2210 | .is-drilldown-submenu-parent > a {
2211 | position: relative; }
2212 | .is-drilldown-submenu-parent > a::after {
2213 | content: '';
2214 | display: block;
2215 | width: 0;
2216 | height: 0;
2217 | border: inset 6px;
2218 | border-color: transparent transparent transparent #2199e8;
2219 | border-left-style: solid;
2220 | border-right-width: 0;
2221 | position: absolute;
2222 | top: 50%;
2223 | margin-top: -6px;
2224 | right: 1rem; }
2225 |
2226 | .js-drilldown-back > a::before {
2227 | content: '';
2228 | display: block;
2229 | width: 0;
2230 | height: 0;
2231 | border: inset 6px;
2232 | border-color: transparent #2199e8 transparent transparent;
2233 | border-right-style: solid;
2234 | border-left-width: 0;
2235 | border-left-width: 0;
2236 | display: inline-block;
2237 | vertical-align: middle;
2238 | margin-right: 0.75rem; }
2239 |
2240 | .dropdown-pane {
2241 | background-color: #fefefe;
2242 | border: 1px solid #cacaca;
2243 | border-radius: 0;
2244 | display: block;
2245 | font-size: 1rem;
2246 | padding: 1rem;
2247 | position: absolute;
2248 | visibility: hidden;
2249 | width: 300px;
2250 | z-index: 10; }
2251 | .dropdown-pane.is-open {
2252 | visibility: visible; }
2253 |
2254 | .dropdown-pane.tiny {
2255 | width: 100px; }
2256 |
2257 | .dropdown-pane.small {
2258 | width: 200px; }
2259 |
2260 | .dropdown-pane.large {
2261 | width: 400px; }
2262 |
2263 | .dropdown.menu > li.opens-left > .is-dropdown-submenu {
2264 | left: auto;
2265 | right: 0;
2266 | top: 100%; }
2267 |
2268 | .dropdown.menu > li.opens-right > .is-dropdown-submenu {
2269 | right: auto;
2270 | left: 0;
2271 | top: 100%; }
2272 |
2273 | .dropdown.menu > li.is-dropdown-submenu-parent > a {
2274 | padding-right: 1.5rem;
2275 | position: relative; }
2276 |
2277 | .dropdown.menu > li.is-dropdown-submenu-parent > a::after {
2278 | content: '';
2279 | display: block;
2280 | width: 0;
2281 | height: 0;
2282 | border: inset 5px;
2283 | border-color: #2199e8 transparent transparent;
2284 | border-top-style: solid;
2285 | border-bottom-width: 0;
2286 | right: 5px;
2287 | margin-top: -2px; }
2288 |
2289 | [data-whatinput='mouse'] .dropdown.menu a {
2290 | outline: 0; }
2291 |
2292 | .no-js .dropdown.menu ul {
2293 | display: none; }
2294 |
2295 | .dropdown.menu.vertical > li .is-dropdown-submenu {
2296 | top: 0; }
2297 |
2298 | .dropdown.menu.vertical > li.opens-left .is-dropdown-submenu {
2299 | left: auto;
2300 | right: 100%; }
2301 |
2302 | .dropdown.menu.vertical > li.opens-right .is-dropdown-submenu {
2303 | right: auto;
2304 | left: 100%; }
2305 |
2306 | .dropdown.menu.vertical > li > a::after {
2307 | right: 14px;
2308 | margin-top: -3px; }
2309 |
2310 | .dropdown.menu.vertical > li.opens-left > a::after {
2311 | content: '';
2312 | display: block;
2313 | width: 0;
2314 | height: 0;
2315 | border: inset 5px;
2316 | border-color: transparent #2199e8 transparent transparent;
2317 | border-right-style: solid;
2318 | border-left-width: 0; }
2319 |
2320 | .dropdown.menu.vertical > li.opens-right > a::after {
2321 | content: '';
2322 | display: block;
2323 | width: 0;
2324 | height: 0;
2325 | border: inset 5px;
2326 | border-color: transparent transparent transparent #2199e8;
2327 | border-left-style: solid;
2328 | border-right-width: 0; }
2329 |
2330 | @media screen and (min-width: 40em) {
2331 | .dropdown.menu.medium-horizontal > li.opens-left > .is-dropdown-submenu {
2332 | left: auto;
2333 | right: 0;
2334 | top: 100%; }
2335 | .dropdown.menu.medium-horizontal > li.opens-right > .is-dropdown-submenu {
2336 | right: auto;
2337 | left: 0;
2338 | top: 100%; }
2339 | .dropdown.menu.medium-horizontal > li.is-dropdown-submenu-parent > a {
2340 | padding-right: 1.5rem;
2341 | position: relative; }
2342 | .dropdown.menu.medium-horizontal > li.is-dropdown-submenu-parent > a::after {
2343 | content: '';
2344 | display: block;
2345 | width: 0;
2346 | height: 0;
2347 | border: inset 5px;
2348 | border-color: #2199e8 transparent transparent;
2349 | border-top-style: solid;
2350 | border-bottom-width: 0;
2351 | right: 5px;
2352 | margin-top: -2px; }
2353 | .dropdown.menu.medium-vertical > li .is-dropdown-submenu {
2354 | top: 0; }
2355 | .dropdown.menu.medium-vertical > li.opens-left .is-dropdown-submenu {
2356 | left: auto;
2357 | right: 100%; }
2358 | .dropdown.menu.medium-vertical > li.opens-right .is-dropdown-submenu {
2359 | right: auto;
2360 | left: 100%; }
2361 | .dropdown.menu.medium-vertical > li > a::after {
2362 | right: 14px;
2363 | margin-top: -3px; }
2364 | .dropdown.menu.medium-vertical > li.opens-left > a::after {
2365 | content: '';
2366 | display: block;
2367 | width: 0;
2368 | height: 0;
2369 | border: inset 5px;
2370 | border-color: transparent #2199e8 transparent transparent;
2371 | border-right-style: solid;
2372 | border-left-width: 0; }
2373 | .dropdown.menu.medium-vertical > li.opens-right > a::after {
2374 | content: '';
2375 | display: block;
2376 | width: 0;
2377 | height: 0;
2378 | border: inset 5px;
2379 | border-color: transparent transparent transparent #2199e8;
2380 | border-left-style: solid;
2381 | border-right-width: 0; } }
2382 |
2383 | @media screen and (min-width: 64em) {
2384 | .dropdown.menu.large-horizontal > li.opens-left > .is-dropdown-submenu {
2385 | left: auto;
2386 | right: 0;
2387 | top: 100%; }
2388 | .dropdown.menu.large-horizontal > li.opens-right > .is-dropdown-submenu {
2389 | right: auto;
2390 | left: 0;
2391 | top: 100%; }
2392 | .dropdown.menu.large-horizontal > li.is-dropdown-submenu-parent > a {
2393 | padding-right: 1.5rem;
2394 | position: relative; }
2395 | .dropdown.menu.large-horizontal > li.is-dropdown-submenu-parent > a::after {
2396 | content: '';
2397 | display: block;
2398 | width: 0;
2399 | height: 0;
2400 | border: inset 5px;
2401 | border-color: #2199e8 transparent transparent;
2402 | border-top-style: solid;
2403 | border-bottom-width: 0;
2404 | right: 5px;
2405 | margin-top: -2px; }
2406 | .dropdown.menu.large-vertical > li .is-dropdown-submenu {
2407 | top: 0; }
2408 | .dropdown.menu.large-vertical > li.opens-left .is-dropdown-submenu {
2409 | left: auto;
2410 | right: 100%; }
2411 | .dropdown.menu.large-vertical > li.opens-right .is-dropdown-submenu {
2412 | right: auto;
2413 | left: 100%; }
2414 | .dropdown.menu.large-vertical > li > a::after {
2415 | right: 14px;
2416 | margin-top: -3px; }
2417 | .dropdown.menu.large-vertical > li.opens-left > a::after {
2418 | content: '';
2419 | display: block;
2420 | width: 0;
2421 | height: 0;
2422 | border: inset 5px;
2423 | border-color: transparent #2199e8 transparent transparent;
2424 | border-right-style: solid;
2425 | border-left-width: 0; }
2426 | .dropdown.menu.large-vertical > li.opens-right > a::after {
2427 | content: '';
2428 | display: block;
2429 | width: 0;
2430 | height: 0;
2431 | border: inset 5px;
2432 | border-color: transparent transparent transparent #2199e8;
2433 | border-left-style: solid;
2434 | border-right-width: 0; } }
2435 |
2436 | .dropdown.menu.align-right .is-dropdown-submenu.first-sub {
2437 | top: 100%;
2438 | left: auto;
2439 | right: 0; }
2440 |
2441 | .is-dropdown-menu.vertical {
2442 | width: 100px; }
2443 | .is-dropdown-menu.vertical.align-right {
2444 | float: right; }
2445 |
2446 | .is-dropdown-submenu-parent {
2447 | position: relative; }
2448 | .is-dropdown-submenu-parent a::after {
2449 | position: absolute;
2450 | top: 50%;
2451 | right: 5px;
2452 | margin-top: -2px; }
2453 | .is-dropdown-submenu-parent.opens-inner .is-dropdown-submenu {
2454 | top: 100%; }
2455 | .is-dropdown-submenu-parent.opens-left .is-dropdown-submenu {
2456 | left: auto;
2457 | right: 0;
2458 | top: 100%; }
2459 |
2460 | .is-dropdown-submenu {
2461 | display: none;
2462 | position: absolute;
2463 | top: 0;
2464 | left: 100%;
2465 | min-width: 200px;
2466 | z-index: 1;
2467 | background: #fefefe;
2468 | border: 1px solid #cacaca; }
2469 | .is-dropdown-submenu .is-dropdown-submenu-parent > a::after {
2470 | right: 14px;
2471 | margin-top: -3px; }
2472 | .is-dropdown-submenu .is-dropdown-submenu-parent.opens-left > a::after {
2473 | content: '';
2474 | display: block;
2475 | width: 0;
2476 | height: 0;
2477 | border: inset 5px;
2478 | border-color: transparent #2199e8 transparent transparent;
2479 | border-right-style: solid;
2480 | border-left-width: 0; }
2481 | .is-dropdown-submenu .is-dropdown-submenu-parent.opens-right > a::after {
2482 | content: '';
2483 | display: block;
2484 | width: 0;
2485 | height: 0;
2486 | border: inset 5px;
2487 | border-color: transparent transparent transparent #2199e8;
2488 | border-left-style: solid;
2489 | border-right-width: 0; }
2490 | .is-dropdown-submenu .is-dropdown-submenu {
2491 | margin-top: -1px; }
2492 | .is-dropdown-submenu > li {
2493 | width: 100%; }
2494 | .is-dropdown-submenu:not(.js-dropdown-nohover) > .is-dropdown-submenu-parent:hover > .is-dropdown-submenu, .is-dropdown-submenu.js-dropdown-active {
2495 | display: block; }
2496 |
2497 | .flex-video {
2498 | position: relative;
2499 | height: 0;
2500 | padding-bottom: 75%;
2501 | margin-bottom: 1rem;
2502 | overflow: hidden; }
2503 | .flex-video iframe,
2504 | .flex-video object,
2505 | .flex-video embed,
2506 | .flex-video video {
2507 | position: absolute;
2508 | top: 0;
2509 | left: 0;
2510 | width: 100%;
2511 | height: 100%; }
2512 | .flex-video.widescreen {
2513 | padding-bottom: 56.25%; }
2514 | .flex-video.vimeo {
2515 | padding-top: 0; }
2516 |
2517 | .label {
2518 | display: inline-block;
2519 | padding: 0.33333rem 0.5rem;
2520 | font-size: 0.8rem;
2521 | line-height: 1;
2522 | white-space: nowrap;
2523 | cursor: default;
2524 | border-radius: 0;
2525 | background: #2199e8;
2526 | color: #fefefe; }
2527 | .label.secondary {
2528 | background: #777;
2529 | color: #fefefe; }
2530 | .label.success {
2531 | background: #3adb76;
2532 | color: #fefefe; }
2533 | .label.warning {
2534 | background: #ffae00;
2535 | color: #fefefe; }
2536 | .label.alert {
2537 | background: #ec5840;
2538 | color: #fefefe; }
2539 |
2540 | .media-object {
2541 | margin-bottom: 1rem;
2542 | display: block; }
2543 | .media-object img {
2544 | max-width: none; }
2545 | @media screen and (max-width: 39.9375em) {
2546 | .media-object.stack-for-small .media-object-section {
2547 | padding: 0;
2548 | padding-bottom: 1rem;
2549 | display: block; }
2550 | .media-object.stack-for-small .media-object-section img {
2551 | width: 100%; } }
2552 |
2553 | .media-object-section {
2554 | display: table-cell;
2555 | vertical-align: top; }
2556 | .media-object-section:first-child {
2557 | padding-right: 1rem; }
2558 | .media-object-section:last-child:not(:nth-child(2)) {
2559 | padding-left: 1rem; }
2560 | .media-object-section.middle {
2561 | vertical-align: middle; }
2562 | .media-object-section.bottom {
2563 | vertical-align: bottom; }
2564 |
2565 | html,
2566 | body {
2567 | height: 100%; }
2568 |
2569 | .off-canvas-wrapper {
2570 | width: 100%;
2571 | overflow-x: hidden;
2572 | position: relative;
2573 | -webkit-backface-visibility: hidden;
2574 | backface-visibility: hidden;
2575 | -webkit-overflow-scrolling: auto; }
2576 |
2577 | .off-canvas-wrapper-inner {
2578 | position: relative;
2579 | width: 100%;
2580 | transition: -webkit-transform 0.5s ease;
2581 | transition: transform 0.5s ease; }
2582 | .off-canvas-wrapper-inner::before, .off-canvas-wrapper-inner::after {
2583 | content: ' ';
2584 | display: table;
2585 | -webkit-flex-basis: 0;
2586 | -ms-flex-preferred-size: 0;
2587 | flex-basis: 0;
2588 | -webkit-order: 1;
2589 | -ms-flex-order: 1;
2590 | order: 1; }
2591 | .off-canvas-wrapper-inner::after {
2592 | clear: both; }
2593 |
2594 | .off-canvas-content,
2595 | .off-canvas-content {
2596 | min-height: 100%;
2597 | background: #fefefe;
2598 | transition: -webkit-transform 0.5s ease;
2599 | transition: transform 0.5s ease;
2600 | -webkit-backface-visibility: hidden;
2601 | backface-visibility: hidden;
2602 | z-index: 1;
2603 | padding-bottom: 0.1px;
2604 | box-shadow: 0 0 10px rgba(10, 10, 10, 0.5); }
2605 |
2606 | .js-off-canvas-exit {
2607 | display: none;
2608 | position: absolute;
2609 | top: 0;
2610 | left: 0;
2611 | width: 100%;
2612 | height: 100%;
2613 | background: rgba(254, 254, 254, 0.25);
2614 | cursor: pointer;
2615 | transition: background 0.5s ease; }
2616 |
2617 | .off-canvas {
2618 | position: absolute;
2619 | background: #e6e6e6;
2620 | z-index: -1;
2621 | max-height: 100%;
2622 | overflow-y: auto;
2623 | -webkit-transform: translateX(0);
2624 | -ms-transform: translateX(0);
2625 | transform: translateX(0); }
2626 | [data-whatinput='mouse'] .off-canvas {
2627 | outline: 0; }
2628 | .off-canvas.position-left {
2629 | left: -250px;
2630 | top: 0;
2631 | width: 250px; }
2632 | .is-open-left {
2633 | -webkit-transform: translateX(250px);
2634 | -ms-transform: translateX(250px);
2635 | transform: translateX(250px); }
2636 | .off-canvas.position-right {
2637 | right: -250px;
2638 | top: 0;
2639 | width: 250px; }
2640 | .is-open-right {
2641 | -webkit-transform: translateX(-250px);
2642 | -ms-transform: translateX(-250px);
2643 | transform: translateX(-250px); }
2644 |
2645 | @media screen and (min-width: 40em) {
2646 | .position-left.reveal-for-medium {
2647 | left: 0;
2648 | z-index: auto;
2649 | position: fixed; }
2650 | .position-left.reveal-for-medium ~ .off-canvas-content {
2651 | margin-left: 250px; }
2652 | .position-right.reveal-for-medium {
2653 | right: 0;
2654 | z-index: auto;
2655 | position: fixed; }
2656 | .position-right.reveal-for-medium ~ .off-canvas-content {
2657 | margin-right: 250px; } }
2658 |
2659 | @media screen and (min-width: 64em) {
2660 | .position-left.reveal-for-large {
2661 | left: 0;
2662 | z-index: auto;
2663 | position: fixed; }
2664 | .position-left.reveal-for-large ~ .off-canvas-content {
2665 | margin-left: 250px; }
2666 | .position-right.reveal-for-large {
2667 | right: 0;
2668 | z-index: auto;
2669 | position: fixed; }
2670 | .position-right.reveal-for-large ~ .off-canvas-content {
2671 | margin-right: 250px; } }
2672 |
2673 | .orbit {
2674 | position: relative; }
2675 |
2676 | .orbit-container {
2677 | position: relative;
2678 | margin: 0;
2679 | overflow: hidden;
2680 | list-style: none; }
2681 |
2682 | .orbit-slide {
2683 | width: 100%;
2684 | max-height: 100%; }
2685 | .orbit-slide.no-motionui.is-active {
2686 | top: 0;
2687 | left: 0; }
2688 |
2689 | .orbit-figure {
2690 | margin: 0; }
2691 |
2692 | .orbit-image {
2693 | margin: 0;
2694 | width: 100%;
2695 | max-width: 100%; }
2696 |
2697 | .orbit-caption {
2698 | position: absolute;
2699 | bottom: 0;
2700 | width: 100%;
2701 | padding: 1rem;
2702 | margin-bottom: 0;
2703 | color: #fefefe;
2704 | background-color: rgba(10, 10, 10, 0.5); }
2705 |
2706 | .orbit-previous, .orbit-next {
2707 | position: absolute;
2708 | top: 50%;
2709 | -webkit-transform: translateY(-50%);
2710 | -ms-transform: translateY(-50%);
2711 | transform: translateY(-50%);
2712 | z-index: 10;
2713 | padding: 1rem;
2714 | color: #fefefe; }
2715 | [data-whatinput='mouse'] .orbit-previous, [data-whatinput='mouse'] .orbit-next {
2716 | outline: 0; }
2717 | .orbit-previous:hover, .orbit-next:hover, .orbit-previous:active, .orbit-next:active, .orbit-previous:focus, .orbit-next:focus {
2718 | background-color: rgba(10, 10, 10, 0.5); }
2719 |
2720 | .orbit-previous {
2721 | left: 0; }
2722 |
2723 | .orbit-next {
2724 | left: auto;
2725 | right: 0; }
2726 |
2727 | .orbit-bullets {
2728 | position: relative;
2729 | margin-top: 0.8rem;
2730 | margin-bottom: 0.8rem;
2731 | text-align: center; }
2732 | [data-whatinput='mouse'] .orbit-bullets {
2733 | outline: 0; }
2734 | .orbit-bullets button {
2735 | width: 1.2rem;
2736 | height: 1.2rem;
2737 | margin: 0.1rem;
2738 | background-color: #cacaca;
2739 | border-radius: 50%; }
2740 | .orbit-bullets button:hover {
2741 | background-color: #8a8a8a; }
2742 | .orbit-bullets button.is-active {
2743 | background-color: #8a8a8a; }
2744 |
2745 | .pagination {
2746 | margin-left: 0;
2747 | margin-bottom: 1rem; }
2748 | .pagination::before, .pagination::after {
2749 | content: ' ';
2750 | display: table;
2751 | -webkit-flex-basis: 0;
2752 | -ms-flex-preferred-size: 0;
2753 | flex-basis: 0;
2754 | -webkit-order: 1;
2755 | -ms-flex-order: 1;
2756 | order: 1; }
2757 | .pagination::after {
2758 | clear: both; }
2759 | .pagination li {
2760 | font-size: 0.875rem;
2761 | margin-right: 0.0625rem;
2762 | border-radius: 0;
2763 | display: none; }
2764 | .pagination li:last-child, .pagination li:first-child {
2765 | display: inline-block; }
2766 | @media screen and (min-width: 40em) {
2767 | .pagination li {
2768 | display: inline-block; } }
2769 | .pagination a,
2770 | .pagination button {
2771 | color: #0a0a0a;
2772 | display: block;
2773 | padding: 0.1875rem 0.625rem;
2774 | border-radius: 0; }
2775 | .pagination a:hover,
2776 | .pagination button:hover {
2777 | background: #e6e6e6; }
2778 | .pagination .current {
2779 | padding: 0.1875rem 0.625rem;
2780 | background: #2199e8;
2781 | color: #fefefe;
2782 | cursor: default; }
2783 | .pagination .disabled {
2784 | padding: 0.1875rem 0.625rem;
2785 | color: #cacaca;
2786 | cursor: default; }
2787 | .pagination .disabled:hover {
2788 | background: transparent; }
2789 | .pagination .ellipsis::after {
2790 | content: '\2026';
2791 | padding: 0.1875rem 0.625rem;
2792 | color: #0a0a0a; }
2793 |
2794 | .pagination-previous a::before,
2795 | .pagination-previous.disabled::before {
2796 | content: '\00ab';
2797 | display: inline-block;
2798 | margin-right: 0.5rem; }
2799 |
2800 | .pagination-next a::after,
2801 | .pagination-next.disabled::after {
2802 | content: '\00bb';
2803 | display: inline-block;
2804 | margin-left: 0.5rem; }
2805 |
2806 | .progress {
2807 | background-color: #cacaca;
2808 | height: 1rem;
2809 | margin-bottom: 1rem;
2810 | border-radius: 0; }
2811 | .progress.primary .progress-meter {
2812 | background-color: #2199e8; }
2813 | .progress.secondary .progress-meter {
2814 | background-color: #777; }
2815 | .progress.success .progress-meter {
2816 | background-color: #3adb76; }
2817 | .progress.warning .progress-meter {
2818 | background-color: #ffae00; }
2819 | .progress.alert .progress-meter {
2820 | background-color: #ec5840; }
2821 |
2822 | .progress-meter {
2823 | position: relative;
2824 | display: block;
2825 | width: 0%;
2826 | height: 100%;
2827 | background-color: #2199e8; }
2828 |
2829 | .progress-meter-text {
2830 | position: absolute;
2831 | top: 50%;
2832 | left: 50%;
2833 | -webkit-transform: translate(-50%, -50%);
2834 | -ms-transform: translate(-50%, -50%);
2835 | transform: translate(-50%, -50%);
2836 | position: absolute;
2837 | margin: 0;
2838 | font-size: 0.75rem;
2839 | font-weight: bold;
2840 | color: #fefefe;
2841 | white-space: nowrap; }
2842 |
2843 | .slider {
2844 | position: relative;
2845 | height: 0.5rem;
2846 | margin-top: 1.25rem;
2847 | margin-bottom: 2.25rem;
2848 | background-color: #e6e6e6;
2849 | cursor: pointer;
2850 | -webkit-user-select: none;
2851 | -moz-user-select: none;
2852 | -ms-user-select: none;
2853 | user-select: none;
2854 | -ms-touch-action: none;
2855 | touch-action: none; }
2856 |
2857 | .slider-fill {
2858 | position: absolute;
2859 | top: 0;
2860 | left: 0;
2861 | display: inline-block;
2862 | max-width: 100%;
2863 | height: 0.5rem;
2864 | background-color: #cacaca;
2865 | transition: all 0.2s ease-in-out; }
2866 | .slider-fill.is-dragging {
2867 | transition: all 0s linear; }
2868 |
2869 | .slider-handle {
2870 | position: absolute;
2871 | top: 50%;
2872 | -webkit-transform: translateY(-50%);
2873 | -ms-transform: translateY(-50%);
2874 | transform: translateY(-50%);
2875 | position: absolute;
2876 | left: 0;
2877 | z-index: 1;
2878 | display: inline-block;
2879 | width: 1.4rem;
2880 | height: 1.4rem;
2881 | background-color: #2199e8;
2882 | transition: all 0.2s ease-in-out;
2883 | -ms-touch-action: manipulation;
2884 | touch-action: manipulation;
2885 | border-radius: 0; }
2886 | [data-whatinput='mouse'] .slider-handle {
2887 | outline: 0; }
2888 | .slider-handle:hover {
2889 | background-color: #1583cc; }
2890 | .slider-handle.is-dragging {
2891 | transition: all 0s linear; }
2892 |
2893 | .slider.disabled,
2894 | .slider[disabled] {
2895 | opacity: 0.25;
2896 | cursor: not-allowed; }
2897 |
2898 | .slider.vertical {
2899 | display: inline-block;
2900 | width: 0.5rem;
2901 | height: 12.5rem;
2902 | margin: 0 1.25rem;
2903 | -webkit-transform: scale(1, -1);
2904 | -ms-transform: scale(1, -1);
2905 | transform: scale(1, -1); }
2906 | .slider.vertical .slider-fill {
2907 | top: 0;
2908 | width: 0.5rem;
2909 | max-height: 100%; }
2910 | .slider.vertical .slider-handle {
2911 | position: absolute;
2912 | top: 0;
2913 | left: 50%;
2914 | width: 1.4rem;
2915 | height: 1.4rem;
2916 | -webkit-transform: translateX(-50%);
2917 | -ms-transform: translateX(-50%);
2918 | transform: translateX(-50%); }
2919 |
2920 | .sticky-container {
2921 | position: relative; }
2922 |
2923 | .sticky {
2924 | position: absolute;
2925 | z-index: 0;
2926 | -webkit-transform: translate3d(0, 0, 0);
2927 | transform: translate3d(0, 0, 0); }
2928 |
2929 | .sticky.is-stuck {
2930 | position: fixed;
2931 | z-index: 5; }
2932 | .sticky.is-stuck.is-at-top {
2933 | top: 0; }
2934 | .sticky.is-stuck.is-at-bottom {
2935 | bottom: 0; }
2936 |
2937 | .sticky.is-anchored {
2938 | position: absolute;
2939 | left: auto;
2940 | right: auto; }
2941 | .sticky.is-anchored.is-at-bottom {
2942 | bottom: 0; }
2943 |
2944 | body.is-reveal-open {
2945 | overflow: hidden; }
2946 |
2947 | .reveal-overlay {
2948 | display: none;
2949 | position: fixed;
2950 | top: 0;
2951 | bottom: 0;
2952 | left: 0;
2953 | right: 0;
2954 | z-index: 1005;
2955 | background-color: rgba(10, 10, 10, 0.45);
2956 | overflow-y: scroll; }
2957 |
2958 | .reveal {
2959 | display: none;
2960 | z-index: 1006;
2961 | padding: 1rem;
2962 | border: 1px solid #cacaca;
2963 | background-color: #fefefe;
2964 | border-radius: 0;
2965 | position: relative;
2966 | top: 100px;
2967 | margin-left: auto;
2968 | margin-right: auto;
2969 | overflow-y: auto; }
2970 | [data-whatinput='mouse'] .reveal {
2971 | outline: 0; }
2972 | @media screen and (min-width: 40em) {
2973 | .reveal {
2974 | min-height: 0; } }
2975 | .reveal .column, .reveal .columns,
2976 | .reveal .columns {
2977 | min-width: 0; }
2978 | .reveal > :last-child {
2979 | margin-bottom: 0; }
2980 | @media screen and (min-width: 40em) {
2981 | .reveal {
2982 | width: 600px;
2983 | max-width: 75rem; } }
2984 | @media screen and (min-width: 40em) {
2985 | .reveal .reveal {
2986 | left: auto;
2987 | right: auto;
2988 | margin: 0 auto; } }
2989 | .reveal.collapse {
2990 | padding: 0; }
2991 | @media screen and (min-width: 40em) {
2992 | .reveal.tiny {
2993 | width: 30%;
2994 | max-width: 75rem; } }
2995 | @media screen and (min-width: 40em) {
2996 | .reveal.small {
2997 | width: 50%;
2998 | max-width: 75rem; } }
2999 | @media screen and (min-width: 40em) {
3000 | .reveal.large {
3001 | width: 90%;
3002 | max-width: 75rem; } }
3003 | .reveal.full {
3004 | top: 0;
3005 | left: 0;
3006 | width: 100%;
3007 | height: 100%;
3008 | height: 100vh;
3009 | min-height: 100vh;
3010 | max-width: none;
3011 | margin-left: 0;
3012 | border: 0; }
3013 | @media screen and (max-width: 39.9375em) {
3014 | .reveal {
3015 | top: 0;
3016 | left: 0;
3017 | width: 100%;
3018 | height: 100%;
3019 | height: 100vh;
3020 | min-height: 100vh;
3021 | max-width: none;
3022 | margin-left: 0;
3023 | border: 0; } }
3024 | .reveal.without-overlay {
3025 | position: fixed; }
3026 |
3027 | .switch {
3028 | margin-bottom: 1rem;
3029 | outline: 0;
3030 | position: relative;
3031 | -webkit-user-select: none;
3032 | -moz-user-select: none;
3033 | -ms-user-select: none;
3034 | user-select: none;
3035 | color: #fefefe;
3036 | font-weight: bold;
3037 | font-size: 0.875rem; }
3038 |
3039 | .switch-input {
3040 | opacity: 0;
3041 | position: absolute; }
3042 |
3043 | .switch-paddle {
3044 | background: #cacaca;
3045 | cursor: pointer;
3046 | display: block;
3047 | position: relative;
3048 | width: 4rem;
3049 | height: 2rem;
3050 | transition: all 0.25s ease-out;
3051 | border-radius: 0;
3052 | color: inherit;
3053 | font-weight: inherit; }
3054 | input + .switch-paddle {
3055 | margin: 0; }
3056 | .switch-paddle::after {
3057 | background: #fefefe;
3058 | content: '';
3059 | display: block;
3060 | position: absolute;
3061 | height: 1.5rem;
3062 | left: 0.25rem;
3063 | top: 0.25rem;
3064 | width: 1.5rem;
3065 | transition: all 0.25s ease-out;
3066 | -webkit-transform: translate3d(0, 0, 0);
3067 | transform: translate3d(0, 0, 0);
3068 | border-radius: 0; }
3069 | input:checked ~ .switch-paddle {
3070 | background: #2199e8; }
3071 | input:checked ~ .switch-paddle::after {
3072 | left: 2.25rem; }
3073 | [data-whatinput='mouse'] input:focus ~ .switch-paddle {
3074 | outline: 0; }
3075 |
3076 | .switch-active, .switch-inactive {
3077 | position: absolute;
3078 | top: 50%;
3079 | -webkit-transform: translateY(-50%);
3080 | -ms-transform: translateY(-50%);
3081 | transform: translateY(-50%); }
3082 |
3083 | .switch-active {
3084 | left: 8%;
3085 | display: none; }
3086 | input:checked + label > .switch-active {
3087 | display: block; }
3088 |
3089 | .switch-inactive {
3090 | right: 15%; }
3091 | input:checked + label > .switch-inactive {
3092 | display: none; }
3093 |
3094 | .switch.tiny .switch-paddle {
3095 | width: 3rem;
3096 | height: 1.5rem;
3097 | font-size: 0.625rem; }
3098 |
3099 | .switch.tiny .switch-paddle::after {
3100 | width: 1rem;
3101 | height: 1rem; }
3102 |
3103 | .switch.tiny input:checked ~ .switch-paddle:after {
3104 | left: 1.75rem; }
3105 |
3106 | .switch.small .switch-paddle {
3107 | width: 3.5rem;
3108 | height: 1.75rem;
3109 | font-size: 0.75rem; }
3110 |
3111 | .switch.small .switch-paddle::after {
3112 | width: 1.25rem;
3113 | height: 1.25rem; }
3114 |
3115 | .switch.small input:checked ~ .switch-paddle:after {
3116 | left: 2rem; }
3117 |
3118 | .switch.large .switch-paddle {
3119 | width: 5rem;
3120 | height: 2.5rem;
3121 | font-size: 1rem; }
3122 |
3123 | .switch.large .switch-paddle::after {
3124 | width: 2rem;
3125 | height: 2rem; }
3126 |
3127 | .switch.large input:checked ~ .switch-paddle:after {
3128 | left: 2.75rem; }
3129 |
3130 | table {
3131 | width: 100%;
3132 | margin-bottom: 1rem;
3133 | border-radius: 0; }
3134 | table thead,
3135 | table tbody,
3136 | table tfoot {
3137 | border: 1px solid #f1f1f1;
3138 | background-color: #fefefe; }
3139 | table caption {
3140 | font-weight: bold;
3141 | padding: 0.5rem 0.625rem 0.625rem; }
3142 | table thead,
3143 | table tfoot {
3144 | background: #f8f8f8;
3145 | color: #0a0a0a; }
3146 | table thead tr,
3147 | table tfoot tr {
3148 | background: transparent; }
3149 | table thead th,
3150 | table thead td,
3151 | table tfoot th,
3152 | table tfoot td {
3153 | padding: 0.5rem 0.625rem 0.625rem;
3154 | font-weight: bold;
3155 | text-align: left; }
3156 | table tbody tr:nth-child(even) {
3157 | background-color: #f1f1f1; }
3158 | table tbody th,
3159 | table tbody td {
3160 | padding: 0.5rem 0.625rem 0.625rem; }
3161 |
3162 | @media screen and (max-width: 63.9375em) {
3163 | table.stack thead {
3164 | display: none; }
3165 | table.stack tfoot {
3166 | display: none; }
3167 | table.stack tr,
3168 | table.stack th,
3169 | table.stack td {
3170 | display: block; }
3171 | table.stack td {
3172 | border-top: 0; } }
3173 |
3174 | table.scroll {
3175 | display: block;
3176 | width: 100%;
3177 | overflow-x: auto; }
3178 |
3179 | table.hover tr:hover {
3180 | background-color: #f9f9f9; }
3181 |
3182 | table.hover tr:nth-of-type(even):hover {
3183 | background-color: #ececec; }
3184 |
3185 | .table-scroll {
3186 | overflow-x: auto; }
3187 | .table-scroll table {
3188 | width: auto; }
3189 |
3190 | .tabs {
3191 | margin: 0;
3192 | list-style-type: none;
3193 | background: #fefefe;
3194 | border: 1px solid #e6e6e6; }
3195 | .tabs::before, .tabs::after {
3196 | content: ' ';
3197 | display: table;
3198 | -webkit-flex-basis: 0;
3199 | -ms-flex-preferred-size: 0;
3200 | flex-basis: 0;
3201 | -webkit-order: 1;
3202 | -ms-flex-order: 1;
3203 | order: 1; }
3204 | .tabs::after {
3205 | clear: both; }
3206 |
3207 | .tabs.vertical > li {
3208 | width: auto;
3209 | float: none;
3210 | display: block; }
3211 |
3212 | .tabs.simple > li > a {
3213 | padding: 0; }
3214 | .tabs.simple > li > a:hover {
3215 | background: transparent; }
3216 |
3217 | .tabs.primary {
3218 | background: #2199e8; }
3219 | .tabs.primary > li > a {
3220 | color: #fefefe; }
3221 | .tabs.primary > li > a:hover, .tabs.primary > li > a:focus {
3222 | background: #1893e4; }
3223 |
3224 | .tabs-title {
3225 | float: left; }
3226 | .tabs-title > a {
3227 | display: block;
3228 | padding: 1.25rem 1.5rem;
3229 | line-height: 1;
3230 | font-size: 0.75rem; }
3231 | .tabs-title > a:hover {
3232 | background: #fefefe; }
3233 | .tabs-title > a:focus, .tabs-title > a[aria-selected='true'] {
3234 | background: #e6e6e6; }
3235 |
3236 | .tabs-content {
3237 | background: #fefefe;
3238 | transition: all 0.5s ease;
3239 | border: 1px solid #e6e6e6;
3240 | border-top: 0; }
3241 |
3242 | .tabs-content.vertical {
3243 | border: 1px solid #e6e6e6;
3244 | border-left: 0; }
3245 |
3246 | .tabs-panel {
3247 | display: none;
3248 | padding: 1rem; }
3249 | .tabs-panel.is-active {
3250 | display: block; }
3251 |
3252 | .thumbnail {
3253 | border: solid 4px #fefefe;
3254 | box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2);
3255 | display: inline-block;
3256 | line-height: 0;
3257 | max-width: 100%;
3258 | transition: box-shadow 200ms ease-out;
3259 | border-radius: 0;
3260 | margin-bottom: 1rem; }
3261 | .thumbnail:hover, .thumbnail:focus {
3262 | box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); }
3263 |
3264 | .title-bar {
3265 | background: #0a0a0a;
3266 | color: #fefefe;
3267 | padding: 0.5rem; }
3268 | .title-bar::before, .title-bar::after {
3269 | content: ' ';
3270 | display: table;
3271 | -webkit-flex-basis: 0;
3272 | -ms-flex-preferred-size: 0;
3273 | flex-basis: 0;
3274 | -webkit-order: 1;
3275 | -ms-flex-order: 1;
3276 | order: 1; }
3277 | .title-bar::after {
3278 | clear: both; }
3279 | .title-bar .menu-icon {
3280 | margin-left: 0.25rem;
3281 | margin-right: 0.5rem; }
3282 |
3283 | .title-bar-left {
3284 | float: left; }
3285 |
3286 | .title-bar-right {
3287 | float: right;
3288 | text-align: right; }
3289 |
3290 | .title-bar-title {
3291 | font-weight: bold;
3292 | vertical-align: middle;
3293 | display: inline-block; }
3294 |
3295 | .menu-icon.dark {
3296 | position: relative;
3297 | display: inline-block;
3298 | vertical-align: middle;
3299 | cursor: pointer;
3300 | width: 20px;
3301 | height: 16px; }
3302 | .menu-icon.dark::after {
3303 | content: '';
3304 | position: absolute;
3305 | display: block;
3306 | width: 100%;
3307 | height: 2px;
3308 | background: #0a0a0a;
3309 | top: 0;
3310 | left: 0;
3311 | box-shadow: 0 7px 0 #0a0a0a, 0 14px 0 #0a0a0a; }
3312 | .menu-icon.dark:hover::after {
3313 | background: #8a8a8a;
3314 | box-shadow: 0 7px 0 #8a8a8a, 0 14px 0 #8a8a8a; }
3315 |
3316 | .has-tip {
3317 | border-bottom: dotted 1px #8a8a8a;
3318 | font-weight: bold;
3319 | position: relative;
3320 | display: inline-block;
3321 | cursor: help; }
3322 |
3323 | .tooltip {
3324 | background-color: #0a0a0a;
3325 | color: #fefefe;
3326 | font-size: 80%;
3327 | padding: 0.75rem;
3328 | position: absolute;
3329 | z-index: 10;
3330 | top: calc(100% + 0.6495rem);
3331 | max-width: 10rem !important;
3332 | border-radius: 0; }
3333 | .tooltip::before {
3334 | content: '';
3335 | display: block;
3336 | width: 0;
3337 | height: 0;
3338 | border: inset 0.75rem;
3339 | border-color: transparent transparent #0a0a0a;
3340 | border-bottom-style: solid;
3341 | border-top-width: 0;
3342 | bottom: 100%;
3343 | position: absolute;
3344 | left: 50%;
3345 | -webkit-transform: translateX(-50%);
3346 | -ms-transform: translateX(-50%);
3347 | transform: translateX(-50%); }
3348 | .tooltip.top::before {
3349 | content: '';
3350 | display: block;
3351 | width: 0;
3352 | height: 0;
3353 | border: inset 0.75rem;
3354 | border-color: #0a0a0a transparent transparent;
3355 | border-top-style: solid;
3356 | border-bottom-width: 0;
3357 | top: 100%;
3358 | bottom: auto; }
3359 | .tooltip.left::before {
3360 | content: '';
3361 | display: block;
3362 | width: 0;
3363 | height: 0;
3364 | border: inset 0.75rem;
3365 | border-color: transparent transparent transparent #0a0a0a;
3366 | border-left-style: solid;
3367 | border-right-width: 0;
3368 | bottom: auto;
3369 | left: 100%;
3370 | top: 50%;
3371 | -webkit-transform: translateY(-50%);
3372 | -ms-transform: translateY(-50%);
3373 | transform: translateY(-50%); }
3374 | .tooltip.right::before {
3375 | content: '';
3376 | display: block;
3377 | width: 0;
3378 | height: 0;
3379 | border: inset 0.75rem;
3380 | border-color: transparent #0a0a0a transparent transparent;
3381 | border-right-style: solid;
3382 | border-left-width: 0;
3383 | bottom: auto;
3384 | left: auto;
3385 | right: 100%;
3386 | top: 50%;
3387 | -webkit-transform: translateY(-50%);
3388 | -ms-transform: translateY(-50%);
3389 | transform: translateY(-50%); }
3390 |
3391 | .top-bar {
3392 | padding: 0.5rem; }
3393 | .top-bar::before, .top-bar::after {
3394 | content: ' ';
3395 | display: table;
3396 | -webkit-flex-basis: 0;
3397 | -ms-flex-preferred-size: 0;
3398 | flex-basis: 0;
3399 | -webkit-order: 1;
3400 | -ms-flex-order: 1;
3401 | order: 1; }
3402 | .top-bar::after {
3403 | clear: both; }
3404 | .top-bar,
3405 | .top-bar ul {
3406 | background-color: #e6e6e6; }
3407 | .top-bar input {
3408 | width: 200px;
3409 | margin-right: 1rem; }
3410 | .top-bar .input-group-field {
3411 | width: 100%;
3412 | margin-right: 0; }
3413 | .top-bar input.button {
3414 | width: auto; }
3415 |
3416 | @media screen and (max-width: 39.9375em) {
3417 | .stacked-for-small .top-bar-left,
3418 | .stacked-for-small .top-bar-right {
3419 | width: 100%; } }
3420 |
3421 | @media screen and (max-width: 63.9375em) {
3422 | .stacked-for-medium .top-bar-left,
3423 | .stacked-for-medium .top-bar-right {
3424 | width: 100%; } }
3425 |
3426 | @media screen and (max-width: 74.9375em) {
3427 | .stacked-for-large .top-bar-left,
3428 | .stacked-for-large .top-bar-right {
3429 | width: 100%; } }
3430 |
3431 | .top-bar-left,
3432 | .top-bar-right {
3433 | width: 100%; }
3434 |
3435 | @media screen and (min-width: 40em) {
3436 | .top-bar-left,
3437 | .top-bar-right {
3438 | width: auto; } }
3439 |
3440 | .top-bar-title {
3441 | float: left;
3442 | margin-right: 1rem; }
3443 |
3444 | .top-bar-left {
3445 | float: left; }
3446 |
3447 | .top-bar-right {
3448 | float: right; }
3449 |
3450 | .hide {
3451 | display: none !important; }
3452 |
3453 | .invisible {
3454 | visibility: hidden; }
3455 |
3456 | @media screen and (max-width: 39.9375em) {
3457 | .hide-for-small-only {
3458 | display: none !important; } }
3459 |
3460 | @media screen and (max-width: 0em), screen and (min-width: 40em) {
3461 | .show-for-small-only {
3462 | display: none !important; } }
3463 |
3464 | @media screen and (min-width: 40em) {
3465 | .hide-for-medium {
3466 | display: none !important; } }
3467 |
3468 | @media screen and (max-width: 39.9375em) {
3469 | .show-for-medium {
3470 | display: none !important; } }
3471 |
3472 | @media screen and (min-width: 40em) and (max-width: 63.9375em) {
3473 | .hide-for-medium-only {
3474 | display: none !important; } }
3475 |
3476 | @media screen and (max-width: 39.9375em), screen and (min-width: 64em) {
3477 | .show-for-medium-only {
3478 | display: none !important; } }
3479 |
3480 | @media screen and (min-width: 64em) {
3481 | .hide-for-large {
3482 | display: none !important; } }
3483 |
3484 | @media screen and (max-width: 63.9375em) {
3485 | .show-for-large {
3486 | display: none !important; } }
3487 |
3488 | @media screen and (min-width: 64em) and (max-width: 74.9375em) {
3489 | .hide-for-large-only {
3490 | display: none !important; } }
3491 |
3492 | @media screen and (max-width: 63.9375em), screen and (min-width: 75em) {
3493 | .show-for-large-only {
3494 | display: none !important; } }
3495 |
3496 | .show-for-sr,
3497 | .show-on-focus {
3498 | position: absolute !important;
3499 | width: 1px;
3500 | height: 1px;
3501 | overflow: hidden;
3502 | clip: rect(0, 0, 0, 0); }
3503 |
3504 | .show-on-focus:active, .show-on-focus:focus {
3505 | position: static !important;
3506 | height: auto;
3507 | width: auto;
3508 | overflow: visible;
3509 | clip: auto; }
3510 |
3511 | .show-for-landscape,
3512 | .hide-for-portrait {
3513 | display: block !important; }
3514 | @media screen and (orientation: landscape) {
3515 | .show-for-landscape,
3516 | .hide-for-portrait {
3517 | display: block !important; } }
3518 | @media screen and (orientation: portrait) {
3519 | .show-for-landscape,
3520 | .hide-for-portrait {
3521 | display: none !important; } }
3522 |
3523 | .hide-for-landscape,
3524 | .show-for-portrait {
3525 | display: none !important; }
3526 | @media screen and (orientation: landscape) {
3527 | .hide-for-landscape,
3528 | .show-for-portrait {
3529 | display: none !important; } }
3530 | @media screen and (orientation: portrait) {
3531 | .hide-for-landscape,
3532 | .show-for-portrait {
3533 | display: block !important; } }
3534 |
3535 | .float-left {
3536 | float: left !important; }
3537 |
3538 | .float-right {
3539 | float: right !important; }
3540 |
3541 | .float-center {
3542 | display: block;
3543 | margin-left: auto;
3544 | margin-right: auto; }
3545 |
3546 | .clearfix::before, .clearfix::after {
3547 | content: ' ';
3548 | display: table;
3549 | -webkit-flex-basis: 0;
3550 | -ms-flex-preferred-size: 0;
3551 | flex-basis: 0;
3552 | -webkit-order: 1;
3553 | -ms-flex-order: 1;
3554 | order: 1; }
3555 |
3556 | .clearfix::after {
3557 | clear: both; }
3558 |
3559 | /*# sourceMappingURL=foundation.css.map */
3560 |
--------------------------------------------------------------------------------
/examples/mation-example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-decorators-legacy"]
4 | }
5 |
--------------------------------------------------------------------------------
/examples/mation-example/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "import/no-unresolved": 0,
10 | "react/prop-types": 0,
11 | "react/no-multi-comp": 0
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/mation-example/index.css:
--------------------------------------------------------------------------------
1 | #container {
2 | margin-top: 40px;
3 | }
4 |
5 | .ui-hint {
6 | color: #DD0000;
7 | display: block;
8 | font-size: 1em;
9 | margin-top: -15px;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/mation-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | react-inform-mation-example
4 |
5 |
6 |
7 |
8 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/mation-example/index.js:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import '../foundation.css';
3 |
4 | import React, { Component } from 'react';
5 | import TextArea from 'react-textarea-autosize';
6 | import { render } from 'react-dom';
7 | import alertify from 'alertify.js';
8 | import { isEmail } from 'string-validator';
9 | import {
10 | form,
11 | from,
12 | DisabledFormSubmit,
13 | FeedbackFormSubmit,
14 | ResetFormButton,
15 | } from 'react-inform';
16 | import { mation } from 'react-mation';
17 | import { presets } from 'mation';
18 |
19 | alertify.logPosition('top right');
20 |
21 | function AnimatedInput({ text, error, id, props }) {
22 | return (
23 |
24 |
{text}
25 |
32 |
{error}
33 |
34 | );
35 | }
36 |
37 | const hintOffset = 50;
38 |
39 | const AnimatedUiHint = mation(presets.wobbly)(
40 | ({ spring, children }) => {
41 | const mationval = spring(children ? 0 : -hintOffset);
42 |
43 | return (
44 |
53 | {children}
54 |
55 | );
56 | }
57 | );
58 |
59 | const isRequired = value => value;
60 |
61 | const rulesMap = {
62 | checkbox: {
63 | 'Must be checked': v => v,
64 | },
65 | username: {
66 | 'Username is required': isRequired,
67 | },
68 | email: {
69 | 'Email is required': isRequired,
70 | 'Must be a valid email': isEmail(),
71 | },
72 | password: {
73 | 'Password is required': isRequired,
74 | },
75 | confirmPassword: {
76 | 'Passwords must match': (value, { password }) => value === password,
77 | },
78 | };
79 |
80 | @form(from(rulesMap))
81 | class MyForm extends Component {
82 | handleSubmit(e) {
83 | e.preventDefault();
84 | alertify.success('Awesome, it submitted!');
85 | }
86 |
87 | render() {
88 | const { username, email, password, confirmPassword, checkbox } = this.props.fields;
89 | return (
90 |
119 | );
120 | }
121 | }
122 |
123 | class App extends Component {
124 | state= {
125 | value: {
126 | checkbox: true,
127 | username: 'test user',
128 | email: 'badEmail',
129 | },
130 | };
131 |
132 | setValue(value) {
133 | this.setState({ value, typedValue: undefined });
134 | }
135 |
136 | handleChange(e) {
137 | const { value } = e.target;
138 | this.setState({ typedValue: value });
139 |
140 | try {
141 | const objValue = JSON.parse(value);
142 | this.setState({ value: objValue });
143 | } catch (error) {
144 | // do nothing
145 | }
146 | }
147 |
148 | render() {
149 | const { value, typedValue } = this.state;
150 | return (
151 |
152 |
153 | this.setValue(v)} value={value} />
154 |
155 |
Try editing this
156 |
162 | );
163 | }
164 | }
165 |
166 | render( , document.getElementById('container'));
167 |
--------------------------------------------------------------------------------
/examples/mation-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inform-mation-example-example",
3 | "version": "1.0.0",
4 | "description": "mation-example",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "babel-node server.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/theadam/react-inform.git"
12 | },
13 | "license": "MIT",
14 | "devDependencies": {
15 | "babel-cli": "^6.11.4",
16 | "babel-core": "^6.11.4",
17 | "babel-eslint": "^6.1.2",
18 | "babel-loader": "^6.2.4",
19 | "babel-plugin-rewire": "^1.0.0-rc-4",
20 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
21 | "babel-preset-es2015": "^6.9.0",
22 | "babel-preset-react": "^6.11.1",
23 | "babel-preset-stage-0": "^6.5.0",
24 | "css-loader": "^0.23.1",
25 | "eslint": "^3.1.1",
26 | "eslint-config-airbnb": "^9.0.1",
27 | "eslint-plugin-import": "^1.11.1",
28 | "eslint-plugin-jsx-a11y": "^2.0.1",
29 | "eslint-plugin-react": "^5.2.2",
30 | "extract-text-webpack-plugin": "^1.0.1",
31 | "react": "^15.2.1",
32 | "style-loader": "^0.13.0",
33 | "webpack": "^1.13.1",
34 | "webpack-dev-server": "^1.14.1"
35 | },
36 | "dependencies": {
37 | "alertify.js": "^1.0.9",
38 | "mation": "0.0.1",
39 | "react": "^15.0.0",
40 | "react-dom": "^15.0.0",
41 | "react-mation": "0.0.1",
42 | "react-textarea-autosize": "^4.0.5",
43 | "string-validator": "^1.0.5"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/mation-example/server.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import WebpackDevServer from 'webpack-dev-server';
3 | import config from './webpack.config.babel';
4 |
5 | config.entry.push('webpack-dev-server/client?http://localhost:3000');
6 | config.entry.push('webpack/hot/only-dev-server');
7 |
8 | config.plugins.push(new webpack.HotModuleReplacementPlugin());
9 |
10 | new WebpackDevServer(webpack(config), {
11 | publicPath: config.output.publicPath,
12 | hot: true,
13 | historyApiFallback: true,
14 | stats: {
15 | colors: true,
16 | },
17 | }).listen(3000, 'localhost', err => {
18 | if (err) {
19 | // eslint-disable-next-line no-console
20 | console.log(err);
21 | }
22 |
23 | // eslint-disable-next-line no-console
24 | console.log('Listening at localhost:3000');
25 | });
26 |
--------------------------------------------------------------------------------
/examples/mation-example/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 |
4 | module.exports = {
5 | devtool: 'eval-source-map',
6 | entry: [
7 | './index',
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | filename: 'bundle.js',
12 | publicPath: '/dist',
13 | },
14 | plugins: [
15 | new webpack.NoErrorsPlugin(),
16 | ],
17 | resolve: {
18 | alias: {
19 | 'react-inform': path.join(__dirname, '..', '..', 'src'),
20 | },
21 | extensions: ['', '.js'],
22 | },
23 | module: {
24 | loaders: [{
25 | test: /\.js$/,
26 | loaders: ['babel'],
27 | exclude: /node_modules/,
28 | include: __dirname,
29 | }, {
30 | test: /\.js$/,
31 | loaders: ['babel'],
32 | include: path.join(__dirname, '..', '..', 'src'),
33 | },
34 | {
35 | test: /\.css$/,
36 | loader: 'style-loader!css-loader',
37 | }],
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-inform",
3 | "version": "0.2.5",
4 | "description": "Simple controlled forms with validations in react",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/theadam/react-inform.git"
9 | },
10 | "keywords": [
11 | "react",
12 | "forms",
13 | "form",
14 | "inform",
15 | "validate",
16 | "validation",
17 | "input",
18 | "decorator",
19 | "easy",
20 | "light",
21 | "lightweight",
22 | "react-component"
23 | ],
24 | "scripts": {
25 | "clean": "rimraf lib dist",
26 | "build:lib": "babel src --loose --out-dir lib",
27 | "build:umd": "webpack src/index.js dist/react-inform.js --display-modules --progress && NODE_ENV=production webpack src/index.js dist/react-inform.min.js --display-modules --progress",
28 | "build": "npm run build:lib && npm run build:umd",
29 | "lint": "eslint src",
30 | "prepublish": "npm run lint && npm run test && npm run clean && npm run build",
31 | "test": "NODE_ENV=test mocha",
32 | "test:watch": "NODE_ENV=test mocha --watch",
33 | "test:cov": "NODE_ENV=test babel-node ./node_modules/.bin/isparta cover ./node_modules/.bin/_mocha",
34 | "test:guard": "babel-node ./bin/guard",
35 | "build-examples": "npm run build:umd; for i in `ls -d examples/*/`; do pushd $i; webpack; popd; done"
36 | },
37 | "author": "Adam Nalisnick",
38 | "license": "MIT",
39 | "devDependencies": {
40 | "alertify.js": "^1.0.12",
41 | "babel-cli": "^6.11.4",
42 | "babel-core": "^6.11.4",
43 | "babel-eslint": "^6.1.2",
44 | "babel-loader": "^6.2.4",
45 | "babel-plugin-rewire": "^1.0.0",
46 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
47 | "babel-preset-es2015": "^6.9.0",
48 | "babel-preset-react": "^6.11.1",
49 | "babel-preset-stage-0": "^6.5.0",
50 | "chai": "^3.5.0",
51 | "chokidar": "^1.6.0",
52 | "css-loader": "^0.28.7",
53 | "enzyme": "^2.4.1",
54 | "eslint": "^3.1.1",
55 | "eslint-config-airbnb": "^9.0.1",
56 | "eslint-plugin-import": "^1.11.1",
57 | "eslint-plugin-jsx-a11y": "^2.0.1",
58 | "eslint-plugin-react": "^5.2.2",
59 | "isparta": "^4.0.0",
60 | "jsdom": "^9.4.1",
61 | "mation": "0.0.2",
62 | "mocha": "^2.5.3",
63 | "react": "^15.2.1",
64 | "react-addons-test-utils": "^15.2.1",
65 | "react-dom": "^15.2.1",
66 | "react-mation": "0.0.1",
67 | "react-textarea-autosize": "^5.1.0",
68 | "rimraf": "^2.5.3",
69 | "sinon": "^1.17.4",
70 | "string-validator": "^1.0.5",
71 | "style-loader": "^0.19.0",
72 | "webpack": "^1.13.1"
73 | },
74 | "peerDependencies": {
75 | "react": "^0.14.7 || ^15.0.0 || ^16.0.0"
76 | },
77 | "dependencies": {
78 | "prop-types": "^15.6.0"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/createValidate.js:
--------------------------------------------------------------------------------
1 | const hasThen = obj => !!obj && obj.then instanceof Function;
2 |
3 | function gen(obj, index = 0, keys = Object.keys(obj)) {
4 | return {
5 | keys,
6 | index,
7 | key: keys[index],
8 | value: obj[keys[index]],
9 | next: () => gen(obj, index + 1, keys),
10 | done: index >= keys.length,
11 | };
12 | }
13 |
14 | function validateField(value, values, rule) {
15 | if (rule.done) return undefined;
16 | const result = rule.value(value, values);
17 |
18 | if (hasThen(result)) {
19 | return result.then((res) => {
20 | if (!res) return rule.key;
21 | return validateField(value, values, rule.next());
22 | });
23 | } else if (!result) return rule.key;
24 | return validateField(value, values, rule.next());
25 | }
26 |
27 |
28 | export default function createValidate(rulesMap) {
29 | return (values) => {
30 | const errors = {};
31 | const promises = [];
32 | Object.keys(rulesMap).forEach((key) => {
33 | const rules = rulesMap[key];
34 | const value = values[key];
35 | const result = validateField(value, values, gen(rules));
36 |
37 | if (hasThen(result)) {
38 | promises.push(result.then((v) => {
39 | if (v !== undefined) {
40 | errors[key] = v;
41 | }
42 | }));
43 | } else if (result !== undefined) errors[key] = result;
44 | });
45 | if (promises.length === 0) return errors;
46 | return Promise.all(promises).then(() => errors);
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/src/disabledFormSubmit.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | export default function DisabledFormSubmit(props, context) {
5 | return (
6 |
7 | );
8 | }
9 |
10 | DisabledFormSubmit.contextTypes = {
11 | form: PropTypes.object.isRequired,
12 | };
13 |
--------------------------------------------------------------------------------
/src/feedbackFormSubmit.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 |
4 | export default class FeedbackFormSubmit extends Component {
5 | static propTypes = {
6 | onInvalid: PropTypes.func,
7 | onClick: PropTypes.func,
8 | }
9 |
10 | static contextTypes = {
11 | form: PropTypes.object.isRequired,
12 | }
13 |
14 | handleClick(e) {
15 | if (!this.context.form.isValid()) {
16 | e.preventDefault();
17 | this.context.form.forceValidate();
18 | if (this.props.onInvalid) this.props.onInvalid(e);
19 | }
20 | if (this.props.onClick) this.props.onClick(e);
21 | }
22 |
23 | render() {
24 | return (
25 | this.handleClick(e)} />
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/form.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 |
4 | import getValue from './getValue';
5 |
6 | function undefinedToEmpty(value) {
7 | if (value === undefined) return '';
8 | return value;
9 | }
10 |
11 | export default function form({
12 | fields: defaultFields = [],
13 | validate: defaultValidate = () => ({}),
14 | } = {}) {
15 | return Wrapped => class FormWrapper extends Component {
16 | static defaultProps = {
17 | fields: defaultFields,
18 | validate: defaultValidate,
19 | }
20 |
21 | static childContextTypes = {
22 | form: PropTypes.object,
23 | fields: PropTypes.object,
24 | }
25 |
26 | static propTypes = {
27 | fields: PropTypes.array,
28 | validate: PropTypes.func,
29 | value: PropTypes.object,
30 | touched: PropTypes.object,
31 | onTouch: PropTypes.func,
32 | onChange: PropTypes.func,
33 | onValidate: PropTypes.func,
34 | }
35 |
36 | state = {
37 | errors: {},
38 | valid: undefined,
39 | }
40 |
41 | componentWillMount() {
42 | const values = this.props.value || {};
43 | this.setState({ values });
44 |
45 | const touched = this.props.touched || {};
46 | this.setState({ touched });
47 |
48 | this.handleValidate(values);
49 | }
50 |
51 | componentWillReceiveProps(nextProps) {
52 | if (nextProps.value !== undefined) {
53 | const values = nextProps.value;
54 | this.setState({ values });
55 | this.handleValidate(values, nextProps.validate);
56 | } else if (nextProps.validate !== undefined) {
57 | this.handleValidate(this.state.values, nextProps.validate);
58 | }
59 | if (nextProps.touched !== undefined) {
60 | const touched = nextProps.touched;
61 | this.setState({ touched });
62 | }
63 | }
64 |
65 | setValues(values) {
66 | if (values === undefined) return;
67 | if (this.props.value !== undefined) {
68 | this.broadcastChange(values);
69 | } else {
70 | this.setState({ values }, () => this.broadcastChange(values));
71 | this.handleValidate(values);
72 | }
73 | }
74 |
75 | setErrors(errors) {
76 | const valid = Object.keys(errors).length === 0;
77 | if (valid !== this.state.valid) {
78 | this.setState({ valid });
79 | if (this.props.onValidate) this.props.onValidate(valid);
80 | }
81 | this.setState({ errors });
82 | }
83 |
84 | handleValidate(values, validate = this.props.validate) {
85 | const errors = validate(values);
86 | if (errors.then instanceof Function) {
87 | errors.then(errs => this.setErrors(errs));
88 | } else {
89 | this.setErrors(errors);
90 | }
91 | }
92 |
93 | broadcastChange = (values) => {
94 | if (this.props.onChange) this.props.onChange(values);
95 | }
96 |
97 | broadcastTouched = (touched) => {
98 | if (this.props.onTouch) this.props.onTouch(touched);
99 | }
100 |
101 | handleChange(name, e) {
102 | const value = getValue(e);
103 | const values = {
104 | ...this.state.values,
105 | [name]: value,
106 | };
107 |
108 | if (value === this.state.values[name]) return;
109 | this.setValues(values);
110 | }
111 |
112 | updateTouched(touched) {
113 | if (this.props.touched !== undefined) {
114 | this.broadcastTouched(touched);
115 | } else {
116 | this.setState({ touched }, () => this.broadcastTouched(touched));
117 | }
118 | }
119 |
120 | touch(preVals, toVal = true) {
121 | const vals = preVals.filter(v => this.props.fields.indexOf(v) !== -1);
122 | const alreadySet =
123 | vals.reduce((acc, name) => acc && this.state.touched[name] === toVal, true);
124 | if (alreadySet) return;
125 |
126 | const touched = vals.reduce((acc, name) => ({ ...acc, [name]: toVal }), this.state.touched);
127 |
128 | this.updateTouched(touched);
129 | }
130 |
131 | formProps() {
132 | return {
133 | isValid: () => this.state.valid,
134 | touch: (vals = []) => this.touch(vals),
135 | forceValidate: () => this.touch(this.props.fields),
136 | untouch: (vals = []) => this.touch(vals, false),
137 | resetTouched: () => this.touch(this.props.fields, false),
138 | values: () => this.state.values,
139 | onValues: (values) => {
140 | this.setValues(values);
141 | },
142 | };
143 | }
144 |
145 | baseProps(name) {
146 | const { values } = this.state;
147 | return {
148 | onChange: e => this.handleChange(name, e),
149 | onBlur: () => this.touch([name]),
150 | value: undefinedToEmpty(values[name]),
151 | checked: typeof values[name] === 'boolean' ? values[name] : undefined,
152 | };
153 | }
154 |
155 | makeField(name) {
156 | const { errors, touched } = this.state;
157 | const baseProps = this.baseProps(name);
158 | return {
159 | ...baseProps,
160 | error: touched[name] ? errors[name] : undefined,
161 | props: baseProps,
162 | };
163 | }
164 |
165 | makeFields() {
166 | return this.props.fields
167 | .reduce((acc, name) => ({ ...acc, [name]: this.makeField(name) }), {});
168 | }
169 |
170 | generatedProps() {
171 | return {
172 | form: this.formProps(),
173 | fields: this.makeFields(),
174 | };
175 | }
176 |
177 | getChildContext() {
178 | return this.generatedProps();
179 | }
180 |
181 | render() {
182 | // eslint-disable-next-line no-unused-vars
183 | const { value, onChange, onValidate, validate, fields, ...otherProps } = this.props;
184 | return ;
185 | }
186 | };
187 | }
188 |
--------------------------------------------------------------------------------
/src/from.js:
--------------------------------------------------------------------------------
1 | import createValidate from './createValidate';
2 |
3 | export default function from(rules) {
4 | return {
5 | fields: Object.keys(rules),
6 | validate: createValidate(rules),
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/getValue.js:
--------------------------------------------------------------------------------
1 | const getSelectedValues = (options) => {
2 | const result = [];
3 | if (options) {
4 | for (let index = 0; index < options.length; index += 1) {
5 | const option = options[index];
6 | if (option.selected) {
7 | result.push(option.value);
8 | }
9 | }
10 | }
11 | return result;
12 | };
13 |
14 | const isEvent = candidate => !!(candidate && candidate.stopPropagation && candidate.preventDefault);
15 |
16 | export default function getValue(event) {
17 | if (isEvent(event)) {
18 | const { target: { type, value, checked, files }, dataTransfer } = event;
19 | if (type === 'checkbox') {
20 | return checked;
21 | }
22 | if (type === 'file') {
23 | return files || (dataTransfer && dataTransfer.files);
24 | }
25 | if (type === 'select-multiple') {
26 | return getSelectedValues(event.target.options);
27 | }
28 | return value;
29 | }
30 | // not an event, so must be either our value or an object containing our value in the 'value' key
31 | return event && typeof event === 'object' && event.value !== undefined ?
32 | event.value : event;
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import form from './form';
2 | import createValidate from './createValidate';
3 | import from from './from';
4 | import ResetFormButton from './resetFormButton';
5 | import DisabledFormSubmit from './disabledFormSubmit';
6 | import FeedbackFormSubmit from './feedbackFormSubmit';
7 |
8 | export default form;
9 |
10 | export {
11 | form,
12 | createValidate,
13 | from,
14 | ResetFormButton,
15 | DisabledFormSubmit,
16 | FeedbackFormSubmit,
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/src/resetFormButton.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 |
4 | export default class ResetFormButton extends Component {
5 | static propTypes = {
6 | children: PropTypes.string,
7 | resetTouched: PropTypes.boolean,
8 | }
9 |
10 | static contextTypes = {
11 | form: PropTypes.object.isRequired,
12 | }
13 |
14 | handleClick(e) {
15 | e.preventDefault();
16 | this.context.form.onValues({});
17 | if (this.props.resetTouched) {
18 | this.context.form.resetTouched();
19 | }
20 | }
21 |
22 | render() {
23 | const { children, resetTouched: ignored, ...rest } = this.props;
24 |
25 | return (
26 | this.handleClick(e)}>
27 | {children || 'Reset'}
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "rules": {
6 | "react/prop-types": 0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/creatValidate-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import createValidate from '../src/createValidate';
3 |
4 | describe('createValidate', () => {
5 | describe('when given a basic rule', () => {
6 | const rulesMap = {
7 | field: {
8 | 'is not valid': value => value !== 'INVALID',
9 | },
10 | };
11 | const validate = createValidate(rulesMap);
12 |
13 | describe('when the field is invalid', () => {
14 | const object = {
15 | field: 'INVALID',
16 | };
17 |
18 | it('returns an error for the field', () => {
19 | expect(validate(object).field).to.equal('is not valid');
20 | });
21 | });
22 |
23 | describe('when the field is valid', () => {
24 | const object = {
25 | field: 'VALID',
26 | };
27 |
28 | it('returns no errors', () => {
29 | expect(validate(object)).to.deep.equal({});
30 | });
31 | });
32 | });
33 |
34 | describe('when rule is async', () => {
35 | const rulesMap = {
36 | field: {
37 | 'is not valid': value => Promise.resolve(value !== 'INVALID'),
38 | },
39 | };
40 | const validate = createValidate(rulesMap);
41 |
42 | describe('when the field is invalid', () => {
43 | const object = {
44 | field: 'INVALID',
45 | };
46 |
47 | it('returns an error for the field', () =>
48 | validate(object).then(errors => {
49 | expect(errors.field).to.equal('is not valid');
50 | })
51 | );
52 | });
53 |
54 | describe('when the field is valid', () => {
55 | const object = {
56 | field: 'VALID',
57 | };
58 |
59 | it('returns no errors', () =>
60 | validate(object).then(errors => {
61 | expect(errors).to.deep.equal({});
62 | })
63 | );
64 | });
65 | });
66 |
67 | describe('when given a rule involving other fields', () => {
68 | const rulesMap = {
69 | field: {
70 | 'is not valid': (value, { field2 }) => value === field2,
71 | },
72 | };
73 | const validate = createValidate(rulesMap);
74 |
75 | describe('when the field is invalid', () => {
76 | const object = {
77 | field: 'one',
78 | field2: 'two',
79 | };
80 |
81 | it('returns an error for the field', () => {
82 | expect(validate(object).field).to.equal('is not valid');
83 | });
84 | });
85 |
86 | describe('when the field is valid', () => {
87 | const object = {
88 | field: 'one',
89 | field2: 'one',
90 | };
91 |
92 | it('returns no errors', () => {
93 | expect(validate(object)).to.deep.equal({});
94 | });
95 | });
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/test/disabledFormSubmit-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import React from 'react';
3 | import { mount } from 'enzyme';
4 |
5 | import DisabledFormSubmit from '../src/disabledFormSubmit';
6 |
7 | describe('DisabledFormSubmit', () => {
8 | let props;
9 | let context;
10 | const render = () => mount( , { context });
11 |
12 | beforeEach(() => {
13 | props = {
14 | value: 'bar',
15 | };
16 | });
17 |
18 | describe('when the form is valid', () => {
19 | beforeEach(() => {
20 | context = {
21 | form: {
22 | isValid: () => true,
23 | },
24 | };
25 | });
26 |
27 | it('renders an input', () => {
28 | expect(render().find('input').name()).to.equal('input');
29 | });
30 |
31 | it('passes extra props through', () => {
32 | expect(render().find('input').props().value).to.equal('bar');
33 | });
34 |
35 | it('is enabled', () => {
36 | expect(render().find('input').props().disabled).to.equal(false);
37 | });
38 | });
39 |
40 | describe('when the form is invalid', () => {
41 | beforeEach(() => {
42 | context = {
43 | form: {
44 | isValid: () => false,
45 | },
46 | };
47 | });
48 |
49 | it('renders an input', () => {
50 | expect(render().find('input').name()).to.equal('input');
51 | });
52 |
53 | it('passes extra props through', () => {
54 | expect(render().find('input').props().value).to.equal('bar');
55 | });
56 |
57 | it('is disabled', () => {
58 | expect(render().find('input').props().disabled).to.equal(true);
59 | });
60 | });
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/test/feedbackFormSubmit-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import React from 'react';
3 | import { spy } from 'sinon';
4 | import { mount } from 'enzyme';
5 |
6 | import FeedbackFormSubmit from '../src/feedbackFormSubmit';
7 |
8 | describe('FeedbackFormSubmit', () => {
9 | let context;
10 | let props;
11 | const render = () => mount( , { context });
12 |
13 | beforeEach(() => {
14 | props = {
15 | value: 'bar',
16 | onClick: spy(),
17 | onInvalid: spy(),
18 | };
19 | });
20 |
21 | describe('when the form is valid', () => {
22 | beforeEach(() => {
23 | context = {
24 | form: {
25 | isValid: () => true,
26 | forceValidate: spy(),
27 | },
28 | };
29 | });
30 |
31 | it('renders an input', () => {
32 | expect(render().find('input').name()).to.equal('input');
33 | });
34 |
35 | it('passes extra props through', () => {
36 | expect(render().find('input').props().value).to.equal('bar');
37 | });
38 |
39 | describe('when clicked', () => {
40 | const clicked = () => {
41 | const comp = render();
42 | comp.simulate('click');
43 | return comp;
44 | };
45 |
46 | it('does not call call forceValidate', () => {
47 | clicked();
48 | expect(context.form.forceValidate.called).to.equal(false);
49 | });
50 |
51 | it('calls the passed onClick handled', () => {
52 | clicked();
53 | expect(props.onClick.called).to.equal(true);
54 | });
55 |
56 | it('does not call the passed onInvalid handled', () => {
57 | clicked();
58 | expect(props.onInvalid.called).to.equal(false);
59 | });
60 | });
61 | });
62 |
63 | describe('when the form is invalid', () => {
64 | beforeEach(() => {
65 | context = {
66 | form: {
67 | isValid: () => false,
68 | forceValidate: spy(),
69 | },
70 | };
71 | });
72 |
73 | it('renders an input', () => {
74 | expect(render().find('input').name()).to.equal('input');
75 | });
76 |
77 | it('passes extra props through', () => {
78 | expect(render().find('input').props().value).to.equal('bar');
79 | });
80 |
81 | describe('when clicked', () => {
82 | const clicked = () => {
83 | const comp = render();
84 | comp.simulate('click');
85 | return comp;
86 | };
87 |
88 | it('calls forceValidate', () => {
89 | clicked();
90 | expect(context.form.forceValidate.called).to.equal(true);
91 | });
92 |
93 | it('calls the passed onClick handled', () => {
94 | clicked();
95 | expect(props.onClick.called).to.equal(true);
96 | });
97 |
98 | it('calls the passed onInvalid handled', () => {
99 | clicked();
100 | expect(props.onInvalid.called).to.equal(true);
101 | });
102 | });
103 | });
104 | });
105 |
106 |
--------------------------------------------------------------------------------
/test/form-combination-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { spy } from 'sinon';
3 | import React from 'react';
4 | import { mount } from 'enzyme';
5 |
6 | import form from '../src/form';
7 |
8 | const DummyInput = () => null;
9 |
10 | // For testing the different combinations of form setup
11 | describe('combinations', () => {
12 | const descriptor = {
13 | fields: ['test'],
14 | validate: ({ test }) => {
15 | if (test === 'valid') return {};
16 | return { test: 'Must be valid' };
17 | },
18 | };
19 |
20 | const combinations = [
21 | {
22 | name: 'in decorator-no parent',
23 | propHandler: (comp, changeSpy) => {
24 | comp.setProps({ onChange: changeSpy });
25 | },
26 | form: (
27 | form(descriptor)(
28 | ({ fields }) =>
29 | )
30 | ),
31 | }, {
32 | name: 'in decorator-parent changes',
33 | propHandler: (comp, changeSpy) => {
34 | const props = {
35 | onChange: value => {
36 | changeSpy(value);
37 | props.value = value;
38 | comp.setProps(props);
39 | },
40 | };
41 | comp.setProps(props);
42 | },
43 | form: (
44 | form(descriptor)(
45 | ({ fields }) =>
46 | )
47 | ),
48 | }, {
49 | name: 'in props-no parent',
50 | propHandler: (comp, changeSpy) => {
51 | comp.setProps({
52 | onChange: changeSpy,
53 | ...descriptor,
54 | });
55 | },
56 | form: (
57 | form()(
58 | ({ fields }) =>
59 | )
60 | ),
61 | }, {
62 | name: 'in props-parent changes',
63 | propHandler: (comp, changeSpy) => {
64 | const props = {
65 | onChange: value => {
66 | changeSpy(value);
67 | props.value = value;
68 | comp.setProps(props);
69 | },
70 | ...descriptor,
71 | };
72 | comp.setProps(props);
73 | },
74 | form: (
75 | form()(
76 | ({ fields }) =>
77 | )
78 | ),
79 | },
80 | ];
81 |
82 | combinations.forEach(({ name, propHandler, form: Form }) => {
83 | describe(name, () => {
84 | let onChange;
85 | beforeEach(() => {
86 | onChange = spy();
87 | });
88 |
89 | const render = () => {
90 | const comp = mount();
91 | propHandler(comp, onChange);
92 | return comp;
93 | };
94 |
95 | it('has the right starting value', () => {
96 | expect(render().find(DummyInput).props().value).to.eq('');
97 | });
98 |
99 | it('has no errors initially', () => {
100 | expect(render().find(DummyInput).props().error).to.eq(undefined);
101 | });
102 |
103 | it('does not call onChange initially', () => {
104 | render();
105 | expect(onChange.callCount).to.eq(0);
106 | });
107 |
108 | describe('when changed to invalid', () => {
109 | const changed = () => {
110 | const comp = render();
111 | comp.find(DummyInput).props().onChange('invalid');
112 | return comp;
113 | };
114 |
115 | it('has the right value', () => {
116 | expect(changed().find(DummyInput).props().value).to.eq('invalid');
117 | });
118 |
119 | it('has no error', () => {
120 | expect(changed().find(DummyInput).props().error).to.eq(undefined);
121 | });
122 |
123 | it('calls onChange once', () => {
124 | changed();
125 | expect(onChange.calledOnce).to.eq(true);
126 | });
127 |
128 | it('calls onChange with the change value', () => {
129 | changed();
130 | expect(onChange.calledWith({ test: 'invalid' })).to.eq(true);
131 | });
132 |
133 | describe('when blurred', () => {
134 | const blurred = () => {
135 | const comp = changed();
136 | comp.find(DummyInput).props().onBlur();
137 | return comp;
138 | };
139 |
140 | it('has the right value', () => {
141 | expect(blurred().find(DummyInput).props().value).to.eq('invalid');
142 | });
143 |
144 | it('has the error', () => {
145 | expect(blurred().find(DummyInput).props().error).to.eq('Must be valid');
146 | });
147 |
148 | it('calls onChange once', () => {
149 | blurred();
150 | expect(onChange.calledOnce).to.eq(true);
151 | });
152 |
153 | it('calls onChange with the change value', () => {
154 | blurred();
155 | expect(onChange.calledWith({ test: 'invalid' })).to.eq(true);
156 | });
157 |
158 | describe('when message changes', () => {
159 | const invalidate = () => {
160 | const comp = blurred();
161 | const props = {
162 | ...comp.props(),
163 | validate: ({ test }) => (test === undefined ? {} : { test: 'Different message' }),
164 | };
165 | comp.setProps(props);
166 | return comp;
167 | };
168 |
169 | it('has an error', () => {
170 | expect(invalidate().find(DummyInput).props().error).to.eq('Different message');
171 | });
172 |
173 | it('does not call onChange again', () => {
174 | invalidate();
175 | expect(onChange.calledOnce).to.eq(true);
176 | });
177 | });
178 | });
179 | });
180 |
181 | describe('when changed to valid', () => {
182 | const changed = () => {
183 | const comp = render();
184 | comp.find(DummyInput).props().onChange('valid');
185 | return comp;
186 | };
187 |
188 | it('has the right value', () => {
189 | expect(changed().find(DummyInput).props().value).to.eq('valid');
190 | });
191 |
192 | it('has no error', () => {
193 | expect(changed().find(DummyInput).props().error).to.eq(undefined);
194 | });
195 |
196 | it('calls onChange once', () => {
197 | changed();
198 | expect(onChange.calledOnce).to.eq(true);
199 | });
200 |
201 | it('calls onChange with the change value', () => {
202 | changed();
203 | expect(onChange.calledWith({ test: 'valid' })).to.eq(true);
204 | });
205 |
206 | describe('when blurred', () => {
207 | const blurred = () => {
208 | const comp = changed();
209 | comp.find(DummyInput).props().onBlur();
210 | return comp;
211 | };
212 |
213 | it('has the right value', () => {
214 | expect(blurred().find(DummyInput).props().value).to.eq('valid');
215 | });
216 |
217 | it('has no error', () => {
218 | expect(blurred().find(DummyInput).props().error).to.eq(undefined);
219 | });
220 |
221 | it('calls onChange once', () => {
222 | blurred();
223 | expect(onChange.calledOnce).to.eq(true);
224 | });
225 |
226 | it('calls onChange with the change value', () => {
227 | blurred();
228 | expect(onChange.calledWith({ test: 'valid' })).to.eq(true);
229 | });
230 |
231 | describe('when validate rejects value', () => {
232 | const invalidate = () => {
233 | const comp = blurred();
234 | const props = {
235 | ...comp.props(),
236 | validate: ({ test }) => (test === 'valid' ? { test: 'Must not be valid' } : {}),
237 | };
238 | comp.setProps(props);
239 | return comp;
240 | };
241 |
242 | it('has an error', () => {
243 | expect(invalidate().find(DummyInput).props().error).to.eq('Must not be valid');
244 | });
245 |
246 | it('does not call onChange again', () => {
247 | invalidate();
248 | expect(onChange.calledOnce).to.eq(true);
249 | });
250 |
251 | describe('when validate prop is removed', () => {
252 | const revalidate = () => {
253 | const comp = invalidate();
254 | const props = {
255 | ...comp.props(),
256 | validate: undefined,
257 | };
258 | comp.setProps(props);
259 | return comp;
260 | };
261 |
262 | it('removes the error', () => {
263 | expect(revalidate().find(DummyInput).props().error).to.eq(undefined);
264 | });
265 |
266 | it('does not call onChange again', () => {
267 | revalidate();
268 | expect(onChange.calledOnce).to.eq(true);
269 | });
270 | });
271 | });
272 |
273 | describe('when validate and value change to be invalid', () => {
274 | const invalidate = () => {
275 | const comp = blurred();
276 | comp.find(DummyInput).props().onChange('other');
277 | const props = {
278 | ...comp.props(),
279 | validate: ({ test }) => (test === 'other' ? { test: 'suddenly invalid' } : {}),
280 | };
281 | comp.setProps(props);
282 | return comp;
283 | };
284 |
285 | it('has an error', () => {
286 | expect(invalidate().find(DummyInput).props().error).to.eq('suddenly invalid');
287 | });
288 |
289 | it('calls onChange again', () => {
290 | invalidate();
291 | expect(onChange.calledTwice).to.eq(true);
292 | });
293 |
294 | it('calls onChange with new value', () => {
295 | invalidate();
296 | expect(onChange.getCall(1).args[0]).to.deep.eq({ test: 'other' });
297 | });
298 | });
299 | });
300 | });
301 |
302 | describe('when value changes to invalid', () => {
303 | const changed = () => {
304 | const comp = render();
305 | comp.setProps({
306 | ...comp.props(),
307 | value: { test: 'invalid' },
308 | });
309 | return comp;
310 | };
311 |
312 | it('has the right value', () => {
313 | expect(changed().find(DummyInput).props().value).to.eq('invalid');
314 | });
315 |
316 | it('has no error', () => {
317 | expect(changed().find(DummyInput).props().error).to.eq(undefined);
318 | });
319 |
320 | it('does not call onChange', () => {
321 | changed();
322 | expect(onChange.callCount).to.eq(0);
323 | });
324 |
325 | describe('when blurred', () => {
326 | const blurred = () => {
327 | const comp = changed();
328 | comp.find(DummyInput).props().onBlur();
329 | return comp;
330 | };
331 |
332 | it('has the right value', () => {
333 | expect(blurred().find(DummyInput).props().value).to.eq('invalid');
334 | });
335 |
336 | it('has the error', () => {
337 | expect(blurred().find(DummyInput).props().error).to.eq('Must be valid');
338 | });
339 |
340 | it('does not call onChange', () => {
341 | blurred();
342 | expect(onChange.callCount).to.eq(0);
343 | });
344 |
345 | describe('when message changes', () => {
346 | const invalidate = () => {
347 | const comp = blurred();
348 | const props = {
349 | ...comp.props(),
350 | validate: ({ test }) => (test === undefined ? {} : { test: 'Different message' }),
351 | };
352 | comp.setProps(props);
353 | return comp;
354 | };
355 |
356 | it('has an error', () => {
357 | expect(invalidate().find(DummyInput).props().error).to.eq('Different message');
358 | });
359 |
360 | it('does not call onChange', () => {
361 | invalidate();
362 | expect(onChange.callCount).to.eq(0);
363 | });
364 | });
365 | });
366 | });
367 |
368 | describe('when value changes to valid', () => {
369 | const changed = () => {
370 | const comp = render();
371 | comp.setProps({
372 | ...comp.props(),
373 | value: { test: 'valid' },
374 | });
375 | return comp;
376 | };
377 |
378 | it('has the right value', () => {
379 | expect(changed().find(DummyInput).props().value).to.eq('valid');
380 | });
381 |
382 | it('has no error', () => {
383 | expect(changed().find(DummyInput).props().error).to.eq(undefined);
384 | });
385 |
386 | it('does not call onChange', () => {
387 | changed();
388 | expect(onChange.callCount).to.eq(0);
389 | });
390 |
391 | describe('when blurred', () => {
392 | const blurred = () => {
393 | const comp = changed();
394 | comp.find(DummyInput).props().onBlur();
395 | return comp;
396 | };
397 |
398 | it('has the right value', () => {
399 | expect(blurred().find(DummyInput).props().value).to.eq('valid');
400 | });
401 |
402 | it('has no error', () => {
403 | expect(blurred().find(DummyInput).props().error).to.eq(undefined);
404 | });
405 |
406 | it('does not call onChange', () => {
407 | blurred();
408 | expect(onChange.callCount).to.eq(0);
409 | });
410 |
411 | describe('when validate rejects value', () => {
412 | const invalidate = () => {
413 | const comp = blurred();
414 | const props = {
415 | ...comp.props(),
416 | validate: ({ test }) => (test === 'valid' ? { test: 'Must not be valid' } : {}),
417 | };
418 | comp.setProps(props);
419 | return comp;
420 | };
421 |
422 | it('has an error', () => {
423 | expect(invalidate().find(DummyInput).props().error).to.eq('Must not be valid');
424 | });
425 |
426 | it('does not call onChange', () => {
427 | invalidate();
428 | expect(onChange.callCount).to.eq(0);
429 | });
430 |
431 | describe('when validate prop is removed', () => {
432 | const revalidate = () => {
433 | const comp = invalidate();
434 | const props = {
435 | ...comp.props(),
436 | validate: undefined,
437 | };
438 | comp.setProps(props);
439 | return comp;
440 | };
441 |
442 | it('removes the error', () => {
443 | expect(revalidate().find(DummyInput).props().error).to.eq(undefined);
444 | });
445 |
446 | it('does not call onChange', () => {
447 | revalidate();
448 | expect(onChange.callCount).to.eq(0);
449 | });
450 | });
451 | });
452 |
453 | describe('when validate and value change to be invalid', () => {
454 | const invalidate = () => {
455 | const comp = blurred();
456 | const props = {
457 | ...comp.props(),
458 | value: { test: 'other' },
459 | validate: ({ test }) => (test === 'other' ? { test: 'suddenly invalid' } : {}),
460 | };
461 | comp.setProps(props);
462 | return comp;
463 | };
464 |
465 | it('has an error', () => {
466 | expect(invalidate().find(DummyInput).props().error).to.eq('suddenly invalid');
467 | });
468 |
469 | it('does not call onChange', () => {
470 | invalidate();
471 | expect(onChange.callCount).to.eq(0);
472 | });
473 | });
474 | });
475 | });
476 | });
477 | });
478 | });
479 |
--------------------------------------------------------------------------------
/test/form-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import React from 'react';
3 | import { spy } from 'sinon';
4 | import { mount } from 'enzyme';
5 |
6 | import form from '../src/form';
7 |
8 | const fields = ['field'];
9 | const validate = ({ field }) => {
10 | const errors = {};
11 | if (!field) errors.field = 'REQUIRED';
12 | else if (field === 'INVALID') errors.field = 'FIELD INVALID';
13 | return errors;
14 | };
15 |
16 | function MyForm({ fields: formFields }) {
17 | return (
18 |
19 |
20 | {formFields.field.error}
21 |
22 | );
23 | }
24 |
25 | describe('form', () => {
26 | let props;
27 | let WrappedForm;
28 | const render = () => mount( );
29 |
30 | beforeEach(() => {
31 | WrappedForm = form({ fields, validate })(MyForm);
32 | props = { foo: 'bar' };
33 | });
34 |
35 | it('renders the underlying class', () => {
36 | expect(render().find(MyForm).length).to.equal(1);
37 | });
38 |
39 | it('passes extra props through', () => {
40 | expect(render().find(MyForm).props().foo).to.equal('bar');
41 | });
42 |
43 | it('does not pass errors before change / blur', () => {
44 | expect(render().find('span').text()).to.equal('');
45 | });
46 |
47 | describe('fields', () => {
48 | describe('onChange', () => {
49 | describe('when there is not a value prop', () => {
50 | const change = () => {
51 | const comp = render();
52 | comp.find(MyForm).props().fields.field.onChange('newValue');
53 | return comp;
54 | };
55 |
56 | it('changes the value prop', () => {
57 | expect(change().find(MyForm).props().fields.field.value).to.equal('newValue');
58 | });
59 | });
60 |
61 | describe('when there is a value prop', () => {
62 | const change = () => {
63 | const comp = render();
64 | comp.find(MyForm).props().fields.field.onChange('newValue');
65 | return comp;
66 | };
67 |
68 | beforeEach(() => {
69 | props = {
70 | value: {
71 | field: 'staticValue',
72 | },
73 | };
74 | });
75 |
76 | it('does not change the value prop', () => {
77 | expect(change().find(MyForm).props().fields.field.value).to.equal('staticValue');
78 | });
79 | });
80 |
81 | describe('when changed to a boolean value', () => {
82 | const change = () => {
83 | const comp = render();
84 | comp.find(MyForm).props().fields.field.onChange(true);
85 | return comp;
86 | };
87 |
88 | it('passes the checked property', () => {
89 | expect(change().find(MyForm).props().fields.field.checked).to.equal(true);
90 | });
91 |
92 | describe('when changed to a string later', () => {
93 | const backToString = () => {
94 | const comp = change();
95 | comp.find(MyForm).props().fields.field.onChange('anything');
96 | return comp;
97 | };
98 |
99 | it('removes the checked property', () => {
100 | expect(backToString().find(MyForm).props().fields.field.checked).to.equal(undefined);
101 | });
102 | });
103 | });
104 | });
105 |
106 | describe('asyncValidation', () => {
107 | describe('when the value is invalid', () => {
108 | const asyncChange = () => {
109 | const comp = render();
110 | comp.find('input').simulate('blur');
111 | return Promise.resolve(comp);
112 | };
113 | beforeEach(() => {
114 | function asyncValidate(values) {
115 | const errors = validate(values);
116 | return Promise.resolve(errors);
117 | }
118 | WrappedForm = form({ fields, validate: asyncValidate })(MyForm);
119 | });
120 |
121 | it('passes down the error property', () =>
122 | asyncChange().then((comp) => {
123 | expect(comp.find('span').text()).to.equal('REQUIRED');
124 | }),
125 | );
126 | });
127 | });
128 |
129 | describe('onBlur', () => {
130 | beforeEach(() => {
131 | props = {
132 | onTouch: spy(),
133 | };
134 | });
135 |
136 | describe('when the value is invalid', () => {
137 | const blur = () => {
138 | const comp = render();
139 | comp.find('input').simulate('blur');
140 | return comp;
141 | };
142 |
143 | it('passes down the error property', () => {
144 | expect(blur().find('span').text()).to.equal('REQUIRED');
145 | });
146 |
147 | it('calls the onTouch listenr', () => {
148 | blur();
149 | expect(props.onTouch.calledOnce).to.equal(true);
150 | expect(props.onTouch.getCall(0).args[0]).to.deep.equal({ field: true });
151 | });
152 |
153 | describe('when there is a touched false prop', () => {
154 | beforeEach(() => {
155 | props = {
156 | ...props,
157 | touched: { field: false },
158 | };
159 | });
160 |
161 | it('does not pass the error', () => {
162 | expect(blur().find('span').text()).to.equal('');
163 | });
164 |
165 | it('calls the onTouch listenr', () => {
166 | blur();
167 | expect(props.onTouch.calledOnce).to.equal(true);
168 | expect(props.onTouch.getCall(0).args[0]).to.deep.equal({ field: true });
169 | });
170 | });
171 |
172 | describe('when there is a touched tru prop', () => {
173 | beforeEach(() => {
174 | props = {
175 | ...props,
176 | touched: { field: true },
177 | };
178 | });
179 |
180 | it('passes down the error', () => {
181 | expect(blur().find('span').text()).to.equal('REQUIRED');
182 | });
183 |
184 | it('does not call the onTouch prop', () => {
185 | blur();
186 | expect(props.onTouch.callCount).to.equal(0);
187 | });
188 | });
189 | });
190 |
191 | describe('when the value is valid', () => {
192 | const blur = () => {
193 | const comp = render();
194 | comp.instance().setValues({ field: 'valid' });
195 | comp.find('input').simulate('blur');
196 | return comp;
197 | };
198 |
199 | it('does not pass down the error property', () => {
200 | expect(blur().find('span').text()).to.equal('');
201 | });
202 |
203 | it('calls the onTouch listenr', () => {
204 | blur();
205 | expect(props.onTouch.calledOnce).to.equal(true);
206 | expect(props.onTouch.getCall(0).args[0]).to.deep.equal({ field: true });
207 | });
208 | });
209 | });
210 | });
211 |
212 | describe('form prop', () => {
213 | describe('isValid', () => {
214 | describe('when there are errors', () => {
215 | beforeEach(() => {
216 | props = {
217 | value: {
218 | field: '',
219 | },
220 | };
221 | });
222 |
223 | it('returns false', () => {
224 | expect(render().find(MyForm).props().form.isValid()).to.equal(false);
225 | });
226 | });
227 |
228 | describe('when there are no errors', () => {
229 | beforeEach(() => {
230 | props = {
231 | value: {
232 | field: 'valid',
233 | },
234 | };
235 | });
236 |
237 | it('returns true', () => {
238 | expect(render().find(MyForm).props().form.isValid()).to.equal(true);
239 | });
240 | });
241 | });
242 |
243 | describe('forceValidate', () => {
244 | describe('when the field is invalid', () => {
245 | const force = () => {
246 | const comp = render();
247 | comp.find(MyForm).props().form.forceValidate();
248 | return comp;
249 | };
250 |
251 | it('returns false', () => {
252 | expect(force().find('span').text()).to.equal('REQUIRED');
253 | });
254 | });
255 | });
256 |
257 | describe('touch', () => {
258 | beforeEach(() => {
259 | props = {
260 | onTouch: spy(),
261 | };
262 | });
263 |
264 | describe('when the field is invalid', () => {
265 | describe('when fieldname is passed', () => {
266 | const touch = () => {
267 | const comp = render();
268 | comp.find(MyForm).props().form.touch(['field']);
269 | return comp;
270 | };
271 |
272 | it('shows the error', () => {
273 | expect(touch().find('span').text()).to.equal('REQUIRED');
274 | });
275 |
276 | it('calls the onTouch handler', () => {
277 | touch();
278 | expect(props.onTouch.calledOnce).to.equal(true);
279 | expect(props.onTouch.getCall(0).args[0]).to.deep.equal({ field: true });
280 | });
281 |
282 | describe('when there is a touched false prop', () => {
283 | beforeEach(() => {
284 | props = {
285 | ...props,
286 | touched: { field: false },
287 | };
288 | });
289 |
290 | it('does not show the error', () => {
291 | expect(touch().find('span').text()).to.equal('');
292 | });
293 |
294 | it('calls the onTouch handler', () => {
295 | touch();
296 | expect(props.onTouch.calledOnce).to.equal(true);
297 | expect(props.onTouch.getCall(0).args[0]).to.deep.equal({ field: true });
298 | });
299 | });
300 | });
301 |
302 |
303 | describe('when non-existent fieldname is passed', () => {
304 | const touch = () => {
305 | const comp = render();
306 | comp.find(MyForm).props().form.touch(['another']);
307 | return comp;
308 | };
309 |
310 | it('does not show the error', () => {
311 | expect(touch().find('span').text()).to.equal('');
312 | });
313 |
314 | it('does not call the onTouch prop', () => {
315 | touch();
316 | expect(props.onTouch.callCount).to.eq(0);
317 | });
318 |
319 | describe('when there is a touched true prop', () => {
320 | beforeEach(() => {
321 | props = {
322 | ...props,
323 | touched: { field: true },
324 | };
325 | });
326 |
327 | it('shows the error', () => {
328 | expect(touch().find('span').text()).to.equal('REQUIRED');
329 | });
330 |
331 | it('does not call the onTouch prop', () => {
332 | touch();
333 | expect(props.onTouch.callCount).to.eq(0);
334 | });
335 | });
336 | });
337 | });
338 | });
339 |
340 | describe('untouch', () => {
341 | beforeEach(() => {
342 | props = {
343 | onTouch: spy(),
344 | };
345 | });
346 |
347 | describe('when the field is invalid', () => {
348 | describe('when fieldname is passed', () => {
349 | const touch = () => {
350 | const comp = render();
351 | comp.find(MyForm).props().form.forceValidate();
352 | props.onTouch.reset();
353 | comp.find(MyForm).props().form.untouch(['field']);
354 | return comp;
355 | };
356 |
357 | it('does not show the error', () => {
358 | expect(touch().find('span').text()).to.equal('');
359 | });
360 |
361 | it('calls the onTouch prop', () => {
362 | touch();
363 | expect(props.onTouch.calledOnce).to.equal(true);
364 | expect(props.onTouch.getCall(0).args[0]).to.deep.equal({ field: false });
365 | });
366 |
367 | describe('when there is a touched true prop', () => {
368 | beforeEach(() => {
369 | props = {
370 | ...props,
371 | touched: { field: true },
372 | };
373 | });
374 |
375 | it('shows the error', () => {
376 | expect(touch().find('span').text()).to.equal('REQUIRED');
377 | });
378 |
379 | it('calls the onTouch prop', () => {
380 | touch();
381 | expect(props.onTouch.callCount).to.eq(1);
382 | expect(props.onTouch.getCall(0).args[0]).to.deep.eq({ field: false });
383 | });
384 | });
385 | });
386 |
387 | describe('non-existent fieldname is passed', () => {
388 | const touch = () => {
389 | const comp = render();
390 | comp.find(MyForm).props().form.forceValidate();
391 | props.onTouch.reset();
392 | comp.find(MyForm).props().form.untouch(['another']);
393 | return comp;
394 | };
395 |
396 | it('shows the error', () => {
397 | expect(touch().find('span').text()).to.equal('REQUIRED');
398 | });
399 |
400 | it('does not call the onTouch prop', () => {
401 | touch();
402 | expect(props.onTouch.callCount).to.equal(0);
403 | });
404 |
405 | describe('when there is a touched false prop', () => {
406 | beforeEach(() => {
407 | props = {
408 | ...props,
409 | touched: { field: false },
410 | };
411 | });
412 |
413 | it('shows the error', () => {
414 | expect(touch().find('span').text()).to.equal('');
415 | });
416 |
417 | it('does not call the onTouch prop', () => {
418 | touch();
419 | expect(props.onTouch.callCount).to.eq(0);
420 | });
421 | });
422 | });
423 | });
424 | });
425 |
426 | describe('resetTouched', () => {
427 | describe('when the field is invalid', () => {
428 | const reset = () => {
429 | const comp = render();
430 | comp.find(MyForm).props().form.forceValidate();
431 | comp.find(MyForm).props().form.resetTouched();
432 | return comp;
433 | };
434 |
435 | it('does not show the error', () => {
436 | expect(reset().find('span').text()).to.equal('');
437 | });
438 | });
439 | });
440 |
441 | describe('values', () => {
442 | const change = () => {
443 | const comp = render();
444 | comp.find(MyForm).props().fields.field.onChange('test');
445 | return comp;
446 | };
447 |
448 | it('returns the values', () => {
449 | expect(change().find(MyForm).props().form.values()).to.deep.equal({ field: 'test' });
450 | });
451 | });
452 |
453 | describe('onValues', () => {
454 | describe('when there is no value prop', () => {
455 | const onValues = () => {
456 | const comp = render();
457 | comp.find(MyForm).props().form.onValues({ field: 'INVALID' });
458 | return comp;
459 | };
460 |
461 | beforeEach(() => {
462 | props = {
463 | onChange: spy(),
464 | };
465 | });
466 |
467 | it('calls the onChange listener', () => {
468 | onValues();
469 | expect(props.onChange.calledOnce).to.equal(true);
470 | expect(props.onChange.calledWith({ field: 'INVALID' })).to.equal(true);
471 | });
472 |
473 | it('changes the values', () => {
474 | expect(onValues().find(MyForm).props().form.values()).to.deep.equal({ field: 'INVALID' });
475 | });
476 |
477 | it('detects invalid form state', () => {
478 | expect(onValues().find(MyForm).props().form.isValid()).to.equal(false);
479 | });
480 |
481 | describe('when there is a value already set by onValues()', () => {
482 | const changes = () => {
483 | const comp = onValues();
484 | comp.find(MyForm).props().form.onValues({ field: 'VALID' });
485 | return comp;
486 | };
487 |
488 | beforeEach(() => {
489 | props = {
490 | onChange: spy(),
491 | };
492 | });
493 |
494 | it('calls the onChange listener', () => {
495 | changes();
496 | expect(props.onChange.calledTwice).to.equal(true);
497 | expect(props.onChange.firstCall.calledWith({ field: 'INVALID' })).to.equal(true);
498 | expect(props.onChange.secondCall.calledWith({ field: 'VALID' })).to.equal(true);
499 | });
500 |
501 | it('changes the values', () => {
502 | expect(changes().find(MyForm).props().form.values())
503 | .to
504 | .deep
505 | .equal({ field: 'VALID' });
506 | });
507 |
508 | it('detects valid form state', () => {
509 | expect(changes().find(MyForm).props().form.isValid()).to.equal(true);
510 | });
511 | });
512 | });
513 |
514 | describe('when there is a value prop', () => {
515 | const values = () => {
516 | const comp = render();
517 | comp.find(MyForm).props().form.onValues({ field: 'VALID' });
518 | return comp;
519 | };
520 |
521 | beforeEach(() => {
522 | props = {
523 | value: {
524 | field: 'staticValue',
525 | },
526 | onChange: spy(),
527 | };
528 | });
529 |
530 | it('does not change the values', () => {
531 | expect(values().find(MyForm).props().form.values())
532 | .to
533 | .deep
534 | .equal({ field: 'staticValue' });
535 | });
536 |
537 | it('calls the onChange listener', () => {
538 | values();
539 | expect(props.onChange.calledOnce).to.equal(true);
540 | expect(props.onChange.calledWith({ field: 'VALID' })).to.equal(true);
541 | });
542 | });
543 | });
544 | });
545 |
546 | describe('props from parent', () => {
547 | describe('onValidate', () => {
548 | beforeEach(() => {
549 | props = {
550 | onValidate: spy(),
551 | };
552 | });
553 |
554 | describe('when fields are valid', () => {
555 | beforeEach(() => {
556 | props = {
557 | ...props,
558 | value: {
559 | field: 'valid',
560 | },
561 | };
562 | });
563 |
564 | it('is called with true', () => {
565 | render();
566 | expect(props.onValidate.calledOnce).to.equal(true);
567 | expect(props.onValidate.calledWith(true)).to.equal(true);
568 | });
569 | });
570 |
571 | describe('when fields are invalid', () => {
572 | it('is called with false', () => {
573 | render();
574 | expect(props.onValidate.calledOnce).to.equal(true);
575 | expect(props.onValidate.calledWith(false)).to.equal(true);
576 | });
577 | });
578 |
579 | describe('when fields are invalid multiple times in a row', () => {
580 | const invalidAgain = () => {
581 | const comp = render();
582 | comp.setProps({ value: { field: 'INVALID' } });
583 | return comp;
584 | };
585 |
586 | it('is called once with false', () => {
587 | invalidAgain();
588 | expect(props.onValidate.calledWith(false)).to.equal(true);
589 | expect(props.onValidate.calledOnce).to.equal(true);
590 | });
591 | });
592 | });
593 | });
594 | });
595 |
596 |
--------------------------------------------------------------------------------
/test/from-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 |
3 | import from from '../src/from';
4 |
5 | describe('from', () => {
6 | const ruleMap = { a: {}, b: {} };
7 |
8 | describe('when passed a rule map with fields', () => {
9 | it('creates the fields property', () => {
10 | expect(from(ruleMap).fields).to.deep.equal(['a', 'b']);
11 | });
12 | });
13 |
14 | describe('when passed a rule map with validations', () => {
15 | const output = 'VALIDATE OUTPUT';
16 |
17 | beforeEach(() => {
18 | // eslint-disable-next-line no-underscore-dangle
19 | from.__Rewire__('createValidate', () => output);
20 | });
21 |
22 | // eslint-disable-next-line no-underscore-dangle
23 | afterEach(() => from.__ResetDependency__('createValidate'));
24 |
25 | it('creates a validate property', () => {
26 | expect(from(ruleMap).validate).to.equal(output);
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/getValue-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 |
3 | import getValue from '../src/getValue';
4 |
5 | describe('getValue', () => {
6 | describe('when the event is not a browser event', () => {
7 | describe('when the event has a value property', () => {
8 | const event = {
9 | value: 'event value',
10 | };
11 |
12 | it('returns the event\'s value', () => {
13 | expect(getValue(event)).to.equal('event value');
14 | });
15 | });
16 |
17 | describe('when the event does not have a value property', () => {
18 | const event = 'event value';
19 |
20 | it('returns the event\'s value', () => {
21 | expect(getValue(event)).to.equal('event value');
22 | });
23 | });
24 | });
25 |
26 | describe('when the event is a browser event', () => {
27 | const eventStarter = { stopPropagation: true, preventDefault: true };
28 |
29 | describe('when the event is a checkbox event', () => {
30 | const event = { ...eventStarter, target: { type: 'checkbox', checked: true } };
31 |
32 | it('returns the checked property', () => {
33 | expect(getValue(event)).to.equal(true);
34 | });
35 | });
36 |
37 | describe('when the event is a file event', () => {
38 | const event = {
39 | ...eventStarter,
40 | target: { type: 'file' },
41 | dataTransfer: { files: 'event value' },
42 | };
43 |
44 | it('returns the checked property', () => {
45 | expect(getValue(event)).to.equal('event value');
46 | });
47 | });
48 |
49 | describe('when the event is a select-multiple event', () => {
50 | const event = {
51 | ...eventStarter,
52 | target: {
53 | type: 'select-multiple',
54 | options: [
55 | { value: 'event', selected: true },
56 | { value: 'bad', selected: false },
57 | { value: 'value', selected: true },
58 | ],
59 | },
60 | };
61 |
62 | it('returns the selected options', () => {
63 | expect(getValue(event)).to.deep.equal(['event', 'value']);
64 | });
65 | });
66 |
67 | describe('when the event is not of these types', () => {
68 | const event = {
69 | ...eventStarter,
70 | target: { value: 'event value' },
71 | };
72 |
73 | it('returns the value property', () => {
74 | expect(getValue(event)).to.equal('event value');
75 | });
76 | });
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers js:babel-core/register
2 | --recursive
3 | --require ./test/test_setup.js
4 |
--------------------------------------------------------------------------------
/test/resetFormButton-test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import React from 'react';
3 | import { spy } from 'sinon';
4 | import { mount } from 'enzyme';
5 |
6 | import ResetFormButton from '../src/resetFormButton';
7 |
8 | describe('ResetFormButton', () => {
9 | let context;
10 | let props;
11 | const render = () => mount( , { context });
12 |
13 | beforeEach(() => {
14 | props = {
15 | value: 'bar',
16 | };
17 | context = { form: {} };
18 | });
19 |
20 | describe('when a child is passed', () => {
21 | beforeEach(() => {
22 | props = { ...props, children: 'Test' };
23 | });
24 |
25 | it('renders a button', () => {
26 | expect(render().find('button').name()).to.equal('button');
27 | });
28 |
29 | it('its children is the passed child', () => {
30 | expect(render().text()).to.equal('Test');
31 | });
32 | });
33 |
34 | describe('when no child is passed', () => {
35 | it('renders a button', () => {
36 | expect(render().find('button').name()).to.equal('button');
37 | });
38 |
39 | it('its child is the text "Reset"', () => {
40 | expect(render().text()).to.equal('Reset');
41 | });
42 | });
43 |
44 | describe('when the button is clicked', () => {
45 | const clicked = () => {
46 | const comp = render();
47 | comp.simulate('click');
48 | return comp;
49 | };
50 |
51 | beforeEach(() => {
52 | context = {
53 | form: {
54 | onValues: spy(),
55 | resetTouched: spy(),
56 | },
57 | };
58 | });
59 |
60 | it('calls the forms onValues with an empty array', () => {
61 | clicked();
62 | expect(context.form.onValues.calledWith({})).to.equal(true);
63 | });
64 |
65 | describe('when resetTouched is set', () => {
66 | beforeEach(() => {
67 | props = {
68 | ...props,
69 | resetTouched: true,
70 | };
71 | });
72 |
73 | it('calls the forms onValues with an empty array', () => {
74 | clicked();
75 | expect(context.form.resetTouched.calledOnce).to.equal(true);
76 | expect(context.form.resetTouched.calledWith()).to.equal(true);
77 | });
78 | });
79 | });
80 | });
81 |
82 |
--------------------------------------------------------------------------------
/test/test_setup.js:
--------------------------------------------------------------------------------
1 | import { jsdom } from 'jsdom';
2 |
3 | const exposedProperties = ['window', 'navigator', 'document'];
4 |
5 | global.document = jsdom('');
6 | global.window = document.defaultView;
7 | Object.keys(document.defaultView).forEach((property) => {
8 | if (typeof global[property] === 'undefined') {
9 | exposedProperties.push(property);
10 | global[property] = document.defaultView[property];
11 | }
12 | });
13 |
14 | global.navigator = {
15 | userAgent: 'node.js',
16 | };
17 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 |
3 | const reactExternal = {
4 | root: 'React',
5 | commonjs2: 'react',
6 | commonjs: 'react',
7 | amd: 'react',
8 | };
9 |
10 | const plugins = [
11 | new webpack.DefinePlugin({
12 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
13 | }),
14 | new webpack.optimize.OccurenceOrderPlugin(),
15 | ];
16 |
17 | if (process.env.NODE_ENV === 'production') {
18 | plugins.push(
19 | new webpack.optimize.UglifyJsPlugin({
20 | compressor: {
21 | screw_ie8: true,
22 | warnings: false,
23 | },
24 | }),
25 | );
26 | }
27 |
28 | module.exports = {
29 | module: {
30 | loaders: [
31 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ },
32 | ],
33 | },
34 | output: {
35 | library: 'reactInform',
36 | libraryTarget: 'umd',
37 | },
38 | externals: {
39 | react: reactExternal,
40 | },
41 | plugins,
42 | resolve: {
43 | extensions: ['', '.js'],
44 | },
45 | };
46 |
--------------------------------------------------------------------------------