├── .gitattributes ├── .travis.yml ├── .gitignore ├── .babelrc ├── example ├── react-f1.gif ├── react-f1-chief.gif ├── chief │ ├── index.js │ ├── states.js │ ├── Menu │ │ ├── states.js │ │ ├── transitions.js │ │ └── index.js │ ├── SelectIndicator │ │ ├── index.js │ │ ├── transitions.js │ │ └── states.js │ ├── transitions.js │ └── FancyButton │ │ ├── transitions.js │ │ ├── states.js │ │ └── index.js └── f1 │ ├── index.js │ ├── getTransitions.js │ ├── ExampleButton.js │ └── getStates.js ├── .npmignore ├── test ├── index.js ├── UI.js ├── jsDomBoiler.js ├── testCustomTargets.js ├── testCustomParsers.js ├── testF1Go.js ├── testMergeStates.js └── testChiefGo.js ├── LICENSE.md ├── CHANGELOG.md ├── package.json ├── src ├── Chief.js └── index.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gif filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | before_install: 5 | - npm i react react-dom -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | lib/ 7 | /index.js 8 | /Chief.js -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["transform-inline-environment-variables"], 3 | "presets": [ 4 | "react", 5 | "es2015" 6 | ] 7 | } -------------------------------------------------------------------------------- /example/react-f1.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9ef33724e30ea60a4ceea9a429b28aef5e7568c9f913d2e94a40d6b4c29a1eab 3 | size 29739 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | example/ 10 | .npmignore 11 | LICENSE.md -------------------------------------------------------------------------------- /example/react-f1-chief.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:63faa92262e7b900e15a8d18f1503bcd9691b9ad12990eb900cff44dbc7af7c8 3 | size 70728 4 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var jsDomBoiler = require('./jsDomBoiler'); 2 | var test = require('tape'); 3 | 4 | // this will drop in some boiler plate to be able to test 5 | // react without the dom if we're not in the dom 6 | jsDomBoiler(); 7 | 8 | test('test f1 via go', require('./testF1Go')); 9 | test('test merging states', require('./testMergeStates')); 10 | test('test chief via go', require('./testChiefGo')); 11 | test('test custom targets', require('./testCustomTargets')); 12 | test('test custom parsers', require('./testCustomParsers')); -------------------------------------------------------------------------------- /example/chief/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactDom = require('react-dom'); 5 | var Menu = require('./Menu'); 6 | 7 | // boilerplate since react doesn't allow rendering to body 8 | var container = document.body.appendChild(document.createElement('div')); 9 | 10 | // this function will render to the dom the react-f1 ui component 11 | render('out'); 12 | render('idle'); 13 | 14 | function render(state) { 15 | ReactDom.render( 16 | , 19 | container 20 | ); 21 | } -------------------------------------------------------------------------------- /example/chief/states.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | out: { 3 | button1: 'out', 4 | button2: 'out', 5 | button3: 'out', 6 | indicator: 'out' 7 | }, 8 | 9 | idle: { 10 | button1: 'idle', 11 | button2: 'idle', 12 | button3: 'idle' 13 | }, 14 | 15 | selected1: { 16 | button1: 'selected', 17 | button2: 'idle', 18 | button3: 'idle', 19 | indicator: 'selected1' 20 | }, 21 | 22 | selected2: { 23 | button1: 'idle', 24 | button2: 'selected', 25 | button3: 'idle', 26 | indicator: 'selected2' 27 | }, 28 | 29 | selected3: { 30 | button1: 'idle', 31 | button2: 'idle', 32 | button3: 'selected', 33 | indicator: 'selected3' 34 | } 35 | }; -------------------------------------------------------------------------------- /example/chief/Menu/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | out: { 5 | button1: 'out', 6 | button2: 'out', 7 | button3: 'out', 8 | indicator: 'out' 9 | }, 10 | 11 | idle: { 12 | button1: 'idle', 13 | button2: 'idle', 14 | button3: 'idle' 15 | }, 16 | 17 | selected1: { 18 | button1: 'selected', 19 | button2: 'idle', 20 | button3: 'idle', 21 | indicator: 'selected1' 22 | }, 23 | 24 | selected2: { 25 | button1: 'idle', 26 | button2: 'selected', 27 | button3: 'idle', 28 | indicator: 'selected2' 29 | }, 30 | 31 | selected3: { 32 | button1: 'idle', 33 | button2: 'idle', 34 | button3: 'selected', 35 | indicator: 'selected3' 36 | } 37 | }; -------------------------------------------------------------------------------- /example/chief/SelectIndicator/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactF1 = require('../../../src/'); 5 | var states = require('./states'); 6 | var transitions = require('./transitions'); 7 | 8 | class SelectIndicator extends React.Component { 9 | render() { 10 | return 17 |
20 | 21 | } 22 | } 23 | 24 | SelectIndicator.defaultProps = { 25 | width: 20, 26 | height: 50, 27 | marginTop: 1 28 | }; 29 | 30 | module.exports = SelectIndicator; -------------------------------------------------------------------------------- /example/chief/transitions.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | from: 'out', to: 'idle', bi: true, animation: { 4 | button1: { 5 | delay: 0.5 6 | }, 7 | 8 | button2: { 9 | delay: 0.6 10 | }, 11 | 12 | button3: { 13 | delay: 0.7 14 | } 15 | } 16 | }, 17 | { 18 | from: 'idle', to: 'selected2', bi: true 19 | }, 20 | { 21 | from: 'selected2', to: 'selected1', bi: true, animation: { 22 | indicator: { 23 | delay: 0.5 24 | } 25 | } 26 | }, 27 | { 28 | from: 'selected2', to: 'selected3', bi: true, animation: { 29 | indicator: { 30 | delay: 0.5 31 | } 32 | } 33 | }, 34 | { 35 | from: 'selected3', to: 'selected1', bi: true, animation: { 36 | indicator: { 37 | delay: 0.5 38 | } 39 | } 40 | } 41 | ]; -------------------------------------------------------------------------------- /example/chief/Menu/transitions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = [ 4 | { 5 | from: 'out', to: 'idle', bi: true, animation: { 6 | button1: { 7 | delay: 0.5 8 | }, 9 | 10 | button2: { 11 | delay: 0.6 12 | }, 13 | 14 | button3: { 15 | delay: 0.7 16 | } 17 | } 18 | }, 19 | { 20 | from: 'idle', to: 'selected2', bi: true 21 | }, 22 | { 23 | from: 'selected2', to: 'selected1', bi: true, animation: { 24 | indicator: { 25 | delay: 0.5 26 | } 27 | } 28 | }, 29 | { 30 | from: 'selected2', to: 'selected3', bi: true, animation: { 31 | indicator: { 32 | delay: 0.5 33 | } 34 | } 35 | }, 36 | { 37 | from: 'selected3', to: 'selected1', bi: true, animation: { 38 | indicator: { 39 | delay: 0.5 40 | } 41 | } 42 | } 43 | ]; -------------------------------------------------------------------------------- /example/f1/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactDom = require('react-dom'); 5 | var ExampleButton = require('./ExampleButton'); 6 | 7 | // boilerplate since react doesn't allow rendering to body 8 | var container = document.body.appendChild(document.createElement('div')); 9 | 10 | // this function will render to the dom the react-f1 ui component 11 | render('idle'); 12 | 13 | function render(state) { 14 | ReactDom.render( 15 | { 21 | console.log('ExampleButton is in', state); 22 | }} 23 | 24 | // The following is to add mouse events 25 | // on mouse over go to the over state 26 | // on mouse out go back to the idle state 27 | onMouseOver={render.bind(null, 'over')} 28 | onMouseOut={render.bind(null, 'idle')} 29 | />, 30 | container 31 | ); 32 | } -------------------------------------------------------------------------------- /test/UI.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactF1 = require('./../src/'); 5 | 6 | var states = { 7 | out: { 8 | item: { 9 | style: { 10 | width: 100, 11 | height: 100 12 | } 13 | } 14 | }, 15 | 16 | idle: { 17 | item: { 18 | style: { 19 | width: 200, 20 | height: 100 21 | } 22 | } 23 | }, 24 | 25 | over: { 26 | item: { 27 | style: { 28 | width: 300, 29 | height: 100 30 | } 31 | } 32 | } 33 | }; 34 | 35 | var transitions = [ 36 | { from: 'out', to: 'idle', bi: true }, 37 | { from: 'idle', to: 'over', bi: true } 38 | ]; 39 | 40 | class UI extends React.Component { 41 | render() { 42 | return 47 |
SNAKES
53 |
; 54 | } 55 | } 56 | 57 | module.exports = UI; -------------------------------------------------------------------------------- /example/chief/SelectIndicator/transitions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var eases = require('eases'); 3 | 4 | module.exports = [ 5 | { 6 | from: 'out', to: 'idle', bi: true, animation: { 7 | duration: 0.1 8 | } 9 | }, 10 | { 11 | from: 'idle', to: 'selected2', bi: true, animation: { 12 | duration: 0.25, 13 | ease: eases.expoOut 14 | } 15 | }, 16 | 17 | { 18 | from: 'selected2', to: 'selected1', animation: { 19 | duration: 0.25, 20 | ease: eases.bounceOut 21 | } 22 | }, 23 | { 24 | from: 'selected1', to: 'selected2', animation: { 25 | duration: 0.25, 26 | ease: eases.backOut 27 | } 28 | }, 29 | 30 | { 31 | from: 'selected2', to: 'selected3', animation: { 32 | duration: 0.25, 33 | ease: eases.bounceOut 34 | } 35 | }, 36 | { 37 | from: 'selected3', to: 'selected2', animation: { 38 | duration: 0.25, 39 | ease: eases.backOut 40 | } 41 | }, 42 | 43 | 44 | { 45 | from: 'selected3', to: 'selected1', bi: true, animation: { 46 | duration: 0.5, 47 | ease: eases.bounceOut 48 | } 49 | } 50 | ]; -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Jam3 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /test/jsDomBoiler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | 5 | // this will throw and if window doesnt exist then we set up jsdom 6 | try { 7 | window 8 | } catch(e) { 9 | initJSDOM(); 10 | } 11 | }; 12 | 13 | function initJSDOM() { 14 | 15 | // this boiler plate is borrowed from 16 | // http://jaketrent.com/post/testing-react-with-jsdom/ 17 | var jsdom = require('jsdom') 18 | 19 | // setup the simplest document possible 20 | var doc = jsdom.jsdom('') 21 | 22 | // get the window object out of the document 23 | var win = doc.defaultView 24 | 25 | // set globals for mocha that make access to document and window feel 26 | // natural in the test environment 27 | global.document = doc 28 | global.window = win 29 | 30 | // take all properties of the window object and also attach it to the 31 | // mocha global object 32 | propagateToGlobal(win) 33 | 34 | // from mocha-jsdom https://github.com/rstacruz/mocha-jsdom/blob/master/index.js#L80 35 | function propagateToGlobal (window) { 36 | for (let key in window) { 37 | if (!window.hasOwnProperty(key)) continue 38 | if (key in global) continue 39 | 40 | global[key] = window[key] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/chief/FancyButton/transitions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var eases = require('eases'); 3 | 4 | module.exports = [ 5 | { from: 'out', to: 'idle', animation: { 6 | duration: 0.5, 7 | ease: eases.backOut 8 | } 9 | }, 10 | { from: 'idle', to: 'out', animation: { 11 | duration: 0.5, 12 | ease: eases.expoIn 13 | } 14 | }, 15 | 16 | { from: 'idle', to: 'over', animation: { 17 | duration: 0.25, 18 | ease: eases.backOut 19 | } 20 | }, 21 | { from: 'over', to: 'idle', animation: { 22 | duration: 0.25, 23 | ease: eases.expoOut 24 | } 25 | }, 26 | 27 | { from: 'over', to: 'preSelected', animation: { 28 | duration: 0.25, 29 | ease: eases.expoIn 30 | } 31 | }, 32 | { from: 'preSelected', to: 'over', animation: { 33 | duration: 0.5, 34 | ease: eases.backOut 35 | } 36 | }, 37 | 38 | 39 | { from: 'preSelected', to: 'swapSelected', bi: true, animation: { 40 | duration: 0 41 | } 42 | }, 43 | 44 | { from: 'swapSelected', to: 'selected', animation: { 45 | duration: 0.5, 46 | ease: eases.backOut 47 | } 48 | }, 49 | { from: 'selected', to: 'swapSelected', animation: { 50 | duration: 0.5, 51 | ease: eases.expoIn 52 | } 53 | } 54 | ]; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 7.1.1 2 | 3 | Fixes: 4 | - Passing targets a second time will cause an update to f1 targets and a re-render. 5 | 6 | # 7.1.0 7 | 8 | Fixes: 9 | - Updated `peerDepencies` to work off `react@15.0.0` amd `react-dom@15.0.0` 10 | 11 | Features: 12 | - Can now pass in custom targets which are not defined via `data-f1` tags. This will allow for `react-f1` to control other targets that might manipulate the dom. Example: 13 | ``` 14 | 19 | ... 20 | ``` 21 | - Custom parsers can now be passed to `react-f1` the format should be the same as what `f1` defines. Example: 22 | ``` 23 | 29 |
42 |
51 | react-f1 52 |
53 |
54 |
; 55 | } 56 | } 57 | 58 | module.exports = ExampleButton; -------------------------------------------------------------------------------- /example/f1/getStates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function getStates() { 4 | return { 5 | // this is defines the idle state for the button 6 | idle: { 7 | // this is what the bg should look like in the idle state 8 | buttonBG: { 9 | style: { 10 | // regular old width, height, and fontSize 11 | width: 120, 12 | height: 120, 13 | fontSize: 14, 14 | 15 | // the following are passed to css transform 16 | // by recomposing a transformation matrix 17 | // 18 | // translate: [ x, y, z ] 19 | // rotate: [ x, y, z ] 20 | translate: [ 0, 0, 0 ], 21 | rotate: [ 0, 0, 0 ] 22 | } 23 | }, 24 | 25 | // this is what the text should look like in the idle state 26 | buttonText: { 27 | style: { 28 | // color's can be represented by arrays that are 29 | // [ red, green, blue, alpha ] 30 | color: [ 0, 0, 0, 1 ], 31 | marginTop: 0 32 | } 33 | } 34 | }, 35 | 36 | // this is defines the over state for the button 37 | over: { 38 | // this is what the bg should look like in the over state 39 | buttonBG: { 40 | style: { 41 | // regular old width and height animation 42 | width: 220, 43 | height: 220, 44 | fontSize: 30, 45 | 46 | // fancy properties 47 | translate: [ -40, 0, -200 ], 48 | rotate: [ -20, -45, 0 ] 49 | } 50 | }, 51 | 52 | // this is what the text should look like in the over state 53 | buttonText: { 54 | style: { 55 | // color's can be represented by arrays that are 56 | // [ red, green, blue, alpha ] 57 | color: [ 255, 255, 255, 0.2 ], 58 | marginTop: -40 59 | } 60 | } 61 | } 62 | }; 63 | }; -------------------------------------------------------------------------------- /example/chief/FancyButton/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var merge = require('deep-extend'); 4 | 5 | module.exports = function(props) { 6 | var width = props.width; 7 | var height = props.height; 8 | 9 | var idle = { 10 | bg1: { 11 | style: { 12 | transformOrigin: [ 1, 0.5 ], 13 | scale: [ 0, 1 ] 14 | } 15 | }, 16 | 17 | bg2: { 18 | style: { 19 | transformOrigin: [ 0, 0.5 ], 20 | scale: [ 1, 1 ] 21 | } 22 | }, 23 | 24 | container: { 25 | style: { 26 | rotate: [ 0, 0, 0 ] 27 | } 28 | } 29 | }; 30 | 31 | var out = merge( 32 | {}, 33 | idle, 34 | { 35 | container: { 36 | style: { 37 | rotate: [ 90, 0, 0 ] 38 | } 39 | } 40 | } 41 | ); 42 | 43 | var over = merge( 44 | {}, 45 | idle, 46 | { 47 | bg1: { 48 | style: { 49 | scale: [ 0.15, 1 ] 50 | } 51 | }, 52 | 53 | bg2: { 54 | style: { 55 | scale: [ 0.85, 1 ] 56 | } 57 | } 58 | } 59 | ); 60 | 61 | var preSelected = merge( 62 | {}, 63 | over, 64 | { 65 | container: { 66 | style: { 67 | rotate: [ 90, 0, 0 ] 68 | } 69 | } 70 | } 71 | ); 72 | 73 | var swapSelected = merge( 74 | {}, 75 | preSelected, 76 | { 77 | bg1: { 78 | style: { 79 | scale: [ 1, 1 ] 80 | } 81 | }, 82 | 83 | bg2: { 84 | style: { 85 | scale: [ 0, 1 ] 86 | } 87 | } 88 | } 89 | ); 90 | 91 | var selected = merge( 92 | {}, 93 | swapSelected, 94 | { 95 | container: { 96 | style: { 97 | rotate: [ 180, 0, 0 ] 98 | } 99 | } 100 | } 101 | ); 102 | 103 | return { 104 | idle: idle, 105 | over: over, 106 | out: out, 107 | preSelected: preSelected, 108 | swapSelected: swapSelected, 109 | selected: selected 110 | }; 111 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-f1", 3 | "version": "8.1.0", 4 | "description": "React UI animation components built on top of f1-dom and f1.", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Mikko Haapoja", 9 | "email": "me@mikkoh.com", 10 | "url": "https://github.com/mikkoh" 11 | }, 12 | "scripts": { 13 | "start": "npm run browser", 14 | "prepublish": "npm run compile", 15 | "postpublish": "npm run clean-compile", 16 | "compile": "babel -d ./ src/", 17 | "clean-compile": "rm index.js; rm Chief.js", 18 | "test": "npm run test-pretranspile; npm run test-transpile;", 19 | "test-pretranspile": "PATH_F1='./../src/' PATH_CHIEF='../src/Chief' npm run node", 20 | "test-transpile": "npm run compile; PATH_F1='../' PATH_CHIEF='../Chief' npm run node; npm run clean-compile", 21 | "test-browser": "PATH_F1='./../src/' PATH_CHIEF='../src/Chief' npm run dev-browser", 22 | "example-f1": "budo example/f1/index.js --live --open", 23 | "example-chief": "budo example/chief/index.js --live --open", 24 | "f1-example": "npm run example-f1", 25 | "chief-example": "npm run example-chief", 26 | "browser": "npm run dev-browser", 27 | "node": "babel-node test/index.js", 28 | "dev-browser": "PATH_F1='./../src/' PATH_CHIEF='../src/Chief' budo test/index.js --live --open", 29 | "dev-node": "PATH_F1='./../src/' PATH_CHIEF='../src/Chief' nodemon --exec \"babel-node test/index.js\"" 30 | }, 31 | "browserify": { 32 | "transform": [ 33 | "babelify" 34 | ] 35 | }, 36 | "dependencies": { 37 | "babel-plugin-transform-inline-environment-variables": "^6.5.0", 38 | "deep-extend": "^0.4.0", 39 | "f1": "^8.0.0", 40 | "f1-dom": "^9.0.0", 41 | "gl-mat4": "^1.1.4" 42 | }, 43 | "peerDependencies": { 44 | "react": "^15.0.0", 45 | "react-dom": "^15.0.0" 46 | }, 47 | "devDependencies": { 48 | "async": "^1.5.2", 49 | "babel-cli": "^6.6.5", 50 | "babel-preset-es2015": "^6.6.0", 51 | "babel-preset-react": "^6.5.0", 52 | "babelify": "^7.2.0", 53 | "budo": "^5.1.5", 54 | "dom-select": "^1.1.0", 55 | "eases": "^1.0.8", 56 | "jsdom": "^8.0.2", 57 | "nodemon": "^1.8.1", 58 | "tape": "^4.5.1" 59 | }, 60 | "keywords": [ 61 | "react,f1,animation,ui,jam3,dom,react-dom" 62 | ], 63 | "repository": { 64 | "type": "git", 65 | "url": "git://github.com/Jam3/react-f1.git" 66 | }, 67 | "homepage": "https://github.com/Jam3/react-f1", 68 | "bugs": { 69 | "url": "https://github.com/Jam3/react-f1/issues" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/testCustomTargets.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | var domSelect = require('dom-select'); 4 | var ReactF1 = require(process.env.PATH_F1); 5 | var async = require('async'); 6 | 7 | var container; 8 | 9 | 10 | var target; 11 | 12 | 13 | var states = { 14 | out: { 15 | item: { 16 | value: "out" 17 | } 18 | }, 19 | 20 | idle: { 21 | item: { 22 | value: "idle" 23 | } 24 | 25 | } 26 | }; 27 | 28 | var transitions = [ 29 | { from: 'out', to: 'idle', bi: true } 30 | ]; 31 | 32 | 33 | module.exports = function(t) { 34 | 35 | var statesVisited = []; 36 | 37 | // each one of these objects will be applied to the `react-f1` component 38 | // a callback will be passed to the onComplete function if no onComplete 39 | // is passed then it is automatically 40 | async.eachSeries( 41 | [ 42 | { go: 'out', states: states, transitions: transitions }, 43 | { 44 | go: 'idle', states: states, transitions: transitions, 45 | onComplete: function(callback, state, stateName) { 46 | var el = domSelect('#target'); 47 | t.equal(stateName, 'idle', 'stateName is idle'); 48 | t.equal(typeof state, 'object', 'state is an object'); 49 | t.equal(el.value, 'idle', 'target input value is set to idle'); 50 | 51 | statesVisited.push(stateName); 52 | 53 | callback(null); 54 | } 55 | }, 56 | { 57 | go: 'out', states: states, transitions: transitions, 58 | onComplete: function(callback, state, stateName) { 59 | var el = domSelect('#target'); 60 | t.equal(stateName, 'out', 'stateName is out'); 61 | t.equal(typeof state, 'object', 'state is an object'); 62 | t.equal(el.value, 'out', 'target input value is set to out'); 63 | 64 | statesVisited.push(stateName); 65 | 66 | callback(null); 67 | } 68 | } 69 | ], 70 | render, 71 | function() { 72 | t.deepEqual(statesVisited, ['idle', 'out'], 'visited all states'); 73 | 74 | container.parentNode.removeChild(container); 75 | target.parentNode.removeChild(target); 76 | 77 | t.end(); 78 | } 79 | ); 80 | }; 81 | 82 | 83 | function render(settings, callback) { 84 | 85 | container = container || document.body.appendChild(document.createElement('div')); 86 | 87 | target = target || document.body.appendChild(document.createElement('input')); 88 | target.id = 'target'; 89 | 90 | settings.onComplete = settings.onComplete && settings.onComplete.bind(null, callback); 91 | 92 | settings.targets = { 93 | item: target 94 | }; 95 | 96 | var component = 100 | ; 101 | 102 | ReactDom.render(component, container); 103 | 104 | if(!settings.onComplete) { 105 | callback(null); 106 | } 107 | } -------------------------------------------------------------------------------- /example/chief/FancyButton/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactF1 = require('../../../src'); 5 | var states = require('./states'); 6 | var transitions = require('./transitions'); 7 | 8 | class FancyButton extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.handleMouseOver = this.handleMouseOver.bind(this); 13 | this.handleMouseOut = this.handleMouseOut.bind(this); 14 | 15 | this.state = { 16 | go: 'out' 17 | }; 18 | } 19 | 20 | handleMouseOver() { 21 | if(this.state.go === 'idle') { 22 | this.setState({ 23 | go: 'over', 24 | onComplete: null 25 | }); 26 | } 27 | } 28 | 29 | handleMouseOut() { 30 | if(this.state.go === 'over') { 31 | this.setState({ 32 | go: 'idle', 33 | onComplete: null 34 | }); 35 | } 36 | } 37 | 38 | updateStateFromProps(props) { 39 | if(this.state.go !== props.go) { 40 | this.setState({ 41 | go: props.go, 42 | onComplete: props.onComplete 43 | }); 44 | } 45 | } 46 | 47 | componentWillMount() { 48 | this.updateStateFromProps(this.props); 49 | } 50 | 51 | componentWillReceiveProps(nextProps) { 52 | this.updateStateFromProps(nextProps) 53 | } 54 | 55 | render() { 56 | var style = Object.assign( 57 | {}, 58 | this.props.style, 59 | { 60 | width: this.props.width, 61 | height: this.props.height 62 | } 63 | ); 64 | 65 | return 75 |
85 |
96 |
107 |
108 | ; 109 | } 110 | } 111 | 112 | FancyButton.defaultProps = { 113 | width: 200, 114 | height: 50, 115 | onSelect: function() {} 116 | }; 117 | 118 | module.exports = FancyButton; -------------------------------------------------------------------------------- /test/testCustomParsers.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | var domSelect = require('dom-select'); 4 | var ReactF1 = require(process.env.PATH_F1); 5 | var async = require('async'); 6 | 7 | var container; 8 | 9 | 10 | var target; 11 | 12 | 13 | var states = { 14 | out: { 15 | item: { 16 | play: true 17 | } 18 | }, 19 | 20 | idle: { 21 | item: { 22 | play: false 23 | } 24 | 25 | } 26 | }; 27 | 28 | var transitions = [ 29 | { from: 'out', to: 'idle', bi: true } 30 | ]; 31 | 32 | 33 | module.exports = function(t) { 34 | 35 | var statesVisited = []; 36 | 37 | // each one of these objects will be applied to the `react-f1` component 38 | // a callback will be passed to the onComplete function if no onComplete 39 | // is passed then it is automatically 40 | async.eachSeries( 41 | [ 42 | { go: 'out', states: states, transitions: transitions }, 43 | { 44 | go: 'idle', states: states, transitions: transitions, 45 | onComplete: function(callback, state, stateName) { 46 | var el = domSelect('[data-f1]'); 47 | t.equal(stateName, 'idle', 'stateName is idle'); 48 | t.equal(typeof state, 'object', 'state is an object'); 49 | t.equal(el.innerHTML, 'The value has been set to false', 'The div innerHTML has been set to the correct string'); 50 | 51 | statesVisited.push(stateName); 52 | 53 | callback(null); 54 | } 55 | }, 56 | { 57 | go: 'out', states: states, transitions: transitions, 58 | onComplete: function(callback, state, stateName) { 59 | var el = domSelect('[data-f1]'); 60 | t.equal(stateName, 'out', 'stateName is out'); 61 | t.equal(typeof state, 'object', 'state is an object'); 62 | t.equal(el.innerHTML, 'The value has been set to true', 'The div innerHTML has been set to the correct string'); 63 | 64 | statesVisited.push(stateName); 65 | 66 | callback(null); 67 | } 68 | } 69 | ], 70 | render, 71 | function() { 72 | t.deepEqual(statesVisited, ['idle', 'out'], 'visited all states'); 73 | 74 | container.parentNode.removeChild(container); 75 | 76 | t.end(); 77 | } 78 | ); 79 | }; 80 | 81 | 82 | function render(settings, callback) { 83 | 84 | container = container || document.body.appendChild(document.createElement('div')); 85 | 86 | 87 | settings.onComplete = settings.onComplete && settings.onComplete.bind(null, callback); 88 | 89 | settings.parsers = { 90 | init: [], 91 | update: [ 92 | function(item, state){ 93 | item.innerHTML = (state.play)? 'The value has been set to true' : 'The value has been set to false'; 94 | } 95 | ] 96 | }; 97 | 98 | var component = 102 |
103 |
; 104 | 105 | ReactDom.render(component, container); 106 | 107 | if(!settings.onComplete) { 108 | callback(null); 109 | } 110 | } -------------------------------------------------------------------------------- /test/testF1Go.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | var domSelect = require('dom-select'); 4 | var ReactF1 = require(process.env.PATH_F1); 5 | var async = require('async'); 6 | 7 | var container; 8 | 9 | var states = { 10 | out: { 11 | item: { 12 | style: { 13 | width: 100, 14 | height: 50 15 | } 16 | } 17 | }, 18 | 19 | idle: { 20 | item: { 21 | style: { 22 | width: 200, 23 | height: 300 24 | } 25 | } 26 | } 27 | }; 28 | 29 | var transitions = [ 30 | { from: 'out', to: 'idle', bi: true } 31 | ]; 32 | 33 | module.exports = function(t) { 34 | 35 | var statesVisited = []; 36 | 37 | // each one of these objects will be applied to the `react-f1` component 38 | // a callback will be passed to the onComplete function if no onComplete 39 | // is passed then it is automatically 40 | async.eachSeries( 41 | [ 42 | { go: 'out', states: states, transitions: transitions }, 43 | { 44 | go: 'idle', states: states, transitions: transitions, 45 | style: { 46 | backgroundColor: '#00FFFF' 47 | }, 48 | onComplete: function(callback, state, stateName) { 49 | var el = domSelect('[data-f1]'); 50 | 51 | t.equal(stateName, 'idle', 'stateName is idle'); 52 | t.equal(typeof state, 'object', 'state is an object'); 53 | t.equal(el.style.width, '200px', 'width is 200px'); 54 | t.equal(el.style.height, '300px', 'height is 300px'); 55 | t.equal(el.style.backgroundColor, 'rgb(0, 255, 255)', 'backgroundColor style was mixed in'); 56 | 57 | statesVisited.push(stateName); 58 | 59 | callback(null); 60 | } 61 | }, 62 | { 63 | go: 'out', states: states, transitions: transitions, 64 | style: { 65 | backgroundColor: '#00FFFF' 66 | }, 67 | onComplete: function(callback, state, stateName) { 68 | var el = domSelect('[data-f1]'); 69 | 70 | t.equal(stateName, 'out', 'stateName is out'); 71 | t.equal(typeof state, 'object', 'state is an object'); 72 | t.equal(el.style.width, '100px', 'width is 100px'); 73 | t.equal(el.style.height, '50px', 'height is 50px'); 74 | t.equal(el.style.backgroundColor, 'rgb(0, 255, 255)', 'backgroundColor style was mixed in'); 75 | 76 | statesVisited.push(stateName); 77 | 78 | callback(null); 79 | } 80 | } 81 | ], 82 | render, 83 | function() { 84 | t.deepEqual(statesVisited, ['idle', 'out'], 'visited all states'); 85 | 86 | container.parentNode.removeChild(container); 87 | 88 | t.end(); 89 | } 90 | ); 91 | }; 92 | 93 | 94 | function render(settings, callback) { 95 | 96 | container = container || document.body.appendChild(document.createElement('div')); 97 | settings.onComplete = settings.onComplete && settings.onComplete.bind(null, callback); 98 | 99 | var component = 103 |
Test
104 |
; 105 | 106 | // TestUtils.renderIntoDocument(component); 107 | ReactDom.render(component, container); 108 | 109 | if(!settings.onComplete) { 110 | callback(null); 111 | } 112 | } -------------------------------------------------------------------------------- /example/chief/Menu/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactF1 = require('../../../src'); 5 | var Chief = require('../../../src/Chief'); 6 | var states = require('./states'); 7 | var transitions = require('./transitions'); 8 | var FancyButton = require('../FancyButton'); 9 | var SelectIndicator = require('../SelectIndicator'); 10 | 11 | /****************************************************************************/ 12 | /***** You should probably just to the `render` function because that's *****/ 13 | /***********************w here all the MAGIC happens ************************/ 14 | /****************************************************************************/ 15 | class Menu extends React.Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = {}; 21 | } 22 | 23 | updateStateFromProps(props) { 24 | 25 | if(this.state.propsGo !== props.go) { 26 | this.setState({ 27 | go: props.go, 28 | propsGo: props.go 29 | }); 30 | } 31 | } 32 | 33 | componentWillMount() { 34 | this.updateStateFromProps(this.props); 35 | } 36 | 37 | componentWillReceiveProps(nextProps) { 38 | this.updateStateFromProps(nextProps); 39 | } 40 | 41 | handleClick(state) { 42 | this.setState({ 43 | go: state 44 | }); 45 | } 46 | 47 | render() { 48 | return 58 | { 59 | // with React it's very hard to manipulate deeply nested (grand child) 60 | // component's properties. 61 | // This is why you need to pass in a function that will accept an Object 62 | (states) => { 63 | 64 | var buttonSize = { 65 | width: 200, 66 | height: 50 67 | }; 68 | 69 | var paddingBetween = 1; 70 | 71 | return
72 | 79 | 89 | 100 | 111 |
112 | } 113 | } 114 |
; 115 | } 116 | } 117 | 118 | module.exports = Menu; -------------------------------------------------------------------------------- /test/testMergeStates.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | var domSelect = require('dom-select'); 4 | var ReactF1 = require(process.env.PATH_F1); 5 | var async = require('async'); 6 | var merge = require('deep-extend'); 7 | 8 | var container; 9 | 10 | var states = { 11 | out: { 12 | item: { 13 | style: { 14 | width: 100, 15 | height: 50 16 | } 17 | } 18 | }, 19 | 20 | idle: { 21 | item: { 22 | style: { 23 | width: 10, 24 | height: 10 25 | } 26 | } 27 | } 28 | }; 29 | 30 | var transitions = [ 31 | { from: 'out', to: 'idle', bi: true } 32 | ]; 33 | 34 | module.exports = function(t) { 35 | 36 | // each one of these objects will be applied to the `react-f1` component 37 | // a callback will be passed to the onComplete function if no onComplete 38 | // is passed then it is automatically 39 | async.eachSeries( 40 | [ 41 | function() { return { go: 'out', states: states, transitions: transitions } }, 42 | function() { 43 | return { 44 | go: 'idle', states: getState('idle', 400, 333), transitions: transitions, 45 | onComplete: function(callback, state, stateName) { 46 | var el = domSelect('[data-f1]'); 47 | 48 | t.equal(el.style.width, '400px', 'modified state width was correct'); 49 | t.equal(el.style.height, '333px', 'modified state height was correct'); 50 | 51 | callback(null); 52 | } 53 | } 54 | }, 55 | 56 | // just to test that out didn't get effected in anyway 57 | function() { 58 | return { 59 | go: 'out', states: states, transitions: transitions, 60 | onComplete: function(callback, state, stateName) { 61 | var el = domSelect('[data-f1]'); 62 | 63 | t.equal(el.style.width, '100px', 'unchanged state width is correct'); 64 | t.equal(el.style.height, '50px', 'unchanged state height is correct'); 65 | 66 | callback(null); 67 | } 68 | }; 69 | }, 70 | 71 | // the following is to test setting states when we're already on that state and not animating 72 | function() { 73 | return { 74 | go: 'out', states: getState('out', 33, 44), transitions: transitions 75 | }; 76 | } 77 | ], 78 | render, 79 | function() { 80 | var el = domSelect('[data-f1]'); 81 | 82 | t.equal(el.style.width, '33px', 'width correct after set state on static state'); 83 | t.equal(el.style.height, '44px', 'height correct after set state on static state'); 84 | 85 | container.parentNode.removeChild(container); 86 | 87 | t.end(); 88 | } 89 | ); 90 | }; 91 | 92 | function getState(stateName, width, height) { 93 | var newState = merge({}, states); 94 | 95 | Object.assign( 96 | newState[ stateName ].item.style, 97 | { 98 | width: width, 99 | height: height 100 | } 101 | ); 102 | 103 | return newState; 104 | } 105 | 106 | function render(settings, callback) { 107 | 108 | settings = settings(); 109 | 110 | container = container || document.body.appendChild(document.createElement('div')); 111 | settings.onComplete = settings.onComplete && settings.onComplete.bind(null, callback); 112 | 113 | var component = 116 |
Test
122 |
; 123 | 124 | // TestUtils.renderIntoDocument(component); 125 | ReactDom.render(component, container); 126 | 127 | if(!settings.onComplete) { 128 | process.nextTick(function() { 129 | callback(null); 130 | }); 131 | } 132 | } -------------------------------------------------------------------------------- /src/Chief.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 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; }; 4 | 5 | var React = require('react'); 6 | var f1Chief = require('f1/chief'); 7 | var merge = require('deep-extend'); 8 | var chiefBridge = function (target, onUpdate) { 9 | 10 | target.state = null; 11 | target.onComplete = null; 12 | 13 | return { 14 | isInitialized: false, 15 | 16 | init: function (state) { 17 | this.isInitialized = true; 18 | target.go = state; 19 | onUpdate(); 20 | }, 21 | 22 | go: function (state, onComplete) { 23 | target.go = state; 24 | target.onComplete = onComplete; 25 | onUpdate(); 26 | } 27 | }; 28 | }; 29 | 30 | class Chief extends React.Component { 31 | 32 | constructor(props) { 33 | super(props); 34 | 35 | this.handleUpdate = this.handleUpdate.bind(this); 36 | 37 | this.chief = null; 38 | this.chiefStates = null; 39 | this.state = {}; 40 | } 41 | 42 | componentWillMount() { 43 | this.chief = f1Chief({ 44 | transitions: this.props.transitions, 45 | states: this.props.states, 46 | targets: this.getTargetsFromStates(this.props.states), 47 | onUpdate: function () { 48 | this.props.onUpdate.apply(undefined, arguments); 49 | }.bind(this) 50 | }); 51 | } 52 | 53 | componentWillUnMount() { 54 | if (this.chief) { 55 | this.chief.destroy(); 56 | } 57 | } 58 | 59 | componentDidMount() { 60 | this.chief.init(this.props.go); 61 | } 62 | 63 | componentWillReceiveProps(nextProps) { 64 | 65 | var goState = nextProps.go; 66 | 67 | if (goState && (this.state.propsState !== goState || this.state.propsOnComplete !== nextProps.onComplete)) { 68 | this.chief.go(goState, nextProps.onComplete); 69 | 70 | this.setState({ 71 | propsState: goState, 72 | propsOnComplete: nextProps.onComplete 73 | }); 74 | } 75 | } 76 | 77 | handleUpdate() { 78 | this.setState({ 79 | chiefStates: this.chiefStates 80 | }); 81 | } 82 | 83 | getTargetsFromStates(states) { 84 | var stateName = Object.keys(states)[0]; 85 | var targets = {}; 86 | var chiefState; 87 | 88 | this.chiefStates = {}; 89 | 90 | for (var targetName in states[stateName]) { 91 | chiefState = {}; 92 | this.chiefStates[targetName] = chiefState; 93 | 94 | targets[targetName] = chiefBridge(chiefState, this.handleUpdate); 95 | } 96 | 97 | return targets; 98 | } 99 | 100 | getChildrenFromFunction(chiefState) { 101 | return this.props.children(chiefState); 102 | } 103 | 104 | cleanProps(props) { 105 | delete props.go; 106 | delete props.transitions; 107 | delete props.states; 108 | delete props.onComplete; 109 | delete props.onUpdate; 110 | delete props.component; 111 | return props; 112 | } 113 | 114 | render() { 115 | 116 | var chiefState = this.state.chiefStates; 117 | var children; 118 | 119 | if (chiefState) { 120 | 121 | if (typeof this.props.children === 'function') { 122 | children = this.getChildrenFromFunction(chiefState); 123 | } else { 124 | throw new Error('props.children should be a function that accepts chief states'); 125 | } 126 | } else { 127 | children = this.props.children; 128 | } 129 | 130 | var props = _extends({}, this.props); 131 | 132 | return React.createElement( 133 | this.props.component || 'div', 134 | this.cleanProps(props), 135 | children 136 | ); 137 | } 138 | }; 139 | 140 | Chief.defaultProps = { 141 | onUpdate: function () {}, // this.props.onUpdate(state, this.props.go); 142 | onComplete: function () {} 143 | }; 144 | 145 | module.exports = Chief; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 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; }; 4 | 5 | var React = require('react'); 6 | var f1DOM = require('f1-dom'); 7 | var merge = require('deep-extend'); 8 | 9 | class ReactF1 extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | 14 | this.hasMounted = false; 15 | this.f1 = null; 16 | this.f1States = null; 17 | 18 | this.state = {}; 19 | } 20 | 21 | setupFromProps(props) { 22 | if (!this.f1) { 23 | this.initFromProps(props); 24 | } else { 25 | this.updateFromProps(props); 26 | } 27 | } 28 | 29 | initFromProps(props) { 30 | if(this.hasMounted && !this.f1 && props.go && props.states && props.transitions) { 31 | 32 | this.f1States = merge({}, props.states); 33 | 34 | var f1 = this.f1 = f1DOM({ 35 | el: this.el, 36 | states: this.f1States, 37 | transitions: props.transitions, 38 | targets: props.targets, 39 | parsers: props.parsers 40 | }); 41 | 42 | f1.on('state', this.handleState.bind(this)); 43 | f1.on('update', this.handleUpdate.bind(this)); 44 | this.updateListenersFromProps(props); 45 | 46 | f1.init(props.go); 47 | } 48 | } 49 | 50 | updateFromProps(props) { 51 | var states; 52 | 53 | if(this.f1) { 54 | // if we've received new states then just mege into current states 55 | if (props.states) { 56 | merge(this.f1States, props.states); 57 | } 58 | 59 | // if we've received targets then reset them 60 | if(props.targets) { 61 | this.f1.targets(props.targets); 62 | } 63 | 64 | // call update to ensure everything looks right and is in its calculated state 65 | if(props.states || props.targets) { 66 | this.f1.update(); 67 | } 68 | 69 | if (props.go) { 70 | this.f1.go(props.go, props.onComplete); 71 | } 72 | } 73 | 74 | this.updateListenersFromProps(props); 75 | } 76 | 77 | updateListenersFromProps(props) { 78 | this.setState({ 79 | onUpdate: props.onUpdate, 80 | onState: props.onState 81 | }); 82 | } 83 | 84 | handleUpdate() { 85 | if (this.state.onUpdate) { 86 | this.state.onUpdate.apply(undefined, arguments); 87 | } 88 | } 89 | 90 | handleState() { 91 | if (this.state.onState) { 92 | this.state.onState.apply(undefined, arguments); 93 | } 94 | } 95 | 96 | componentWillReceiveProps(nextProps) { 97 | this.setupFromProps(nextProps); 98 | } 99 | 100 | componentDidMount() { 101 | this.hasMounted = true; 102 | this.setupFromProps(this.props); 103 | } 104 | 105 | componentWillUnmount() { 106 | if (this.f1) { 107 | this.f1.destroy(); 108 | } 109 | } 110 | 111 | cleanProps(props) { 112 | delete props.go; 113 | delete props.transitions; 114 | delete props.states; 115 | delete props.onComplete; 116 | delete props.onUpdate; 117 | delete props.component; 118 | return props; 119 | } 120 | 121 | getElement(el) { 122 | this.el = el; 123 | } 124 | 125 | render() { 126 | var style = merge( 127 | { 128 | perspective: '1000px' 129 | }, 130 | this.props.style 131 | ); 132 | 133 | if (!this.f1) { 134 | style = merge({}, this.props.style, { 135 | display: 'none' 136 | }); 137 | } 138 | 139 | var props = _extends({}, this.props, { 140 | style: style, 141 | ref: this.getElement.bind(this) 142 | }); 143 | 144 | return React.createElement( 145 | this.props.component || 'div', 146 | this.cleanProps(props), 147 | this.props.children 148 | ); 149 | } 150 | } 151 | 152 | module.exports = ReactF1; -------------------------------------------------------------------------------- /test/testChiefGo.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | var domSelect = require('dom-select'); 4 | var Chief = require(process.env.PATH_CHIEF); 5 | var UI = require('./UI'); 6 | var async = require('async'); 7 | 8 | var container; 9 | 10 | var states = { 11 | out: { 12 | item1: 'out', 13 | item2: 'out' 14 | }, 15 | 16 | idle: { 17 | item1: 'idle', 18 | item2: 'idle' 19 | }, 20 | 21 | idle2: { 22 | item1: 'idle', 23 | item2: 'out' 24 | } 25 | }; 26 | 27 | var transitions = [ 28 | { 29 | from: 'out', to: 'idle', bi: true, animation: { 30 | item1: { 31 | delay: 0.5 32 | } 33 | } 34 | }, 35 | { 36 | from: 'idle', to: 'idle2', bi: true 37 | } 38 | ]; 39 | 40 | module.exports = function(t) { 41 | 42 | var statesVisited = []; 43 | var updateItem1WasDelayed = false; 44 | var updateItemsWereInSameState = false; 45 | 46 | // each one of these objects will be applied to the `react-f1` component 47 | // a callback will be passed to the onComplete function if no onComplete 48 | // is passed then it is automatically 49 | async.eachSeries( 50 | [ 51 | { go: 'out', states: states, transitions: transitions }, 52 | { 53 | go: 'idle', states: states, transitions: transitions, 54 | style: { 55 | backgroundColor: '#00FFFF' 56 | }, 57 | onUpdate: function(state, stateName) { 58 | updateItem1WasDelayed = updateItem1WasDelayed || (state.item1 === 'out' && state.item2 === 'idle'); 59 | updateItemsWereInSameState = state.item1 === 'idle' && state.item2 === 'idle'; 60 | }, 61 | onComplete: function(callback, state, stateName) { 62 | 63 | t.deepEqual(stateName, 'idle', 'idle: state name was correct'); 64 | t.deepEqual(state, states.idle, 'idle: state was correct for complete'); 65 | statesVisited.push(stateName); 66 | 67 | callback(null); 68 | } 69 | }, 70 | { 71 | go: 'idle2', states: states, transitions: transitions, 72 | style: { 73 | backgroundColor: '#00FFFF' 74 | }, 75 | onComplete: function(callback, state, stateName) { 76 | 77 | t.deepEqual(stateName, 'idle2', 'idle2: state name was correct'); 78 | t.deepEqual(state, states.idle2, 'idle2: state was correct for complete'); 79 | statesVisited.push(stateName); 80 | 81 | callback(null); 82 | } 83 | }, 84 | { 85 | go: 'out', states: states, transitions: transitions, 86 | style: { 87 | backgroundColor: '#00FFFF' 88 | }, 89 | onComplete: function(callback, state, stateName) { 90 | 91 | t.deepEqual(stateName, 'out', 'out: state name was correct'); 92 | t.deepEqual(state, states.out, 'out: state was correct for complete'); 93 | statesVisited.push(stateName); 94 | 95 | callback(null); 96 | } 97 | } 98 | ], 99 | render, 100 | function() { 101 | t.deepEqual(statesVisited, ['idle', 'idle2', 'out'], 'visited all states'); 102 | t.ok(updateItem1WasDelayed, 'item 1 was delayed from item 2'); 103 | t.ok(updateItemsWereInSameState, 'final call to update had item1 and item2 in idle'); 104 | 105 | container.parentNode.removeChild(container); 106 | 107 | t.end(); 108 | } 109 | ); 110 | }; 111 | 112 | 113 | function render(settings, callback) { 114 | 115 | container = container || document.body.appendChild(document.createElement('div')); 116 | settings.onComplete = settings.onComplete && settings.onComplete.bind(null, callback); 117 | 118 | var component = 122 | { 123 | (states) => { 124 | return
125 | 126 | 127 |
; 128 | } 129 | } 130 |
; 131 | 132 | ReactDom.render(component, container); 133 | 134 | if(!settings.onComplete) { 135 | callback(null); 136 | } 137 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-f1 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | React UI animation components built on top of [`f1-dom`](https://www.npmjs.com/package/f1-dom) and [`f1`](https://www.npmjs.com/package/f1). 6 | 7 | ### Features 8 | 9 | - Create complex animations with custom delays, durations, eases 10 | - Animate individual properties (scale, opacity, etc.) independently of each other eg. delay the opacity animation by 0.5 seconds but scale immediately 11 | - Special handling for properties such as css colors and transforms (translate, scale, rotate) to allow for easier animation 12 | - True separation of concerns. Animations defined outside of application implementation (less Spaghetti code) 13 | - Absolute control over animations from page transitions down to individual ui components 14 | - Uses path finding to figure out how to animate from one state to another. For instance how to animate from a button from an out state to a pressed state (write less logic for complex animations) 15 | 16 | ### Table Of Contents 17 | - [Usage](#usage) 18 | - [Examples](#examples) 19 | + [Example ReactF1](#example-reactf1) 20 | + [Example Chief](#example-chief) 21 | - [Documentation](#documentation) 22 | + [ReactF1](#const-reactf1--requirereact-f1) 23 | + [Chief](#const-chief--requirereact-f1chief) 24 | 25 | ### Components 26 | 27 | There are two components which are exposed in this module: 28 | ``` 29 | var ReactF1 = require('react-f1'); 30 | // and 31 | var Chief = require('react-f1/Chief'); 32 | ``` 33 | 34 | #### `ReactF1` 35 | `ReactF1` can be used to create complete animated UI components such as buttons, toggles, accordians, etc. 36 | 37 | #### `Chief` 38 | `Chief` is used to control `ReactF1` components. For instance when a page animate's in one `Chief` component can tell all button's (which are `ReactF1` components or other `Chief` components) on a page to animate in staggered/delayed from one another. 39 | 40 | ## Usage 41 | 42 | [![NPM](https://nodei.co/npm/react-f1.png)](https://www.npmjs.com/package/react-f1) 43 | 44 | #### Install 45 | ```bash 46 | $ npm i react-f1 react react-dom --save 47 | ``` 48 | 49 | ## Examples 50 | 51 | There is an example folder distributed with this module. It contains two examples. One being a small example of how to use `ReactF1` and the other on how to use `Chief`. 52 | 53 | Details on how to run these examples are noted below. Once running in your browser you can simply edit the js files listed below and see changes immediately in browser. 54 | 55 | ## Example ReactF1 56 | 57 | ![Example ReactF1](https://media.githubusercontent.com/media/Jam3/react-f1/dev/example/react-f1.gif) 58 | 59 | This example builds and renders a small animated button that you can see in use above. 60 | 61 | #### To run the ReactF1 example: 62 | ```bash 63 | $ npm run example-f1 64 | ``` 65 | 66 | Below is a description of all example files that you might want to edit. All of these files have comments which explain each piece of the application: 67 | 68 | **example/f1/ExampleButton.js:** Example Button Component built using ReactF1. 69 | 70 | **example/f1/getStates.js:** a function which returns an Object which defines what the button should look like in each state. 71 | 72 | **example/f1/getTransitions.js:** a function which returns an Array which defines how to animate between states. 73 | 74 | ## Example Chief 75 | 76 | ![Example Chief](https://media.githubusercontent.com/media/Jam3/react-f1/dev/example/react-f1-chief.gif) 77 | 78 | The above example uses two components `SelectIndicator` (small line that moves up and down on the left) and `FancyButton` which is the buttons on the right. States and logic for selected buttons are handled by `Chief`. 79 | 80 | **To run the Chief example:** 81 | ```bash 82 | $ npm run example-chief 83 | ``` 84 | 85 | **example/chief/Menu/:** This folder contains all code including states and transitions used by `Chief` to create a Menu. (this is the menu in the above gif) 86 | 87 | **example/chief/FancyButton/:** This folder contains all code including states and transitions used by the FancyButton. (buttons on the right side of the menu in the above gif) 88 | 89 | **example/chief/SelectIndicator/:** This folder contains all code including states and transitions used by the SelectIndicator. (small line that moves up and down in the above gif) 90 | 91 | ## Documentation 92 | 93 | The following describes on a high level how ReactF1 and Chief components are used. 94 | 95 | ### `const ReactF1 = require('react-f1')` 96 | 97 | ```jsx 98 | 121 | // the following defines components which will be animated 122 | // data-f1 associates these components with properties which 123 | // be defines in the states Object described above 124 |
125 |
126 | 127 | ``` 128 | 129 | #### ReactF1 `states` 130 | 131 | ```javascript 132 | // this Object describes what this ui should look like in each state 133 | var states = { 134 | 135 | // this is the name of a state for instance this could be idle 136 | stateName1: { 137 | 138 | // this describes what a piece of the ui should look like in this state 139 | // it links to an item above described through data-f1 140 | targetName1: { 141 | 142 | // the following will describe what css styles should look like 143 | // for targetName1 in this state 144 | style: { 145 | // it should be noted that in order to have predictable behaviour with 146 | // react-f1 you should define each css property in each state that 147 | // maybe animated at some point. 148 | // eg. `left` css property in out is set to 0, idle `left` is 0 again, 149 | // and finally in the over state `left` is 100xw 150 | cssProperty1: value, 151 | cssProperty2: value 152 | } 153 | }, 154 | 155 | // this describes another piece of ui associted via the 156 | // data-f1 attribute 157 | targetName2: { 158 | style: { 159 | cssProperty1: value, 160 | cssProperty2: value 161 | } 162 | } 163 | }, 164 | 165 | // this is the name of a state for instance this could be mouseOver 166 | stateName2: { 167 | targetName1: { 168 | style: { 169 | cssProperty1: value, 170 | cssProperty2: value 171 | } 172 | }, 173 | 174 | targetName2: { 175 | style: { 176 | cssProperty1: value, 177 | cssProperty2: value 178 | } 179 | } 180 | } 181 | } 182 | ``` 183 | 184 | #### ReactF1 `transitions` 185 | 186 | ```javascript 187 | var transitions = [ 188 | // to define that it's possible to animate from one 189 | // state to another. 190 | // If no animation is defined then a default animation 191 | // will be used 192 | { from: 'stateName1', to: 'stateName2' }, 193 | 194 | // Transitions are not bi-directional 195 | // which means we will need to do this also 196 | // { from: 'stateName2', to: 'stateName1' } 197 | // however as a short form you can do 198 | // { from: 'stateName1', to: 'stateName2', bi: true } 199 | { 200 | from: 'stateName2', to: 'stateName1', 201 | 202 | // the following will define the animation to go from 203 | // stateName2 to stateName1 204 | animation: { 205 | // the following would define that all parts of the ui 206 | // will animate in 0.5 seconds and be delayed by 0.1 seconds 207 | duration: 0.5, 208 | delay: 0.1, 209 | 210 | // this will define that all animations will use this easeFunction 211 | // typically this would be one of the ease functions from: 212 | // https://www.npmjs.com/eases 213 | ease: easeFunction1, 214 | 215 | // the following would override the duraration and delay 216 | // which were defined globally just for targetName1 217 | targetName1: { 218 | duration: 0.3, 219 | delay: 0 220 | }, 221 | 222 | // you can also override animations all the way down to a 223 | // a property. 224 | // In this case everything for targetName2 would use the globally 225 | // defined animation settings: 226 | // duration 0.5 seconds and be delayed 0.1 and use easeFunction1 227 | // however you can override down to a single animatable property 228 | // in thise case cssProperty1 will have duration of 0.25, delay 0.2, 229 | // and use easeFunction2 for easing 230 | targetName2: { 231 | style: { 232 | cssProperty1: { 233 | duration: 0.25, 234 | delay: 0.2, 235 | ease: easeFunction2 236 | } 237 | } 238 | } 239 | } 240 | } 241 | ] 242 | ``` 243 | ### `const Chief = require('react-f1/Chief')` 244 | 245 | ```jsx 246 | // Chief from an API perspective looks very much the way that ReactF1 does 247 | 260 | { 261 | // with React it's very hard to manipulate deeply nested (grand child) 262 | // component's properties. 263 | // This is why you need to pass in a function that will accept an Object 264 | (states) => { 265 | // this function needs to return a single root component 266 | return
267 | 268 | // for simplicity this example does not defines states or transitions 269 | // for these ReactF1 ui components however states.target1 does 270 | // contain a variable called go and a function onComplete which 271 | // will be the state in which this ui Component should be 272 | 277 | 278 | 283 | 284 | // It should be Noted also that a Chief component can control other 285 | // Chief's 286 | 291 |
; 292 | } 293 | } 294 |
295 | ``` 296 | 297 | #### Chief `states` 298 | 299 | ```javascript 300 | var states = { 301 | // this will define what state each of the ui components 302 | // should be in during the out state 303 | out: { 304 | target1: 'out', 305 | target2: 'out', 306 | target3: 'out' 307 | }, 308 | 309 | // this will define what state each of the ui components 310 | // should be in during the idle state 311 | idle: { 312 | target1: 'idle', 313 | target2: 'idle', 314 | target3: 'idle' 315 | } 316 | }; 317 | ``` 318 | 319 | #### Chief `transitions` 320 | ```javascript 321 | var transitions = [ 322 | // this defines a path for Chief to go from out to idle 323 | // if no animations are passed then all ui's states will 324 | // be set at the same time. 325 | // 326 | // It should be noted you can also do this: 327 | // { from: 'out', to: 'idle', bi: true } 328 | // Which will create a bi-directional transition. 329 | { from: 'out', to: 'idle' }, 330 | 331 | // you may want to "stagger" ui going to a state 332 | // this is how you'd do it 333 | { from: 'out', to: 'idle', animation: { 334 | target2: { 335 | delay: 0.25 336 | }, 337 | 338 | target3: { 339 | delay: 0.5 340 | } 341 | } 342 | } 343 | ]; 344 | ``` 345 | 346 | ## License 347 | 348 | MIT, see [LICENSE.md](http://github.com/Jam3/react-f1/blob/master/LICENSE.md) for details. 349 | --------------------------------------------------------------------------------