Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor API #24

Merged
merged 9 commits into from Jun 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 5 additions & 19 deletions .eslintrc
@@ -1,24 +1,10 @@

parser: babel-eslint
extends: airbnb
extends:
- jason/react
- plugin:jsx-a11y/recommended
env:
node: true
browser: true

rules:
prefer-const: off
prefer-template: off
no-prototype-builtins: off
no-underscore-dangle: off
import/prefer-default-export: off
no-param-reassign: off
react/jsx-filename-extension: off
react/no-did-mount-set-state: off
react/require-default-props: off
no-plusplus: off
guard-for-in: off
no-restricted-syntax:
- error
- ForOfStatement
- LabeledStatement
- WithStatement
plugins:
- jsx-a11y
7 changes: 7 additions & 0 deletions .storybook/config.js
@@ -0,0 +1,7 @@
import { configure } from '@kadira/storybook';

function loadStories() {
require('../stories');
}

configure(loadStories, module);
20 changes: 20 additions & 0 deletions .storybook/webpack.config.js
@@ -0,0 +1,20 @@
module.exports = (config) => {
config.plugins = config.plugins
.filter(p => p.constructor.name !== 'CaseSensitivePathsPlugin');

config.module = {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader!css-literal-loader',
},
{
test: /\.css$/,
loader: 'style-loader!css-loader',
},
],
};

return config;
};
23 changes: 15 additions & 8 deletions package.json
Expand Up @@ -9,7 +9,9 @@
"build": "rimraf lib && babel src --out-dir lib && npm run build:dist",
"build:dist": "rimraf lib/dist && webpack && NODE_ENV=production webpack -p",
"lint": "eslint src test",
"release": "release"
"release": "release",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"repository": {
"type": "git",
Expand All @@ -34,25 +36,26 @@
"testRegex": "-test\\.js",
"roots": [
"<rootDir>/test"
],
"timers": "fake"
]
},
"peerDependencies": {
"react": "^15.0.0",
"react-dom": "^15.0.0"
},
"dependencies": {
"chain-function": "^1.0.0",
"classnames": "^2.2.5",
"dom-helpers": "^3.2.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.5.6",
"prop-types": "^15.5.8",
"warning": "^3.0.0"
},
"devDependencies": {
"@kadira/storybook": "^2.21.0",
"babel-cli": "^6.24.0",
"babel-core": "^6.24.0",
"babel-eslint": "^7.1.1",
"babel-jest": "^19.0.0",
"babel-jest": "^20.0.3",
"babel-loader": "^6.4.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-dev-expression": "^0.2.1",
Expand All @@ -61,16 +64,20 @@
"babel-preset-latest": "^6.24.0",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-2": "^6.18.0",
"css-literal-loader": "^0.2.0",
"css-loader": "^0.28.0",
"eslint": "^3.17.1",
"eslint-config-airbnb": "^14.1.0",
"eslint-config-jason": "^4.0.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.10.0",
"jest-cli": "^19.0.2",
"eslint-plugin-react": "^6.10.3",
"jest": "^20.0.4",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"release-script": "^1.0.2",
"rimraf": "^2.6.1",
"sinon": "^2.1.0",
"style-loader": "^0.16.1",
"teaspoon": "^6.4.3",
"webpack": "^2.2.1"
},
Expand Down
223 changes: 223 additions & 0 deletions src/CSSTransition.js
@@ -0,0 +1,223 @@
import * as PropTypes from 'prop-types';
import addClass from 'dom-helpers/class/addClass';
import removeClass from 'dom-helpers/class/removeClass';
import React from 'react';

import Transition from './Transition';
import { classNamesShape } from './utils/PropTypes';

