├── examples
├── src
│ ├── components
│ │ ├── index.js
│ │ ├── Code
│ │ │ ├── index.js
│ │ │ ├── Code.scss
│ │ │ └── Code.jsx
│ │ ├── Example
│ │ │ ├── index.js
│ │ │ ├── Button
│ │ │ │ ├── index.js
│ │ │ │ ├── Button.jsx
│ │ │ │ └── Button.scss
│ │ │ ├── Example.scss
│ │ │ └── Example.jsx
│ │ ├── Examples
│ │ │ ├── index.js
│ │ │ ├── SpecifyClassName
│ │ │ │ ├── SpecifyClassName.scss
│ │ │ │ └── index.jsx
│ │ │ ├── animate.scss
│ │ │ ├── Examples.scss
│ │ │ ├── PropChange
│ │ │ │ └── index.jsx
│ │ │ ├── AtMount
│ │ │ │ └── index.jsx
│ │ │ ├── AtMountTimeout
│ │ │ │ └── index.jsx
│ │ │ └── Examples.jsx
│ │ ├── App.scss
│ │ └── App.jsx
│ ├── favicon.gif
│ ├── styles
│ │ └── _colors.scss
│ ├── index.jsx
│ └── index.html
├── .babelrc
├── firebase.json
├── publish.js
├── .eslintrc.json
├── .gitignore
├── .sass-lint.yml
├── LICENSE
├── webpack.config.js
└── package.json
├── .babelrc
├── misc
├── logo.xcf
└── testSetup.js
├── .eslintrc.json
├── .gitignore
├── webpack.config.js
├── circle.yml
├── LICENSE
├── package.json
├── src
├── index.jsx
└── index.spec.js
├── README.md
└── index.js
/examples/src/components/index.js:
--------------------------------------------------------------------------------
1 | export default from './App'
2 |
--------------------------------------------------------------------------------
/examples/src/components/Code/index.js:
--------------------------------------------------------------------------------
1 | export default from './Code'
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2017", "es2015", "react", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/examples/src/components/Example/index.js:
--------------------------------------------------------------------------------
1 | export default from './Example'
2 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/index.js:
--------------------------------------------------------------------------------
1 | export default from './Examples'
2 |
--------------------------------------------------------------------------------
/examples/src/components/Example/Button/index.js:
--------------------------------------------------------------------------------
1 | export default from './Button'
2 |
--------------------------------------------------------------------------------
/misc/logo.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unirakun/hoc-react-animate/HEAD/misc/logo.xcf
--------------------------------------------------------------------------------
/examples/src/favicon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unirakun/hoc-react-animate/HEAD/examples/src/favicon.gif
--------------------------------------------------------------------------------
/examples/src/components/Examples/SpecifyClassName/SpecifyClassName.scss:
--------------------------------------------------------------------------------
1 | :global(.custom) {
2 | transform: scale(.5);
3 | }
4 |
--------------------------------------------------------------------------------
/examples/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-class-properties", "transform-object-rest-spread"],
3 | "presets": ["es2017", "es2015", "react", "stage-0"]
4 | }
5 |
--------------------------------------------------------------------------------
/examples/src/styles/_colors.scss:
--------------------------------------------------------------------------------
1 | $primary: #e8f5e9;
2 | $secondary: #a5d6a7;
3 | $color: #fff;
4 | $shadow-start: rgba(0, 0, 0, .16);
5 | $shadow-end: rgba(0, 0, 0, .12);
6 |
--------------------------------------------------------------------------------
/examples/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "public",
4 | "rewrites": [
5 | {
6 | "source": "**",
7 | "destination": "/index.html"
8 | }
9 | ]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/animate.scss:
--------------------------------------------------------------------------------
1 | .component {
2 | transition: all 1000ms;
3 |
4 | &:global(.animate) {
5 | transform: translateX(200px);
6 | }
7 |
8 | pre {
9 | display: inline;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/Examples.scss:
--------------------------------------------------------------------------------
1 | .examples {
2 | .example {
3 | margin: 0 auto;
4 | margin-bottom: 20px;
5 | min-width: 900px;
6 | width: 40%;
7 | }
8 |
9 | p {
10 | margin-top: 0;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/src/components/Code/Code.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/colors';
2 |
3 | .code {
4 | align-items: center;
5 | background-color: darken($primary, 10);
6 | display: flex;
7 | font-size: 80%;
8 | padding: 20px 10px;
9 | position: relative;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/publish.js:
--------------------------------------------------------------------------------
1 | var ghpages = require('gh-pages');
2 | var path = require('path');
3 |
4 | ghpages.publish(path.join(__dirname, 'public'), function(err) {
5 | if(err) {
6 | console.error(err)
7 | } else {
8 | console.log("Success")
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["airbnb"],
4 | "rules": {
5 | "semi": [2, "never"],
6 | "arrow-body-style": 0,
7 | "import/no-named-as-default": 0,
8 | "import/prefer-default-export": 0,
9 | "import/no-unresolved": 0
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["airbnb"],
4 | "globals": {
5 | "fetch": false
6 | },
7 | "rules": {
8 | "semi": [2, "never"],
9 | "arrow-body-style": 0,
10 | "import/no-named-as-default": 0,
11 | "import/prefer-default-export": 0,
12 | "import/no-unresolved": 0
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/misc/testSetup.js:
--------------------------------------------------------------------------------
1 | import { jsdom } from 'jsdom'
2 | import chai from 'chai'
3 |
4 | global.document = jsdom('
')
5 | global.window = document.defaultView
6 | global.navigator = global.window.navigator
7 |
8 | /** Configuring chai. */
9 | chai.should()
10 | chai.config.includeStack = true
11 | chai.config.truncateThreshold = 0
12 |
--------------------------------------------------------------------------------
/examples/src/components/Example/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import Ink from 'react-ink'
3 |
4 | import styles from './Button.scss'
5 |
6 | const Button = ({ style, className, onClick }) => (
7 |
8 | Click to change prop
9 |
10 |
11 | )
12 |
13 | export default Button
14 |
--------------------------------------------------------------------------------
/examples/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 |
4 | import 'normalize.css/normalize.css'
5 |
6 | /* eslint import/no-extraneous-dependencies: 0 import/no-unresolved: 0 */
7 | import 'file?name=[name].[ext]!./index.html'
8 | import 'file?name=[name].[ext]!./favicon.gif'
9 |
10 | import App from './components'
11 |
12 | /* eslint-env browser */
13 | render(
14 |
15 | , document.getElementById('app')
16 | )
17 |
--------------------------------------------------------------------------------
/examples/src/components/Example/Button/Button.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/colors';
2 |
3 | .button {
4 | background-color: darken($primary, 20);
5 | border: 0;
6 | box-shadow: none;
7 | color: lighten($secondary, 20);
8 | cursor: pointer;
9 | font-family: 'Roboto Condensed', sans-serif;
10 | font-size: 1.2em;
11 | height: 50px;
12 | outline: 0;
13 | padding: 0 2rem;
14 | position: relative;
15 | text-transform: uppercase;
16 |
17 | &:hover {
18 | background-color: darken($primary, 22);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/src/components/Code/Code.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import '!!style!css!highlight.js/styles/github.css'
3 |
4 | import styles from './Code.scss'
5 |
6 | import Highlight from 'react-highlight'
7 |
8 | const Code = ({ style, className, children }) => (
9 |
10 | {children}
11 |
12 | )
13 |
14 | Code.propTypes = {
15 | style: PropTypes.object,
16 | className: PropTypes.string,
17 | children: PropTypes.node.isRequired,
18 | }
19 |
20 | export default Code
21 |
--------------------------------------------------------------------------------
/examples/src/components/App.scss:
--------------------------------------------------------------------------------
1 | .app {
2 | align-items: center;
3 | display: flex;
4 | flex-direction: column;
5 | font-family: 'Roboto', sans-serif;
6 |
7 | .icon {
8 | left: 10px;
9 | position: absolute;
10 | top: 10px;
11 | width: 100px;
12 | }
13 |
14 | .description {
15 | margin-bottom: 30px;
16 | width: 50%;
17 | }
18 |
19 | .github {
20 | position: absolute;
21 | right: 10px;
22 | top: 10px;
23 | }
24 |
25 | h1 {
26 | font-family: 'Roboto Condensed', sans-serif;
27 | }
28 | }
29 |
30 | :global(.github-button) {
31 | display: none;
32 | }
33 |
--------------------------------------------------------------------------------
/examples/src/components/Example/Example.scss:
--------------------------------------------------------------------------------
1 | @import '~styles/colors';
2 |
3 | .sample {
4 | background-color: $primary;
5 | box-shadow: 0 2px 5px 0 $shadow-start, 0 2px 10px 0 $shadow-end;
6 | display: flex;
7 | flex-direction: column;
8 |
9 |
10 | &:hover {
11 | box-shadow: 0 4px 10px 0 $shadow-start, 0 4px 20px 0 $shadow-end;
12 | }
13 |
14 | .render {
15 | display: flex;
16 | }
17 |
18 | .result {
19 | margin: 10px;
20 | min-width: 700px;
21 | }
22 |
23 | a {
24 | flex: 1;
25 | position: relative;
26 | text-decoration: none;
27 | }
28 |
29 | pre {
30 | margin: 0;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/PropChange/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import animate from 'hoc-react-animate'
3 |
4 | import styles from '../animate.scss'
5 |
6 | const AtMount = ({ ex, className }) => (
7 |
8 |
9 | {"I'm animated when my "}
ex {" prop is changed."}
10 |
11 |
ex = {ex}
12 |
13 | )
14 |
15 | AtMount.propTypes = {
16 | style: PropTypes.object,
17 | className: PropTypes.string,
18 | ex: PropTypes.string.isRequired,
19 | }
20 |
21 | export default animate(AtMount, { watchedProps: ['ex'] })
22 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/AtMount/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import animate from 'hoc-react-animate'
3 |
4 | import styles from '../animate.scss'
5 |
6 | const AtMount = ({ ex, className }) => (
7 |
8 |
9 | {"I'm animated at mount."}
10 |
11 | {"With default timeout."}
12 |
13 |
ex = {ex}
14 |
15 | )
16 |
17 | AtMount.propTypes = {
18 | style: PropTypes.object,
19 | className: PropTypes.string,
20 | ex: PropTypes.string.isRequired,
21 | }
22 |
23 | export default animate(AtMount, { atMount: true })
24 |
--------------------------------------------------------------------------------
/examples/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | react-animate: examples
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/AtMountTimeout/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import animate from 'hoc-react-animate'
3 |
4 | import styles from '../animate.scss'
5 |
6 | const AtMount = ({ ex, className }) => (
7 |
8 |
9 | {"I'm animated at mount."}
10 |
11 | {"With custom timeout."}
12 |
13 |
14 |
ex = {ex}
15 |
16 | )
17 |
18 | AtMount.propTypes = {
19 | style: PropTypes.object,
20 | className: PropTypes.string,
21 | ex: PropTypes.string.isRequired,
22 | }
23 |
24 | export default animate(AtMount, { atMount: true, timeout: 10 })
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 | public
29 |
30 | # Dependency directories
31 | node_modules
32 | jspm_packages
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 |
3 | module.exports = {
4 | output: {
5 | library: 'ReactLoader',
6 | libraryTarget: 'umd',
7 | },
8 |
9 | externals: [
10 | {
11 | react: {
12 | root: 'React',
13 | commonjs2: 'react',
14 | commonjs: 'react',
15 | amd: 'react',
16 | },
17 | },
18 | ],
19 |
20 | module: {
21 | loaders: [
22 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
23 | ],
24 | },
25 |
26 | node: {
27 | Buffer: false,
28 | },
29 |
30 | plugins: [
31 | new webpack.optimize.OccurenceOrderPlugin(),
32 | new webpack.DefinePlugin({
33 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
34 | }),
35 | ],
36 | }
37 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/SpecifyClassName/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import animate from 'hoc-react-animate'
3 |
4 | import styles from '../animate.scss'
5 | import customStyles from './SpecifyClassName.scss'
6 |
7 | const SpecifyClassName = ({ ex, className }) => (
8 |
9 |
10 | {"I'm animated when my "}
ex {" prop is changed."}
11 |
12 | {"My CSS animation class name is customized."}
13 |
14 |
ex = {ex}
15 |
16 | )
17 |
18 | SpecifyClassName.propTypes = {
19 | style: PropTypes.object,
20 | className: PropTypes.string,
21 | ex: PropTypes.string.isRequired,
22 | }
23 |
24 | export default animate(
25 | SpecifyClassName,
26 | { watchedProps: ['ex'], className: 'custom' }
27 | )
28 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | services:
3 | - docker
4 |
5 | dependencies:
6 | cache_directories:
7 | - examples/node_modules
8 | override:
9 | - docker run --rm -v $(pwd):/usr/src/app zenika/alpine-node npm install
10 | - docker run --rm -v $(pwd)/examples:/usr/src/app zenika/alpine-node npm install
11 |
12 | test:
13 | override:
14 | - docker run --rm -v $(pwd):/usr/src/app zenika/alpine-node npm run test
15 | - docker run --rm -v $(pwd):/usr/src/app zenika/alpine-node npm run lint
16 | - docker run --rm -v $(pwd)/examples:/usr/src/app zenika/alpine-node npm run lint
17 |
18 | deployment:
19 | production:
20 | branch: master
21 | commands:
22 | - npm install -g firebase-tools
23 | - docker run --rm -v $(pwd)/examples:/usr/src/app zenika/alpine-node npm run build
24 | - cd examples && firebase deploy --token=${FIREBASE_TOKEN} --non-interactive --project ${FIREBASE_PROJECT}
25 |
--------------------------------------------------------------------------------
/examples/.sass-lint.yml:
--------------------------------------------------------------------------------
1 | rules:
2 | extends-before-mixins: 2
3 | extends-before-declarations: 2
4 | placeholder-in-extend: 0
5 | hex-notation: 2
6 | indentation: 2
7 | property-sort-order: 2
8 | variable-for-property: 2
9 | no-color-literals: 2
10 | clean-import-paths: 2
11 | hex-length: 2
12 | force-pseudo-nesting: 2
13 | force-element-nesting: 2
14 | quotes: 2
15 | class-name-format: 0
16 | empty-line-between-blocks: 2
17 | leading-zero: 2
18 | space-before-brace: 2
19 | space-before-bang: 2
20 | no-important: 0
21 | no-css-comments: 2
22 | no-color-keywords: 2
23 | space-after-colon: 2
24 | no-vendor-prefixes: 0
25 | border-zero: 2
26 | no-empty-rulesets: 2
27 | trailing-semicolon: 2
28 | no-duplicate-properties: 2
29 | shorthand-values: 2
30 | pseudo-element: 2
31 | single-line-per-selector: 2
32 | zero-unit: 2
33 | no-mergeable-selectors: 2
34 | no-qualifying-elements: 2
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 ZenNante5
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Fabien JUIF
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Examples from './Examples'
4 | import styles from './App.scss'
5 | import Icon from 'favicon.gif'
6 |
7 | const App = () => (
8 |
9 |
10 |
11 |
12 | hoc-react-animate
13 |
14 |
15 |
28 |
29 |
30 | hoc-react-animate is an higher order component to animate React component. It lets you write animated components
31 | as pure functions.
32 |
33 |
34 |
35 | Check out the examples below. Use the button to trigger animations or click the code to read the full source.
36 |
37 |
38 |
39 |
40 | )
41 |
42 | export default App
43 |
--------------------------------------------------------------------------------
/examples/src/components/Example/Example.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react'
2 | import Ink from 'react-ink'
3 |
4 | import Code from 'Code'
5 | import Button from './Button'
6 | import styles from './Example.scss'
7 |
8 | const words = ['Banana', 'House', 'Train', 'Dog', 'Cat', 'River']
9 | const BASE_URL = 'https://github.com/Zenika/react-animate/blob/master/examples/src/components/Examples/'
10 |
11 | class Example extends Component {
12 | constructor() {
13 | super()
14 |
15 | this.state = {
16 | ex: 'Hi',
17 | }
18 | }
19 |
20 | onClick = () => {
21 | const index = Math.round((Math.random() * 10) % (words.length - 1))
22 |
23 | this.setState({
24 | ex: words[index],
25 | })
26 | }
27 |
28 | render() {
29 | const { style, className, code, children, link } = this.props
30 |
31 | return (
32 |
33 | {React.cloneElement(children, { ex: this.state.ex, className: styles.result })}
34 |
35 | {code}
36 |
37 |
38 |
39 |
40 | )
41 | }
42 | }
43 |
44 | Example.propTypes = {
45 | style: PropTypes.object,
46 | className: PropTypes.string,
47 | code: PropTypes.string.isRequired,
48 | children: PropTypes.node.isRequired,
49 | link: PropTypes.string.isRequired,
50 | }
51 |
52 | export default Example
53 |
--------------------------------------------------------------------------------
/examples/src/components/Examples/Examples.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 |
3 | import Example from 'Example'
4 | import AtMount from './AtMount'
5 | import AtMountTimeout from './AtMountTimeout'
6 | import PropChange from './PropChange'
7 | import SpecifyClassName from './SpecifyClassName'
8 |
9 | import styles from './Examples.scss'
10 |
11 | const Examples = ({ style, className }) => (
12 |
13 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
45 |
46 |
47 |
48 | )
49 |
50 | Examples.propTypes = {
51 | style: PropTypes.object,
52 | className: PropTypes.string,
53 | }
54 |
55 | export default Examples
56 |
--------------------------------------------------------------------------------
/examples/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 |
4 | const dev = (process.env.NODE_ENV !== 'production')
5 |
6 | function getEntrySources(sources) {
7 | if (dev) {
8 | sources.push('webpack-dev-server/client?http://localhost:3000')
9 | sources.push('webpack/hot/only-dev-server')
10 | }
11 |
12 | return sources
13 | }
14 |
15 | function getLoaders(loaders) {
16 | if (dev) {
17 | loaders.push('react-hot')
18 | }
19 | loaders.push('babel')
20 |
21 | return loaders
22 | }
23 |
24 | function getPlugins(plugins) {
25 | if (dev) {
26 | plugins.push(new webpack.HotModuleReplacementPlugin())
27 | }
28 |
29 | return plugins
30 | }
31 |
32 | module.exports = {
33 | devtool: dev ? 'eval' : '',
34 | entry: {
35 | examples: getEntrySources([
36 | './src',
37 | ]),
38 | },
39 | output: {
40 | path: path.join(__dirname, 'public'),
41 | filename: '[name].js',
42 | publicPath: './',
43 | },
44 | resolve: {
45 | root: [path.resolve('./src'), path.resolve('./src/components')],
46 | extensions: ['', '.js', '.jsx'],
47 | },
48 | plugins: getPlugins([]),
49 | module: {
50 | loaders: [{
51 | test: /\.jsx?$/,
52 | loaders: getLoaders([]),
53 | include: [path.join(__dirname, 'src'), path.join(__dirname, '..', 'src')],
54 | }, {
55 | test: /\.s?css$/,
56 | loaders: [
57 | 'style',
58 | 'css?modules&localIdentName=[path]_[local]__[hash:base64:5]',
59 | 'sass',
60 | ],
61 | }, {
62 | test: /\.(png|svg|gif|jpg)$/,
63 | loader: 'file?name=[name].[ext]',
64 | },],
65 | },
66 | }
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hoc-react-animate",
3 | "version": "0.3.0",
4 | "description": "Add a CSS class whenever a props change (or/and at mount)",
5 | "main": "index.js",
6 | "dependencies": {},
7 | "peerDependencies": {
8 | "lodash": "^4.15.0",
9 | "react": "^15.3.0"
10 | },
11 | "devDependencies": {
12 | "babel-cli": "^6.11.4",
13 | "babel-core": "^6.13.2",
14 | "babel-eslint": "^6.1.2",
15 | "babel-loader": "^6.2.4",
16 | "babel-preset-es2015": "^6.13.2",
17 | "babel-preset-es2017": "^1.6.1",
18 | "babel-preset-react": "^6.11.1",
19 | "babel-preset-stage-0": "^6.5.0",
20 | "chai": "^3.5.0",
21 | "cross-env": "^2.0.0",
22 | "enzyme": "^2.4.1",
23 | "eslint": "^3.2.2",
24 | "eslint-config-airbnb": "^10.0.0",
25 | "eslint-plugin-import": "^1.12.0",
26 | "eslint-plugin-jsx-a11y": "^2.0.1",
27 | "eslint-plugin-react": "^6.0.0",
28 | "jsdom": "^9.4.2",
29 | "lodash": "^4.15.0",
30 | "mocha": "^3.0.2",
31 | "react": "^15.3.0",
32 | "react-addons-test-utils": "^15.3.0",
33 | "react-dom": "^15.3.0",
34 | "webpack": "^1.13.1"
35 | },
36 | "scripts": {
37 | "lint": "eslint src/index.jsx",
38 | "build": "cross-env BABEL_ENV=cjs babel --ignore \"*.spec.js\" ./src/ -d .",
39 | "test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require ./misc/testSetup.js \"src/**/*.spec.js\" "
40 | },
41 | "repository": {
42 | "type": "git",
43 | "url": "git+https://github.com/Zenika/react-animate.git"
44 | },
45 | "keywords": [
46 | "react",
47 | "animate",
48 | "hoc"
49 | ],
50 | "author": "Fabien JUIF ",
51 | "contributors": [
52 | "Laurent MAILLET "
53 | ],
54 | "license": "MIT",
55 | "bugs": {
56 | "url": "https://github.com/Zenika/react-animate/issues"
57 | },
58 | "homepage": "https://github.com/Zenika/react-animate#readme"
59 | }
60 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hoc-react-animate-examples",
3 | "version": "0.1.0",
4 | "description": "",
5 | "directories": {
6 | "doc": "doc"
7 | },
8 | "scripts": {
9 | "start": "set NODE_ENV=development && webpack-dev-server --inline --hot --port=3000 --history-api-fallback",
10 | "build": "NODE_ENV=production webpack --define process.env.NODE_ENV='\"production\"' -p ",
11 | "lint": "eslint src && find ./src/ -iname \"*.scss\" -exec sass-lint -v -q {} +",
12 | "publish_gh_pages": "node publish"
13 | },
14 | "author": "Fabien JUIF",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "babel-core": "~6.7.5",
18 | "babel-eslint": "~6.0.0",
19 | "babel-loader": "~6.2.4",
20 | "babel-plugin-syntax-class-properties": "~6.5.0",
21 | "babel-plugin-transform-class-properties": "~6.6.0",
22 | "babel-plugin-transform-object-rest-spread": "~6.6.5",
23 | "babel-polyfill": "~6.7.4",
24 | "babel-preset-es2015": "^6.9.0",
25 | "babel-preset-es2017": "^1.4.0",
26 | "babel-preset-react": "~6.5.0",
27 | "babel-preset-stage-0": "~6.5.0",
28 | "babel-register": "~6.7.2",
29 | "chai": "~3.5.0",
30 | "css-loader": "^0.23.1",
31 | "eslint": "^3.2.2",
32 | "eslint-config-airbnb": "^10.0.0",
33 | "eslint-plugin-import": "^1.12.0",
34 | "eslint-plugin-jsx-a11y": "^2.0.1",
35 | "eslint-plugin-react": "^6.0.0",
36 | "expect": "~1.16.0",
37 | "file-loader": "~0.8.5",
38 | "gh-pages": "^0.11.0",
39 | "jsdom": "^9.4.2",
40 | "mocha": "~2.4.5",
41 | "node-sass": "^3.7.0",
42 | "react-hot-loader": "~1.3.0",
43 | "sass-lint": "^1.8.2",
44 | "sass-loader": "^3.2.0",
45 | "sinon": "~1.17.3",
46 | "style-loader": "~0.13.1",
47 | "webpack": "~1.12.2",
48 | "webpack-dev-server": "~1.12.1"
49 | },
50 | "dependencies": {
51 | "hoc-react-animate": "^0.2.24",
52 | "lodash": "^4.15.0",
53 | "normalize.css": "~4.2.0",
54 | "react": "15.3.0",
55 | "react-dom": "15.3.0",
56 | "react-highlight": "^0.8.0",
57 | "react-ink": "^5.1.1"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import pick from 'lodash/pick'
3 | import isEqual from 'lodash/isEqual'
4 |
5 | const getDisplayName = (c) => c.displayName || c.name || 'Component'
6 |
7 | export default (
8 | ComposedComponent,
9 | config,
10 | ) => {
11 | const {
12 | watchedProps = [],
13 | timeout = 1000,
14 | className = 'animate',
15 | atMount = false,
16 | } = config || {}
17 |
18 | return class extends Component {
19 | static displayName = `Animate(${getDisplayName(ComposedComponent)})`
20 | static propTypes = {
21 | className: PropTypes.string,
22 | }
23 |
24 | state = {
25 | props: {},
26 | }
27 |
28 | applyClassName = (shouldAnimate, props) => {
29 | if (shouldAnimate) {
30 | let composedClassName = className
31 | if (this.props.className) composedClassName = `${composedClassName} ${this.props.className}`
32 |
33 | this.setState({
34 | props,
35 | className: composedClassName,
36 | })
37 |
38 | // -1 timeout means we don't delete the animate classname
39 | if (timeout !== -1) {
40 | this.timer = setTimeout(
41 | () => this.applyClassName(false, pick(this.props, watchedProps)),
42 | timeout
43 | )
44 | }
45 | } else {
46 | this.setState({
47 | props,
48 | className: this.props.className,
49 | })
50 | }
51 | }
52 |
53 | componentWillMount() {
54 | this.applyClassName(atMount, pick(this.props, watchedProps))
55 | }
56 |
57 | componentWillReceiveProps(nextProps) {
58 | const pickedProps = pick(nextProps, watchedProps)
59 | this.applyClassName(!isEqual(pickedProps, this.state.props), pickedProps)
60 | }
61 |
62 | componentWillUnmount() {
63 | clearTimeout(this.timer)
64 | }
65 |
66 | render() {
67 | const newProps = {}
68 | if (this.state.className) newProps.className = this.state.className
69 |
70 | return
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hoc-react-animate
2 |
3 | ## what is this?
4 | This is a higher order component ("HOC") that adds a CSS class to its child component whenever a prop change or at mount (or both) to animate it.
5 |
6 | ## try it
7 | You can test some examples [here](https://react-animate.firebaseapp.com/).
8 |
9 | ## install
10 | `npm i --save hoc-react-animate`
11 |
12 | ## use
13 | You have to wrap your component, and give some informations:
14 |
15 | Parameter | Required | Default value | Description
16 | ----------|--------|---------------|-------------
17 | `watchedProps` | no | `[]` | The props to watch (they are compared with `lodash.isEqual`)
18 | `timeout` | no | `1000` | The time (in ms) for which the CSS class is applied to the wrapped component
19 | `className` | no | `'animate'` | The class to add when a prop changes (or at mount)
20 | `atMount` | no | `false` | Set to `true` if you want to animate the component at mount
21 |
22 | **Component.js**
23 | ```(javascript)
24 | import React, { PropTypes } from 'react'
25 | import animate from 'hoc-react-animate'
26 |
27 | const Component = ({ className, text }) => {
28 | return (
29 |
32 | {text}
33 |
34 | )
35 | }
36 |
37 | Component.propTypes = {
38 | className: PropTypes.string,
39 | text: PropTypes.string,
40 | }
41 |
42 | export default animate(
43 | Component,
44 | {
45 | watchedProps: ['text'],
46 | timeout: 200,
47 | }
48 | )
49 | ```
50 |
51 | **css**
52 | ```(css)
53 | .component {
54 | transition: all .2s;
55 | }
56 | .component.animate {
57 | transform: scale(2);
58 | }
59 | ```
60 |
61 | # About uni rakun
62 | **uni rakun** is created by two passionate french developers.
63 |
64 | Do you want to contact them ? Go to their [website](https://unirakun.fr)
65 |
66 |
75 |
--------------------------------------------------------------------------------
/src/index.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { mount } from 'enzyme'
3 | import animate from './index'
4 |
5 | /* eslint-env mocha */
6 |
7 | const Component = () =>
8 | const getWrapped = (config, props) => {
9 | const Container = animate(Component, config)
10 | return mount( )
11 | }
12 |
13 | const getProps = (mounted) => mounted.find(Component).props()
14 | const equals = (mounted, object) => getProps(mounted).should.be.deep.equals(object)
15 |
16 | describe('react-animate', () => {
17 | it('should have the right default parameters', () => {
18 | const animated = getWrapped()
19 |
20 | equals(animated, {})
21 | })
22 |
23 | it('should not filter props, animate at mount for 1s', done => {
24 | const animated = getWrapped({ atMount: true }, { testProps: '1' })
25 |
26 | setTimeout(() => {
27 | equals(animated, {
28 | className: 'animate',
29 | testProps: '1',
30 | })
31 |
32 | setTimeout(() => {
33 | equals(animated, {
34 | testProps: '1',
35 | })
36 | done()
37 | }, 900)
38 | }, 100)
39 | equals(animated, {
40 | className: 'animate',
41 | testProps: '1',
42 | })
43 | })
44 |
45 | it('should not delete animate classname', done => {
46 | const animated = getWrapped({ atMount: true, timeout: -1 }, { testProps: '1' })
47 |
48 | setTimeout(() => {
49 | equals(animated, {
50 | className: 'animate',
51 | testProps: '1',
52 | })
53 |
54 | setTimeout(() => {
55 | equals(animated, {
56 | className: 'animate',
57 | testProps: '1',
58 | })
59 | done()
60 | }, 1100)
61 | }, 100)
62 | equals(animated, {
63 | className: 'animate',
64 | testProps: '1',
65 | })
66 | })
67 |
68 | it('should animate at mount for 10ms with custom class', done => {
69 | const animated = getWrapped({ className: 'test', atMount: true, timeout: 10 })
70 |
71 | setTimeout(() => {
72 | equals(animated, { })
73 | done()
74 | }, 10)
75 |
76 | equals(animated, { className: 'test' })
77 | })
78 |
79 | it('should merge classNames', done => {
80 | const animated = getWrapped({ atMount: true, timeout: 10 }, { className: 'test' })
81 |
82 | setTimeout(() => {
83 | equals(animated, { className: 'test' })
84 | done()
85 | }, 10)
86 |
87 | equals(animated, { className: 'animate test' })
88 | })
89 |
90 | it('should animate on props changing', done => {
91 | const animated = getWrapped({ timeout: 10, watchedProps: ['test'] }, { test: 'oldValue' })
92 |
93 | equals(animated, {
94 | test: 'oldValue',
95 | })
96 |
97 | animated.setProps({ test: 'oldValue', newProp: 'test' })
98 | equals(animated, {
99 | test: 'oldValue',
100 | newProp: 'test',
101 | })
102 |
103 | animated.setProps({ test: 'newValue', newProp: 'update' })
104 | setTimeout(() => {
105 | equals(animated, {
106 | test: 'newValue',
107 | newProp: 'update',
108 | })
109 | done()
110 | }, 10)
111 |
112 | equals(animated, {
113 | className: 'animate',
114 | test: 'newValue',
115 | newProp: 'update',
116 | })
117 | })
118 |
119 | it('should clear timeout', done => {
120 | /* eslint-disable no-underscore-dangle */
121 | const animated = getWrapped({ atMount: true })
122 | setTimeout(() => {
123 | animated.node.timer._called.should.be.deep.equals(false)
124 | }, 10)
125 |
126 | animated.unmount()
127 | animated.node.timer._called.should.be.deep.equals(false)
128 | animated.node.timer._idleTimeout.should.be.deep.equals(-1)
129 |
130 | setTimeout(() => {
131 | animated.node.timer._called.should.be.deep.equals(false)
132 | done()
133 | }, 1000)
134 |
135 | /* eslint-enable no-underscore-dangle */
136 | })
137 | })
138 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8 |
9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10 |
11 | var _react = require('react');
12 |
13 | var _react2 = _interopRequireDefault(_react);
14 |
15 | var _pick = require('lodash/pick');
16 |
17 | var _pick2 = _interopRequireDefault(_pick);
18 |
19 | var _isEqual = require('lodash/isEqual');
20 |
21 | var _isEqual2 = _interopRequireDefault(_isEqual);
22 |
23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24 |
25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
26 |
27 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
28 |
29 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
30 |
31 | var getDisplayName = function getDisplayName(c) {
32 | return c.displayName || c.name || 'Component';
33 | };
34 |
35 | exports.default = function (ComposedComponent, config) {
36 | var _class, _temp2;
37 |
38 | var _ref = config || {};
39 |
40 | var _ref$watchedProps = _ref.watchedProps;
41 | var watchedProps = _ref$watchedProps === undefined ? [] : _ref$watchedProps;
42 | var _ref$timeout = _ref.timeout;
43 | var timeout = _ref$timeout === undefined ? 1000 : _ref$timeout;
44 | var _ref$className = _ref.className;
45 | var className = _ref$className === undefined ? 'animate' : _ref$className;
46 | var _ref$atMount = _ref.atMount;
47 | var atMount = _ref$atMount === undefined ? false : _ref$atMount;
48 |
49 |
50 | return _temp2 = _class = function (_Component) {
51 | _inherits(_class, _Component);
52 |
53 | function _class() {
54 | var _ref2;
55 |
56 | var _temp, _this, _ret;
57 |
58 | _classCallCheck(this, _class);
59 |
60 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
61 | args[_key] = arguments[_key];
62 | }
63 |
64 | return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref2 = _class.__proto__ || Object.getPrototypeOf(_class)).call.apply(_ref2, [this].concat(args))), _this), _this.state = {
65 | props: {}
66 | }, _this.applyClassName = function (shouldAnimate, props) {
67 | if (shouldAnimate) {
68 | var composedClassName = className;
69 | if (_this.props.className) composedClassName = composedClassName + ' ' + _this.props.className;
70 |
71 | _this.setState({
72 | props: props,
73 | className: composedClassName
74 | });
75 |
76 | // -1 timeout means we don't delete the animate classname
77 | if (timeout !== -1) {
78 | _this.timer = setTimeout(function () {
79 | return _this.applyClassName(false, (0, _pick2.default)(_this.props, watchedProps));
80 | }, timeout);
81 | }
82 | } else {
83 | _this.setState({
84 | props: props,
85 | className: _this.props.className
86 | });
87 | }
88 | }, _temp), _possibleConstructorReturn(_this, _ret);
89 | }
90 |
91 | _createClass(_class, [{
92 | key: 'componentWillMount',
93 | value: function componentWillMount() {
94 | this.applyClassName(atMount, (0, _pick2.default)(this.props, watchedProps));
95 | }
96 | }, {
97 | key: 'componentWillReceiveProps',
98 | value: function componentWillReceiveProps(nextProps) {
99 | var pickedProps = (0, _pick2.default)(nextProps, watchedProps);
100 | this.applyClassName(!(0, _isEqual2.default)(pickedProps, this.state.props), pickedProps);
101 | }
102 | }, {
103 | key: 'componentWillUnmount',
104 | value: function componentWillUnmount() {
105 | clearTimeout(this.timer);
106 | }
107 | }, {
108 | key: 'render',
109 | value: function render() {
110 | var newProps = {};
111 | if (this.state.className) newProps.className = this.state.className;
112 |
113 | return _react2.default.createElement(ComposedComponent, _extends({}, this.props, newProps));
114 | }
115 | }]);
116 |
117 | return _class;
118 | }(_react.Component), _class.displayName = 'Animate(' + getDisplayName(ComposedComponent) + ')', _class.propTypes = {
119 | className: _react.PropTypes.string
120 | }, _temp2;
121 | };
--------------------------------------------------------------------------------