diff --git a/.gitignore b/.gitignore index 6d2037a..0b3302f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ node_modules dist build lib +coverage +ios +android diff --git a/HISTORY.md b/HISTORY.md index 2c663d4..7b1c3c3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # History +## 1.1.0 + +- [`new`] react-native support + ## 1.0.2 - [`fix`] error if this.refs.left/right is [] diff --git a/README.md b/README.md index 4ed6ecf..cbb8dce 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # rc-swipeout --- -iOS-style swipeout buttons that appear from behind a component +iOS-style swipeout buttons that appear from behind a component (web & react-native support) [![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] @@ -25,8 +25,12 @@ iOS-style swipeout buttons that appear from behind a component ## Development ``` +web: npm install npm start + +rn: +tnpm run rn-start ``` ## Example diff --git a/examples/reactNative.js b/examples/reactNative.js new file mode 100644 index 0000000..8b8b95d --- /dev/null +++ b/examples/reactNative.js @@ -0,0 +1,49 @@ +import { View, Text, AppRegistry } from 'react-native'; +import Swipeout from './src'; +import React from 'react'; + +class SwipeoutExample extends React.Component { + render() { + return ( + + console.log('more'), + style: { backgroundColor: 'orange', color: 'white' }, + }, + { text: 'delete', + onPress: () => console.log('delete'), + style: { backgroundColor: 'red', color: 'white' }, + }, + ]} + left={[ + { + text: 'read', + onPress: () => console.log('read'), + style: { backgroundColor: 'blue', color: 'white' }, + }, + { + text: 'reply', + onPress: () => console.log('reply'), + style: { backgroundColor: 'green', color: 'white' }, + }, + ]} + onOpen={() => console.log('open')} + onClose={() => console.log('close')} + > + this is Demo + + + ); + } +} + +AppRegistry.registerComponent('swipeout', () => SwipeoutExample); diff --git a/index.js b/index.js index 60cc4e5..86dbf69 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ -module.exports = require('./src/'); \ No newline at end of file +import Swipeout from './src/'; +export default Swipeout; \ No newline at end of file diff --git a/package.json b/package.json index 4a97a7a..9f76d8c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rc-swipeout", - "version": "1.0.2", - "description": "swipe out ui component for react", + "version": "1.1.0", + "description": "swipe out ui component for react(web and react-native)", "keywords": [ "react", "react-component", @@ -23,7 +23,7 @@ "assets/*.css" ], "licenses": "MIT", - "main": "./lib/index", + "main": "./lib/index.web", "config": { "port": 8000 }, @@ -34,22 +34,25 @@ "pub": "rc-tools run pub", "lint": "rc-tools run lint", "karma": "rc-tools run karma", - "saucelabs": "rc-tools run saucelabs", "test": "rc-tools run test", "chrome-test": "rc-tools run chrome-test", - "coverage": "rc-tools run coverage" + "coverage": "rc-tools run coverage", + "rn-start": "node node_modules/react-native/local-cli/cli.js start" }, "dependencies": { - "react-hammerjs": "^0.4.6" + "object.omit": "~2.0.0", + "react-hammerjs": "^0.5.0", + "react-native-swipeout": "https://github.com/dancormier/react-native-swipeout.git#master" }, "devDependencies": { "expect.js": "0.3.x", "hammer-simulator": "0.0.1", "pre-commit": "1.x", "rc-tools": "5.x", - "react": "0.14.x", - "react-addons-test-utils": "0.14.x", - "react-dom": "0.14.x" + "react": "~15.2.1", + "react-addons-test-utils": "~15.2.1", + "react-dom": "~15.2.1", + "react-native": "0.28.0" }, "pre-commit": [ "lint" diff --git a/src/Swipeout.js b/src/Swipeout.js index 0a85fa2..bb8d447 100644 --- a/src/Swipeout.js +++ b/src/Swipeout.js @@ -1,5 +1,7 @@ import React, { PropTypes } from 'react'; -import Hammer from 'react-hammerjs'; +import { View, Text } from 'react-native'; +import Swipe from 'react-native-swipeout'; +import splitObject from './util/splitObject'; class Swipeout extends React.Component { static propTypes = { @@ -9,216 +11,77 @@ class Swipeout extends React.Component { left: PropTypes.arrayOf(PropTypes.object), right: PropTypes.arrayOf(PropTypes.object), onOpen: PropTypes.func, - onClose: PropTypes.func, children: PropTypes.any, - } + }; static defaultProps = { - prefixCls: 'rc-swipeout', autoClose: false, disabled: false, left: [], right: [], onOpen() {}, - onClose() {}, - } + }; constructor(props) { super(props); - - this.onPanStart = this.onPanStart.bind(this); - this.onPan = this.onPan.bind(this); - this.onPanEnd = this.onPanEnd.bind(this); - this.onTap = this.onTap.bind(this); - - this.openedLeft = false; - this.openedRight = false; - this.disabledPan = false; - } - - componentDidMount() { - const { left, right } = this.props; - const width = this.refs.content.offsetWidth; - - this.contentWidth = width; - this.btnsLeftWidth = left ? (width / 5) * left.length : 0; - this.btnsRightWidth = right ? (width / 5) * right.length : 0; - } - - onPanStart(e) { - // cannot set direction by react-harmmerjs, fix left & right direction temporarily - // wait react-harmmerjs pr #46 to merge - const { left, right } = this.props; - const aev = e.additionalEvent; - if (aev === 'panright' && !left.length) { - this.disabledPan = true; - } else if (aev === 'panleft' && !right.length) { - this.disabledPan = true; - } else { - this.disabledPan = false; - } - - if (this.props.disabled || this.disabledPan) { - return; - } - this.panStartX = e.deltaX; - } - - onPan(e) { - if (this.props.disabled || this.disabledPan) { - return; - } - - // get pan distance - let posX = e.deltaX - this.panStartX; - if (this.openedRight) { - posX = posX - this.btnsRightWidth; - } else if (this.openedLeft) { - posX = posX + this.btnsLeftWidth; - } - - if (posX < 0 && this.props.right) { - this._setStyle(Math.min(posX, 0)); - } else if (posX > 0 && this.props.left) { - this._setStyle(Math.max(posX, 0)); - } - } - - onPanEnd(e) { - if (this.props.disabled || this.disabledPan) { - return; - } - - const posX = e.deltaX - this.panStartX; - const contentWidth = this.contentWidth; - const btnsLeftWidth = this.btnsLeftWidth; - const btnsRightWidth = this.btnsRightWidth; - const openX = contentWidth * 0.33; - let openLeft = posX > openX || posX > btnsLeftWidth / 2; - let openRight = posX < -openX || posX < -btnsRightWidth / 2; - - if (this.openedRight) { - openRight = posX - openX < -openX; - } - if (this.openedLeft) { - openLeft = posX + openX > openX; - } - - if (openRight && posX < 0) { - this.open(-btnsRightWidth, false, true); - } else if (openLeft && posX > 0) { - this.open(btnsLeftWidth, true, false); - } else { - this.close(); - } - } - - onTap() { - if (this.openedLeft || this.openedRight) { - this.close(); - } - } - - // left & right button click - onBtnClick(btn) { - const onPress = btn.onPress; - if (onPress) { - onPress(); - } - if (this.props.autoClose) { - this.close(); - } - } - - _getContentEasing(value, limit) { - // limit content style left when value > actions width - if (value < 0 && value < limit) { - return limit - Math.pow(limit - value, 0.85); - } else if (value > 0 && value > limit) { - return limit + Math.pow(value - limit, 0.85); - } - return value; - } - - // set content & actions style - _setStyle(value) { - const { left, right } = this.props; - const limit = value > 0 ? this.btnsLeftWidth : -this.btnsRightWidth; - const contentLeft = this._getContentEasing(value, limit); - this.refs.content.style.left = `${contentLeft}px`; - if (left.length) { - const leftWidth = Math.max(Math.min(value, Math.abs(limit)), 0); - this.refs.left.style.width = `${leftWidth}px`; - } - if (right.length) { - const rightWidth = Math.max(Math.min(-value, Math.abs(limit)), 0); - this.refs.right.style.width = `${rightWidth}px`; - } + this.state = { + show: false, + paddingTop: 0, + }; } - open(value, openedLeft, openedRight) { - const onOpen = this.props.onOpen; - if (onOpen && !this.openedLeft && !this.openedRight) { - onOpen(); - } - - this.openedLeft = openedLeft; - this.openedRight = openedRight; - this._setStyle(value); - } - - close() { - const onClose = this.props.onClose; - if (onClose && (this.openedLeft || this.openedRight)) { - onClose(); - } - this.openedLeft = false; - this.openedRight = false; - this._setStyle(0); - } - - renderButtons(buttons, ref) { - const prefixCls = this.props.prefixCls; - - return (buttons && buttons.length) ? ( -
- {buttons.map((btn, i) => { - return ( -
this.onBtnClick(btn)} - > -
{btn.text || 'Click'}
-
- ); - })} -
- ) : null; + renderCustomButton(button) { + const buttonStyle = button.style || {}; + const bgColor = buttonStyle.backgroundColor || 'transparent'; + const Component = ( + + + {button.text} + + + ); + return { + text: button.text || 'Click', + onPress: button.onPress, + type: 'default', + component: Component, + backgroundColor: 'transparent', + color: '#999', + disabled: false, + }; } render() { - const { prefixCls, left, right, children, ...others } = this.props; - - return (left.length || right.length) ? ( -
- -
- {children} -
-
+ const [{ disabled, autoClose, style, left, right, onOpen, children }, restProps] = splitObject( + this.props, + ['disabled', 'autoClose', 'style', 'left', 'right', 'onOpen', 'children'] + ); - { this.renderButtons(left, 'left') } - { this.renderButtons(right, 'right') } -
+ const cutsomLeft = left.map(btn => { + return this.renderCustomButton(btn); + }); + const cutsomRight = right.map(btn => { + return this.renderCustomButton(btn); + }); + + return (left.length || right.length) && !disabled ? ( + + {children} + ) : ( -
{children}
+ + {children} + ); } } diff --git a/src/Swipeout.web.js b/src/Swipeout.web.js new file mode 100644 index 0000000..498b6fc --- /dev/null +++ b/src/Swipeout.web.js @@ -0,0 +1,244 @@ +import React, { PropTypes } from 'react'; +import Hammer from 'react-hammerjs'; +import omit from 'object.omit'; +import splitObject from './util/splitObject'; + +class Swipeout extends React.Component { + static propTypes = { + prefixCls: PropTypes.string, + autoClose: PropTypes.bool, + disabled: PropTypes.bool, + left: PropTypes.arrayOf(PropTypes.object), + right: PropTypes.arrayOf(PropTypes.object), + onOpen: PropTypes.func, + onClose: PropTypes.func, + children: PropTypes.any, + }; + + static defaultProps = { + prefixCls: 'rc-swipeout', + autoClose: false, + disabled: false, + left: [], + right: [], + onOpen() {}, + onClose() {}, + }; + + constructor(props) { + super(props); + + this.onPanStart = this.onPanStart.bind(this); + this.onPan = this.onPan.bind(this); + this.onPanEnd = this.onPanEnd.bind(this); + this.onTap = this.onTap.bind(this); + + this.openedLeft = false; + this.openedRight = false; + this.disabledPan = false; + } + + componentDidMount() { + const { left, right } = this.props; + const width = this.refs.content.offsetWidth; + + this.contentWidth = width; + this.btnsLeftWidth = left ? (width / 5) * left.length : 0; + this.btnsRightWidth = right ? (width / 5) * right.length : 0; + } + + onPanStart(e) { + // cannot set direction by react-harmmerjs, fix left & right direction temporarily + // wait react-harmmerjs pr #46 to merge + const { left, right } = this.props; + const aev = e.additionalEvent; + if (aev === 'panright' && !left.length) { + this.disabledPan = true; + } else if (aev === 'panleft' && !right.length) { + this.disabledPan = true; + } else { + this.disabledPan = false; + } + + if (this.props.disabled || this.disabledPan) { + return; + } + this.panStartX = e.deltaX; + } + + onPan(e) { + if (this.props.disabled || this.disabledPan) { + return; + } + + // get pan distance + let posX = e.deltaX - this.panStartX; + if (this.openedRight) { + posX = posX - this.btnsRightWidth; + } else if (this.openedLeft) { + posX = posX + this.btnsLeftWidth; + } + + if (posX < 0 && this.props.right) { + this._setStyle(Math.min(posX, 0)); + } else if (posX > 0 && this.props.left) { + this._setStyle(Math.max(posX, 0)); + } + } + + onPanEnd(e) { + if (this.props.disabled || this.disabledPan) { + return; + } + + const posX = e.deltaX - this.panStartX; + const contentWidth = this.contentWidth; + const btnsLeftWidth = this.btnsLeftWidth; + const btnsRightWidth = this.btnsRightWidth; + const openX = contentWidth * 0.33; + let openLeft = posX > openX || posX > btnsLeftWidth / 2; + let openRight = posX < -openX || posX < -btnsRightWidth / 2; + + if (this.openedRight) { + openRight = posX - openX < -openX; + } + if (this.openedLeft) { + openLeft = posX + openX > openX; + } + + if (openRight && posX < 0) { + this.open(-btnsRightWidth, false, true); + } else if (openLeft && posX > 0) { + this.open(btnsLeftWidth, true, false); + } else { + this.close(); + } + } + + onTap() { + if (this.openedLeft || this.openedRight) { + this.close(); + } + } + + // left & right button click + onBtnClick(btn) { + const onPress = btn.onPress; + if (onPress) { + onPress(); + } + if (this.props.autoClose) { + this.close(); + } + } + + _getContentEasing(value, limit) { + // limit content style left when value > actions width + if (value < 0 && value < limit) { + return limit - Math.pow(limit - value, 0.85); + } else if (value > 0 && value > limit) { + return limit + Math.pow(value - limit, 0.85); + } + return value; + } + + // set content & actions style + _setStyle(value) { + const { left, right } = this.props; + const limit = value > 0 ? this.btnsLeftWidth : -this.btnsRightWidth; + const contentLeft = this._getContentEasing(value, limit); + this.refs.content.style.left = `${contentLeft}px`; + if (left.length) { + const leftWidth = Math.max(Math.min(value, Math.abs(limit)), 0); + this.refs.left.style.width = `${leftWidth}px`; + } + if (right.length) { + const rightWidth = Math.max(Math.min(-value, Math.abs(limit)), 0); + this.refs.right.style.width = `${rightWidth}px`; + } + } + + open(value, openedLeft, openedRight) { + const onOpen = this.props.onOpen; + if (onOpen && !this.openedLeft && !this.openedRight) { + onOpen(); + } + + this.openedLeft = openedLeft; + this.openedRight = openedRight; + this._setStyle(value); + } + + close() { + const onClose = this.props.onClose; + if (onClose && (this.openedLeft || this.openedRight)) { + onClose(); + } + this.openedLeft = false; + this.openedRight = false; + this._setStyle(0); + } + + renderButtons(buttons, ref) { + const prefixCls = this.props.prefixCls; + + return (buttons && buttons.length) ? ( +
+ {buttons.map((btn, i) => { + return ( +
this.onBtnClick(btn)} + > +
{btn.text || 'Click'}
+
+ ); + })} +
+ ) : null; + } + + render() { + const [{ prefixCls, left, right, children }, restProps] = splitObject( + this.props, + ['prefixCls', 'left', 'right', 'children'] + ); + const divProps = omit(restProps, [ + 'disabled', + 'autoClose', + 'onOpen', + 'onClose', + ]); + + let direction = 'DIRECTION_HORIZONTAL'; + if (left.length && right.length === 0) { + direction = 'DIRECTION_RIGHT'; + } + if (right.length && left.length === 0) { + direction = 'DIRECTION_LEFT'; + } + return (left.length || right.length) ? ( +
+ +
+ {children} +
+
+ + { this.renderButtons(left, 'left') } + { this.renderButtons(right, 'right') } +
+ ) : ( +
{children}
+ ); + } +} + +export default Swipeout; diff --git a/src/index.js b/src/index.js index e3543d7..e196e39 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1 @@ -import Swipeout from './Swipeout'; -export default Swipeout; +export { default } from './Swipeout'; diff --git a/src/index.web.js b/src/index.web.js new file mode 100644 index 0000000..e0d152f --- /dev/null +++ b/src/index.web.js @@ -0,0 +1 @@ +export { default } from './Swipeout.web'; diff --git a/src/util/splitObject.js b/src/util/splitObject.js new file mode 100644 index 0000000..802caec --- /dev/null +++ b/src/util/splitObject.js @@ -0,0 +1,12 @@ +export default function splitObject(obj, parts) { + const left = {}; + const right = {}; + Object.keys(obj).forEach((k) => { + if (parts.indexOf(k) !== -1) { + left[k] = obj[k]; + } else { + right[k] = obj[k]; + } + }); + return [left, right]; +} diff --git a/tests/index.js b/tests/index.js index 2e43f47..15cfadb 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,3 +1,2 @@ require('../assets/index.less'); -const req = require.context('.', false, /\.spec\.js$/); -req.keys().forEach(req); +import './usage'; diff --git a/tests/usage.spec.js b/tests/usage.js similarity index 98% rename from tests/usage.spec.js rename to tests/usage.js index c1164ed..d48ae5b 100644 --- a/tests/usage.spec.js +++ b/tests/usage.js @@ -1,7 +1,7 @@ const expect = require('expect.js'); -const React = require('react'); -const ReactDOM = require('react-dom'); -const TestUtils = require('react-addons-test-utils'); +import React from 'react'; +import ReactDOM from 'react-dom'; +import TestUtils from 'react-addons-test-utils'; require('hammer-simulator'); const Simulator = window.Simulator; Simulator.setType('pointer');