├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .size-snapshot.json ├── .travis.yml ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── babel.config.js ├── docs ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── components │ │ ├── App.js │ │ ├── AppContent.js │ │ ├── AppFrame.js │ │ ├── AppRouter.js │ │ ├── ComponentDoc.js │ │ ├── Demo.js │ │ ├── Github.js │ │ ├── MarkdownDocs.js │ │ ├── MarkdownElement.js │ │ ├── PropsDescription │ │ │ ├── PropsDescription.js │ │ │ ├── index.js │ │ │ └── props-description.css │ │ ├── Surface.js │ │ └── files.js │ ├── index.js │ ├── pages │ │ ├── Home.js │ │ ├── component-api │ │ │ ├── animate.md │ │ │ └── node-group.md │ │ ├── demos │ │ │ ├── animate │ │ │ │ ├── Example1.js │ │ │ │ ├── Example2.js │ │ │ │ ├── animate.md │ │ │ │ └── states.json │ │ │ ├── animated-bar │ │ │ │ ├── Example1.js │ │ │ │ └── animated-bar.md │ │ │ ├── charts │ │ │ │ ├── DonutChart1.js │ │ │ │ ├── DonutChart2.js │ │ │ │ └── charts.md │ │ │ ├── collapsible-tree │ │ │ │ ├── Example1.js │ │ │ │ └── collapsible-tree.md │ │ │ ├── draggable-list │ │ │ │ ├── Example1.js │ │ │ │ └── draggable-list.md │ │ │ ├── node-group │ │ │ │ ├── Example1.js │ │ │ │ ├── Example2.js │ │ │ │ └── node-group.md │ │ │ └── simple │ │ │ │ ├── Bars1.js │ │ │ │ ├── Bars2.js │ │ │ │ ├── Circles.js │ │ │ │ ├── Toggle.js │ │ │ │ └── simple.md │ │ ├── getting-started │ │ │ ├── features.md │ │ │ └── installation.md │ │ └── logo-react-move.png │ └── utils │ │ ├── helpers.js │ │ └── prism.js ├── webpack.dev.config.js ├── webpack.dev.server.js └── webpack.prd.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts ├── build.js ├── copy-files.js └── run-travis-tests.sh ├── src ├── Animate │ ├── index.d.ts │ ├── index.js │ └── index.spec.js ├── NodeGroup │ ├── index.d.ts │ ├── index.js │ └── index.spec.js ├── core │ ├── mergeKeys.js │ ├── mergeKeys.spec.js │ └── types.js ├── index.d.ts ├── index.js ├── index.spec.js └── utils.js └── test ├── README.md ├── mocha.opts └── utils ├── createDOM.js ├── createMount.js ├── init.js ├── performance.js └── setup.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.md] 7 | trim_trailing_whitespace = false 8 | 9 | [*.js] 10 | trim_trailing_whitespace = true 11 | 12 | # Unix-style newlines with a newline ending every file 13 | [*] 14 | indent_style = space 15 | indent_size = 2 16 | end_of_line = lf 17 | charset = utf-8 18 | insert_final_newline = true 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /flow 4 | /tmp 5 | /docs/build 6 | /test/coverage 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: ['eslint:recommended', 'plugin:react/recommended'], 8 | plugins: ['react', 'babel'], 9 | settings: { 10 | react: { 11 | version: 'detect', 12 | }, 13 | }, 14 | parser: 'babel-eslint', 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | ecmaVersion: 2018, 20 | sourceType: 'module', 21 | }, 22 | rules: { 23 | 'react/jsx-uses-vars': 2, 24 | 'react/no-direct-mutation-state': 0, 25 | indent: ['error', 2, { SwitchCase: 1 }], 26 | 'linebreak-style': ['error', 'unix'], 27 | quotes: ['error', 'single'], 28 | semi: ['error', 'never'], 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude compiled files 2 | build 3 | coverage 4 | /lib 5 | .eslintcache 6 | .nyc_output 7 | tmp 8 | 9 | # OS files 10 | .DS_STORE 11 | 12 | # npm files 13 | node_modules 14 | *.log 15 | 16 | # Editor files 17 | .idea 18 | .vscode 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /.size-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "build/dist/react-move.js": { 3 | "bundled": 64502, 4 | "minified": 21384, 5 | "gzipped": 7169 6 | }, 7 | "build/dist/react-move.min.js": { 8 | "bundled": 9875, 9 | "minified": 9835, 10 | "gzipped": 3588 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | sudo: false 6 | cache: 7 | directories: 8 | - node_modules 9 | script: 10 | - ./scripts/run-travis-tests.sh 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Opening an Issue 4 | 5 | Thanks for contributing! If you think you have found a bug, or have a new feature idea, please make sure it hasn't already been reported. You can search through existing issues and PRs to see if someone has reported one similar to yours. 6 | 7 | Next, create a new issue that briefly explains the problem, and provides a bit of background as to the circumstances that triggered it, and steps to reproduce it. 8 | 9 | For code issues please include: 10 | 11 | - React-Move version 12 | - React version 13 | - Browser version 14 | - A code example or link to a repo, gist or running site. 15 | 16 | ## Submitting a Pull Request 17 | 18 | Pull requests are always welcome, but before working on a large change, it is best to open an issue first. 19 | 20 | When in doubt, keep your pull requests small. To give a PR the best chance of getting accepted, don't bundle more than one feature or bug fix per pull request. It's always best to create two smaller PRs than one big one. 21 | 22 | When adding new features or modifying existing, please attempt to include tests to confirm the new behaviour. 23 | 24 | ### Branch Structure 25 | 26 | All stable releases are tagged ([view tags](https://github.com/sghall/react-move/tags)). At any given time, `master` represents the latest development version of the library. Patches or hotfix releases are prepared on an independent branch. 27 | 28 | ## Getting started 29 | 30 | Please create a new branch from an up to date master on your fork. (Note, urgent hotfixes should be branched off the latest stable release rather than master) 31 | 32 | 1. Fork the react-move repository on Github 33 | 2. Clone your fork to your local machine `git clone git@github.com:/react-move.git` 34 | 3. Create a branch `git checkout -b my-topic-branch` 35 | 4. Make your changes, lint, then push to to github with `git push --set-upstream origin my-topic-branch`. 36 | 5. Visit github and make your pull request. 37 | 38 | If you have an existing local repository, please update it before you start, to minimise the chance of merge conflicts. 39 | 40 | ```js 41 | git remote add upstream git@github.com:sghall/react-move.git 42 | git checkout master 43 | git pull upstream master 44 | git checkout -b my-topic-branch 45 | npm update 46 | ``` 47 | 48 | ### The documentation site 49 | 50 | ```js 51 | npm install 52 | cd docs 53 | npm install 54 | npm start 55 | ``` 56 | 57 | You can now access the documentation site [locally](http://localhost:3000). 58 | 59 | ### Coding style 60 | 61 | Please follow the coding style of the current code base. react-move uses eslint, so if possible, enable linting in your editor to get realtime feedback. The linting rules are also run when Webpack recompiles your changes, and can be run manually with `npm run lint`. 62 | 63 | ## Roadmap 64 | 65 | More tests. 66 | More documentation. 67 | More examples. 68 | 69 | ## License 70 | 71 | By contributing your code you agree to license your contribution under the MIT license. 72 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Problem or feature description 2 | 3 | ### Steps to reproduce (only needed for problems) 4 | 5 | ### Versions (only needed for problems) 6 | 7 | - React-Move: 8 | - React: 9 | - Browser: 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2017 Steven Hall and Tanner Linsley 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [ ] PR has tests / docs demo, and is linted. 4 | - [ ] Commit and PR titles begin with [ComponentName], and are in imperative form: "[Component] Fix leaky abstraction". 5 | - [ ] Description explains the issue / use-case resolved, and auto-closes the related issue(s) (http://tr.im/vFqem). 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | React Table Logo 3 |
4 | 5 | # React-Move 6 | 7 | Beautiful, data-driven animations for React. Just 3.5kb (gzipped)! 8 | 9 | ### [Documentation and Examples](https://react-move-docs.netlify.app) 10 | 11 | [![npm version](https://img.shields.io/npm/v/react-move.svg)](https://www.npmjs.com/package/react-move) 12 | [![npm downloads](https://img.shields.io/npm/dm/react-move.svg)](https://www.npmjs.com/package/react-move) 13 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/sghall/react-move/blob/master/LICENSE) 14 | 15 | ## Features 16 | 17 | - Animate HTML, SVG & React-Native 18 | - Fine-grained control of delay, duration and easing 19 | - Animation lifecycle events: start, interrupt, end 20 | - Custom tweening functions 21 | - Awesome documentation and lots of examples 22 | - Supports TypeScript 23 | 24 | ## Installation 25 | 26 | ```bash 27 | // React ^16.3.0 28 | npm install react-move 29 | 30 | // React ^0.14.9 || ^15.3.0 || ^16.0.0 31 | npm install react-move@^5.0.0 32 | ``` 33 | 34 | **Note:** The API for React Move 5.x and 6.x is exactly the same. The 5.x version just includes [react-lifecycles-compat](https://www.npmjs.com/package/react-lifecycles-compat) to make the library work with earlier versions of React. This adds a little to the bundle so use 6.x if you're using React 16.3+. 35 | 36 | ### Upgrading from React Move 2.x and 3.x 37 | 38 | The API for React Move has been essentially stable since the 2.0 version. The 4.0 version of React Move introduced a change that broke the hard dependency on d3-interpolate and introduced the `interpolation` prop. The current version of React Move will by default only do numeric interpolation and apply easing functions. **If you only need to do numeric interpolation you don't need to do anything. Just upgrade and done.** 39 | 40 | To get the same interpolation found in React Move 2.x and 3.x which includes support for colors, paths and SVG transforms do this: 41 | 42 | Install d3-interpolate: 43 | 44 | ``` 45 | npm install d3-interpolate 46 | ``` 47 | 48 | Then in your app: 49 | 50 | ```js 51 | import { NodeGroup } from 'react-move' 52 | import { interpolate, interpolateTransformSvg } from 'd3-interpolate' 53 | 54 | ... 55 | d.name} 58 | 59 | start={(data, index) => ({ 60 | ... 61 | })} 62 | 63 | enter={(data, index) => ([ // An array 64 | ... 65 | ])} 66 | 67 | update={(data) => ({ 68 | ... 69 | })} 70 | 71 | leave={() => ({ 72 | ... 73 | })} 74 | 75 | interpolation ={(begValue, endValue, attr) => { // pass as prop 76 | if (attr === 'transform') { 77 | return interpolateTransformSvg(begValue, endValue) 78 | } 79 | 80 | return interpolate(begValue, endValue) 81 | }} 82 | > 83 | ...children 84 | 85 | ``` 86 | 87 | ## Demos 88 | 89 | - [CodeSandbox - Animated Bars](https://codesandbox.io/s/w0ol90x9z5) ([@peterrcook](https://github.com/peterrcook)) 90 | - [Blog Post by Peter Cook](https://www.createwithdata.com/react-move-bar-chart) 91 | - [CodeSandbox - Collapsible Tree](https://codesandbox.io/s/ww0xkyqonk) ([@techniq](https://github.com/techniq)) 92 | - [CodeSandbox - Draggable List](https://codesandbox.io/s/j2povnz8ly) 93 | - [CodeSandbox - Circle Inferno](https://codesandbox.io/s/n033m6nw00) 94 | - [CodeSandbox - Animated Mount/Unmount](https://codesandbox.io/s/9z04rpypny) 95 | - [Examples](https://react-move-docs.netlify.app) 96 | 97 | # Documentation 98 | 99 | The docs below are for version **6.x.x** of React-Move. 100 | 101 | Older versions: 102 | 103 | - [Version 1.x.x](https://github.com/sghall/react-move/tree/v1.6.1) 104 | 105 | The API for `NodeGroup` and `Animate` have not changed except for the `interpolation`xw prop, but if you want to refer back: 106 | 107 | - [Version 2.x.x](https://github.com/sghall/react-move/tree/v2.9.1) 108 | - [Version 3.x.x](https://github.com/sghall/react-move/tree/v3.1.0) 109 | - [Version 4.x.x](https://github.com/sghall/react-move/tree/v4.0.0) 110 | - [Version 5.x.x](https://github.com/sghall/react-move/tree/v5.0.0) 111 | 112 | # Getting Started 113 | 114 | React Move exports just two components: 115 | 116 | - NodeGroup - If you have an **array of items** that enter, update and leave 117 | - Animate - If you have a **singe item** that enters, updates and leaves 118 | 119 | ## < NodeGroup /> 120 | 121 | ### Component Props 122 | 123 | | Name | Type | Default | Description | 124 | | :------------------------------------------------- | :------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | 125 | | data \* | array | | An array. The data prop is treated as immutable so the nodes will only update if prev.data !== next.data. | 126 | | keyAccessor \* | function | | Function that returns a string key given the data and its index. Used to track which nodes are entering, updating and leaving. | 127 | | interpolation | function | numeric | A function that returns an interpolator given the begin value, end value, attr and namespace. Defaults to numeric interpolation. See docs for more. | 128 | | start \* | function | | A function that returns the starting state. The function is passed the data and index and must return an object. | 129 | | enter | function | () => {} | A function that **returns an object or array of objects** describing how the state should transform on enter. The function is passed the data and index. | 130 | | update | function | () => {} | A function that **returns an object or array of objects** describing how the state should transform on update. The function is passed the data and index. | 131 | | leave | function | () => {} | A function that **returns an object or array of objects** describing how the state should transform on leave. The function is passed the data and index. | 132 | | children \* | function | | A function that receives an array of nodes. | 133 | 134 | ## < Animate /> 135 | 136 | ### Component Props 137 | 138 | | Name | Type | Default | Description | 139 | | :---------------------------------------------- | :------------------------------------------------------ | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 140 | | show | bool | true | Boolean value that determines if the child should be rendered or not. | 141 | | interpolation | function | numeric | A function that returns an interpolator given the begin value, end value, atrr and namespace. See docs for more. | 142 | | start | union:
 func
 object
