├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── contributing.md ├── docs ├── contributing │ ├── index.md │ └── versions │ │ └── index.md └── demo.gif ├── example ├── Card.css ├── Card.js ├── index.html └── index.js ├── package.json ├── source ├── SVGPath.js └── index.js ├── test └── index.js ├── webpack.config.js └── webpack.prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 1 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // I want to use babel-eslint for parsing! 3 | "parser": "babel-eslint", 4 | "env": { 5 | // I write for browser 6 | "browser": true, 7 | // in CommonJS 8 | "node": true, 9 | // use ES6 10 | "es6": true 11 | }, 12 | "ecmaProperties": { 13 | // enable JSX support 14 | "jsx": true 15 | }, 16 | "plugins": [ 17 | // enable react plugin 18 | "react" 19 | ], 20 | "globals": { 21 | "__WEBPACK__": true 22 | }, 23 | // To give you an idea how to override rule options 24 | "rules": { 25 | // Possible Errors 26 | "comma-dangle": 0, 27 | "no-console": 2, 28 | "no-debugger": 1, 29 | "no-dupe-keys": 2, 30 | "no-dupe-args": 2, 31 | "no-empty": 2, 32 | "no-extra-boolean-cast": 2, 33 | "no-extra-semi": 2, 34 | "no-invalid-regexp": 2, 35 | "no-irregular-whitespace": 2, 36 | "quote-props": [ 37 | 2, 38 | "consistent-as-needed", 39 | { 40 | "keywords": true 41 | } 42 | ], 43 | "no-sparse-arrays": 2, 44 | "no-unreachable": 2, 45 | "use-isnan": 2, 46 | "valid-jsdoc": 1, 47 | "valid-typeof": 2, 48 | // Best Practices 49 | "consistent-return": 1, 50 | "curly": 2, 51 | "default-case": 2, 52 | "dot-notation": 2, 53 | "eqeqeq": 2, 54 | "no-alert": 2, 55 | "no-caller": 2, 56 | "no-else-return": 2, 57 | "no-eq-null": 2, 58 | "no-eval": 2, 59 | "no-extend-native": 2, 60 | "no-floating-decimal": 2, 61 | "no-implied-eval": 2, 62 | "no-iterator": 2, 63 | "no-labels": 2, 64 | "no-loop-func": 1, 65 | "no-lone-blocks": 2, 66 | "no-multi-spaces": 2, 67 | "no-native-reassign": 2, 68 | "no-new": 2, 69 | "no-new-func": 2, 70 | "no-new-wrappers": 2, 71 | "no-proto": 2, 72 | "no-redeclare": 2, 73 | "no-return-assign": 2, 74 | "no-script-url": 2, 75 | "no-self-compare": 2, 76 | "no-sequences": 2, 77 | "no-throw-literal": 2, 78 | "no-unused-expressions": 2, 79 | "no-void": 2, 80 | "radix": 2, 81 | "yoda": 0, 82 | // Strict Mode 83 | "strict": 0, 84 | // Variables 85 | "no-catch-shadow": 2, 86 | "no-delete-var": 2, 87 | "no-shadow": 2, 88 | "no-shadow-restricted-names": 2, 89 | "no-undef": 2, 90 | "no-unused-vars": [ 91 | 2, 92 | { 93 | "vars": "all", 94 | "args": "after-used" 95 | } 96 | ], 97 | "no-use-before-define": 2, 98 | // Node 99 | "handle-callback-err": 2, 100 | "no-new-require": 2, 101 | "no-path-concat": 2, 102 | // Stylistic Issues 103 | "indent": [2,2], 104 | // 4 spaces 105 | "camelcase": 0, 106 | "comma-spacing": [ 107 | 2, 108 | { 109 | "before": false, 110 | "after": true 111 | } 112 | ], 113 | "comma-style": [ 114 | 2, 115 | "last" 116 | ], 117 | "eol-last": 2, 118 | "func-style": [ 119 | 2, 120 | "expression" 121 | ], 122 | "max-nested-callbacks": [ 123 | 2, 124 | 3 125 | ], 126 | "no-array-constructor": 2, 127 | "no-mixed-spaces-and-tabs": 2, 128 | "no-multiple-empty-lines": [ 129 | 1, 130 | { 131 | "max": 2 132 | } 133 | ], 134 | "no-nested-ternary": 2, 135 | "no-new-object": 2, 136 | "semi-spacing": [ 137 | 2, 138 | { 139 | "before": false, 140 | "after": true 141 | } 142 | ], 143 | "no-spaced-func": 2, 144 | "no-trailing-spaces": 2, 145 | "no-underscore-dangle": 2, 146 | "no-extra-parens": [ 147 | 2, 148 | "functions" 149 | ], 150 | "quote-props": [ 151 | 1, 152 | "as-needed" 153 | ], 154 | "quotes": [ 155 | 1, 156 | "single" 157 | ], 158 | "semi": [ 159 | 2, 160 | "always" 161 | ], 162 | "semi-spacing": [ 163 | 2, 164 | { 165 | "before": false, 166 | "after": true 167 | } 168 | ], 169 | "space-before-function-paren": [ 170 | 1, 171 | { 172 | "anonymous": "never", 173 | "named": "never" 174 | } 175 | ], 176 | "space-after-keywords": [ 177 | 1, 178 | "always" 179 | ], 180 | "space-before-blocks": [ 181 | 1, 182 | "always" 183 | ], 184 | "object-curly-spacing": [ 185 | 1, 186 | "never" 187 | ], 188 | "array-bracket-spacing": [ 189 | 1, 190 | "never" 191 | ], 192 | "computed-property-spacing": [ 193 | 1, 194 | "never" 195 | ], 196 | "space-in-parens": [ 197 | 1, 198 | "never" 199 | ], 200 | "key-spacing": [ 201 | 1, 202 | { 203 | "beforeColon": false, 204 | "afterColon": true 205 | } 206 | ], 207 | "object-curly-spacing": [ 208 | 1, 209 | "never" 210 | ], 211 | "space-infix-ops": 2, 212 | // complexity rules 213 | "max-depth": [ 214 | 2, 215 | 3 216 | ], 217 | "max-statements": [ 218 | 1, 219 | 20 220 | ], 221 | "complexity": [ 222 | 1, 223 | 3 224 | ], 225 | "max-len": [ 226 | 2, 227 | 120 228 | ], 229 | "max-params": [ 230 | 2, 231 | 0 232 | ], 233 | // jsx rules 234 | "react/jsx-quotes": 0, 235 | "react/jsx-no-undef": 1, 236 | "react/jsx-uses-react": 1, 237 | "react/jsx-uses-vars": 1, 238 | "react/no-did-mount-set-state": 1, 239 | "react/no-did-update-set-state": 1, 240 | "react/no-multi-comp": 0, 241 | "react/react-in-jsx-scope": 1, 242 | "react/self-closing-comp": 1, 243 | "react/wrap-multilines": 1, 244 | "jsx-quotes": [ 245 | 2, 246 | "prefer-double" 247 | ], 248 | // ES6 249 | "prefer-const": 2, 250 | "object-shorthand": [ 251 | 2, 252 | "always" 253 | ], 254 | "no-var": 2 255 | } 256 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 27 | node_modules 28 | 29 | build 30 | .validate.json 31 | .jshintrc 32 | .jshintignore 33 | .idea 34 | .DS_Store 35 | example/bundle.js 36 | lib -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | source 2 | docs 3 | example 4 | coverage 5 | webpack.config.js 6 | webpack.prod.config.js 7 | .babelrc 8 | .editorconfig 9 | .eslintrc 10 | bower.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: 4 | directories: 5 | - node_modules 6 | 7 | node_js: 8 | - "iojs-2" 9 | 10 | branches: 11 | only: 12 | - master 13 | 14 | script: 15 | - npm run check 16 | 17 | before_install: 18 | - npm install -g npm@3.x -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Michele Memoli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-morphine 2 | Relieving the pain of morphing UIs in React. 3 | 4 | [![Travis](https://img.shields.io/travis/mmmoli/react-morphine.svg?style=flat-square)](https://travis-ci.org/mmmoli/react-morphine) 5 | [![npm](https://img.shields.io/npm/v/react-morphine.svg?style=flat-square)](https://www.npmjs.com/package/react-morphine) 6 | 7 | ![React-morphine demo](./docs/demo.gif?raw=true) 8 | 9 | 10 | 11 | ## Contents 12 | 13 | - [Summary](#summary) 14 | - [Description](#description) 15 | - [Guide](#guide) 16 | - [1. Installation](#1-installation) 17 | - [2. Define your shape](#2-define-your-shape) 18 | - [3. Define your component](#3-define-your-component) 19 | - [Tips](#tips) 20 | - [It's a pain getting the SVG path](#its-a-pain-getting-the-svg-path) 21 | - [It's still too low level](#its-still-too-low-level) 22 | - [Contributing](#contributing) 23 | 24 | 25 | 26 | ## Summary 27 | 28 | React-morphine uses React-motion and SVG to help you draw shapes which can _morph_. More precisely, it allows you to 29 | reposition points within the SVG Path definition. 30 | 31 | Thoughts? [Tweet me](http://twitter.com/share?text=%23react-morphine%20@mm0li) 32 | 33 | ## Description 34 | 35 | React-morphine defines an SVGPath component class which has a series of required props to making a morphine UI. It's 36 | designed to be used to draw a shape using an SVG `path`. However, the trick is that instead of defining the path as 37 | a static string, we define it as a function that takes a parameter of the current spring state. 38 | 39 | Think of it as React-motion's `interpolatedValues` but applied to a shape definition. 40 | 41 | All you have to do create the states you want to animate between, define the shape using a function and the library 42 | does the rest. 43 | 44 | See [example Card component](example/Card.js) and [live demo](http://mmmoli.github.io/react-morphine/). 45 | 46 | ## Guide 47 | Just a quick guide for now. I'll write-up a better description soon. For now, follow the guide below and check out 48 | the example. 49 | 50 | ### 1. Installation 51 | 52 | React-morphine is available on npm or bower: 53 | 54 | npm install react-mophine --save 55 | bower install react-mophine 56 | 57 | ### 2. Define your shape 58 | 59 | Use whatever drawing package you want. I used Sketch. 60 | 61 | Warning: it helps to tidy-up SVGs first so that you don't go insane later on. 62 | 63 | Here's some great tips I found: 64 | [Optimising SVGs for the Web – part 1](https://medium.com/@larsenwork/optimising-svgs-for-web-use-part-1-67e8f2d4035#.9piykd6bb) & [part 2](https://medium.com/@larsenwork/optimising-svgs-for-web-use-part-2-6711cc15df46#.7mm9uzb86) 65 | 66 | The key is to note the `d` attribute of the `path` node. you're going to use this to describe how the path changes 67 | using React-motions spring mechanics. 68 | 69 | ### 3. Define your component 70 | 71 | See [example Card component](example/Card.js) but in summary: 72 | 73 | ``` 74 | class Card extends Component { 75 | 76 | constructor(props) { 77 | super(props); 78 | this.state = { 79 | isHovering: false 80 | }; 81 | } 82 | 83 | onEnter() { 84 | this.setState({ 85 | isHovering: true 86 | }); 87 | } 88 | 89 | onLeave() { 90 | this.setState({ 91 | isHovering: false 92 | }); 93 | } 94 | 95 | render() { 96 | const {isHovering} = this.state; 97 | 98 | const destination = isHovering ? SECONDARY : DEFAULT; 99 | 100 | return ( 101 |
104 | `M0,${delta.tl} L220,${delta.tr} L220,300 L0,300 Z`}/> 112 |
113 | ); 114 | } 115 | } 116 | ``` 117 | 118 | See the `path` prop passed to the `SVGPath` component? It's a function that defines how the path changes based on the 119 | spring values by returning a string. 120 | 121 | The `path` prop function is passed a single argument that describes the current state of all the springs. You can 122 | define as many parameters as you want off the back of it. I defined `tr` and `tl` to represent the rightmost and 123 | leftmost points of the SVG definition. 124 | 125 | ## Tips 126 | 127 | ### It's a pain getting the SVG path 128 | 129 | Tell me about it. The SVG spec is pretty straightforward but just play around with your `path` prop until it draws 130 | what you're expecting. 131 | 132 | I found it useful to return a constant string (ignoring the `delta` param) until I need which point I wanted to change. 133 | 134 | Would be great to have tools to help us with this in the future. Bear with us. 135 | 136 | ### It's still too low level 137 | 138 | I know what you mean. I like the power we have with this API, but I'm thinking of ways which are slightly more how 139 | designers think. 140 | 141 | ## Contributing 142 | 143 | - [Contributing](docs/contributing/index.md) 144 | - [Versions: Release Names vs Version Numbers](docs/contributing/versions/index.md) 145 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-morphine", 3 | "version": "0.1.3", 4 | "homepage": "https://github.com/mmmoli/react-morphine", 5 | "authors": [ 6 | "Michele Memoli (http://100shapes.com)" 7 | ], 8 | "description": "Relieving the pain of morphing UIs", 9 | "main": [ 10 | "build/react-morphine.min.js", 11 | "build/react-morphine.map" 12 | ], 13 | "dependencies": { 14 | "react": ">=0.13.2 || ^0.14" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "react-motion", 19 | "svg", 20 | "morph", 21 | "component", 22 | "react-component", 23 | "spring", 24 | "tween", 25 | "motion", 26 | "animation", 27 | "transition", 28 | "ui" 29 | ], 30 | "license": "MIT", 31 | "ignore": [ 32 | "**/.*", 33 | "node_modules", 34 | "coverage", 35 | "example", 36 | "bower_components", 37 | "test", 38 | "tests", 39 | "src", 40 | "package.json", 41 | "webpack.config.js", 42 | "webpack.prod.config.js" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | ## Contents 5 | 6 | 7 | - [Contributing](docs/contributing/index.md) 8 | - [Versions: Release Names vs Version Numbers](docs/contributing/versions/index.md) 9 | -------------------------------------------------------------------------------- /docs/contributing/index.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | ## Contents 5 | 6 | - [Reporting bugs](#reporting-bugs) 7 | - [Example](#example) 8 | - [Getting Started](#getting-started) 9 | - [Clone the repo](#clone-the-repo) 10 | - [If there's no issue, please create one](#if-theres-no-issue-please-create-one) 11 | - [Let us Know you're working on the issue](#let-us-know-youre-working-on-the-issue) 12 | - [Create a feature branch:](#create-a-feature-branch) 13 | - [Make your changes and commit:](#make-your-changes-and-commit) 14 | - [Create a Pull Request](#create-a-pull-request) 15 | - [PR Merge Exception](#pr-merge-exception) 16 | - [PR Hints](#pr-hints) 17 | - [For large changes spanning many commits / PRs](#for-large-changes-spanning-many-commits--prs) 18 | 19 | 20 | - [Versions: Release Names vs Version Numbers](versions/index.md) 21 | 22 | ## Reporting bugs 23 | 24 | Bug reports should contain the following information: 25 | 26 | * Summary: A brief description. 27 | * Steps to reproduce: How did you encounter the bug? Instructions to reproduce it. 28 | * Expected behavior: How did you expect it to behave? 29 | * Actual behavior: How did it actually behave? 30 | * Screenshot or animated gif: If possible, attach visual documentation of the bug. 31 | * References: Links to any related tickets or information sources. 32 | 33 | ### Example 34 | 35 | Here's a [real issue](https://github.com/woothemes/woocommerce/issues/8563#issue-94518347) to demonstrate. 36 | 37 | 38 | ## Getting Started 39 | 40 | ### Clone the repo 41 | 42 | * Click the GitHub fork button to create your own fork 43 | * Clone your fork of the repo to your dev system 44 | 45 | ``` 46 | git clone git@github.com:/prod-module-boilerplate.git 47 | ``` 48 | 49 | ### If there's no issue, please create one 50 | 51 | 52 | ### Let us Know you're working on the issue 53 | 54 | If you're actively working on an issue, please comment in the issue thread stating that you're working on a fix, or (if you're an official contributor) assign it to yourself. 55 | 56 | This way, others will know they shouldn't try to work on a fix at the same time. 57 | 58 | 59 | ### Create a feature branch: 60 | 61 | ``` 62 | git checkout -b 63 | ``` 64 | 65 | ### Make your changes and commit: 66 | 67 | * Make sure you comply with the [.editorconfig](http://editorconfig.org/) 68 | 69 | ``` 70 | git commit -m '[Issue #] ' 71 | ``` 72 | 73 | ### Create a Pull Request 74 | 75 | Please don't merge your own changes. Create a pull request so others can review the changes. 76 | 77 | **Push changes:** 78 | 79 | ``` 80 | git push origin 81 | ``` 82 | 83 | * Open your repository fork on GitHub 84 | * You should see a button to create a pull request - Press it 85 | * Consider mentioning a contributor in your pull request comments to alert them that it's available for review 86 | * **Wait for the reviewer to approve and merge the request** 87 | 88 | ### PR Merge Exception 89 | 90 | * Minor documentation grammar/spelling fixes (code example changes should be reviewed) 91 | 92 | 93 | ### PR Hints 94 | 95 | Reference the issue number in your commit message e.g.: 96 | 97 | ``` 98 | $ git commit -m '[#5] Make sure to follow the PR process for contributions' 99 | ``` 100 | 101 | #### For large changes spanning many commits / PRs 102 | 103 | * Create a meta-issue with a bullet list using the `* [ ] item` markdown syntax. 104 | * Create issues for each bullet point 105 | * Link to the meta-issue from each bullet point issue 106 | * Check off the bullet list as items get completed 107 | 108 | Linking from the bullet point issues to the meta issue will create a list of issues with status indicators in the issue comments stream, which will give us a quick visual reference to see what's done and what still needs doing. 109 | -------------------------------------------------------------------------------- /docs/contributing/versions/index.md: -------------------------------------------------------------------------------- 1 | # Versions: Release Names vs Version Numbers 2 | 3 | 4 | ## Contents 5 | 6 | - [What?](#what) 7 | - [Why?](#why) 8 | - [Details](#details) 9 | - [Release Names (AKA code names)](#release-names-aka-code-names) 10 | - [MVP](#mvp) 11 | - [Version Numbers](#version-numbers) 12 | - [Breaking.Feature.Fix](#breakingfeaturefix) 13 | - [Breaking](#breaking) 14 | - [Feature](#feature) 15 | - [Fix](#fix) 16 | - [Examples](#examples) 17 | 18 | 19 | 20 | ## What? 21 | 22 | Version numbers are **only** there to communicate the nature of a change: **Breaking.Feature.Fix**. 23 | 24 | Human names are there to communicate, "Hey everybody, we have a new release! Here are the new features!" 25 | 26 | ## Why? 27 | 28 | Our releases and versions are separate concepts because the need to communicate new stable release information and the need to inform developers about the nature of changes (breaking, new features, or fixes/security patches) are two separarete concerns which advance on separate timetables. 29 | 30 | The conflating of version numbers and public releases has led to a big problem in the software development community. Developers tend to break semantic version numbering, for example, resisting the need to advance the breaking (major) version number because they're not yet ready to release their mvp (which many developers think of as 1.0). 31 | 32 | In other words, we need two separate ways of tracking changes: 33 | 34 | * One for people & public announcements (names). 35 | * One for resolving version conflict problems (numbers). 36 | 37 | ## Details 38 | 39 | ### Release Names (AKA code names) 40 | 41 | Our major releases have code-names instead of version numbers. The current release is identified by the "latest" tag. The first version is "mvp". After that we pick a theme, and work through the alphabet from A to Z. 42 | 43 | When talking about release versions, we don't say "version Arty" we say "the newest version was released today, code named 'Arty'". After that, we just refer to it as "Arty" or "latest version". More recognizable codename examples include "Windows Vista" or "OS X Yosemite". 44 | 45 | 46 | #### MVP 47 | 48 | MVP stands for "Minimum **Valuable** Product" (a better version of the common "Minimum Viable Product"). The minimum number of features to make the product valuable to users. 49 | 50 | ![mvp](https://cloud.githubusercontent.com/assets/364727/8585378/4222fd84-259e-11e5-804c-33ec952ca88d.png) 51 | 52 | 53 | ### Version Numbers 54 | 55 | [Semver](http://semver.org), except the version roles have the semantic names, "Breaking.Feature.Fix" instead of "Major.Minor.Patch". 56 | 57 | 58 | #### Breaking.Feature.Fix 59 | 60 | We don't decide what the version will be. The API changes decide. Version numbers are for computers, not people. Release names are for people. 61 | 62 | ##### Breaking 63 | 64 | Any breaking change, no matter how small increments the Breaking version number. Incrementing the Breaking version number has absolutely no relationship with issuing a release. 65 | 66 | ##### Feature 67 | 68 | When any new feature is added. This could be as small as a new public property, or as large as a new module contract being exposed. 69 | 70 | ##### Fix 71 | 72 | When a documented feature does not behave as documented, or when a security issue is discovered and fixed without altering documented behavior. 73 | 74 | 75 | 76 | ## Examples 77 | 78 | If it's time to write a blog post to inform the community about new features or important changes, we find the version we want to publicize, tag it "latest", give it a human-readable name, (i.e. "MVP" or "Art Nouveau" in the case of the [JSHomes API](https://github.com/jshomes/jshomes-platform-api/blob/master/README.md#jshomes-api-)). 79 | 80 | That human readable release name **does not replace semver**. "MVP" might correspond to `v1.6.23` or `v2.2.5` -- the point is, **the numbered version has nothing to do with the named release**. 81 | 82 | The numbered version is there so npm and developers can tell whether or not a new version is a breaking change, an added feature change, or a bug / security fix. 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmmoli/react-morphine/6a5b7a6e1cc1b801eba67de99ed52da9b5fa18c3/docs/demo.gif -------------------------------------------------------------------------------- /example/Card.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #FFD605; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | #root { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | height: 100vh; 12 | width: 100vw; 13 | } 14 | 15 | .Card { 16 | display: flex; 17 | position: relative; 18 | background-color: #215CFF; 19 | border-radius: 5px; 20 | width: 350px; 21 | height: 70vh; 22 | 23 | box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); 24 | overflow: hidden; 25 | } 26 | 27 | .Card svg { 28 | overflow: hidden; 29 | border-radius: 5px; 30 | position: absolute; 31 | top: -1px; /* fixes rendering issue in FF */ 32 | z-index: 10; 33 | width: 100%; 34 | height: 100%; 35 | } 36 | 37 | .Card svg path { 38 | fill: #0038CC; 39 | } -------------------------------------------------------------------------------- /example/Card.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {spring, presets} from 'react-motion'; 3 | 4 | import './Card.css'; 5 | import SVGPath from '../source/SVGPath'; 6 | 7 | const DEFAULT = { 8 | tr: 20, 9 | tl: 120 10 | }; 11 | 12 | const SECONDARY = { 13 | tr: 210, 14 | tl: 240 15 | }; 16 | 17 | class Card extends Component { 18 | 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | isHovering: false 23 | }; 24 | } 25 | 26 | onEnter() { 27 | this.setState({ 28 | isHovering: true 29 | }); 30 | } 31 | 32 | onLeave() { 33 | this.setState({ 34 | isHovering: false 35 | }); 36 | } 37 | 38 | render() { 39 | const {isHovering} = this.state; 40 | 41 | const destination = isHovering ? SECONDARY : DEFAULT; 42 | 43 | return ( 44 |
47 | `M0,${delta.tl} L220,${delta.tr} L220,300 L0,300 Z`}/> 55 |
56 | ); 57 | } 58 | } 59 | 60 | export default Card; 61 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-Morphine Demo 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Card from './Card'; 4 | 5 | ReactDOM.render(, document.querySelector('#root')); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-morphine", 3 | "description": "Relieving the pain of morphing UIs.", 4 | "version": "0.1.3", 5 | "main": "./lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mmmoli/react-morphine.git" 9 | }, 10 | "author": "Michele Memoli (http://100shapes.com)", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/mmmoli/react-morphine/issues" 14 | }, 15 | "homepage": "https://github.com/mmmoli/react-morphine", 16 | "keywords": [ 17 | "react", 18 | "react-motion", 19 | "svg", 20 | "morph", 21 | "component", 22 | "react-component", 23 | "spring", 24 | "tween", 25 | "motion", 26 | "animation", 27 | "transition", 28 | "ui" 29 | ], 30 | "scripts": { 31 | "init": "rimraf .validate.json && rimraf .jshintrc", 32 | "clean": "rimraf build && rimraf lib", 33 | "lint": "eslint source test", 34 | "prebuild": "npm run clean", 35 | "build": "npm run build:umd && npm run build:umd:min && npm run build:lib && npm run build:doc", 36 | "build:lib": "babel source --out-dir lib", 37 | "build:umd": "NODE_ENV=production webpack --config webpack.prod.config.js", 38 | "build:umd:min": "NODE_ENV=production MINIFY=1 webpack --config webpack.prod.config.js", 39 | "build:doc": "doctoc --github --title \"## Contents\" ./", 40 | "build:example": "gh-pages -d example", 41 | "start": "NODE_ENV=development webpack --watch", 42 | "test": "babel-node test/index.js | faucet", 43 | "cov": "npm run cov:clean && npm run cov:generate", 44 | "cov:clean": "rimraf coverage", 45 | "cov:generate": "babel-node node_modules/.bin/isparta cover --report text --report html test/index.js", 46 | "prepublish": "npm run build", 47 | "validate": "npm run lint && npm test", 48 | "validate-dev": "npm run lint && npm run build && npm test | faucet", 49 | "audit": "nsp check", 50 | "precheck": "npm run validate", 51 | "check": "npm run audit && npm outdated --depth 0" 52 | }, 53 | "pre-commit": [ 54 | "lint" 55 | ], 56 | "files": [ 57 | "build", 58 | "lib", 59 | "source" 60 | ], 61 | "devDependencies": { 62 | "babel": "^5.8.21", 63 | "babel-core": "^5.8.34", 64 | "babel-eslint": "^4.0.5", 65 | "babel-loader": "^5.3.2", 66 | "babel-plugin-object-assign": "^1.2.1", 67 | "blue-tape": "^0.1.10", 68 | "css-loader": "^0.23.1", 69 | "doctoc": "^0.14.2", 70 | "eslint": "^1.1.0", 71 | "eslint-loader": "^1.0.0", 72 | "eslint-plugin-react": "^3.15.0", 73 | "faucet": "0.0.1", 74 | "gh-pages": "^0.8.0", 75 | "isparta": "^3.0.3", 76 | "node-libs-browser": "^0.5.2", 77 | "nsp": "^2.2.0", 78 | "precommit-hook": "^3.0.0", 79 | "react-addons-test-utils": "^0.14.6", 80 | "rimraf": "^2.4.2", 81 | "style-loader": "^0.13.0", 82 | "webpack": "^1.11.0", 83 | "react": "^0.14.6", 84 | "react-dom": "^0.14.6", 85 | "react-motion": "^0.3.1" 86 | }, 87 | "peerDependencies": { 88 | "react": ">=0.13.2 || ^0.14", 89 | "react-motion": "^0.3.1" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /source/SVGPath.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import {Motion} from 'react-motion'; 3 | 4 | const DEFAULT_PRESERVEASPECTRATIO = 'xMinYMax slice'; 5 | 6 | const SVGPath = ({ 7 | defaultStyle, 8 | style, 9 | path, 10 | width, 11 | height, 12 | preserveAspectRatio: preserveAspectRatio = DEFAULT_PRESERVEASPECTRATIO, 13 | }) => { 14 | return ( 15 | 16 | {delta => { 17 | return ( 18 | 21 | 22 | 23 | ); 24 | }} 25 | 26 | ); 27 | }; 28 | 29 | SVGPath.propTypes = { 30 | defaultStyle: PropTypes.object.isRequired, 31 | style: PropTypes.object.isRequired, 32 | path: PropTypes.func.isRequired, 33 | width: PropTypes.number.isRequired, 34 | height: PropTypes.number.isRequired, 35 | preserveAspectRatio: PropTypes.string 36 | }; 37 | 38 | export default SVGPath; 39 | -------------------------------------------------------------------------------- /source/index.js: -------------------------------------------------------------------------------- 1 | import SVGPath from './SVGPath'; 2 | 3 | export default SVGPath; 4 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TestUtils from 'react-addons-test-utils'; 3 | import test from 'blue-tape'; 4 | import SVGPath from '../source/index'; 5 | 6 | 7 | test('SVGPath SVG node', assert => { 8 | 9 | const renderer = TestUtils.createRenderer(); 10 | renderer.render( 11 | `M0,20 L220,40 L220,300 L0,300 Z`} 15 | height={10} /> 16 | ); 17 | const output = renderer.getRenderOutput(); 18 | 19 | const actual = output.props.children().type; 20 | const expected = 'svg'; 21 | 22 | assert.equal(actual, expected, 23 | 'Should render an SVG child node'); 24 | 25 | assert.end(); 26 | }); 27 | 28 | 29 | test('SVGPath viewbox attr', assert => { 30 | 31 | const width = 10; 32 | const height = 10; 33 | 34 | const renderer = TestUtils.createRenderer(); 35 | renderer.render( 36 | `M0,20 L220,40 L220,300 L0,300 Z`} 40 | height={height} /> 41 | ); 42 | const output = renderer.getRenderOutput(); 43 | 44 | const actual = output.props.children().props.viewBox; 45 | const expected = '0 0 10 10'; 46 | 47 | assert.equal(actual, expected, 48 | 'Should render a viewBox attr'); 49 | 50 | assert.end(); 51 | }); 52 | 53 | test('SVGPath default preserveAspectRatio attr', assert => { 54 | 55 | const renderer = TestUtils.createRenderer(); 56 | renderer.render( 57 | `M0,20 L220,40 L220,300 L0,300 Z`} 61 | height={20} /> 62 | ); 63 | const output = renderer.getRenderOutput(); 64 | 65 | const actual = output.props.children().props.preserveAspectRatio; 66 | const expected = 'xMinYMax slice'; 67 | 68 | assert.equal(actual, expected, 69 | 'Should render a default preserveAspectRatio attr'); 70 | assert.end(); 71 | }); 72 | 73 | test('SVGPath custom preserveAspectRatio attr', assert => { 74 | 75 | const renderer = TestUtils.createRenderer(); 76 | renderer.render( 77 | `M0,20 L220,40 L220,300 L0,300 Z`} 82 | height={20} /> 83 | ); 84 | const output = renderer.getRenderOutput(); 85 | 86 | const actual = output.props.children().props.preserveAspectRatio; 87 | const expected = 'xMinYMid'; 88 | 89 | assert.equal(actual, expected, 90 | 'Should render custom preserveAspectRatio attr'); 91 | 92 | assert.end(); 93 | }); 94 | 95 | test('SVGPath forces CSS hardware acceleration', assert => { 96 | 97 | const renderer = TestUtils.createRenderer(); 98 | renderer.render( 99 | `M0,20 L220,40 L220,300 L0,300 Z`} 103 | height={20} /> 104 | ); 105 | const output = renderer.getRenderOutput(); 106 | 107 | const actual = output.props.children().props.style.transform; 108 | const expected = 'translate3d(0, 0, 0)'; 109 | 110 | assert.equal(actual, expected, 111 | 'Should render a transform3d style to force hardware acceleration'); 112 | 113 | assert.end(); 114 | }); 115 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var env = process.env.NODE_ENV = process.env.NODE_ENV || 'production'; 4 | 5 | var eslintLoader = { 6 | test: /\.js$/, 7 | loaders: ['eslint'], 8 | include: [path.resolve('./source'), path.resolve('./example')] 9 | }; 10 | 11 | module.exports = { 12 | devtool: 'eval-source-map', 13 | 14 | entry: { 15 | example:'./example/index.js' 16 | }, 17 | 18 | output: { 19 | filename: 'bundle.js', 20 | publicPath: '/example/', 21 | path: path.resolve('./example') 22 | }, 23 | 24 | plugins: [ 25 | new webpack.DefinePlugin({ 26 | 'process.env': { 27 | NODE_ENV: '"' + env + '"' 28 | } 29 | }) 30 | ], 31 | 32 | module: { 33 | preLoaders: env === 'development' ? [ 34 | eslintLoader 35 | ] : [], 36 | loaders: [ 37 | { 38 | test: /\.js$/, 39 | loader: 'babel?plugins=object-assign', 40 | include: [path.resolve('./source'), path.resolve('./example')] 41 | }, 42 | { 43 | test: /\.css$/, 44 | loader: 'style!css', 45 | include: [path.resolve('./source'), path.resolve('./example')] 46 | } 47 | ], 48 | noParse: [ 49 | path.join(__dirname, 'node_modules', 'babel-core', 'browser.min.js') 50 | ] 51 | }, 52 | 53 | resolve: { 54 | extensions: ['', '.js', '.jsx'] 55 | }, 56 | 57 | stats: { 58 | colors: true 59 | }, 60 | 61 | eslint: { 62 | configFile: './.eslintrc' 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var env = process.env.NODE_ENV = process.env.NODE_ENV || 'production'; 4 | var minify = process.env.MINIFY || false; 5 | 6 | var eslintLoader = { 7 | test: /\.js$/, 8 | loaders: ['eslint'], 9 | include: [path.resolve('./source'), path.resolve('./example')] 10 | }; 11 | 12 | var uglifyPlugin = new webpack.optimize.UglifyJsPlugin({ 13 | sourceMap: true 14 | }); 15 | 16 | 17 | module.exports = { 18 | devtool: 'sourcemap', 19 | 20 | entry: { 21 | lib:'./source/index.js' 22 | }, 23 | 24 | externals: [{ 25 | 'react-motion': 'react-motion', 26 | react: { 27 | root: 'React', 28 | commonjs2: 'react', 29 | commonjs: 'react', 30 | amd: 'react' 31 | } 32 | }], 33 | 34 | output: { 35 | path: path.join(__dirname, 'build'), 36 | publicPath: 'build/', 37 | filename: minify ? 'react-morphine.min.js' : 'react-morphine.js', 38 | sourceMapFilename: 'react-morphine.map', 39 | library: 'ReactMorphine', 40 | libraryTarget: 'umd' 41 | }, 42 | 43 | plugins: [ 44 | new webpack.DefinePlugin({ 45 | 'process.env': { 46 | NODE_ENV: '"' + env + '"' 47 | } 48 | }) 49 | ].concat(minify ? [uglifyPlugin] : []), 50 | 51 | module: { 52 | loaders: [ 53 | { 54 | test: /\.js$/, 55 | loader: 'babel?plugins=object-assign', 56 | include: [path.resolve('./source'), path.resolve('./example')] 57 | }, 58 | { 59 | test: /\.css$/, 60 | loader: 'style!css', 61 | include: [path.resolve('./source'), path.resolve('./example')] 62 | } 63 | ] 64 | }, 65 | 66 | resolve: { 67 | extensions: ['', '.js'] 68 | }, 69 | 70 | stats: { 71 | colors: true 72 | }, 73 | 74 | eslint: { 75 | configFile: './.eslintrc' 76 | } 77 | }; 78 | --------------------------------------------------------------------------------