├── .gitignore
├── LICENSE
├── README.md
├── TouchableSetActive.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jeff Stout
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.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-TouchableSetActive
2 | Touchable component for [React Native](https://github.com/facebook/react-native) that enables more advanced styling by setting an active state. Most useful for building your own touchable/button components on top of.
3 |
4 | ## Install
5 | ```sh
6 | $ npm install react-native-touchable-set-active --save
7 | ```
8 |
9 | ## Usage
10 | First, require the `TouchableSetActive` component in your project.
11 | ```javascript
12 | var TouchableSetActive = require('react-native-touchable-set-active');
13 | ```
14 |
15 | There are two different ways you can use this component. They both involve passing a value to the `setActive` property on `TouchableSetActive`.
16 |
17 | ###setActive={this}
18 | The simplest implementation is achieved by just passing `this`. The component will set an `active` state (using `this.setState`) on the parent component. To toggle a style, set one conditionally in the style property that is dependent on `this.state.active`.
19 |
20 | ```javascript
21 | class ExampleButton extends React.Component {
22 | constructor(props) {
23 | super(props);
24 | this.state = {};
25 | }
26 | render() {
27 | return(
28 |
35 |
38 | Example Button
39 |
40 |
41 | );
42 | }
43 | }
44 | ```
45 |
46 | ###setActive={*function*}
47 | Instead of passing `this`, you can provide a function. It will be called whenever the component's active state changes, with a boolean value representing the active state as the only argument.
48 | ```javascript
49 | class ExampleButton extends React.Component {
50 | constructor(props) {
51 | super(props);
52 | this.state = {
53 | active: false,
54 | };
55 | }
56 | render() {
57 | return(
58 | {
60 | this.setState({active: isActive});
61 | }}
62 | style={[
63 | !this.state.active && styles.inactiveButton,
64 | this.state.active && styles.activeButton,
65 | ]}
66 | >
67 |
70 | Example Button
71 |
72 |
73 | );
74 | }
75 | }
76 | ```
77 |
78 | ## Additional Props
79 | `TouchableSetActive` is just like any other [Touchable component](https://facebook.github.io/react-native/docs/touchablewithoutfeedback.html) in that it supports the following properties:
80 | ```javascript
81 | onPressIn
82 | onPressOut
83 | onPress
84 | onLongPress
85 | ```
86 |
87 | It also supports touchable delay properties that are (*hopefully*) landing in React Native core soon (via [\#1255](https://github.com/facebook/react-native/pull/1255)):
88 | ```javascript
89 | /**
90 | * Delay in ms, from the release of the touch, before onPress is called.
91 | */
92 | delayOnPress: React.PropTypes.number,
93 | /**
94 | * Delay in ms, from the start of the touch, before onPressIn is called.
95 | */
96 | delayOnPressIn: React.PropTypes.number,
97 | /**
98 | * Delay in ms, from the release of the touch, before onPressOut is called.
99 | */
100 | delayOnPressOut: React.PropTypes.number,
101 | /**
102 | * Delay in ms, from onPressIn, before onLongPress is called.
103 | */
104 | delayOnLongPress: React.PropTypes.number,
105 | ```
106 | *Support for `delayOnLongPress` is dependent on some underlying changes to the `Touchable` library. Unfortunately, it won't be available until those changes are committed. If you really need it now, take a look at [the PR](https://github.com/facebook/react-native/pull/1255) or [my branch](https://github.com/jmstout/react-native/tree/touchable-custom-delays) which adds this functionality. It should also be noted that until this PR lands, `delayOnPressIn` can be set to a maximum of `249` ms before throwing an error.*
107 |
108 | Additionally, the props `delayActive` and `delayInactive` can be used to decouple the active state from the press events.
109 | ```javascript
110 | /**
111 | * Delay in ms, from the start of the touch, before the active state is shown.
112 | */
113 | delayActive: React.PropTypes.number,
114 | /**
115 | * Delay in ms, from the start of the active state, before it becomes inactive.
116 | */
117 | delayInactive: React.PropTypes.number,
118 | ```
119 |
120 | ## License
121 | MIT © [Jeff Stout](http://jmstout.com)
--------------------------------------------------------------------------------
/TouchableSetActive.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015 Jeff Stout
3 | * MIT License
4 | *
5 | * The TouchableSetActive component was adapted from a fork of React Native's
6 | * original Touchable components. Therefore, the following license notice also
7 | * applies to parts of this source code.
8 | * See http://github.com/facebook/react-native for the files it refers to.
9 | *
10 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
11 | *
12 | * Copyright (c) 2015-present, Facebook, Inc.
13 | * All rights reserved.
14 | *
15 | * This source code is licensed under the BSD-style license found in the
16 | * LICENSE file in the root directory of this source tree. An additional grant
17 | * of patent rights can be found in the PATENTS file in the same directory.
18 | */
19 | 'use strict';
20 |
21 | var React = require('react-native');
22 | var TimerMixin = require('react-timer-mixin');
23 | var Touchable = require('Touchable');
24 | var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
25 | var View = require('View');
26 |
27 | var merge = require('merge');
28 |
29 | var DEFAULT_HIDE_MS = 150;
30 | var DEFAULT_ACTIVE_MS = 120;
31 | var DEFAULT_LONG_PRESS_MS = 400;
32 |
33 | var DEFAULT_PROPS = {
34 | delayOnPressOut: DEFAULT_HIDE_MS,
35 | };
36 |
37 | var TouchableSetActive = React.createClass({
38 | propTypes: {
39 | ...TouchableWithoutFeedback.propTypes,
40 | style: View.propTypes.style,
41 | /**
42 | * Required property used for setting the active state.
43 | * Accepts a React component class (this) or function.
44 | */
45 | setActive: function(props, propName, componentName) {
46 | if (!props[propName] || typeof(props[propName]) !== 'function' &&
47 | !React.addons.TestUtils.isCompositeComponent(props[propName])) {
48 | return new Error(
49 | componentName + ': prop type `' + propName + '` is ' +
50 | (props[propName] ? 'invalid' : 'missing') +
51 | '; it must be a React component class (this) or function.'
52 | );
53 | }
54 | },
55 | /**
56 | * Delay in ms, from the start of the touch, before the active state is shown.
57 | */
58 | delayActive: React.PropTypes.number,
59 | /**
60 | * Delay in ms, from the start of the active state, before it becomes inactive.
61 | */
62 | delayInactive: React.PropTypes.number,
63 |
64 | // Note: remove the following delay props when they land in core
65 |
66 | /**
67 | * Delay in ms, from the release of the touch, before onPress is called.
68 | */
69 | delayOnPress: React.PropTypes.number,
70 | /**
71 | * Delay in ms, from the start of the touch, before onPressIn is called.
72 | */
73 | delayOnPressIn: React.PropTypes.number,
74 | /**
75 | * Delay in ms, from the release of the touch, before onPressOut is called.
76 | */
77 | delayOnPressOut: React.PropTypes.number,
78 | /**
79 | * Delay in ms, from onPressIn, before onLongPress is called.
80 | */
81 | delayOnLongPress: React.PropTypes.number,
82 | },
83 |
84 | mixins: [TimerMixin, Touchable.Mixin],
85 |
86 | getDefaultProps: () => DEFAULT_PROPS,
87 |
88 | _computeState: function(props) {
89 | return {
90 | setActive: props.setActive,
91 | componentStyle: props.style,
92 | };
93 | },
94 |
95 | getInitialState: function() {
96 | return merge(
97 | this.touchableGetInitialState(), this._computeState(this.props)
98 | );
99 | },
100 |
101 | componentDidMount: function() {
102 | this._activeType = this._getActiveType();
103 | this._delayActive = !!(this.props.delayActive ||
104 | this.props.delayActive === 0);
105 | this._delayInactive = !!(this.props.delayInactive ||
106 | this.props.delayInactive === 0);
107 | },
108 |
109 | componentWillReceiveProps: function(nextProps) {
110 | if (nextProps.setActive !== this.props.setActive ||
111 | nextProps.style !== this.props.style) {
112 | this.setState(this._computeState(nextProps));
113 | }
114 | },
115 |
116 | _getActiveType: function() {
117 | var activeProp = this.state.setActive || this.props.setActive;
118 | if (!activeProp) return false;
119 | if (typeof(activeProp) === 'function') return 'func';
120 | if (React.addons.TestUtils.isCompositeComponent(activeProp)) return 'class';
121 | return false;
122 | },
123 |
124 | /**
125 | * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
126 | * defined on your component.
127 | */
128 | touchableHandleActivePressIn: function() {
129 | this._fromPressIn = true;
130 | this.clearTimeout(this._hideTimeout);
131 | this._hideTimeout = null;
132 | !this._delayActive && this._showActive();
133 | if (this._delayInactive) {
134 | this._hideTimeout = this.setTimeout(this._hideActive,
135 | this.props.delayInactive);
136 | }
137 | this.props.onPressIn && this.props.onPressIn();
138 | },
139 |
140 | touchableHandleActivePressOut: function() {
141 | if (this.props.delayOnPressOut) {
142 | this._onPressOutTimeout = this.setTimeout(function() {
143 | this._onPressOut();
144 | }, this.props.delayOnPressOut);
145 | } else {
146 | this._onPressOut();
147 | }
148 | },
149 |
150 | _onPressOut: function() {
151 | if (!this._hideTimeout && !this._delayInactive) {
152 | this._isHiding = true;
153 | this._hideActive();
154 | }
155 | this.props.onPressOut && this.props.onPressOut();
156 | },
157 |
158 | touchableHandlePress: function() {
159 | if (this.props.delayOnPress) {
160 | if (!this._onPressTimeout) {
161 | this._onPressTimeout = this.setTimeout(function() {
162 | this.clearTimeout(this._onPressTimeout);
163 | this._onPressTimeout = null;
164 | this._onPress();
165 | }, this.props.delayOnPress);
166 | }
167 | } else {
168 | this._onPress();
169 | }
170 | },
171 |
172 | _onPress: function() {
173 | if (!this._fromPressIn) {
174 | !this._delayActive && this._showActive();
175 | this._isHiding = true;
176 | this._hideTimeout = this.setTimeout(this._hideActive,
177 | this._delayInactive ? this.props.delayInactive :
178 | this.props.delayOnPressOut || DEFAULT_HIDE_MS);
179 | }
180 | this.props.onPress && this.props.onPress();
181 | },
182 |
183 | touchableHandleLongPress: function() {
184 | this.props.onLongPress && this.props.onLongPress();
185 | },
186 |
187 | touchableGetPressRectOffset: function() {
188 | return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
189 | },
190 |
191 | touchableGetHighlightDelayMS: function() {
192 | return this.props.delayOnPressIn === 0 ? 0 :
193 | this.props.delayOnPressIn || DEFAULT_ACTIVE_MS;
194 | },
195 |
196 | touchableGetLongPressDelayMS: function() {
197 | return this.props.delayOnLongPress === 0 ? 0 :
198 | this.props.delayOnLongPress || DEFAULT_LONG_PRESS_MS;
199 | },
200 |
201 | _showActive: function() {
202 | if (!this._activeStatus) {
203 | this._activeStatus = true;
204 | if (this._activeType === 'class') {
205 | this.state.setActive.setState({active: true});
206 | } else if (this._activeType === 'func') {
207 | this.state.setActive(true);
208 | }
209 | }
210 | },
211 |
212 | _hideActive: function() {
213 | if (this._activeStatus) {
214 | this._activeStatus = false;
215 | this.clearTimeout(this._hideTimeout);
216 | this._hideTimeout = null;
217 | if (this._activeType === 'class') {
218 | this.state.setActive.setState({active: false});
219 | } else if (this._activeType === 'func') {
220 | this.state.setActive(false);
221 | }
222 | }
223 | },
224 |
225 | _componentHandleResponderGrant: function(e, dispatchID) {
226 | this._fromPressIn = false;
227 | this._isHiding = false;
228 | this.clearTimeout(this._onPressOutTimeout);
229 | this._onPressOutTimeout = null;
230 | this.clearTimeout(this._hideTimeout);
231 | this._hideTimeout = null;
232 | if (this._delayActive && !this._showTimeout) {
233 | this._showTimeout = this.setTimeout(function() {
234 | this.clearTimeout(this._showTimeout);
235 | this._showTimeout = null;
236 | this._showActive();
237 | if (this._delayInactive || this._isHiding) {
238 | this._isHiding && this.clearTimeout(this._hideTimeout);
239 | this._hideTimeout = this.setTimeout(this._hideActive,
240 | this._delayInactive ? this.props.delayInactive :
241 | this.props.delayOnPressOut || DEFAULT_HIDE_MS);
242 | }
243 | }, this.props.delayActive);
244 | }
245 | this.touchableHandleResponderGrant(e, dispatchID);
246 | },
247 |
248 | _renderChildren: function(onlyChild) {
249 | if (this.props.children && onlyChild) return this.props.children;
250 | else return ({this.props.children});
251 | },
252 |
253 | render: function() {
254 | var { children } = this.props;
255 | var onlyChild = React.Children.count(children) === 1;
256 | return React.cloneElement(this._renderChildren(onlyChild), {
257 | style: [this.state.componentStyle, onlyChild && children.props.style],
258 | accessible: true,
259 | testID: this.props.testID,
260 | onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
261 | onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
262 | onResponderGrant: this._componentHandleResponderGrant,
263 | onResponderMove: this.touchableHandleResponderMove,
264 | onResponderRelease: this.touchableHandleResponderRelease,
265 | onResponderTerminate: this.touchableHandleResponderTerminate,
266 | });
267 | }
268 | });
269 |
270 | var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
271 |
272 | module.exports = TouchableSetActive;
273 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-touchable-set-active",
3 | "version": "0.0.5",
4 | "description": "Touchable component for React Native that enables more advanced styling by setting an active state. Most useful for building your own touchable/button components on top of.",
5 | "license": "MIT",
6 | "repository": "jmstout/react-native-TouchableSetActive",
7 | "author": {
8 | "name": "Jeff Stout",
9 | "url": "http://jmstout.com"
10 | },
11 | "main": "TouchableSetActive.js",
12 | "peerDependencies": {
13 | "react-native": "^0.4.x"
14 | },
15 | "keywords": [ "react-component", "react-native", "react native", "react", "touchable", "touch", "setState", "active", "style", "css", "highlight", "ios" ]
16 | }
17 |
--------------------------------------------------------------------------------