├── .babelrc
├── docs
├── demo.gif
└── contributing
│ ├── index.md
│ └── versions
│ └── index.md
├── source
├── index.js
└── SVGPath.js
├── .npmignore
├── example
├── index.js
├── index.html
├── Card.css
└── Card.js
├── .editorconfig
├── .travis.yml
├── contributing.md
├── .gitignore
├── bower.json
├── LICENSE
├── webpack.config.js
├── webpack.prod.config.js
├── package.json
├── test
└── index.js
├── README.md
└── .eslintrc
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 1
3 | }
4 |
--------------------------------------------------------------------------------
/docs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmmoli/react-morphine/HEAD/docs/demo.gif
--------------------------------------------------------------------------------
/source/index.js:
--------------------------------------------------------------------------------
1 | import SVGPath from './SVGPath';
2 |
3 | export default SVGPath;
4 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React-Morphine Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-morphine
2 | Relieving the pain of morphing UIs in React.
3 |
4 | [](https://travis-ci.org/mmmoli/react-morphine)
5 | [](https://www.npmjs.com/package/react-morphine)
6 |
7 | 
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 |
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------