| | An object or function that returns an obejct to be used as the starting state. | 143 | | enter | union:
 func
 array
 object
| | An object, array of objects, or function that returns an object or array of objects describing how the state should transform on enter. | 144 | | update | union:
 func
 array
 object
| | An object, array of objects, or function that returns an object or array of objects describing how the state should transform on update. **_Note:_** although not required, in most cases it make sense to specify an update prop to handle interrupted enter and leave transitions. | 145 | | leave | union:
 func
 array
 object
| | An object, array of objects, or function that returns an object or array of objects describing how the state should transform on leave. | 146 | | children \* | function | | A function that receives the state. | 147 | 148 | ## Starting state 149 | 150 | Before looking at the components it might be good to look at starting state. You are going to be asked to define starting states for each item in your `NodeGroup` and `Animate` components. This is a key concept and probably the most error prone for developers working with React Move. The starting state for each item is always **an object with string or number leaves**. The leaf keys are referred to as "attrs" as in "attribute." There are also "namespaces" which are a purely organizational concept. 151 | 152 | Two rules to live by for starting states: 153 | 154 | - Don't use the strings "timing" or "events" as an attr or namespace. 155 | - There should never be an array anywhere in your object. 156 | 157 | Example starting state: 158 | 159 | ```js 160 | // GOOD 161 | { 162 | attr1: 100, 163 | attr2: 200, 164 | attr3: '#dadada' 165 | } 166 | 167 | // BAD 168 | { 169 | attr1: [100], // NO ARRAYS 170 | attr2: 200, 171 | attr3: '#dadada' 172 | } 173 | ``` 174 | 175 | A more concrete example might be: 176 | 177 | ```js 178 | { 179 | opacity: 0.1, 180 | x: 200, 181 | y: 100, 182 | color: '#dadada' 183 | } 184 | ``` 185 | 186 | You can add "namespaces" to help organize your state: 187 | 188 | ```js 189 | { 190 | attr1: 100, 191 | attr2: 200, 192 | attr3: '#ddaabb', 193 | namespace1: { 194 | attr1: 100, 195 | attr2: 200 196 | } 197 | } 198 | ``` 199 | 200 | Or something like: 201 | 202 | ```js 203 | { 204 | namespace1: { 205 | attr1: 100, 206 | attr2: 200 207 | }, 208 | namespace2: { 209 | attr1: 100, 210 | attr2: 200 211 | } 212 | } 213 | ``` 214 | 215 | You might use namespaces like so: 216 | 217 | ```js 218 | { 219 | inner: { 220 | x: 100, 221 | y: 150, 222 | color: '#545454' 223 | }, 224 | outer: { 225 | x: 300, 226 | y: 350, 227 | color: '#3e3e3e' 228 | } 229 | } 230 | ``` 231 | 232 | #### Starting state in NodeGroup 233 | 234 | In `NodeGroup` you are working with an array of items and you pass a start prop (a function) that receives the data item and its index. The start prop will be called when that data item (identified by its key) enters. Note it could leave and come back and that prop will be called again. Immediately after the starting state is set your enter transition (optional) is called allowing you to transform that state. 235 | 236 | ```js 237 | item.name} // function to get the key of each object (required) 240 | start={(item, index) => ({ // returns the starting state of node (required) 241 | ... 242 | })} 243 | > 244 | {(nodes) => ( 245 | ... 246 | {nodes.map(({ key, data, state }) => { 247 | ... 248 | })} 249 | ... 250 | )} 251 | 252 | ``` 253 | 254 | #### Starting state in Animate 255 | 256 | In `Animate` you are animating a single item and pass a start prop that is an object or a function. The start prop will be called when that the item enters. Note it could leave and come back by toggling the show prop. Immediately after the starting state is set your enter transition (optional) is called allowing you to transform that state. 257 | 258 | ```js 259 | 264 | {state => ( 265 | ... 266 | )} 267 | 268 | ``` 269 | 270 | ## Transitioning state 271 | 272 | You return a config object or an array of config objects in your **enter**, **update** and **leave** props functions for both `NodeGroup` and `Animate`. Instead of simply returning the next state these objects describe how to transform the state. Each config object can specify its own duration, delay, easing and events independently. 273 | 274 | There are two special keys you can use: **timing** and **events**. Both are optional. 275 | Timing and events are covered in more detail below. 276 | 277 | If you aren't transitioning anything then it wouldn't make sense to be using NodeGroup. 278 | That said, it's convenient to be able to set a key to value when a node enters, updates or leaves without transitioning. 279 | To support this you can return four different types of values to specify how you want to transform the state. 280 | 281 | - `string or number`: Set the key to the value immediately with no transition. Ignores all timing values. 282 | 283 | - `array [value]`: Transition from the key's current value to the specified value. Value is a string or number. 284 | 285 | - `array [value, value]`: Transition from the first value to the second value. Each value is a string or number. 286 | 287 | - `function`: Function will be used as a custom tween function. 288 | 289 | Example config object: 290 | 291 | ```js 292 | { 293 | attr1: [200], 294 | attr2: 300, 295 | attr3: ['#dadada'] 296 | timing: { duration: 300, delay: 100 } 297 | } 298 | ``` 299 | 300 | Using namespaces: 301 | 302 | ```js 303 | { 304 | attr1: [100], 305 | attr3: '#ddaabb', 306 | namespace1: { 307 | attr1: [300], 308 | attr2: 200 309 | }, 310 | timing: { duration: 300, delay: 100 } 311 | } 312 | ``` 313 | 314 | To have different timing for some keys use an array of config objects: 315 | 316 | ```js 317 | [ 318 | { 319 | attr1: [200, 500], 320 | timing: { duration: 300, delay: 100 } 321 | }, 322 | { 323 | attr2: 300, // this item, not wrapped in an array, will be set immediately, so which object it's in doesn't matter 324 | attr3: ['#dadada'] 325 | timing: { duration: 600 } 326 | }, 327 | ] 328 | ``` 329 | 330 | ### Example Transitions in NodeGroup 331 | 332 | ```js 333 | d.name} 336 | 337 | start={(data, index) => ({ 338 | opacity: 1e-6, 339 | x: 1e-6, 340 | fill: 'green', 341 | width: scale.bandwidth(), 342 | })} 343 | 344 | enter={(data, index) => ({ 345 | opacity: [0.5], // transition opacity on enter 346 | x: [scale(data.name)], // transition x on on enter 347 | timing: { duration: 1500 }, // timing for transitions 348 | })} 349 | 350 | update={(data) => ({ 351 | ... 352 | })} 353 | 354 | leave={() => ({ 355 | ... 356 | })} 357 | > 358 | {(nodes) => ( 359 | ... 360 | )} 361 | 362 | ``` 363 | 364 | Using an array of config objects: 365 | 366 | ```js 367 | import { easeQuadInOut } from 'd3-ease'; 368 | 369 | ... 370 | 371 | d.name} 374 | 375 | start={(data, index) => ({ 376 | opacity: 1e-6, 377 | x: 1e-6, 378 | fill: 'green', 379 | width: scale.bandwidth(), 380 | })} 381 | 382 | enter={(data, index) => ([ // An array 383 | { 384 | opacity: [0.5], // transition opacity on enter 385 | timing: { duration: 1000 }, // timing for transition 386 | }, 387 | { 388 | x: [scale(data.name)], // transition x on on enter 389 | timing: { delay: 750, duration: 1500, ease: easeQuadInOut }, // timing for transition 390 | }, 391 | ])} 392 | 393 | update={(data) => ({ 394 | ... 395 | })} 396 | 397 | leave={() => ({ 398 | ... 399 | })} 400 | > 401 | {(nodes) => ( 402 | ... 403 | )} 404 | 405 | ``` 406 | 407 | ## Timing 408 | 409 | If there's no timing key in your object you'll get the timing defaults. 410 | You can specify just the things you want to override on your timing key. 411 | 412 | Here's the timing defaults... 413 | 414 | ```js 415 | const defaultTiming = { 416 | delay: 0, 417 | duration: 250, 418 | ease: easeLinear 419 | }; 420 | ``` 421 | 422 | For the ease key, just provide the function. You can use any easing function, like those from d3-ease... 423 | 424 | [List of ease functions exported from d3-ease](https://github.com/d3/d3-ease/blob/master/src/index.js) 425 | 426 | ## Events 427 | 428 | You can add events on your config objects. You can pass a function that will run when the transition starts, is interrupted (an update to the data occurs) or ends. 429 | 430 | Using Events: 431 | 432 | ```js 433 | { 434 | attr1: [100], 435 | attr3: '#ddaabb', 436 | namespace1: { 437 | attr1: [300], 438 | attr2: 200 439 | }, 440 | timing: { duration: 300, delay: 100 }, 441 | events: { 442 | start: () => { 443 | ..do stuff - use an arrow function to keep the context of the outer component 444 | }, 445 | interrupt: () => { 446 | ..do stuff - use an arrow function to keep the context of the outer component 447 | }, 448 | end: () => { 449 | ..do stuff - use an arrow function to keep the context of the outer component 450 | }, 451 | } 452 | } 453 | ``` 454 | 455 | ### Interpolation 456 | 457 | You can wire your components in `react-move` to handle different types of interpolation using the `interpolation` prop in both `NodeGroup` and `Animate`. The code for interpolating strings or SVG paths can be bulky and, in many cases, it's not needed so by default components only handle numeric interpolation. 458 | 459 | Your `interpolation` prop is a function that should avoid a lot of logic and computation. It will get called at high frequency when transitions fire in your components. You get the begin and end values and what the attribute name (string) is. You will also get the namespace string (less common) if you are using them in your state. **See the sections on starting states and transitions for more on attrs and namespaces.** 460 | 461 | #### Cadillac Interpolation - Depends on d3-interpolate 462 | 463 | To wire up a full service interpolation that will interpolate colors, paths, numbers and SVG transforms you can use a setup like this: 464 | 465 | ``` 466 | npm install react-move d3-interpolate 467 | ``` 468 | 469 | Then in your app: 470 | 471 | ```js 472 | import { NodeGroup, Animate } from 'react-move' 473 | import { interpolate, interpolateTransformSvg } from 'd3-interpolate' 474 | 475 | ... 476 | d.name} 479 | 480 | start={(data, index) => ({ 481 | ... 482 | })} 483 | 484 | enter={(data, index) => ([ // An array 485 | ... 486 | ])} 487 | 488 | update={(data) => ({ 489 | ... 490 | })} 491 | 492 | leave={() => ({ 493 | ... 494 | })} 495 | 496 | interpolation ={(begValue, endValue, attr, namespace) => { // pass as prop 497 | if (attr === 'transform') { 498 | return interpolateTransformSvg(begValue, endValue) 499 | } 500 | 501 | return interpolate(begValue, endValue) 502 | }} 503 | > 504 | ...children 505 | 506 | ``` 507 | 508 | This setup mimics how `d3.js` works for selecting interpolators and will not force you to think too much about the values your are using. For example, if you use colors (in any format) they will be recognized and interpolated correctly. The `interpolate` function exported from d3-interpolate does a great job of guessing what you're trying to do and handles it for you but it also includes a lot of code (e.g. d3-color) that may not be needed for your project. 509 | 510 | #### Numeric Interpolation Only - Default - No dependencies 511 | 512 | To do numeric interpolation you don't need to do anything in your components. The default numeric interpolator looks like this: 513 | 514 | ```js 515 | // The default interpolator used in NodeGroup and Animate 516 | 517 | const numeric = (beg, end) => { 518 | const a = +beg; 519 | const b = +end - a; 520 | 521 | return function(t) { 522 | return a + b * t; 523 | }; 524 | }; 525 | ``` 526 | 527 | ## React-Move vs React-Motion 528 | 529 | - React-move allows you to define your animations using durations, delays and ease functions. 530 | In react-motion you use spring configurations to define your animations. 531 | 532 | - React-move is designed to easily plugin interpolation for strings, numbers, colors, SVG paths and SVG transforms. 533 | With react-motion you can only interpolate numbers so you have to do a bit more work use colors, paths, etc. 534 | 535 | - In react-move you can define different animations for entering, updating and leaving with the ability to specify delay, duration and ease on each individual key. 536 | React-motion allows you to define a spring configuration for each key in the "style" object. 537 | 538 | - React-move has lifecycle events on its transitions. 539 | You can pass a function to be called on transition start, interrupt or end. 540 | React-motion has an "onRest" prop that fires a callback when the animation stops (just the `Motion` component not `TransitionMotion` or `StaggeredMotion`). 541 | 542 | - React-move also allows you to pass your own custom tween functions. It's all springs in react-motion. 543 | 544 | ## Contributing 545 | 546 | We love contributions from the community! Read the [contributing info here](https://github.com/sghall/react-move/blob/master/CONTRIBUTING.md). 547 | 548 | #### Run the repo locally 549 | 550 | - Fork this repo 551 | - `npm install` 552 | - `cd docs` 553 | - `npm install` 554 | - `npm start` 555 | 556 | #### Scripts 557 | 558 | Run these from the root of the repo 559 | 560 | - `npm run lint` Lints all files in src and docs 561 | - `npm run test` Runs the test suite locally 562 | - `npm run test:coverage` Get a coverage report in the console 563 | - `npm run test:coverage:html` Get an HTML coverage report in coverage folder 564 | 565 | Go to [live examples, code and docs](https://react-move-docs.netlify.app)! 566 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | esm: { 4 | exclude: /node_modules/, 5 | presets: [ 6 | [ 7 | '@babel/preset-env', 8 | { 9 | modules: false, 10 | }, 11 | ], 12 | '@babel/preset-react', 13 | ], 14 | plugins: [ 15 | '@babel/plugin-proposal-class-properties', 16 | ['transform-react-remove-prop-types', { mode: 'wrap' }], 17 | ['@babel/transform-runtime', { useESModules: true }], 18 | ], 19 | }, 20 | cjs: { 21 | exclude: /node_modules/, 22 | presets: [ 23 | [ 24 | '@babel/preset-env', 25 | { 26 | modules: 'commonjs', 27 | }, 28 | ], 29 | '@babel/preset-react', 30 | ], 31 | plugins: [ 32 | '@babel/plugin-proposal-class-properties', 33 | ['transform-react-remove-prop-types', { mode: 'wrap' }], 34 | ], 35 | }, 36 | test: { 37 | exclude: /node_modules/, 38 | presets: [ 39 | [ 40 | '@babel/preset-env', 41 | { 42 | modules: 'commonjs', 43 | }, 44 | ], 45 | '@babel/preset-react', 46 | ], 47 | plugins: ['@babel/plugin-proposal-class-properties'], 48 | }, 49 | coverage: { 50 | exclude: /node_modules/, 51 | presets: [ 52 | [ 53 | '@babel/preset-env', 54 | { 55 | modules: 'commonjs', 56 | }, 57 | ], 58 | '@babel/preset-react', 59 | ], 60 | plugins: [ 61 | '@babel/plugin-proposal-class-properties', 62 | 'babel-plugin-istanbul', 63 | ], 64 | }, 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Move 6 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-move-docs", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "React move docs", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/sghall/react-move.git" 9 | }, 10 | "scripts": { 11 | "start": "cross-env ../node_modules/.bin/babel-node webpack.dev.server.js", 12 | "build": "cross-env NODE_ENV=production webpack --config webpack.prd.config.js", 13 | "clean": "../node_modules/.bin/rimraf node_modules" 14 | }, 15 | "dependencies": { 16 | "@material-ui/core": "^4.11.4", 17 | "@material-ui/icons": "^4.11.2", 18 | "babel-loader": "^8.1.0", 19 | "classnames": "^2.3.1", 20 | "cross-env": "^5.2.1", 21 | "css-loader": "^5.2.6", 22 | "d3-array": "^2.4.0", 23 | "d3-ease": "^1.0.6", 24 | "d3-format": "^1.4.4", 25 | "d3-scale": "^2.2.2", 26 | "d3-shape": "^1.3.7", 27 | "file-loader": "^6.2.0", 28 | "flubber": "^0.4.2", 29 | "history": "^3.3.0", 30 | "image-webpack-loader": "^4.1.0", 31 | "lodash": "^4.17.15", 32 | "marked": "^0.7.0", 33 | "prismjs": "^1.20.0", 34 | "raw-loader": "^4.0.2", 35 | "react": "^17.0.2", 36 | "react-docgen": "^2.21.0", 37 | "react-dom": "^17.0.2", 38 | "react-hot-loader": "^4.12.21", 39 | "react-redux": "^7.2.4", 40 | "react-router": "^5.2.0", 41 | "react-router-dom": "^5.2.0", 42 | "recast": "^0.16.2", 43 | "redux": "^4.0.5", 44 | "sass-loader": "^12.1.0", 45 | "style-loader": "^0.23.1", 46 | "topojson-client": "^3.1.0", 47 | "webpack": "^5.38.1", 48 | "webpack-cli": "^4.7.2", 49 | "webpack-dev-server": "^3.11.2" 50 | }, 51 | "devDependencies": { 52 | "os-browserify": "^0.3.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/src/components/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | import { connect } from 'react-redux' 6 | import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles' 7 | import { createMuiTheme } from '@material-ui/core/styles' 8 | import grey from '@material-ui/core/colors/grey' 9 | import deepOrange from '@material-ui/core/colors/deepOrange' 10 | import { lightTheme, darkTheme, setPrismTheme } from '../utils/prism' 11 | import { AppRouter } from './AppRouter' 12 | 13 | function AppContainer(props) { 14 | const { dark } = props 15 | 16 | const theme = createMuiTheme({ 17 | typography: { 18 | suppressWarning: true, 19 | }, 20 | palette: { 21 | primary: grey, 22 | secondary: grey, 23 | accent: deepOrange, 24 | type: dark ? 'dark' : 'light', 25 | }, 26 | }) 27 | 28 | /* eslint-disable-next-line */ 29 | console.log('theme: ', theme) 30 | 31 | if (dark) { 32 | setPrismTheme(darkTheme) 33 | } else { 34 | setPrismTheme(lightTheme) 35 | } 36 | 37 | return ( 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | AppContainer.propTypes = { 45 | dark: PropTypes.bool.isRequired, 46 | } 47 | 48 | const ConnectedApp = connect(state => ({ dark: state.dark }))(AppContainer) 49 | 50 | function App() { 51 | return 52 | } 53 | 54 | export default App 55 | -------------------------------------------------------------------------------- /docs/src/components/AppContent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { withStyles } from '@material-ui/core/styles' 5 | 6 | const styles = theme => ({ 7 | content: theme.mixins.gutters({ 8 | flex: '1 1 100%', 9 | maxWidth: '100%', 10 | margin: '0 auto', 11 | }), 12 | [theme.breakpoints.up(948)]: { 13 | content: { 14 | maxWidth: 900, 15 | }, 16 | }, 17 | }) 18 | 19 | function AppContent(props) { 20 | const { className, classes, children } = props 21 | 22 | return ( 23 |
{children}
24 | ) 25 | } 26 | 27 | AppContent.propTypes = { 28 | children: PropTypes.node, 29 | classes: PropTypes.object.isRequired, 30 | className: PropTypes.string, 31 | route: PropTypes.object.isRequired, 32 | } 33 | 34 | export default withStyles(styles)(AppContent) 35 | -------------------------------------------------------------------------------- /docs/src/components/AppFrame.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import AppBar from '@material-ui/core/AppBar' 4 | import CssBaseline from '@material-ui/core/CssBaseline' 5 | import Divider from '@material-ui/core/Divider' 6 | import Drawer from '@material-ui/core/Drawer' 7 | import Hidden from '@material-ui/core/Hidden' 8 | import IconButton from '@material-ui/core/IconButton' 9 | import List from '@material-ui/core/List' 10 | import ListItem from '@material-ui/core/ListItem' 11 | import Home from '@material-ui/icons/HomeRounded' 12 | import ListItemText from '@material-ui/core/ListItemText' 13 | import MenuIcon from '@material-ui/icons/Menu' 14 | import Toolbar from '@material-ui/core/Toolbar' 15 | import Typography from '@material-ui/core/Typography' 16 | import { withStyles } from '@material-ui/core/styles' 17 | import { Link } from 'react-router-dom' 18 | import GitHub from './Github' 19 | 20 | const drawerWidth = 240 21 | 22 | const styles = theme => ({ 23 | root: { 24 | display: 'flex', 25 | color: theme.palette.text.primary, 26 | }, 27 | navTitle: { 28 | marginLeft: 10, 29 | }, 30 | drawer: { 31 | [theme.breakpoints.up('sm')]: { 32 | width: drawerWidth, 33 | flexShrink: 0, 34 | }, 35 | }, 36 | appBar: { 37 | marginLeft: drawerWidth, 38 | [theme.breakpoints.up('sm')]: { 39 | width: `calc(100% - ${drawerWidth}px)`, 40 | }, 41 | }, 42 | menuButton: { 43 | marginRight: 20, 44 | [theme.breakpoints.up('sm')]: { 45 | display: 'none', 46 | }, 47 | }, 48 | grow: { 49 | flexGrow: 1, 50 | }, 51 | toolbar: theme.mixins.toolbar, 52 | drawerPaper: { 53 | width: drawerWidth, 54 | }, 55 | content: { 56 | flexGrow: 1, 57 | }, 58 | }) 59 | 60 | class ResponsiveDrawer extends React.Component { 61 | state = { 62 | mobileOpen: false, 63 | } 64 | 65 | handleDrawerToggle = () => { 66 | this.setState(state => ({ mobileOpen: !state.mobileOpen })) 67 | } 68 | 69 | render() { 70 | const { theme, classes, children } = this.props 71 | 72 | const drawer = ( 73 |
74 |
75 | 76 | 77 | Documentation 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Component API 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Demos 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 143 | 144 | 145 | 146 | 147 | 151 | 152 | 153 | 154 |
155 | ) 156 | 157 | return ( 158 |
159 | 160 | 161 | 162 | 168 | 169 | 170 | 176 | React Move 177 | 178 | 183 | 184 | 185 | 186 | 187 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 223 |
224 |
225 | {children} 226 |
227 |
228 | ) 229 | } 230 | } 231 | 232 | ResponsiveDrawer.propTypes = { 233 | theme: PropTypes.object.isRequired, 234 | classes: PropTypes.object.isRequired, 235 | children: PropTypes.object.isRequired, 236 | } 237 | 238 | export default withStyles(styles, { withTheme: true })(ResponsiveDrawer) 239 | -------------------------------------------------------------------------------- /docs/src/components/AppRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' 3 | import AppFrame from 'docs/src/components/AppFrame' 4 | import AppContent from 'docs/src/components/AppContent' 5 | import MarkdownDocs from 'docs/src/components/MarkdownDocs' 6 | import ComponentDoc from 'docs/src/components/ComponentDoc' 7 | import Home from 'docs/src/pages/Home' 8 | import { 9 | requireMarkdown, 10 | requireDemo, 11 | demo, 12 | srcContext, 13 | } from 'docs/src/components/files' 14 | 15 | export function AppRouter() { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 36 | 40 | 41 | 42 | 46 | 47 | {demo.map(d => { 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | })} 56 | 57 | 58 | 59 | 60 | 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /docs/src/components/ComponentDoc.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | import { withStyles } from '@material-ui/core/styles' 6 | import PropsDescription from 'docs/src/components/PropsDescription' 7 | import MarkdownElement from 'docs/src/components/MarkdownElement' 8 | 9 | const styles = { 10 | root: { 11 | marginBottom: 100, 12 | }, 13 | header: { 14 | display: 'flex', 15 | flexDirection: 'column', 16 | alignItems: 'flex-end', 17 | }, 18 | } 19 | 20 | function ComponentDoc({ classes, code, content }) { 21 | return ( 22 |
23 | 24 | 25 |
26 | ) 27 | } 28 | 29 | ComponentDoc.propTypes = { 30 | classes: PropTypes.object.isRequired, 31 | code: PropTypes.object.isRequired, 32 | content: PropTypes.object.isRequired, 33 | } 34 | 35 | export default withStyles(styles)(ComponentDoc) 36 | -------------------------------------------------------------------------------- /docs/src/components/Demo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react' 4 | import PropTypes from 'prop-types' 5 | import { withStyles } from '@material-ui/core/styles' 6 | import IconButton from '@material-ui/core/IconButton' 7 | import Paper from '@material-ui/core/Paper' 8 | import Collapse from '@material-ui/core/Collapse' 9 | import CodeIcon from '@material-ui/icons/Code' 10 | import MarkdownElement from 'docs/src/components/MarkdownElement' 11 | 12 | const requireDemos = require.context('docs/src', true, /\.js$/) 13 | const requireDemoSource = require.context('!raw-loader!docs/src', true, /\.js$/) 14 | 15 | const styles = theme => ({ 16 | root: { 17 | fontFamily: theme.typography.fontFamily, 18 | position: 'relative', 19 | backgroundColor: theme.palette.background.contentFrame, 20 | marginBottom: 40, 21 | }, 22 | paper: { 23 | padding: theme.spacing.unit * 2, 24 | }, 25 | codeButton: { 26 | display: 'none', 27 | zIndex: 10, 28 | position: 'absolute', 29 | top: 2, 30 | right: 7, 31 | fontSize: 8, 32 | }, 33 | code: { 34 | display: 'none', 35 | padding: 0, 36 | margin: 0, 37 | '& pre': { 38 | overflow: 'auto', 39 | margin: '0px !important', 40 | borderRadius: '0px !important', 41 | }, 42 | }, 43 | [theme.breakpoints.up(600)]: { 44 | codeButton: { 45 | display: 'block', 46 | }, 47 | code: { 48 | display: 'block', 49 | }, 50 | root: { 51 | marginLeft: 0, 52 | marginRight: 0, 53 | }, 54 | }, 55 | }) 56 | 57 | class Demo extends Component { 58 | state = { 59 | codeOpen: false, 60 | } 61 | 62 | handleCodeButtonClick = () => { 63 | this.setState({ 64 | codeOpen: !this.state.codeOpen, 65 | }) 66 | } 67 | 68 | render() { 69 | const DemoComponent = requireDemos(`./${this.props.demo}`).default 70 | const demoSource = requireDemoSource(`./${this.props.demo}`) 71 | 72 | const classes = this.props.classes 73 | 74 | return ( 75 |
76 | 80 | Source 81 | 82 | 83 |

