├── .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 |
18 |
32 |
33 | `; 34 | 35 | exports[`FadeTranform renders 1`] = ` 36 |
49 |
62 |
63 | `; 64 | 65 | exports[`FadeTranform sets fadeProps 1`] = ` 66 |
79 |
92 |
93 | `; 94 | 95 | exports[`FadeTranform sets tranformProps 1`] = ` 96 |
109 |
122 |
123 | `; 124 | 125 | exports[`FadeTranform sets transitionDelay with delay prop 1`] = ` 126 |
139 |
152 |
153 | `; 154 | 155 | exports[`FadeTranform sets transitionDuration with duration prop 1`] = ` 156 |
169 |
182 |
183 | `; 184 | 185 | exports[`FadeTranform sets transitionTimingFunction with timingFn prop 1`] = ` 186 |
199 |
212 |
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 | [![Travis](https://img.shields.io/travis/unruffledBeaver/react-animation-components.svg?style=flat-square)]() 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 | --------------------------------------------------------------------------------