const propTypes = {
...Transition.propTypes,

/**
* The animation classNames applied to the component as it enters or exits.
* A single name can be provided and it will be suffixed for each stage: e.g.
*
* `classNames="fade"` applies `fade-enter`, `fade-enter-active`,
* `fade-exit`, `fade-exit-active`, `fade-appear`, and `fade-appear-active`.
* Each individual classNames can also be specified independently like:
*
* ```js
* classNames={{
* appear: 'my-appear',
* appearActive: 'my-active-appear',
* enter: 'my-enter',
* enterActive: 'my-active-enter',
* exit: 'my-exit',
* exitActive: 'my-active-exit',
* }}
* ```
*/
classNames: classNamesShape,

/**
* A `<Transition>` callback fired immediately after the 'enter' or 'appear' class is
* applied.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEnter: PropTypes.func,

/**
* A `<Transition>` callback fired immediately after the 'enter-active' or
* 'appear-active' class is applied.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEntering: PropTypes.func,

/**
* A `<Transition>` callback fired immediately after the 'enter' or
* 'appear' classes are **removed** from the DOM node.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onEntered: PropTypes.func,


/**
* A `<Transition>` callback fired immediately after the 'exit' class is
* applied.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onExit: PropTypes.func,

/**
* A `<Transition>` callback fired immediately after the 'exit-active' is
* class is applied.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onExiting: PropTypes.func,

/**
* A `<Transition>` callback fired immediately after the 'exit' classes
* are **removed** from the DOM node.
*
* @type Function(node: HtmlElement, isAppearing: bool)
*/
onExited: PropTypes.func,
};

/**
* A `Transition` component using CSS transitions and animations.
* It's inspired by the excellent [ng-animate](http://www.nganimate.org/) libary.
*
* `CSSTransition` applies a pair of class names during the `appear`, `enter`,
* and `exit` stages of the transition. The first class is applied and then a
* second "active" class in order to activate the css animation.
*
* When the `in` prop is toggled to `true` the Component will get
* the `example-enter` CSS class and the `example-enter-active` CSS class
* added in the next tick. This is a convention based on the `classNames` prop.
*
* ```css
* .example-enter {
* opacity: 0.01;
* }
*
* .example-enter.example-enter-active {
* opacity: 1;
* transition: opacity 500ms ease-in;
* }
*
* .example-leave {
* opacity: 1;
* }
*
* .example-leave.example-leave-active {
* opacity: 0.01;
* transition: opacity 300ms ease-in;
* }
* ```
*/
class CSSTransition extends React.Component {
onEnter = (node, appearing) => {
const { className } = this.getClassNames(appearing ? 'appear' : 'enter')

this.removeClasses(node, 'exit');
addClass(node, className)

if (this.props.onEnter) {
this.props.onEnter(node)
}
}

onEntering = (node, appearing) => {
const { activeClassName } = this.getClassNames(
appearing ? 'appear' : 'enter'
);

this.reflowAndAddClass(node, activeClassName)

if (this.props.onEntering) {
this.props.onEntering(node)
}
}

onEntered = (node, appearing) => {
this.removeClasses(node, appearing ? 'appear' : 'enter');

if (this.props.onEntered) {
this.props.onEntered(node)
}
}

onExit = (node) => {
const { className } = this.getClassNames('exit')

this.removeClasses(node, 'appear');
this.removeClasses(node, 'exit');
addClass(node, className)

if (this.props.onExit) {
this.props.onExit(node)
}
}

onExiting = (node) => {
const { activeClassName } = this.getClassNames('exit')

this.reflowAndAddClass(node, activeClassName)

if (this.props.onExiting) {
this.props.onExiting(node)
}
}

onExited = (node) => {
this.removeClasses(node, 'exit');

if (this.props.onExited) {
this.props.onExited(node)
}
}

getClassNames = (type) => {
const { classNames } = this.props

let className = typeof classNames !== 'string' ?
classNames[type] : classNames + '-' + type;

let activeClassName = typeof classNames !== 'string' ?
classNames[type + 'Active'] : className + '-active';

return { className, activeClassName }
}

removeClasses(node, type) {
const { className, activeClassName } = this.getClassNames(type)
className && removeClass(node, className);
activeClassName && removeClass(node, activeClassName);
}

reflowAndAddClass(node, className) {
// This is for to force a repaint,
// which is necessary in order to transition styles when adding a class name.
/* eslint-disable no-unused-expressions */
node.scrollTop;
/* eslint-enable no-unused-expressions */
addClass(node, className);
}

render() {
const props = { ...this.props };
Object.keys(propTypes).forEach(key => delete props[key]);
return (
<Transition
{...props}
onEnter={this.onEnter}
onEntered={this.onEntered}
onEntering={this.onEntering}
onExit={this.onExit}
onExiting={this.onExiting}
onExited={this.onExited}
/>
);
}
}

CSSTransition.propTypes = propTypes;

export default CSSTransition