react-native-material-kit-edge
Version:
Bringing Material Design to React Native. Fork by Airbitz
237 lines (200 loc) • 6.14 kB
JavaScript
//
// RangeSlider component.
//
// - [Props](#props)
// - [Defaults](#defaults)
// - [Built-in builders](#builders)
//
// Created by awaidman on 16/1/21.
//
import React, {
Component,
} from 'react';
import PropTypes from 'prop-types';
import { ViewPropTypes } from '../utils';
import {
Animated,
PanResponder,
View,
} from 'react-native';
// Default color of the upper part of the track
const DEFAULT_UPPER_TRACK_COLOR = '#cccccc';
// Color of the thumb when lowest value is chosen
const LOWEST_VALUE_THUMB_COLOR = 'white';
// The max scale of the thumb
const THUMB_SCALE_RATIO = 1.3;
// Width of the thumb border
const THUMB_BORDER_WIDTH = 2;
// extra spacing enlarging the touchable area
const TRACK_EXTRA_MARGIN_H = 5;
// ## <section id='Thumb'>Thumb</section>
// `Thumb` component of the [`Slider`](#Slider).
class Thumb extends Component {
constructor(props) {
super(props);
this.x = 0; // current x-axis position
this._trackMarginH = (props.radius + THUMB_BORDER_WIDTH) * THUMB_SCALE_RATIO +
TRACK_EXTRA_MARGIN_H;
this._panResponder = {};
this._animatedLeft = new Animated.Value(0);
this._animatedScale = new Animated.Value(1);
this.state = {
color: LOWEST_VALUE_THUMB_COLOR,
borderColor: DEFAULT_UPPER_TRACK_COLOR,
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderTerminationRequest: () => false,
onShouldBlockNativeResponder: () => true,
onPanResponderGrant: (evt) => { this.props.onGrant(this, evt); },
onPanResponderMove: (evt) => { this.props.onMove(this, evt); },
onPanResponderRelease: (evt) => { this.props.onEnd(this, evt); },
onPanResponderTerminate: (evt) => { this.props.onEnd(this, evt); },
});
this._onRadiiUpdate(this.props);
this.setState({
borderColor: this.props.disabledColor,
});
}
componentDidMount() {
this._animatedLeft.addListener(this._getOnSliding());
}
componentWillReceiveProps(nextProps) {
this._onRadiiUpdate(nextProps);
}
componentWillUnmount() {
this._animatedLeft.removeAllListeners();
}
// when thumb radii updated, re-calc the dimens
_onRadiiUpdate(props) {
this._radii = props.radius;
this._dia = this._radii * 2;
this._containerRadii = this._radii + THUMB_BORDER_WIDTH;
this._containerDia = this._containerRadii * 2;
}
// return a memoized function to handle sliding animation events
_getOnSliding() {
let prevX = this.x; // memorize the previous x
// on sliding of the thumb
// `value` - the `left` of the thumb, relative to the container
return ({ value }) => {
// convert to value relative to the track
const x = value + this._containerRadii - this._trackMarginH;
if (prevX <= 0 && x > 0) {
// leaving the lowest value, scale up the thumb
this._onExplode();
} else if (prevX > 0 && x <= 0) {
// at lowest value, scale down the thumb
this._onCollapse();
}
prevX = x;
};
}
// animate the sliding
// `x` - target position, relative to the track
moveTo(x) {
this.x = x;
const x0 = this.x + this._trackMarginH;
Animated.parallel([
Animated.timing(this._animatedScale, {
toValue: THUMB_SCALE_RATIO,
duration: 100,
}),
Animated.timing(this._animatedLeft, {
toValue: x0 - this._containerRadii,
duration: 0,
}),
]).start();
}
// stop sliding
confirmMoveTo() {
Animated.timing(this._animatedScale, {
toValue: 1,
duration: 100,
}).start();
}
// from 'lowest' to 'non-lowest'
_onExplode() {
this.setState({
borderColor: this.props.enabledColor,
color: this.props.enabledColor,
});
}
// from 'non-lowest' to 'lowest'
_onCollapse() {
this.setState({
borderColor: this.props.disabledColor,
color: LOWEST_VALUE_THUMB_COLOR,
});
}
// Rendering the `Thumb`
render() {
return (
<Animated.View
style={[ // the outer circle to draw the border
this.props.style,
{
width: this._containerDia,
height: this._containerDia,
backgroundColor: this.state.borderColor,
borderRadius: this._containerRadii,
position: 'absolute',
left: this._animatedLeft,
transform: [
{ scale: this._animatedScale },
],
},
]}
{ ...this._panResponder.panHandlers }
hitSlop={{
top: this.props.touchPadding,
left: this.props.touchPadding,
bottom: this.props.touchPadding,
right: this.props.touchPadding,
}}
>
<View
style={{ // the inner circle
width: this._dia,
height: this._dia,
backgroundColor: this.state.color,
borderRadius: this._radii,
top: THUMB_BORDER_WIDTH,
left: THUMB_BORDER_WIDTH,
}}
/>
</Animated.View>
);
}
}
Thumb.propTypes = {
// [RN.View Props](https://facebook.github.io/react-native/docs/view.html#props)...
...ViewPropTypes,
// Callback to handle onPanResponderGrant gesture
onGrant: PropTypes.func,
// Callback to handle onPanResponderMove gesture
onMove: PropTypes.func,
// Callback to handle onPanResponderRelease/Terminate gesture
onEnd: PropTypes.func,
// Color when thumb has no value
disabledColor: PropTypes.string,
// Color when thumb has value
enabledColor: PropTypes.string,
// Radius of thumb component
radius: PropTypes.number,
// Padding for the hitSlop on the Thumb component
touchPadding: PropTypes.number,
};
// ## <section id='defaults'>Defaults</section>
Thumb.defaultProps = {
radius: 6,
disabledColor: DEFAULT_UPPER_TRACK_COLOR,
touchPadding: 0,
};
// ## Public interface
module.exports = Thumb;