diff --git a/README.md b/README.md index 9bef544..1ccca16 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,138 @@ $ npm run demo Visit in your browser. Hot reloading is enabled, if you want to tweak the code in main.jsx. +## Requirements + +The `VelocityComponent` and `VelocityTransitionGroup` components, as well as the `velocityHelpers` +utilities, are provided as ES5-compatible JavaScript files with [CommonJS](http://www.commonjs.org/) +`require` statements. You will need a dependency tool such as [Browserify](http://browserify.org/), +[RequireJS](http://requirejs.org/), or [webpack](https://webpack.github.io/) to use them. + +This package depends directly on Velocity, as well as [lodash](https://lodash.com/) for a handful +of utility functions (which are required individually to try and keep bundle size down). + +It is assumed that your application already depends on React. The `VelocityTransitionGroup` +component particularly requires the React addons at version 0.13 or higher. + ## Usage -TODO(phopkins): Documentation +### `VelocityComponent` + +Component to add Velocity animations to one or more children. Wraps a single child without adding +additional DOM nodes. + +#### Example +```JSX + + + +``` + +#### Details + +The API attempts to be as declarative as possible. A single `animation` property declares what +animation the child should have. If that property changes, this component applies the new animation +to the child on the next tick. + +By default, the animation is not run when the component is mounted. Instead, Velocity's `finish` +command is used to jump to the animation's end state. For a component that animates out of and +back in to a default state, this allows the parent to specify the "animate into" animation as +the default, and therefore not have to distinguish between the initial state and the state to +return to after animating away. + +#### Properties + +`animation`: Either an animation key or hash defining the animation. See Velocity's documentation + for what this can be. (It is passed to Velocity exactly.) + +`runOnMount`: If true, runs the animation even when the component is first mounted. + +`targetQuerySelector`: By default, this component's single child is animated. If `targetQuerySelector` + is provided, it is used to select descendants to apply the animation to. Use with caution, only + when you're confident that React's reconcilliation will preserve these nodes during animation. + Also note `querySelectorAll`'s [silly behavior](http://ejohn.org/blog/thoughts-on-queryselectorall/) w.r.t. pruning results when being called on a node. + A special value of "children" will use the direct children of the node, since there isn't a + great way to specify that to `querySelectorAll`. + +Unrecognized properties are passed as options to Velocity (e.g. `duration`, `delay`, `loop`). + +#### Methods + +`runAnimation`: Triggers the animation immediately. Useful for when you want an animation that + corresponds to an event but not a particular model state change (e.g. a "bump" when a click + occurs). + + +### `VelocityTransitionGroup` + +Component to add Velocity animations around React transitions. Delegates to the React `TransitionGroup` +addon. + +#### Example +```JSX + + {this.state.renderSubComponent ? : undefined} + +``` + +#### Properties +`enter`: Animation to run on a child component being added + +`leave`: Animation to run on a child component leaving + +`runOnMount`: if true, runs the `enter` animation on the elements that exist as children when this + component is mounted. + +Any additional properties (e.g. `className`, `component`) will be passed to the internal +`TransitionGroup`. + +`enter` and `leave` should either be a string naming an animation registered with UI Pack, or a hash +with an `animation` key that can either be a string itself, or a hash of style attributes to animate +(this value is passed to Velocity its the first arg). + +If `enter` or `leave` is a hash, it can additionally have a `style` value that is applied the tick +before the Velocity animation starts. Use this for non-animating properties (like `position`) that +are prerequisites for correct animation. The style value is applied using Velocity's JS -> CSS +routines, which may differ from React's. + +Any hash entries beyond `animation` and `style` are passed in an options hash to Velocity. Use this +for options like `stagger`, `reverse`, *&tc.* + +#### Statics + +`disabledForTest`: Set this to true globally to turn off all custom animation logic. Instead, this + component will behave like a vanilla TransitionGroup`. + +### `velocityHelper` + +#### `registerEffect` + +Takes a Velocity "UI pack effect" definition and registers it with a unique key, returning that +key (to later pass as a value for the `animation` property). Takes an optional `suffix`, which can +be "In" or "Out" to modify UI Pack's behavior. + +Unlike what you get from passing a style hash to `VelocityComponent`'s `animation` property, +Velocity "UI pack effects" can have chained animation calls and specify a `defaultDuration`, and +also can take advantage of `stagger` and `reverse` properties on the `VelocityComponent`. + +You will need to manually register the UI Pack with the global Velocity in your application with: +```JS + require('velocity'); + require('velocity-animate/velocity.ui'); +``` + +See: (http://julian.com/research/velocity/#uiPack) + +## Bugs +Please report any bugs to: + +## Acknowledgments +Thanks to Julian Shapiro and Ken Wheeler for creating and maintaining Velocity, respectively, +and for working with us to release this library. -### VelocityComponent +Thanks to Kevin Robinson and Sam Phillips for all of the discussions and code reviews. -### VelocityTransitionGroup +## License +Copyright 2015 Twitter, Inc. +Licensed under the MIT License: https://opensource.org/licenses/MIT diff --git a/demo/components/loading-crossfade-component.jsx b/demo/components/loading-crossfade-component.jsx index bf9f6f5..b219735 100644 --- a/demo/components/loading-crossfade-component.jsx +++ b/demo/components/loading-crossfade-component.jsx @@ -1,21 +1,26 @@ // Component for a VelocityTransitionGroup that crossfades between its children. - +// // To use this component, render with a single child that contains the "loading" version of your // UI. When data has loaded, switch the "key" of this child so that React considers it a brand // new element and triggers the enter / leave effects. The two versions of the UI are expected to // have identical heights. - -// Properties on this component are applied to the VelocityTransitionGroup component that this -// delegates to. A postion: 'relative' style is also applied since the loading effect requires -// position: 'absolute' on the child. - +// +// Properties on this component (such as "style") are applied to the VelocityTransitionGroup +// component that this delegates to. We set the VelocityTransitionGroup's container to a
by +// default, and provide enter and leave animations, though these could be overridden if it makes +// sense for your use case. A postion: 'relative' style is also applied by default since the loading +// effect requires position: 'absolute' on the child. +// +// This component defines a "duration" property that is used for both the enter and leave animation +// durations. +// // Use the property "opaque" if the children have opaque backgrounds. This will make the new element // come in 100% opacity and fade the old element out from on top of it. (Without this, opaque // elements end up bleeding the background behind the LoadingCrossfadeComponent through.) var React = require('react'); var _ = require('lodash'); -var VelocityTransitionGroup = require('../../lib/velocity-transition-group'); +var VelocityTransitionGroup = require('../../velocity-transition-group'); var LoadingCrossfadeComponent = React.createClass({ displayName: 'LoadingCrossfadeComponent', @@ -23,6 +28,10 @@ var LoadingCrossfadeComponent = React.createClass({ propTypes: { opaque: React.PropTypes.bool, duration: React.PropTypes.number, + // At most 1 child should be supplied at a time, though the animation does correctly handle + // elements moving in and out faster than the duration (so you can have 2 leaving elements + // simultaneously, for example). + children: React.PropTypes.element, }, getDefaultProps: function () { @@ -32,10 +41,11 @@ var LoadingCrossfadeComponent = React.createClass({ }, render: function () { - // position: 'relative' lets us absolutely-position the leaving child during the fade. + // We pull style out explicitly so that we can merge the position: 'relative' over any provided + // value. position: 'relative' lets us absolutely-position the leaving child during the fade. var style = _.defaults((this.props.style || {}), { position: 'relative' }); - var transitionGroupProps = _.defaults(_.omit(this.props, 'children', 'style'), { + var transitionGroupProps = _.defaults(_.omit(this.props, _.keys(this.constructor.propTypes), 'style'), { component: 'div', style: style, diff --git a/demo/examples/crossfade-example.jsx b/demo/examples/crossfade-example.jsx index 13c5cb2..858674b 100644 --- a/demo/examples/crossfade-example.jsx +++ b/demo/examples/crossfade-example.jsx @@ -1,9 +1,8 @@ var _ = require('lodash'); var React = require('react'); -var VelocityComponent = require('../../lib/velocity-component'); -var VelocityTransitionGroup = require('../../lib/velocity-transition-group'); -var VelocityHelpers = require('../../lib/velocity-helpers'); +var VelocityComponent = require('../../velocity-component'); +var VelocityTransitionGroup = require('../../velocity-transition-group'); var Box = require('../components/box'); var EmojiSpan = require('../components/emoji-span'); @@ -71,15 +70,16 @@ var CrossfadeExample = React.createClass({ render: function () { var groupStyle = { - width: 198, + borderBottom: '1px solid #3f83b7', + padding: '0 1px', }; var boxStyle = { margin: '-10px 0 0 0', + width: '100%', }; var toggleStyle = { - width: 200, backgroundColor: '#3f83b7', color: 'white', padding: 8, @@ -94,21 +94,23 @@ var CrossfadeExample = React.createClass({ }; return ( -
-
+
+
Points of Interest 👇
- - {this.state.expanded ? this.renderLocations() : null} - +
+ + {this.state.expanded ? this.renderLocations() : null} + +
-
+ @@ -125,12 +127,13 @@ var CrossfadeExample = React.createClass({ var boxStyle = { backgroundColor: '#fefefe', padding: '5px 10px', + width: 198, }; var locations = this.state.items != null ? this.state.items : Array.apply(null, Array(LOCATION_COUNT)); return ( - +
{locations.map(this.renderLocation)}
); @@ -156,8 +159,8 @@ var CrossfadeExample = React.createClass({ }; return ( -
- + {location.building}
diff --git a/demo/examples/flap-box.jsx b/demo/examples/flap-box.jsx index e6dfe2d..220deaf 100644 --- a/demo/examples/flap-box.jsx +++ b/demo/examples/flap-box.jsx @@ -1,7 +1,7 @@ var _ = require('lodash'); var React = require('react'); -var VelocityComponent = require('../../lib/velocity-component'); -var VelocityHelpers = require('../../lib/velocity-helpers'); +var VelocityComponent = require('../../velocity-component'); +var velocityHelpers = require('../../velocity-helpers'); var Box = require('../components/box'); var EmojiSpan = require('../components/emoji-span'); @@ -16,7 +16,7 @@ on a state value. var FlipAnimations = { // Brings the box from flipped up to down. Also the default state that the box starts in. When // this animates, includes a little swing at the end so it feels more like a flap. - down: VelocityHelpers.registerEffect({ + down: velocityHelpers.registerEffect({ // longer due to spring timing defaultDuration: 1100, calls: [ @@ -36,7 +36,7 @@ var FlipAnimations = { }), // Flips the box up nearly 180°. - up: VelocityHelpers.registerEffect({ + up: velocityHelpers.registerEffect({ defaultDuration: 200, calls: [ [{ @@ -57,14 +57,14 @@ var FlipAnimations = { // immediately with no tweening, since that doesn't make sense for the effect. We're using // Velocity here only to co-ordinate the timing of the change. var BlurAnimations = { - blur: VelocityHelpers.registerEffect({ + blur: velocityHelpers.registerEffect({ defaultDuration: 200, calls: [ [{ blur: [3, 3], opacity: [.4, .4] }, 1, { delay: 50 }], ], }), - unblur: VelocityHelpers.registerEffect({ + unblur: velocityHelpers.registerEffect({ defaultDuration: 200, calls: [ [{ blur: [0, 0], opacity: [1, 1] }, 1, { delay: 150 }], diff --git a/demo/examples/scrolling-group.jsx b/demo/examples/scrolling-group.jsx index fdac24e..875a989 100644 --- a/demo/examples/scrolling-group.jsx +++ b/demo/examples/scrolling-group.jsx @@ -1,6 +1,6 @@ var React = require('react'); -var VelocityTransitionGroup = require('../../lib/velocity-transition-group'); -var VelocityHelpers = require('../../lib/velocity-helpers'); +var VelocityTransitionGroup = require('../../velocity-transition-group'); +var velocityHelpers = require('../../velocity-helpers'); var Box = require('../components/box'); var EmojiSpan = require('../components/emoji-span'); @@ -11,7 +11,7 @@ var FOODS = ['🍅', '🍆', '🍇', '🍈', '🍉', '🍊', '🍌', '🍍', ' var Animations = { // Register these with UI Pack so that we can use stagger later. - In: VelocityHelpers.registerEffect({ + In: velocityHelpers.registerEffect({ calls: [ [{ transformPerspective: [ 800, 800 ], @@ -27,7 +27,7 @@ var Animations = { ], }), - Out: VelocityHelpers.registerEffect({ + Out: velocityHelpers.registerEffect({ calls: [ [{ transformPerspective: [ 800, 800 ], diff --git a/demo/examples/toggle-box.jsx b/demo/examples/toggle-box.jsx index 502da24..96857cf 100644 --- a/demo/examples/toggle-box.jsx +++ b/demo/examples/toggle-box.jsx @@ -1,5 +1,5 @@ var React = require('react'); -var VelocityComponent = require('../../lib/velocity-component'); +var VelocityComponent = require('../../velocity-component'); var tweenState = require('react-tween-state'); var s = require('underscore.string'); diff --git a/demo/examples/trigger-box.jsx b/demo/examples/trigger-box.jsx index c377040..6bfcb61 100644 --- a/demo/examples/trigger-box.jsx +++ b/demo/examples/trigger-box.jsx @@ -1,5 +1,5 @@ var React = require('react'); -var VelocityComponent = require('../../lib/velocity-component'); +var VelocityComponent = require('../../velocity-component'); var s = require('underscore.string'); var Box = require('../components/box'); diff --git a/demo/main.jsx b/demo/main.jsx index 1b24bda..edd1c02 100644 --- a/demo/main.jsx +++ b/demo/main.jsx @@ -1,8 +1,9 @@ require('./css/flexbox.css'); var React = require('react'); -var VelocityComponent = require('../lib/velocity-component'); -var VelocityTransitionGroup = require('../lib/velocity-transition-group'); + +require('velocity-animate'); +require('velocity-animate/velocity.ui'); var CrossfadeExample = require('./examples/crossfade-example'); var FlapBox = require('./examples/flap-box'); @@ -10,8 +11,6 @@ var ScrollingGroup = require('./examples/scrolling-group'); var ToggleBox = require('./examples/toggle-box'); var TriggerBox = require('./examples/trigger-box'); -require('velocity-animate/velocity.ui'); - var Demo = React.createClass({ render: function () { var boxStyle = { diff --git a/index.js b/index.js new file mode 100644 index 0000000..9bf6593 --- /dev/null +++ b/index.js @@ -0,0 +1,19 @@ +// Convenience main entrypoint if you are running with destructuring support: +// +// import {VelocityComponent, VelocityTransitionGroup} from 'velocity-react'; +// +// You can also require just the component(s) you're using: +// +// var VelocityComponent = require('velocity-react/velocity-component'); +// +// Note that if you want to use UI Pack you will need to require 'velocity' and +// 'velocity.ui' at a top level in your app: +// +// require('velocity'); +// require('velocity-animate/velocity.ui'); + +module.exports = { + VelocityComponent: require('./velocity-component'), + VelocityTransitionGroup: require('./velocity-transition-group'), + velocityHelpers: require('./velocity-helpers'), +}; diff --git a/package.json b/package.json index 91d7258..48cf49c 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "velocity-react", - "version": "0.0.1", + "version": "1.0.0", "description": "React components to wrap Velocity animations", - "main": "lib", + "main": "index.js", "scripts": { "demo": "./node_modules/.bin/webpack-dev-server --progress --colors --content-base demo --hot --inline --config demo/webpack.config.js" }, @@ -23,13 +23,14 @@ "homepage": "https://github.com/crashlytics/velocity-react#readme", "dependencies": { "lodash": "^3.10.1", - "velocity-animate": "phopkins/velocity#feature/finish-all-queued" + "velocity-animate": "^1.2.3" }, "peerDependencies": { - "react": "^0.13.3" + "react": ">=0.13.0" }, "devDependencies": { "babel-loader": "^5.3.2", + "css-loader": "^0.19.0", "react": "^0.13.3", "react-hot-loader": "^1.2.9", "react-tween-state": "^0.1.3", diff --git a/lib/velocity-component.js b/velocity-component.js similarity index 98% rename from lib/velocity-component.js rename to velocity-component.js index 598e0f2..9f4dc96 100644 --- a/lib/velocity-component.js +++ b/velocity-component.js @@ -84,7 +84,9 @@ var VelocityComponent = React.createClass({ // It's ok to call this externally! By default the animation will be queued up. Pass stop: true in // to stop the current animation before running. Pass finish: true to finish the current animation // before running. - runAnimation: function (config = {}) { + runAnimation: function (config) { + config = config || {}; + this._shouldRunAnimation = false; if (!this.isMounted() || this.props.animation == null) { diff --git a/lib/velocity-helpers.js b/velocity-helpers.js similarity index 80% rename from lib/velocity-helpers.js rename to velocity-helpers.js index 266c935..7dc5007 100644 --- a/lib/velocity-helpers.js +++ b/velocity-helpers.js @@ -1,8 +1,9 @@ +// Copyright (c) 2015 Twitter, Inc. and other contributors + var _ = { isObject: require('lodash/lang/isObject'), }; var Velocity = require('velocity-animate'); -require('velocity-animate/velocity.ui'); var effectCounter = 0; @@ -14,6 +15,11 @@ var effectCounter = 0; // Velocity "UI pack effects" can have chained animation calls and specify a "defaultDuration", and // also can take advantage of "stagger" and "reverse" options on the VelocityComponent. // +// You will need to manually register the UI Pack with the global Velocity in your application with: +// +// require('velocity'); +// require('velocity-animate/velocity.ui'); +// // See: http://julian.com/research/velocity/#uiPack // // Typical usage: @@ -54,6 +60,10 @@ function registerEffect(suffix, animation) { suffix = ''; } + if (Velocity.RegisterEffect === undefined) { + throw "Velocity.RegisterEffect not found. You need to require('velocity-animate/velocity.ui') at a top level for UI Pack."; + } + var key = 'VelocityHelper.animation.' + (effectCounter++) + suffix; Velocity.RegisterEffect(key, animation); return key; diff --git a/lib/velocity-transition-group.js b/velocity-transition-group.js similarity index 99% rename from lib/velocity-transition-group.js rename to velocity-transition-group.js index 15020cc..2ab50fa 100644 --- a/lib/velocity-transition-group.js +++ b/velocity-transition-group.js @@ -1,8 +1,8 @@ /* Copyright (c) 2015 Twitter, Inc. and other contributors -Component to add Velocity animations around React transitions. Delegate's to the React -TransitionGroup addon. +Component to add Velocity animations around React transitions. Delegates to the React TransitionGroup +addon. Properties enter: Animation to run on a child component being added