Example Source

84 | 88 |
89 | 90 | 91 | 92 |
93 | ) 94 | } 95 | } 96 | 97 | Demo.propTypes = { 98 | classes: PropTypes.object.isRequired, 99 | demo: PropTypes.string.isRequired, 100 | } 101 | 102 | export default withStyles(styles)(Demo) 103 | -------------------------------------------------------------------------------- /docs/src/components/Github.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React from 'react' 4 | import SvgIcon from '@material-ui/core/SvgIcon' 5 | 6 | function Github(props) { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default Github 15 | -------------------------------------------------------------------------------- /docs/src/components/MarkdownDocs.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | import { withStyles } from '@material-ui/core/styles' 6 | import MarkdownElement from 'docs/src/components/MarkdownElement' 7 | import Demo from 'docs/src/components/Demo' 8 | 9 | const styles = { 10 | root: { 11 | marginBottom: 100, 12 | }, 13 | header: { 14 | display: 'flex', 15 | flexDirection: 'column', 16 | alignItems: 'flex-end', 17 | }, 18 | } 19 | 20 | const headerRegexp = /---\n(.*)\n---/ 21 | const demoRegexp = /^demo='(.*)'$/ 22 | const emptyRegexp = /^\s*$/ 23 | 24 | function MarkdownDocs(props) { 25 | const { classes } = props 26 | 27 | const contents = props.content.default 28 | .replace(headerRegexp, '') // Remove header information 29 | .split(/^{{|}}$/gm) // Split markdown into an array, separating demos 30 | .filter(content => !emptyRegexp.test(content)) // Remove empty lines 31 | 32 | return ( 33 |
34 | {contents.map(content => { 35 | if (demoRegexp.test(content)) { 36 | return 37 | } 38 | 39 | return 40 | })} 41 |
42 | ) 43 | } 44 | 45 | MarkdownDocs.propTypes = { 46 | classes: PropTypes.object.isRequired, 47 | content: PropTypes.object.isRequired, 48 | } 49 | 50 | export default withStyles(styles)(MarkdownDocs) 51 | -------------------------------------------------------------------------------- /docs/src/components/MarkdownElement.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint no-div-regex: "off" */ 3 | 4 | import React from 'react' 5 | import PropTypes from 'prop-types' 6 | import classNames from 'classnames' 7 | import { withStyles } from '@material-ui/core/styles' 8 | import marked from 'marked' 9 | import prism from 'docs/src/utils/prism' 10 | 11 | const renderer = new marked.Renderer() 12 | 13 | renderer.heading = (text, level) => { 14 | const escapedText = text 15 | .toLowerCase() 16 | .replace(/=>|<| \/>||<\/code>/g, '') 17 | .replace(/[^\w]+/g, '-') 18 | 19 | return ( 20 | ` 21 | 22 | ${text}` + 23 | `${'#'} 24 | 25 | ` 26 | ) 27 | } 28 | 29 | marked.setOptions({ 30 | gfm: true, 31 | tables: true, 32 | breaks: false, 33 | pedantic: false, 34 | sanitize: false, 35 | smartLists: true, 36 | smartypants: false, 37 | highlight(code) { 38 | return prism.highlight(code, prism.languages.jsx) 39 | }, 40 | renderer, 41 | }) 42 | 43 | const anchorLinkStyle = theme => ({ 44 | '& .anchor-link-style': { 45 | opacity: 0, 46 | // To prevent the link to get the focus. 47 | display: 'none', 48 | }, 49 | '&:hover .anchor-link-style': { 50 | display: 'inline', 51 | opacity: 1, 52 | fontSize: '0.8em', 53 | lineHeight: '1', 54 | paddingLeft: theme.spacing.unit, 55 | color: theme.palette.text.hint, 56 | }, 57 | }) 58 | 59 | const styles = theme => ({ 60 | root: { 61 | fontFamily: theme.typography.fontFamily, 62 | marginTop: theme.spacing.unit * 2, 63 | marginBottom: theme.spacing.unit * 2, 64 | padding: '0 10px', 65 | '& .anchor-link': { 66 | marginTop: -theme.spacing.unit * 12, // Offset for the anchor. 67 | position: 'absolute', 68 | }, 69 | '& pre': { 70 | margin: `${theme.spacing.unit * 3}px 0`, 71 | padding: '12px 18px', 72 | backgroundColor: theme.palette.background.paper, 73 | borderRadius: 3, 74 | overflow: 'auto', 75 | }, 76 | '& code': { 77 | display: 'inline-block', 78 | fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', 79 | padding: '3px 6px', 80 | color: theme.palette.text.primary, 81 | backgroundColor: theme.palette.background.paper, 82 | }, 83 | '& p code, & ul code, & pre code': { 84 | fontSize: 14, 85 | lineHeight: 1.6, 86 | }, 87 | '& h1': { 88 | ...theme.typography.h1, 89 | color: theme.palette.text.primary, 90 | margin: '0.7em 0', 91 | }, 92 | '& h2': { 93 | ...theme.typography.h2, 94 | color: theme.palette.text.primary, 95 | margin: '0.5em 0 0.7em', 96 | ...anchorLinkStyle(theme), 97 | }, 98 | '& h3': { 99 | ...theme.typography.h3, 100 | color: theme.palette.text.primary, 101 | margin: '0.5em 0 0.7em', 102 | ...anchorLinkStyle(theme), 103 | }, 104 | '& h4': { 105 | ...theme.typography.h4, 106 | color: theme.palette.text.primary, 107 | margin: '0.5em 0 0.7em', 108 | ...anchorLinkStyle(theme), 109 | }, 110 | '& h5': { 111 | ...theme.typography.h5, 112 | color: theme.palette.text.primary, 113 | margin: '0.5em 0 0.7em', 114 | ...anchorLinkStyle(theme), 115 | }, 116 | '& h6': { 117 | ...theme.typography.h6, 118 | color: theme.palette.text.primary, 119 | margin: '0.5em 0 0.7em', 120 | ...anchorLinkStyle(theme), 121 | }, 122 | '& p, & ul, & ol': { 123 | lineHeight: 1.6, 124 | color: theme.palette.text.primary, 125 | }, 126 | '& table': { 127 | width: '100%', 128 | display: 'block', 129 | overflowX: 'auto', 130 | borderCollapse: 'collapse', 131 | borderSpacing: 0, 132 | overflow: 'hidden', 133 | }, 134 | '& thead': { 135 | fontSize: 12, 136 | fontWeight: theme.typography.fontWeightMedium, 137 | color: theme.palette.text.secondary, 138 | }, 139 | '& tbody': { 140 | fontSize: 13, 141 | lineHeight: 1.5, 142 | color: theme.palette.text.primary, 143 | }, 144 | '& td': { 145 | borderBottom: `1px solid ${theme.palette.text.lightDivider}`, 146 | padding: `${theme.spacing.unit}px ${theme.spacing.unit * 5}px ${ 147 | theme.spacing.unit 148 | }px ${theme.spacing.unit * 3}px`, 149 | textAlign: 'left', 150 | }, 151 | '& td:last-child': { 152 | paddingRight: theme.spacing.unit * 3, 153 | }, 154 | '& td compact': { 155 | paddingRight: theme.spacing.unit * 3, 156 | }, 157 | '& td code': { 158 | fontSize: 13, 159 | lineHeight: 1.6, 160 | }, 161 | '& th': { 162 | whiteSpace: 'pre', 163 | borderBottom: `1px solid ${theme.palette.text.lightDivider}`, 164 | padding: `0 ${theme.spacing.unit * 5}px 0 ${theme.spacing.unit * 3}px`, 165 | textAlign: 'left', 166 | }, 167 | '& th:last-child': { 168 | paddingRight: theme.spacing.unit * 3, 169 | }, 170 | '& tr': { 171 | height: 48, 172 | }, 173 | '& thead tr': { 174 | height: 64, 175 | }, 176 | '& strong': { 177 | fontWeight: theme.typography.fontWeightMedium, 178 | }, 179 | '& blockquote': { 180 | borderLeft: `5px solid ${theme.palette.text.hint}`, 181 | background: theme.palette.background.paper, 182 | padding: `${theme.spacing.unit / 2}px ${theme.spacing.unit * 3}px`, 183 | margin: `${theme.spacing.unit * 3}px 0`, 184 | }, 185 | '& a, & a code': { 186 | color: theme.palette.accent.A400, 187 | textDecoration: 'none', 188 | '&:hover': { 189 | textDecoration: 'underline', 190 | }, 191 | }, 192 | }, 193 | }) 194 | 195 | function MarkdownElement(props) { 196 | const { classes, className, text, ...other } = props 197 | 198 | return ( 199 |
204 | ) 205 | } 206 | 207 | MarkdownElement.propTypes = { 208 | classes: PropTypes.object.isRequired, 209 | className: PropTypes.string, 210 | text: PropTypes.string.isRequired, 211 | } 212 | 213 | export default withStyles(styles)(MarkdownElement) 214 | -------------------------------------------------------------------------------- /docs/src/components/PropsDescription/PropsDescription.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React, { Component } from 'react' 3 | import PropTypes from 'prop-types' 4 | import { parse } from 'react-docgen' 5 | import recast from 'recast' 6 | import { parse as parseDoctrine } from 'doctrine' 7 | import MarkdownElement from '../MarkdownElement' 8 | import './props-description.css' 9 | 10 | function getDeprecatedInfo(type) { 11 | const deprecatedPropType = 'deprecated(PropTypes.' 12 | 13 | const indexStart = type.raw.indexOf(deprecatedPropType) 14 | 15 | if (indexStart !== -1) { 16 | return { 17 | propTypes: type.raw.substring( 18 | indexStart + deprecatedPropType.length, 19 | type.raw.indexOf(','), 20 | ), 21 | explanation: recast.parse(type.raw).program.body[0].expression 22 | .arguments[1].value, 23 | } 24 | } 25 | 26 | return false 27 | } 28 | 29 | function generatePropType(type) { 30 | switch (type.name) { 31 | case 'func': 32 | return 'function' 33 | 34 | case 'custom': 35 | const deprecatedInfo = getDeprecatedInfo(type) 36 | 37 | if (deprecatedInfo !== false) { 38 | return generatePropType({ 39 | name: deprecatedInfo.propTypes, 40 | }) 41 | } 42 | 43 | return type.raw 44 | 45 | case 'enum': 46 | case 'union': 47 | const values = type.value.map(v => v.value || v.name).join('
 ') 48 | return `${type.name}:
 ${values}
` 49 | 50 | default: 51 | return type.name 52 | } 53 | } 54 | 55 | function generateDescription(required, description, type) { 56 | let deprecated = '' 57 | 58 | if (type.name === 'custom') { 59 | const deprecatedInfo = getDeprecatedInfo(type) 60 | 61 | if (deprecatedInfo) { 62 | deprecated = `*Deprecated*. ${deprecatedInfo.explanation}

` 63 | } 64 | } 65 | 66 | const parsed = parseDoctrine(description) 67 | 68 | // two new lines result in a newline in the table. all other new lines 69 | // must be eliminated to prevent markdown mayhem. 70 | const jsDocText = parsed.description 71 | .replace(/\n\n/g, '
') 72 | .replace(/\n/g, ' ') 73 | 74 | if (parsed.tags.some(tag => tag.title === 'ignore')) { 75 | return null 76 | } 77 | let signature = '' 78 | 79 | if (type.name === 'func' && parsed.tags.length > 0) { 80 | // Remove new lines from tag descriptions to avoid markdown errors. 81 | parsed.tags.forEach(tag => { 82 | if (tag.description) { 83 | tag.description = tag.description.replace(/\n/g, ' ') 84 | } 85 | }) 86 | 87 | // Split up the parsed tags into 'arguments' and 'returns' parsed objects. If there's no 88 | // 'returns' parsed object (i.e., one with title being 'returns'), make one of type 'void'. 89 | const parsedLength = parsed.tags.length 90 | let parsedArgs = [] 91 | let parsedReturns 92 | 93 | if (parsed.tags[parsedLength - 1].title === 'returns') { 94 | parsedArgs = parsed.tags.slice(0, parsedLength - 1) 95 | parsedReturns = parsed.tags[parsedLength - 1] 96 | } else { 97 | parsedArgs = parsed.tags 98 | parsedReturns = { type: { name: 'void' } } 99 | } 100 | 101 | signature += '

**Signature:**
`function(' 102 | signature += parsedArgs 103 | .map(tag => `${tag.name}: ${tag.type.name}`) 104 | .join(', ') 105 | signature += `) => ${parsedReturns.type.name}` + '`
' 106 | signature += parsedArgs 107 | .map(tag => `*${tag.name}:* ${tag.description}`) 108 | .join('
') 109 | if (parsedReturns.description) { 110 | signature += `
*returns* (${parsedReturns.type.name}): ${parsedReturns.description}` 111 | } 112 | } 113 | 114 | return `${deprecated} ${jsDocText}${signature}` 115 | } 116 | 117 | class PropTypeDescription extends Component { 118 | static propTypes = { 119 | code: PropTypes.string, 120 | header: PropTypes.string.isRequired, 121 | } 122 | 123 | static defaultProps = { 124 | header: '###### Props', 125 | } 126 | 127 | render() { 128 | const { code, header } = this.props 129 | 130 | let requiredProps = 0 131 | 132 | let text = `${header} 133 | | Name | Type | Default | Description | 134 | |:-----|:-----|:-----|:-----|\n` 135 | 136 | const componentInfo = parse(code) 137 | 138 | for (let key in componentInfo.props) { 139 | const prop = componentInfo.props[key] 140 | 141 | const description = generateDescription( 142 | prop.required, 143 | prop.description, 144 | prop.type, 145 | ) 146 | 147 | if (description === null) { 148 | continue 149 | } 150 | 151 | let defaultValue = '' 152 | 153 | if (prop.defaultValue) { 154 | defaultValue = prop.defaultValue.value.replace(/\n/g, '') 155 | } 156 | 157 | if (prop.required) { 158 | key = `${key} \*` 159 | requiredProps += 1 160 | } 161 | 162 | if (prop.type.name === 'custom') { 163 | if (getDeprecatedInfo(prop.type)) { 164 | key = `~~${key}~~` 165 | } 166 | } 167 | 168 | text += `| ${key} | ${generatePropType( 169 | prop.type, 170 | )} | ${defaultValue} | ${description} |\n` 171 | } 172 | 173 | text += '' 174 | 175 | const requiredPropFootnote = 176 | requiredProps === 1 177 | ? '* required property' 178 | : requiredProps > 1 179 | ? '' 180 | : '* required properties' 181 | 182 | return ( 183 |
184 | 185 |
186 | * required properites 187 |
188 |
189 | ) 190 | } 191 | } 192 | 193 | export default PropTypeDescription 194 | -------------------------------------------------------------------------------- /docs/src/components/PropsDescription/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './PropsDescription' 2 | -------------------------------------------------------------------------------- /docs/src/components/PropsDescription/props-description.css: -------------------------------------------------------------------------------- 1 | .propTypeDescription table td, .propTypeDescription table th { 2 | border-top: 1px solid lightgray; 3 | border-left: 0 none; 4 | border-right: 0 none; 5 | display: table-cell; 6 | vertical-align: top; 7 | } 8 | 9 | .propTypeDescription table th { 10 | border-bottom: 1px solid lightgray; 11 | border-top: 0 none; 12 | display: table-cell; 13 | font-weight: normal; 14 | text-align: left; 15 | } 16 | 17 | .propTypeDescription table td { 18 | font-size: 95%; 19 | min-width: 150px; 20 | } 21 | 22 | .propTypeDescription table td + td { 23 | font-family: "Roboto", sans-serif; 24 | font-size: 90%; 25 | } 26 | 27 | .propTypeDescription table td + td + td + td { 28 | font-family: "Roboto", sans-serif; 29 | font-size: 95%; 30 | } -------------------------------------------------------------------------------- /docs/src/components/Surface.js: -------------------------------------------------------------------------------- 1 | // @flow weak 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | 6 | export default function Surface(props) { 7 | const { className, view, trbl, style, children, ...other } = props 8 | const paddingBottom = `${Math.round((view[1] / view[0]) * 100)}%` 9 | 10 | // uses bottom-padding hack. See https://css-tricks.com/scale-svg/ 11 | return ( 12 |
23 | 33 | {children} 34 | 35 |
36 | ) 37 | } 38 | 39 | Surface.propTypes = { 40 | /** 41 | * SVG children to be rendered 42 | */ 43 | children: PropTypes.node, 44 | /** 45 | * The CSS class name of the root div element. 46 | */ 47 | className: PropTypes.string, 48 | /** 49 | * Styles to be spread on the root div element. 50 | */ 51 | style: PropTypes.object, 52 | /** 53 | * Top, right, bottom, left (trbl) margins. 54 | */ 55 | trbl: PropTypes.array, 56 | /** 57 | * Width and height attributes of the SVG view box. 58 | */ 59 | view: PropTypes.array, 60 | } 61 | 62 | Surface.defaultProps = { 63 | view: [1000, 350], 64 | trbl: [10, 10, 10, 10], 65 | } 66 | -------------------------------------------------------------------------------- /docs/src/components/files.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // ******************************************************** 4 | // MARKDOWN FILES 5 | // ******************************************************** 6 | export const requireMarkdown = require.context( 7 | '../pages', 8 | true, 9 | /^((?![\\/]component-demos[\\/]).)*\.md$/, 10 | ) 11 | 12 | // ******************************************************** 13 | // SRC CONTEXT 14 | // ******************************************************** 15 | export const srcContext = require.context('!raw-loader!../../../src', true) 16 | 17 | // ******************************************************** 18 | // DEMO FILES 19 | // ******************************************************** 20 | export const requireDemo = require.context('../pages/demos', true, /\.md$/) 21 | 22 | export const demo = requireDemo.keys().map(n => ({ 23 | path: n, 24 | name: n.replace(/.*\//, '').replace('.md', ''), 25 | })) 26 | -------------------------------------------------------------------------------- /docs/src/index.js: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import { createStore } from 'redux' 3 | import { Provider } from 'react-redux' 4 | import React from 'react' 5 | import ReactDOM from 'react-dom' 6 | import App from './components/App' 7 | 8 | const docs = (state = { dark: true }, action) => { 9 | if (action.type === 'TOGGLE_THEME_SHADE') { 10 | return Object.assign({}, state, { dark: !state.dark }) 11 | } 12 | return state 13 | } 14 | 15 | const store = createStore(docs) 16 | const rootEl = document.querySelector('#app') 17 | 18 | const render = Component => { 19 | ReactDOM.render( 20 | { 22 | throw error 23 | }} 24 | > 25 | 26 | 27 | 28 | , 29 | rootEl, 30 | ) 31 | } 32 | 33 | render(App) 34 | 35 | if (process.env.NODE_ENV !== 'production' && module.hot) { 36 | module.hot.accept('./components/App', () => { 37 | const NextApp = require('./components/App').default // eslint-disable-line global-require 38 | render(NextApp) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /docs/src/pages/Home.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react' 4 | import PropTypes from 'prop-types' 5 | import { withStyles } from '@material-ui/core/styles' 6 | import { Link } from 'react-router-dom' 7 | import Button from '@material-ui/core/Button' 8 | import Grid from '@material-ui/core/Grid' 9 | import logo from './logo-react-move.png' 10 | 11 | const styles = () => ({ 12 | root: { 13 | marginTop: 30, 14 | textAlign: 'center', 15 | }, 16 | }) 17 | 18 | function Home(props) { 19 | const classes = props.classes 20 | 21 | return ( 22 | 23 | 24 | react-move 30 | 31 | 32 | 35 | 36 | 37 | ) 38 | } 39 | 40 | Home.propTypes = { 41 | classes: PropTypes.object.isRequired, 42 | } 43 | 44 | export default withStyles(styles)(Home) 45 | -------------------------------------------------------------------------------- /docs/src/pages/component-api/animate.md: -------------------------------------------------------------------------------- 1 | ##### Animate 2 | -------------------------------------------------------------------------------- /docs/src/pages/component-api/node-group.md: -------------------------------------------------------------------------------- 1 | ##### NodeGroup 2 | -------------------------------------------------------------------------------- /docs/src/pages/demos/animate/Example1.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { feature } from 'topojson-client' 3 | import { easeQuadOut } from 'd3-ease' 4 | import { Animate } from 'react-move' 5 | import { interpolate } from 'flubber' 6 | import Surface from 'docs/src/components/Surface' // this is just a responsive SVG 7 | import statesJSON from './states.json' 8 | 9 | // ************************************************** 10 | // SVG Layout 11 | // ************************************************** 12 | const view = [1000, 450] // [width, height] 13 | const trbl = [10, 10, 10, 10] // [top, right, bottom, left] margins 14 | 15 | class Example extends PureComponent { 16 | state = { 17 | states: feature(statesJSON, statesJSON.objects.states).features.map(d => { 18 | return d.geometry.coordinates[0] 19 | }), 20 | } 21 | 22 | update = () => { 23 | // take the first one, put it at the end 24 | this.setState(({ states }) => ({ 25 | states: [...states.slice(1), states[0]], 26 | })) 27 | } 28 | 29 | render() { 30 | const { 31 | update, 32 | state: { states }, 33 | } = this 34 | const interpolator = interpolate(states[0], states[1]) 35 | 36 | return ( 37 |
38 | 39 | 40 | 56 | {state => { 57 | return ( 58 | 59 | 60 | 61 | ) 62 | }} 63 | 64 | 65 |
66 | ) 67 | } 68 | } 69 | 70 | export default Example 71 | -------------------------------------------------------------------------------- /docs/src/pages/demos/animate/Example2.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { interpolate, interpolateTransformSvg } from 'd3-interpolate' 3 | import { range } from 'd3-array' 4 | import { easeExpInOut } from 'd3-ease' 5 | import { Animate } from 'react-move' 6 | 7 | function getRandomColor() { 8 | return range(6).reduce((m) => { 9 | return `${m}${'0123456789ABCDEF'[Math.floor(Math.random() * 16)]}` 10 | }, '#') 11 | } 12 | 13 | class Example extends PureComponent { 14 | state = { 15 | show: false, 16 | color: '#00cf77', 17 | } 18 | 19 | updateShow = () => { 20 | this.setState((prev) => ({ show: !prev.show })) 21 | } 22 | 23 | updateColor = () => { 24 | this.setState(() => ({ show: true, color: getRandomColor() })) 25 | } 26 | 27 | render() { 28 | const { updateShow, updateColor, state: { show, color } } = this 29 | 30 | return ( 31 |
32 | 35 | {show ? ( 36 | 39 | ) : null} 40 | { 71 | if (attr === 'transform') { 72 | return interpolateTransformSvg(begValue, endValue) 73 | } 74 | 75 | return interpolate(begValue, endValue) 76 | }} 77 | > 78 | {({ opacity, backgroundColor }) => { 79 | return ( 80 |
89 | {opacity.toFixed(3)} 90 |
91 | ) 92 | }} 93 |
94 |
95 | ) 96 | } 97 | } 98 | 99 | export default Example 100 | -------------------------------------------------------------------------------- /docs/src/pages/demos/animate/animate.md: -------------------------------------------------------------------------------- 1 | ##### Flubber 2 | {{demo='pages/demos/animate/Example1.js'}} 3 | 4 | ##### Enter/Exit Transition 5 | {{demo='pages/demos/animate/Example2.js'}} 6 | -------------------------------------------------------------------------------- /docs/src/pages/demos/animated-bar/Example1.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Example = () => { 4 | return ( 5 |
6 |