├── .npmignore
├── .storybook
├── addons.js
├── AppDecorator.js
├── config.js
└── webpack.config.js
├── setup-tests.js
├── ISSUE_TEMPLATE.md
├── src
├── index.js
├── wrappers
│ ├── Random
│ │ ├── Random.test.js
│ │ └── index.jsx
│ ├── Stagger
│ │ ├── Stagger.test.js
│ │ └── index.jsx
│ └── Loop
│ │ └── index.js
├── animations
│ ├── FadeTransform
│ │ ├── index.jsx
│ │ ├── FadeTransform.test.js
│ │ └── __snapshots__
│ │ │ └── FadeTransform.test.js.snap
│ ├── Fade
│ │ ├── Fade.test.js
│ │ ├── index.jsx
│ │ └── __snapshots__
│ │ │ └── Fade.test.js.snap
│ └── Transform
│ │ ├── index.jsx
│ │ ├── Transform.test.js
│ │ └── __snapshots__
│ │ └── Transform.test.js.snap
├── utilities.js
└── utilities.test.js
├── .babelrc
├── .travis.yml
├── stories
├── Stagger.js
├── Random.js
├── Transform.js
├── Loop.js
├── Fade.js
└── FadeTransform.js
├── webpack.config.js
├── LICENSE
├── .gitignore
├── package.json
├── .eslintrc
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | /src
2 | /.storybook
3 | /stories
4 | /storybook-static
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-knobs/register';
2 |
--------------------------------------------------------------------------------
/setup-tests.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({ adapter: new Adapter() });
5 |
6 | global.requestAnimationFrame = function(callback) {
7 | setTimeout(callback, 0);
8 | };
9 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | - **Are you filing a Bug or a Feature Request?**
2 |
3 | - **What is the current behavior?**
4 |
5 | - **What is the expected behavior?**
6 |
7 | - **If its a bug, how can I reproduce? (codepen, jsbin, etc. much appreciated)**
8 |
9 | - **What version are you using?**
10 |
--------------------------------------------------------------------------------
/.storybook/AppDecorator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const styles = {
4 | margin: '2em',
5 | display: 'flex',
6 | justifyContent: 'center',
7 | };
8 |
9 | const AppDecorator = storyFn => {
10 | return
{storyFn()}
;
11 | };
12 |
13 | export default AppDecorator;
14 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure, addDecorator } from '@storybook/react';
2 | import AppDecorator from './AppDecorator';
3 |
4 | const req = require.context('../stories', true, /\.js$/);
5 |
6 | function loadStories() {
7 | req.keys().forEach(filename => req(filename));
8 | }
9 |
10 | addDecorator(AppDecorator);
11 |
12 | configure(loadStories, module);
13 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Fade from 'animations/Fade';
2 | import FadeTransform from 'animations/FadeTransform';
3 | import Transform from 'animations/Transform';
4 |
5 | import Loop from 'wrappers/Loop';
6 | import Random from 'wrappers/Random';
7 | import Stagger from 'wrappers/Stagger';
8 |
9 | module.exports = {
10 | Fade,
11 | FadeTransform,
12 | Loop,
13 | Random,
14 | Stagger,
15 | Transform,
16 | };
17 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": [
4 | [
5 | "module-resolver",
6 | {
7 | "root": ["./src"],
8 | "alias": {
9 | "utilities": "./utilities",
10 | "animations": "./animations",
11 | "groups": "./groups"
12 | }
13 | }
14 | ],
15 | "transform-react-remove-prop-types"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache:
3 | directories:
4 | - node_modules
5 | notifications:
6 | email: false
7 | node_js:
8 | - '8'
9 | - '7'
10 | before_script:
11 | - npm prune
12 | script:
13 | - npm run test:single
14 | - npm run build
15 | after_success:
16 | - npm run semantic-release
17 | before_deploy:
18 | - npm run build-storybook
19 | deploy:
20 | skip_cleanup: true
21 | provider: surge
22 | project: ./storybook-static
23 | domain: react-animation-components.surge.sh
24 | branches:
25 | only:
26 | - master
27 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | module.exports = {
10 | plugins: [
11 | // your custom plugins
12 | ],
13 | module: {
14 | rules: [
15 | // add your custom rules.
16 | ],
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/src/wrappers/Random/Random.test.js:
--------------------------------------------------------------------------------
1 | import { getRandomDelay } from './index.jsx';
2 |
3 | describe('Random', () => {
4 | test('getRandomDelay returns no more than maxDelay and no less than 0', () => {
5 | const props = {
6 | minDelay: 0,
7 | maxDelay: 5000,
8 | };
9 | const numberOfRuns = 1000;
10 |
11 | let actual = true;
12 |
13 | for (let i = 0; i < numberOfRuns; i++) {
14 | const delay = getRandomDelay(props.minDelay, props.maxDelay);
15 | actual = delay <= props.maxDelay && delay >= props.minDelay;
16 | }
17 |
18 | expect(actual).toBe(true);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/stories/Stagger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import { withKnobs, boolean, number } from '@storybook/addon-knobs';
5 |
6 | import { Fade, Stagger } from '../src/index';
7 |
8 | const exampleArray = ['Example', 'Example', 'Example', 'Example', 'Example'];
9 |
10 | storiesOf('Wrappers/Stagger', module)
11 | .addDecorator(withKnobs)
12 | .add('default', () => (
13 |
18 | {exampleArray.map((example, i) => (
19 |
20 | {example}
21 |
22 | ))}
23 |
24 | ));
25 |
--------------------------------------------------------------------------------
/stories/Random.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import { withKnobs, boolean, number } from '@storybook/addon-knobs';
5 |
6 | import { Fade, Random } from '../src/index';
7 |
8 | const exampleArray = ['Example', 'Example', 'Example', 'Example', 'Example'];
9 |
10 | storiesOf('Wrappers/Random', module)
11 | .addDecorator(withKnobs)
12 | .add('default', () => (
13 |
19 | {exampleArray.map((example, i) => (
20 |
21 | {example}
22 |
23 | ))}
24 |
25 | ));
26 |
--------------------------------------------------------------------------------
/src/animations/FadeTransform/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bool, node, object } from 'prop-types';
3 | import Fade from '../Fade';
4 | import Transform from '../Transform';
5 |
6 | import { defaultAnimationProps } from 'utilities';
7 |
8 | const FadeTransform = ({ children, fadeProps, transformProps, ...props }) => {
9 | return (
10 |
11 |
12 | {children}
13 |
14 |
15 | );
16 | };
17 |
18 | FadeTransform.propTypes = {
19 | children: node.isRequired,
20 | fadeProps: object,
21 | in: bool,
22 | transformProps: object,
23 | };
24 |
25 | FadeTransform.defaultProps = {
26 | ...defaultAnimationProps,
27 | fadeProps: {},
28 | transformProps: {},
29 | };
30 |
31 | export default FadeTransform;
32 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src',
5 | output: {
6 | filename: 'react-animation-components.js',
7 | path: path.resolve(__dirname, 'lib'),
8 | library: 'react-animations',
9 | libraryTarget: 'umd',
10 | umdNamedDefine: true,
11 | },
12 | resolve: {
13 | modules: [path.join(__dirname, './src'), 'node_modules'],
14 | extensions: ['.js', '.jsx'],
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.(js|jsx|es6)?$/,
20 | exclude: /\.test.js/,
21 | loader: 'babel-loader',
22 | },
23 | ],
24 | },
25 | externals: {
26 | react: 'react',
27 | 'react-dom': 'react-dom',
28 | 'prop-types': 'prop-types',
29 | 'react-transition-group': 'react-transition-group',
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/stories/Transform.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import * as knobs from '@storybook/addon-knobs';
5 |
6 | import { createCommonKnobs } from '../src/utilities';
7 |
8 | import { Transform } from '../src/index';
9 |
10 | storiesOf('Animations/Transform', module)
11 | .addDecorator(knobs.withKnobs)
12 | .add('default', () => {
13 | const commonKnobs = createCommonKnobs(knobs);
14 | const enterTransform = knobs.text('enterTransform', 'translateY(50vh)');
15 | const exitTransform = knobs.text('exitTransform', 'none');
16 |
17 | return (
18 |
26 | Example
27 |
28 | );
29 | });
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Chris Johnson
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /lib
2 | /storybook-static
3 |
4 | .DS_Store
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | # Runtime data
13 | pids
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (http://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # Typescript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 |
--------------------------------------------------------------------------------
/src/utilities.js:
--------------------------------------------------------------------------------
1 | export const getInlineStyles = ({
2 | style = {},
3 | delay,
4 | duration,
5 | timingFn,
6 | } = {}) => ({
7 | ...style,
8 | transitionDelay: `${delay}ms`,
9 | transitionDuration: `${duration}ms`,
10 | transitionTimingFunction: timingFn,
11 | });
12 |
13 | export const getTimeoutValue = ({ delay = 0, duration = 0 } = {}) =>
14 | delay + duration;
15 |
16 | export const defaultAnimationProps = {
17 | appear: true,
18 | delay: 0,
19 | duration: 500,
20 | timingFn: 'ease',
21 | };
22 |
23 | export const createCommonKnobs = knobs => {
24 | return {
25 | inProp: knobs.boolean('in', true),
26 | delay: knobs.number('delay', defaultAnimationProps.delay),
27 | duration: knobs.number('duration', defaultAnimationProps.duration),
28 | timingFn: knobs.text('timingFn', defaultAnimationProps.timingFn),
29 | };
30 | };
31 |
32 | export const onceEvery = function(times, func) {
33 | const orig = times;
34 | return function() {
35 | if (--times < 1) {
36 | times = orig;
37 | return func.apply(this, arguments);
38 | }
39 |
40 | return null;
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/stories/Loop.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 |
5 | import { Fade, Loop, Transform } from '../src/index';
6 |
7 | storiesOf('Wrappers/Loop', module)
8 | .add('Bounce', () => (
9 |
10 |
11 | Example
12 |
13 |
14 | ))
15 | .add('Pulse', () => (
16 |
17 |
22 | Example
23 |
24 |
25 | ))
26 | .add('Rotate', () => (
27 |
28 |
32 | Example
33 |
34 |
35 | ))
36 | .add('Blink', () => (
37 |
38 |
39 | Example
40 |
41 |
42 | ));
43 |
--------------------------------------------------------------------------------
/stories/Fade.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import * as knobs from '@storybook/addon-knobs';
5 |
6 | import { createCommonKnobs } from '../src/utilities';
7 |
8 | import { Fade } from '../src/index';
9 |
10 | storiesOf('Animations/Fade', module)
11 | .addDecorator(knobs.withKnobs)
12 | .add('default', () => {
13 | const commonKnobs = createCommonKnobs(knobs);
14 |
15 | const unmountOnExit = knobs.boolean('unmountOnExit', false);
16 | const mountOnEnter = knobs.boolean('mountOnEnter', false);
17 |
18 | const enterOpacity = knobs.number('enterOpacity', 1);
19 | const exitOpacity = knobs.number('exitOpacity', 0);
20 |
21 | return (
22 |
32 | Example
33 |
34 | );
35 | });
36 |
--------------------------------------------------------------------------------
/stories/FadeTransform.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import * as knobs from '@storybook/addon-knobs';
5 |
6 | import { createCommonKnobs } from '../src/utilities';
7 |
8 | import { FadeTransform } from '../src/index';
9 |
10 | storiesOf('Animations/FadeTransform', module)
11 | .addDecorator(knobs.withKnobs)
12 | .add('default', () => {
13 | const commonKnobs = createCommonKnobs(knobs);
14 |
15 | const unmountOnExit = knobs.boolean('unmountOnExit', false);
16 | const mountOnEnter = knobs.boolean('mountOnEnter', false);
17 |
18 | const fadeProps = knobs.object('fadeProps', {
19 | enterOpacity: 1,
20 | exitOpacity: 0,
21 | });
22 |
23 | const transformProps = knobs.object('transformProps', {
24 | enterTransform: 'none',
25 | exitTransform: 'translateY(50vh)',
26 | });
27 |
28 | return (
29 |
39 | Example
40 |
41 | );
42 | });
43 |
--------------------------------------------------------------------------------
/src/animations/Fade/Fade.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Fade from './index.jsx';
3 | import renderer from 'react-test-renderer';
4 |
5 | describe('Fade', () => {
6 | test('renders', () => {
7 | const component = renderer.create();
8 | const tree = component.toJSON();
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
12 | test('can accept className', () => {
13 | const component = renderer.create();
14 | const tree = component.toJSON();
15 | expect(tree).toMatchSnapshot();
16 | });
17 |
18 | test('can accept custom styles', () => {
19 | const component = renderer.create(
20 |
21 | );
22 | const tree = component.toJSON();
23 | expect(tree).toMatchSnapshot();
24 | });
25 |
26 | test('sets transitionDelay with delay prop', () => {
27 | const component = renderer.create();
28 | const tree = component.toJSON();
29 | expect(tree).toMatchSnapshot();
30 | });
31 |
32 | test('sets transitionDuration with duration prop', () => {
33 | const component = renderer.create();
34 | const tree = component.toJSON();
35 | expect(tree).toMatchSnapshot();
36 | });
37 |
38 | test('sets transitionTimingFunction with timingFn prop', () => {
39 | const component = renderer.create();
40 | const tree = component.toJSON();
41 | expect(tree).toMatchSnapshot();
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/src/animations/Transform/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bool, node, number, object, string } from 'prop-types';
3 | import { Transition } from 'react-transition-group';
4 |
5 | import {
6 | defaultAnimationProps,
7 | getInlineStyles,
8 | getTimeoutValue,
9 | } from 'utilities';
10 |
11 | const Transform = ({ children, enterTransform, exitTransform, ...props }) => {
12 | const pos = {
13 | entering: exitTransform,
14 | entered: enterTransform,
15 | exiting: exitTransform,
16 | exited: exitTransform,
17 | };
18 |
19 | return (
20 |
21 | {status => (
22 |
31 | {children}
32 |
33 | )}
34 |
35 | );
36 | };
37 |
38 | Transform.propTypes = {
39 | appear: bool,
40 | children: node.isRequired,
41 | className: string,
42 | delay: number,
43 | duration: number,
44 | enterTransform: string,
45 | exitTransform: string,
46 | style: object,
47 | timingFn: string,
48 | };
49 |
50 | Transform.defaultProps = {
51 | ...defaultAnimationProps,
52 | enterTransform: 'none',
53 | exitTransform: 'none',
54 | };
55 |
56 | export default Transform;
57 |
--------------------------------------------------------------------------------
/src/animations/Fade/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { bool, node, number, string } from 'prop-types';
3 | import { Transition } from 'react-transition-group';
4 |
5 | import {
6 | defaultAnimationProps,
7 | getInlineStyles,
8 | getTimeoutValue,
9 | } from 'utilities';
10 |
11 | const Fade = props => {
12 | const statusStyles = {
13 | entered: {
14 | opacity: props.enterOpacity,
15 | },
16 | entering: {
17 | opacity: props.exitOpacity,
18 | },
19 | exited: {
20 | opacity: props.exitOpacity,
21 | },
22 | exiting: {
23 | opacity: props.exitOpacity,
24 | },
25 | };
26 |
27 | return (
28 |
29 | {status => (
30 |
39 | {props.children}
40 |
41 | )}
42 |
43 | );
44 | };
45 |
46 | Fade.propTypes = {
47 | appear: bool,
48 | children: node.isRequired,
49 | className: string,
50 | delay: number,
51 | duration: number,
52 | enterOpacity: number,
53 | exitOpacity: number,
54 | timingFn: string,
55 | };
56 |
57 | Fade.defaultProps = {
58 | ...defaultAnimationProps,
59 | enterOpacity: 1,
60 | exitOpacity: 0,
61 | };
62 |
63 | export default Fade;
64 |
--------------------------------------------------------------------------------
/src/wrappers/Stagger/Stagger.test.js:
--------------------------------------------------------------------------------
1 | import { getStaggerDelay, getMaxDelay } from './index.jsx';
2 |
3 | describe('Stagger', () => {
4 | test('getStaggerDelay returns correct delay', () => {
5 | const props = {
6 | delay: 100,
7 | };
8 | const idx = 10;
9 | const expected = props.delay * idx;
10 | const actual = getStaggerDelay(idx, props.chunk, props.delay);
11 |
12 | expect(actual).toBe(expected);
13 | });
14 |
15 | test('getStaggerDelay returns chunked delays', () => {
16 | const props = {
17 | delay: 100,
18 | chunk: 5,
19 | };
20 | const idx = 5;
21 | const expected = 0;
22 | const actual = getStaggerDelay(idx, props.chunk, props.delay);
23 |
24 | expect(actual).toBe(expected);
25 | });
26 |
27 | test('getMaxDelay returns correct value', () => {
28 | const props = {
29 | chunk: 0,
30 | delay: 100,
31 | duration: 500,
32 | };
33 |
34 | const count = 5;
35 | const expected = 900;
36 | const actual = getMaxDelay(
37 | count,
38 | props.chunk,
39 | props.delay,
40 | props.duration
41 | );
42 |
43 | expect(actual).toBe(expected);
44 | });
45 |
46 | test('getMaxDelay returns correct value when chunking', () => {
47 | const props = {
48 | delay: 100,
49 | duration: 500,
50 | chunk: 2,
51 | };
52 |
53 | const count = 5;
54 | const expected = 600;
55 | const actual = getMaxDelay(
56 | count,
57 | props.chunk,
58 | props.delay,
59 | props.duration
60 | );
61 |
62 | expect(actual).toBe(expected);
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/animations/FadeTransform/FadeTransform.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FadeTranform from './index.jsx';
3 | import renderer from 'react-test-renderer';
4 |
5 | describe('FadeTranform', () => {
6 | test('renders', () => {
7 | const component = renderer.create();
8 | const tree = component.toJSON();
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
12 | test('can accept custom styles', () => {
13 | const component = renderer.create(
14 |
15 | );
16 | const tree = component.toJSON();
17 | expect(tree).toMatchSnapshot();
18 | });
19 |
20 | test('sets transitionDelay with delay prop', () => {
21 | const component = renderer.create();
22 | const tree = component.toJSON();
23 | expect(tree).toMatchSnapshot();
24 | });
25 |
26 | test('sets transitionDuration with duration prop', () => {
27 | const component = renderer.create();
28 | const tree = component.toJSON();
29 | expect(tree).toMatchSnapshot();
30 | });
31 |
32 | test('sets transitionTimingFunction with timingFn prop', () => {
33 | const component = renderer.create(
34 |
35 | );
36 | const tree = component.toJSON();
37 | expect(tree).toMatchSnapshot();
38 | });
39 |
40 | test('sets tranformProps', () => {
41 | const component = renderer.create(
42 |
45 | );
46 | const tree = component.toJSON();
47 | expect(tree).toMatchSnapshot();
48 | });
49 |
50 | test('sets fadeProps', () => {
51 | const component = renderer.create(
52 |
53 | );
54 | const tree = component.toJSON();
55 | expect(tree).toMatchSnapshot();
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/src/wrappers/Loop/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { bool, element, func, number } from 'prop-types';
3 |
4 | export default class Loop extends PureComponent {
5 | static propTypes = {
6 | children: element.isRequired,
7 | in: bool,
8 | interval: number,
9 | iterations: number,
10 | onComplete: func,
11 | onIterate: func,
12 | };
13 |
14 | static defaultProps = {
15 | in: false,
16 | interval: 500,
17 | iterations: Infinity,
18 | onComplete: Function.prototype,
19 | onIterate: Function.prototype,
20 | };
21 |
22 | state = {
23 | in: this.props.in,
24 | };
25 |
26 | componentWillReceiveProps(nextProps) {
27 | if (nextProps.in) {
28 | this.setState({ in: true });
29 | }
30 | }
31 |
32 | componentWillUnmount() {
33 | this._clearTimeouts();
34 | }
35 |
36 | count = 0;
37 | pendingOnComplete = null;
38 | pendingStateChange = null;
39 |
40 | _clearTimeouts() {
41 | clearTimeout(this.pendingStateChange);
42 | clearTimeout(this.pendingOnComplete);
43 | }
44 |
45 | _toggleIn = () => {
46 | this.setState(state => {
47 | return {
48 | in: !state.in,
49 | };
50 | });
51 | };
52 |
53 | _iterate = () => {
54 | this.count = this.count + 0.5;
55 | this.props.onIterate(this.count);
56 |
57 | if (this.count < this.props.iterations) {
58 | this.pendingStateChange = setTimeout(
59 | this._toggleIn,
60 | this.props.interval
61 | );
62 | } else {
63 | this.pendingOnComplete = setTimeout(
64 | this.props.onComplete,
65 | this.props.interval
66 | );
67 | }
68 | };
69 |
70 | render() {
71 | return React.cloneElement(this.props.children, {
72 | duration: this.props.interval,
73 | in: this.state.in,
74 | onEntered: this._iterate,
75 | onExiting: this._iterate,
76 | });
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/animations/Transform/Transform.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Transform from './index.jsx';
3 | import renderer from 'react-test-renderer';
4 |
5 | describe('Transform', () => {
6 | test('renders', () => {
7 | const component = renderer.create();
8 | const tree = component.toJSON();
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
12 | test('can accept className', () => {
13 | const component = renderer.create();
14 | const tree = component.toJSON();
15 | expect(tree).toMatchSnapshot();
16 | });
17 |
18 | test('can accept custom styles', () => {
19 | const component = renderer.create(
20 |
21 | );
22 | const tree = component.toJSON();
23 | expect(tree).toMatchSnapshot();
24 | });
25 |
26 | test('sets transitionDelay with delay prop', () => {
27 | const component = renderer.create();
28 | const tree = component.toJSON();
29 | expect(tree).toMatchSnapshot();
30 | });
31 |
32 | test('sets transitionDuration with duration prop', () => {
33 | const component = renderer.create();
34 | const tree = component.toJSON();
35 | expect(tree).toMatchSnapshot();
36 | });
37 |
38 | test('sets transitionTimingFunction with timingFn prop', () => {
39 | const component = renderer.create();
40 | const tree = component.toJSON();
41 | expect(tree).toMatchSnapshot();
42 | });
43 |
44 | test('sets enterTransform', () => {
45 | const component = renderer.create(
46 |
47 | );
48 | const tree = component.toJSON();
49 | expect(tree).toMatchSnapshot();
50 | });
51 |
52 | test('sets exitTransform', () => {
53 | const component = renderer.create(
54 |
55 | );
56 | const tree = component.toJSON();
57 | expect(tree).toMatchSnapshot();
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/src/animations/Fade/__snapshots__/Fade.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Fade can accept className 1`] = `
4 |
17 | `;
18 |
19 | exports[`Fade can accept custom styles 1`] = `
20 |
34 | `;
35 |
36 | exports[`Fade renders 1`] = `
37 |
50 | `;
51 |
52 | exports[`Fade sets transitionDelay with delay prop 1`] = `
53 |
66 | `;
67 |
68 | exports[`Fade sets transitionDuration with duration prop 1`] = `
69 |
82 | `;
83 |
84 | exports[`Fade sets transitionTimingFunction with timingFn prop 1`] = `
85 |
98 | `;
99 |
--------------------------------------------------------------------------------
/src/utilities.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | getInlineStyles,
3 | getTimeoutValue,
4 | defaultAnimationProps,
5 | } from './utilities.js';
6 |
7 | describe('Utilities', () => {
8 | describe('getInlineStyles', () => {
9 | test('returns an object', () => {
10 | const actual = getInlineStyles(defaultAnimationProps);
11 |
12 | const expected = expect.objectContaining({
13 | transitionDelay: '0ms',
14 | transitionDuration: '500ms',
15 | transitionTimingFunction: 'ease',
16 | });
17 |
18 | expect(actual).toEqual(expected);
19 | });
20 |
21 | test('sets transition properties with props object', () => {
22 | const props = {
23 | delay: 1000,
24 | duration: 1000,
25 | timingFn: 'ease-in-out',
26 | };
27 |
28 | const actual = getInlineStyles(props);
29 |
30 | const expected = expect.objectContaining({
31 | transitionDelay: '1000ms',
32 | transitionDuration: '1000ms',
33 | transitionTimingFunction: 'ease-in-out',
34 | });
35 |
36 | expect(actual).toEqual(expected);
37 | });
38 |
39 | test('does not overwrite transition styles', () => {
40 | const props = {
41 | delay: 1000,
42 | duration: 1000,
43 | timingFn: 'ease-in-out',
44 | style: {
45 | transitionDelay: '2s',
46 | transitionDuration: '2s',
47 | transitionTimingFunction: 'linear',
48 | },
49 | };
50 |
51 | const actual = getInlineStyles(props);
52 |
53 | const expected = expect.objectContaining({
54 | transitionDelay: '1000ms',
55 | transitionDuration: '1000ms',
56 | transitionTimingFunction: 'ease-in-out',
57 | });
58 |
59 | expect(actual).toEqual(expected);
60 | });
61 | });
62 |
63 | describe('getTimeoutValue', () => {
64 | test('Returns value when delay and/or duration are undefined', () => {
65 | const props = {
66 | delay: undefined,
67 | duration: undefined,
68 | };
69 |
70 | const actual = getTimeoutValue(props);
71 | const expected = 0;
72 | expect(actual).toEqual(expected);
73 | });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/wrappers/Random/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bool, func, node, number } from 'prop-types';
3 | import TransitionGroup from 'react-transition-group/TransitionGroup';
4 | import _omit from 'lodash/omit';
5 | import _reverse from 'lodash/reverse';
6 |
7 | import { defaultAnimationProps, onceEvery } from 'utilities';
8 |
9 | export const getRandomDelay = (minDelay, maxDelay) => {
10 | const delay = Math.round(Math.random() * maxDelay);
11 | return delay >= minDelay ? delay : minDelay;
12 | };
13 |
14 | class Random extends Component {
15 | componentWillUnmount() {
16 | clearTimeout(this.onCompleteTimeout);
17 | }
18 |
19 | delays = React.Children.map(this.props.children, () =>
20 | getRandomDelay(this.props.minDelay, this.props.maxDelay)
21 | );
22 |
23 | reversedDelays = _reverse([...this.delays]);
24 |
25 | onCompleteTimeout = null;
26 | totalChildren = React.Children.count(this.props.children);
27 |
28 | onComplete = onceEvery(this.totalChildren, () => {
29 | const maxDelay = Math.max(...this.delays);
30 |
31 | this.onCompleteTimeout = setTimeout(
32 | this.props.onComplete,
33 | maxDelay + this.props.duration
34 | );
35 | });
36 |
37 | getTransitionProps() {
38 | return _omit(this.props, [
39 | 'children',
40 | 'duration',
41 | 'in',
42 | 'maxDelay',
43 | 'minDelay',
44 | 'onComplete',
45 | 'reverse',
46 | ]);
47 | }
48 |
49 | render() {
50 | const { children, duration, in: inProp, reverse } = this.props;
51 |
52 | const delays = reverse ? this.reversedDelays : this.delays;
53 |
54 | return (
55 |
56 | {inProp &&
57 | React.Children.map(children, (child, i) =>
58 | React.cloneElement(child, {
59 | delay: delays[i],
60 | duration,
61 | onEntered: this.onComplete,
62 | onExited: this.onComplete,
63 | })
64 | )}
65 |
66 | );
67 | }
68 | }
69 | Random.propTypes = {
70 | children: node.isRequired,
71 | duration: number,
72 | in: bool,
73 | maxDelay: number,
74 | minDelay: number,
75 | onComplete: func,
76 | reverse: bool,
77 | };
78 |
79 | Random.defaultProps = {
80 | duration: defaultAnimationProps.duration,
81 | in: false,
82 | maxDelay: 1500,
83 | minDelay: 0,
84 | onComplete: Function.prototype,
85 | reverse: false,
86 | };
87 |
88 | export default Random;
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-animation-components",
3 | "version": "0.0.0-development",
4 | "description": "A set of react transition components for basic animations.",
5 | "main": "lib/react-animation-components.js",
6 | "scripts": {
7 | "commit": "git-cz",
8 | "build": "NODE_ENV=production webpack -p --progress",
9 | "test:single": "jest",
10 | "test": "jest --watch",
11 | "semantic-release": "semantic-release pre && npm publish && semantic-release post",
12 | "start": "npm run storybook",
13 | "storybook": "start-storybook -p 6006",
14 | "build-storybook": "build-storybook"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/unruffledBeaver/react-animation-components.git"
19 | },
20 | "keywords": [
21 | "react",
22 | "animations",
23 | "react",
24 | "transition",
25 | "group"
26 | ],
27 | "author": "Chris Johnson",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/unruffledBeaver/react-animation-components/issues"
31 | },
32 | "homepage": "https://github.com/unruffledBeaver/react-animation-components#readme",
33 | "peerDependencies": {
34 | "react": "^16.0.0",
35 | "react-dom": "^16.0.0",
36 | "react-transition-group": "^2.2.1",
37 | "prop-types": "^15.6.0"
38 | },
39 | "devDependencies": {
40 | "@storybook/addon-actions": "^3.2.12",
41 | "@storybook/addon-knobs": "^3.2.12",
42 | "@storybook/addon-links": "^3.2.12",
43 | "@storybook/react": "^3.2.12",
44 | "babel-core": "^6.26.0",
45 | "babel-jest": "^21.2.0",
46 | "babel-loader": "^7.1.2",
47 | "babel-plugin-module-resolver": "^2.7.1",
48 | "babel-plugin-transform-react-remove-prop-types": "^0.4.9",
49 | "babel-preset-es2015": "^6.24.1",
50 | "babel-preset-react": "^6.24.1",
51 | "babel-preset-stage-0": "^6.24.1",
52 | "commitizen": "^2.9.6",
53 | "cz-conventional-changelog": "^2.0.0",
54 | "enzyme": "^3.1.0",
55 | "enzyme-adapter-react-16": "^1.0.1",
56 | "ghooks": "^2.0.0",
57 | "jest": "^21.2.1",
58 | "react": "^16.0.0",
59 | "react-dom": "^16.0.0",
60 | "react-test-renderer": "^16.0.0",
61 | "react-transition-group": "^2.2.1",
62 | "semantic-release": "^8.0.3",
63 | "webpack": "^3.6.0"
64 | },
65 | "dependencies": {
66 | "jest-cli": "^21.2.1",
67 | "lodash": "^4.17.5"
68 | },
69 | "jest": {
70 | "testEnvironment": "node",
71 | "setupTestFrameworkScriptFile": "./setup-tests.js"
72 | },
73 | "config": {
74 | "commitizen": {
75 | "path": "node_modules/cz-conventional-changelog"
76 | },
77 | "ghooks": {
78 | "pre-commit": "npm run test:single"
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/wrappers/Stagger/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bool, func, node, number } from 'prop-types';
3 | import { TransitionGroup } from 'react-transition-group';
4 | import _omit from 'lodash/omit';
5 | import _reverse from 'lodash/reverse';
6 |
7 | import { defaultAnimationProps, onceEvery } from 'utilities';
8 |
9 | export const getStaggerDelay = (idx, chunk, delay) => {
10 | if (chunk) {
11 | return (idx % chunk) * delay;
12 | }
13 | return idx * delay;
14 | };
15 |
16 | export const getMaxDelay = (count, chunk, delay, duration) => {
17 | if (chunk) {
18 | return (chunk - 1) * delay + duration;
19 | }
20 | return (count - 1) * delay + duration;
21 | };
22 |
23 | class Stagger extends Component {
24 | componentWillUnmount() {
25 | clearTimeout(this.onCompleteTimeout);
26 | }
27 |
28 | delays = React.Children.map(this.props.children, (_, i) =>
29 | getStaggerDelay(
30 | i,
31 | this.props.chunk,
32 | this.props.delay,
33 | this.props.duration
34 | )
35 | );
36 |
37 | reversedDelays = _reverse([...this.delays]);
38 |
39 | onCompleteTimeout = null;
40 | totalChildren = React.Children.count(this.props.children);
41 |
42 | onComplete = onceEvery(this.totalChildren, () => {
43 | const { chunk, delay, duration } = this.props;
44 | const waitTime = getMaxDelay(
45 | this.totalChildren,
46 | chunk,
47 | delay,
48 | duration
49 | );
50 | this.onCompleteTimeout = setTimeout(this.props.onComplete, waitTime);
51 | });
52 |
53 | getTransitionGroupProps() {
54 | return _omit(this.props, [
55 | 'delay',
56 | 'duration',
57 | 'chunk',
58 | 'in',
59 | 'onComplete',
60 | 'reverse',
61 | ]);
62 | }
63 |
64 | render() {
65 | const { children, duration, in: inProp, reverse } = this.props;
66 |
67 | const delays = reverse ? this.reversedDelays : this.delays;
68 |
69 | return (
70 |
71 | {inProp &&
72 | React.Children.map(children, (child, i) =>
73 | React.cloneElement(child, {
74 | delay: delays[i],
75 | duration,
76 | onEntered: this.onComplete,
77 | onExited: this.onComplete,
78 | })
79 | )}
80 |
81 | );
82 | }
83 | }
84 |
85 | Stagger.propTypes = {
86 | children: node,
87 | chunk: number,
88 | delay: number,
89 | duration: number,
90 | in: bool,
91 | onComplete: func,
92 | reverse: bool,
93 | };
94 |
95 | Stagger.defaultProps = {
96 | chunk: 0,
97 | delay: 100,
98 | duration: defaultAnimationProps.duration,
99 | in: false,
100 | onComplete: Function.prototype,
101 | reverse: false,
102 | };
103 |
104 | export default Stagger;
105 |
--------------------------------------------------------------------------------
/src/animations/Transform/__snapshots__/Transform.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Transform can accept className 1`] = `
4 |
17 | `;
18 |
19 | exports[`Transform can accept custom styles 1`] = `
20 |
34 | `;
35 |
36 | exports[`Transform renders 1`] = `
37 |
50 | `;
51 |
52 | exports[`Transform sets enterTransform 1`] = `
53 |
66 | `;
67 |
68 | exports[`Transform sets exitTransform 1`] = `
69 |
82 | `;
83 |
84 | exports[`Transform sets transitionDelay with delay prop 1`] = `
85 |
98 | `;
99 |
100 | exports[`Transform sets transitionDuration with duration prop 1`] = `
101 |
114 | `;
115 |
116 | exports[`Transform sets transitionTimingFunction with timingFn prop 1`] = `
117 |
130 | `;
131 |
--------------------------------------------------------------------------------
/src/animations/FadeTransform/__snapshots__/FadeTransform.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`FadeTranform can accept custom styles 1`] = `
4 |
33 | `;
34 |
35 | exports[`FadeTranform renders 1`] = `
36 |
63 | `;
64 |
65 | exports[`FadeTranform sets fadeProps 1`] = `
66 |
93 | `;
94 |
95 | exports[`FadeTranform sets tranformProps 1`] = `
96 |
123 | `;
124 |
125 | exports[`FadeTranform sets transitionDelay with delay prop 1`] = `
126 |
153 | `;
154 |
155 | exports[`FadeTranform sets transitionDuration with duration prop 1`] = `
156 |
183 | `;
184 |
185 | exports[`FadeTranform sets transitionTimingFunction with timingFn prop 1`] = `
186 |
213 | `;
214 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "globals": {
8 | "describe": true,
9 | "test": true,
10 | "expect": true
11 | },
12 | "parser": "babel-eslint",
13 | "plugins": ["babel", "react"],
14 | "rules": {
15 | /* Possible Errors */
16 | "no-cond-assign": [1, "except-parens"],
17 | "no-console": 0,
18 | "no-constant-condition": 1,
19 | "no-control-regex": 1,
20 | "no-debugger": 1,
21 | "no-dupe-args": 1,
22 | "no-dupe-keys": 1,
23 | "no-duplicate-case": 0,
24 | "no-empty": 1,
25 | "no-empty-character-class": 1,
26 | "no-ex-assign": 1,
27 | "no-extra-boolean-cast": 1,
28 | "no-extra-parens": 0,
29 | "no-func-assign": 1,
30 | "no-inner-declarations": [1, "functions"],
31 | "no-invalid-regexp": 1,
32 | "no-irregular-whitespace": 1,
33 | "no-negated-in-lhs": 1,
34 | "no-obj-calls": 1,
35 | "no-regex-spaces": 1,
36 | "no-reserved-keys": 0,
37 | "no-sparse-arrays": 1,
38 | "no-unexpected-multiline": 1,
39 | "no-unreachable": 1,
40 | "use-isnan": 1,
41 | "valid-jsdoc": 1,
42 | "valid-typeof": 1,
43 |
44 | /* Best Practices */
45 | "accessor-pairs": 0,
46 | "block-scoped-var": 0, // see Babel section
47 | "complexity": 0,
48 | "consistent-return": 1,
49 | "curly": [1, "all"],
50 | "default-case": 0,
51 | "dot-notation": [1, { "allowKeywords": true, "allowPattern": "" }],
52 | "eqeqeq": 1,
53 | "guard-for-in": 0,
54 | "no-alert": 1,
55 | "no-caller": 1,
56 | "no-div-regex": 1,
57 | "no-else-return": 1,
58 | "no-eq-null": 0,
59 | "no-eval": 1,
60 | "no-extend-native": 1,
61 | "no-extra-bind": 1,
62 | "no-fallthrough": 0,
63 | "no-floating-decimal": 1,
64 | "no-implied-eval": 1,
65 | "no-iterator": 1,
66 | "no-labels": 1,
67 | "no-lone-blocks": 1,
68 | "no-loop-func": 1,
69 | "no-multi-spaces": 0,
70 | "no-multi-str": 1,
71 | "no-native-reassign": 1,
72 | "no-new": 1,
73 | "no-new-func": 1,
74 | "no-new-wrappers": 1,
75 | "no-octal": 1,
76 | "no-octal-escape": 1,
77 | "no-param-reassign": 0,
78 | "no-process-env": 0,
79 | "no-proto": 1,
80 | "no-redeclare": 1,
81 | "no-return-assign": 1,
82 | "no-script-url": 1,
83 | "no-self-compare": 1,
84 | "no-sequences": 1,
85 | "no-throw-literal": 1,
86 | "no-unused-expressions": 0,
87 | "no-void": 0,
88 | "no-warning-comments": [
89 | 1,
90 | { "terms": ["todo", "tofix"], "location": "start" }
91 | ],
92 | "no-with": 1,
93 | "radix": 1,
94 | "vars-on-top": 1,
95 | "yoda": [1, "never"],
96 |
97 | /* Strict Mode */
98 | "strict": [1, "never"],
99 |
100 | /* Variables */
101 | "no-catch-shadow": 0,
102 | "no-delete-var": 1,
103 | "no-label-var": 1,
104 | "no-shadow": 1,
105 | "no-shadow-restricted-names": 1,
106 | "no-undef": 1,
107 | "no-undef-init": 1,
108 | "no-undefined": 1,
109 | "no-unused-vars": [1, { "vars": "local", "args": "after-used" }],
110 | "no-use-before-define": 1,
111 |
112 | /* Node.js */
113 | "handle-callback-err": 1,
114 | "no-mixed-requires": 1,
115 | "no-new-require": 1,
116 | "no-path-concat": 1,
117 | "no-process-exit": 1,
118 | "no-restricted-modules": [1, ""], // add any unwanted Node.js core modules
119 | "no-sync": 1,
120 |
121 | /* Stylistic Issues */
122 | "camelcase": [1, { "properties": "always" }],
123 | "computed-property-spacing": 0,
124 | "consistent-this": 0,
125 | "func-names": 0,
126 | "func-style": 0,
127 | "linebreak-style": 0,
128 | "max-nested-callbacks": [0, 3],
129 | "new-cap": 0, // see Babel section
130 | "newline-after-var": 0,
131 | "no-array-constructor": 1,
132 | "no-continue": 1,
133 | "no-inline-comments": 0,
134 | "no-lonely-if": 1,
135 | "no-nested-ternary": 0,
136 | "no-new-object": 1,
137 | "no-ternary": 0,
138 | "no-underscore-dangle": 0,
139 | "no-unneeded-ternary": 1,
140 | "object-curly-spacing": 0, // see Babel section
141 | "one-var": [1, "never"],
142 | "operator-assignment": [1, "never"],
143 | "padded-blocks": [0, "never"],
144 | "quote-props": [0, "as-needed"],
145 | "sort-vars": 0,
146 | "space-after-keywords": 0,
147 | "space-unary-ops": 0,
148 | "spaced-comment": [1, "always"],
149 |
150 | /* ECMAScript 6 */
151 | "constructor-super": 1,
152 | "no-this-before-super": 1,
153 | "no-var": 1,
154 | "object-shorthand": [1, "always"],
155 | "prefer-const": 1,
156 |
157 | /* Babel */
158 | "babel/new-cap": 1,
159 | "babel/object-curly-spacing": [1, "always"],
160 |
161 | /* React */
162 | "react/display-name": 1,
163 | "react/jsx-boolean-value": 1,
164 | "react/jsx-no-bind": 1,
165 | "react/jsx-no-duplicate-props": 1,
166 | "react/jsx-no-undef": 1,
167 | "react/jsx-sort-props": 0,
168 | "react/jsx-uses-react": 1,
169 | "react/jsx-uses-vars": 1,
170 | "react/no-danger": 1,
171 | "react/no-did-mount-set-state": 1,
172 | "react/no-did-update-set-state": 1,
173 | "react/no-multi-comp": 1,
174 | "react/no-unknown-property": 1,
175 | "react/prop-types": 1,
176 | "react/react-in-jsx-scope": 1,
177 | "react/self-closing-comp": 1,
178 | "react/sort-comp": 1,
179 | "react/wrap-multilines": 0
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-animation-components
2 |
3 | []()
4 |
5 | A set of React components using [React Transition Group](https://github.com/reactjs/react-transition-group) to provide drop in GPU accelerated animations and wrappers for group effects.
6 |
7 | [Checkout the Storybook!](http://animationcomponents.com/)
8 |
9 | * [Installation](#installation)
10 | * [Animation Components](#animation-components)
11 | * [Fade](#fade)
12 | * [Transform](#transform)
13 | * [FadeTransform](#fadetransform)
14 | * [Wrapper Components](#group-components)
15 | * [Stagger](#stagger)
16 | * [Random](#random)
17 | * [Loop](#loop)
18 |
19 | ## Installation
20 |
21 | `npm install react-animation-components`
22 |
23 | Make sure you also have installed the following peer dependencies:
24 |
25 | ```
26 | "react": "^16.0.0",
27 | "react-dom": "^16.0.0",
28 | "react-transition-group": "^2.2.1",
29 | "prop-types": "^15.6.0"
30 | ```
31 |
32 | ## Animation Components
33 |
34 | ### Props available on all animation components
35 |
36 | The following are available on any animation component as well as **any valid `Transition` props**. Transitions are set to `appear` and their `timeout` is calculated by combining the `delay` and `duration` by default but can be overwritten.
37 |
38 | | Key | Description | Example | Type | Default Value |
39 | | --------- | ---------------------------------------------- | -------------------- | -------- | ------------- |
40 | | className | Passes className to wrapper `div` | `some-class` | _string_ | `undefined` |
41 | | delay | Sets the animations `transitionDelay` | `500` | _number_ | `0` |
42 | | duration | Sets the animations `transitionDuration` | `1000` | _number_ | `500` |
43 | | style | Passes styles to wrapper `div` | `{ display:'flex' }` | _object_ | `{}` |
44 | | timingFn | Sets the animations `transitionTimingFunction` | `'ease-in-out'` | _string_ | `'ease'` |
45 |
46 | ### Fade
47 |
48 | Transitions the wrapped element's opacity from one value to another
49 |
50 | #### Props
51 |
52 | | Key | Description | Example | Type | Default Value |
53 | | ------------ | -------------------------------------- | ------- | -------- | ------------- |
54 | | enterOpacity | The opacity value when `in` is `true` | `0.85` | _number_ | `0` |
55 | | exitOpacity | The opacity value when `in` is `false` | `0.25` | _number_ | `0` |
56 |
57 | #### Examples
58 |
59 | ```
60 | import { Fade } from 'react-animation-components'
61 |
62 |
63 | I'm transitioning to opacity:1
64 |
65 |
66 |
67 | I'm transitioning to opacity:0.85
68 |
69 |
70 |
71 | I'm transitioning to opacity:0
72 |
73 |
74 |
75 | I'm transitioning to opacity:0.25
76 |
77 | ```
78 |
79 | ### Transform
80 |
81 | Transitions the wrapped element from one transform property to another. Any valid `transform` property will work.
82 |
83 | #### Props
84 |
85 | | Key | Description | Example | Type | Default Value |
86 | | -------------- | ---------------------------------------- | --------------------- | -------- | ------------- |
87 | | enterTransform | The transform value when `in` is `true` | `'translateX(100px)'` | _string_ | `'none'` |
88 | | exitTransform | The transform value when `in` is `false` | `'translateX(100px)'` | _string_ | `'none'` |
89 |
90 | #### Examples
91 |
92 | ```
93 | import { Transform } from 'react-animation-components'
94 |
95 |
96 | I'm transitioning from my initial position to 100px right when `in` is `true`
97 |
98 |
99 |
100 |
101 | I'm 100px to the left of my initial position and
102 | I transition 100px right of my initial when `in` is `true`
103 |
104 |
105 |
106 |
107 | I transition from initial positon to rotate 90deg when `in` is `true`
108 |
109 | ```
110 |
111 | ### FadeTransform
112 |
113 | Composes `Fade` and `Transform`. All top level props are passed to both components. You can also pass props to individual components in the composition.
114 |
115 | **Props passed to indivudal components via `fadeProps` or `transformProps` will override any top level props**
116 |
117 | #### Props
118 |
119 | | Key | Description | Example | Type | Default Value |
120 | | -------------- | ----------------------------------------- | ----------------------------------------- | -------- | ------------- |
121 | | fadeProps | The props that only `Fade` recieves. | `{ enterOpacity: 0.85 }` | _object_ | `{}` |
122 | | transformProps | The props that only `Transform` recieves. | `{ enterTransform: 'translateX(100px)' }` | _object_ | `{}` |
123 |
124 | #### Examples
125 |
126 | ```
127 | import { FadeTransform } from 'react-animation-components'
128 |
129 |
130 | I'm transitioning from my initial position to 100px right when `in` is `true`
131 |
132 |
133 |
140 |
141 | I'm 100px to the left of my initial position and
142 | I transition 100px right of my initial when `in` is `true`
143 |
144 |
145 |
146 |
155 | I transition from `-100px` horizontally of my initial positon and to 0.85 opacity when `in` is `true`
156 |
157 | ```
158 |
159 | ## Wrapper Components
160 |
161 | Wrapper components use the inner animation components `onEntered` and `onExited`. **Setting those callbacks inside these wrappers will not work**
162 |
163 | ### Stagger
164 |
165 | Uses `TransitionGroup` to stagger `delay` on a set of animation components
166 |
167 | #### Props
168 |
169 | | Key | Description | Example | Type | Default Value |
170 | | ---------- | ----------------------------------------------------------------- | ------------------ | ---------- | -------------------------- |
171 | | chunk | Used to limit the stagger into "chunks". | `5` | _number_ | `0` |
172 | | delay | The amount to separate each stagger by | `1000` | _number_ | `100` |
173 | | duration | A value to set the inner child animations transition duration | `800` | _number_ | `500` |
174 | | in | A boolean to tell the children to mount or unmount | `true` | _boolean_ | `false` |
175 | | onComplete | A function that is called after the last animation finishes | any valid function | _function_ | `Function.prototype(noop)` |
176 | | reverse | A boolean to tell the component to reverse how delays are applied | `true` | _boolean_ | `false` |
177 |
178 | #### Examples
179 |
180 | ```
181 | import { Fade, Stagger } from 'react-animation-components'
182 |
183 | const items = ['first', 'second', 'third', 'fourth', 'fifth'];
184 |
185 |
186 | {items.map(
187 | item => (
188 |
189 | Each {item} will transition in with an incrementally larger delay than the previous
190 |
191 | )
192 | )}
193 |
194 |
195 |
196 | {items.map(
197 | item => (
198 |
199 |
200 | Each {item} will increment in segments of 4.
201 | First is 0, Second is 100, Third is 200, Fourth is 0, fifth is 100, and so on
202 |
203 |
204 | )
205 | )}
206 |
207 | ```
208 |
209 | ### Random
210 |
211 | Uses `TransitionGroup` to randomize `delay` on a set of animation components
212 |
213 | #### Props
214 |
215 | | Key | Description | Example | Type | Default Value |
216 | | ---------- | ----------------------------------------------------------------- | ------------------ | ---------- | -------------------------- |
217 | | duration | A value to set the inner child animations transition duration | `800` | _number_ | `500` |
218 | | in | A boolean to tell the children to mount or unmount | `true` | _boolean_ | `false` |
219 | | maxDelay | Maximum delay possible | `5000` | _number_ | `1500` |
220 | | minDelay | Minimum delay possible | `100` | _number_ | `0` |
221 | | onComplete | A function that is called after the last animation finishes | any valid function | _function_ | `Function.prototype(noop)` |
222 | | reverse | A boolean to tell the component to reverse how delays are applied | `true` | _boolean_ | `false` |
223 |
224 | #### Examples
225 |
226 | ```
227 | import { Fade, Random } from 'react-animation-components'
228 |
229 | const items = ['first', 'second', 'third', 'fourth', 'fifth'];
230 |
231 |
232 | {items.map(
233 | item => (
234 |
235 | Each {item} will randomly FadeIn between 0 and 1500ms
236 |
237 | )
238 | )}
239 |
240 |
241 |
242 | {items.map(
243 | item => (
244 |
245 | Each {item} will randomly FadeIn between 1000ms and 5000ms
246 |
247 | )
248 | )}
249 |
250 | ```
251 |
252 | ### Loop
253 |
254 | Loops using the `onEntered` and `onExited` callbacks to toggle `in` on a **single** animation component.
255 |
256 | #### Props
257 |
258 | | Key | Description | Example | Type | Default Value |
259 | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ---------- | -------------------------- |
260 | | in | Initializes the loop when `true` | `true` | _bool_ | `false` |
261 | | interval | Sets the interval to toggle `in`. Also sets the `duration` | `1000` | _number_ | `500` |
262 | | iterations | Maximum number of loops | `5.5` | _number_ | `Infinity` |
263 | | onComplete | Callback that is called when the `iterations` have been met. Waits an additional `interval` to ensure its called when the last iteration has completed | any valid function | _function_ | `Function.prototype(noop)` |
264 | | onIterate | Callback that is called with the current count each time the loop iterates. Count is incremented by `0.5` | any valid function | _function_ | `Function.prototype(noop)` |
265 |
266 | #### Examples
267 |
268 | ```
269 | import { Fade, Loop } from 'react-animation-components'
270 |
271 |
272 |
273 | I will Fade in and out repeatedly on 500ms intervals
274 |
275 |
276 |
277 |
278 |
279 | I will Fade in and out repeatedly on 500ms intervals 5.5 times
280 |
281 |
282 | ```
283 |
--------------------------------------------------------------------------------