UNPKG

@uiw/react-native

Version:
232 lines (216 loc) 5.48 kB
import React from 'react'; import { TouchableOpacity, Animated, View, StyleSheet } from 'react-native'; export default class Switch extends React.Component { translateXValue = 2; height = 16; static defaultProps = { checked: false, size: 'default', thumbColor: '#fff', color: '#4DD964', onValueChange: () => {} }; constructor(props) { super(props); this.animatedStart = checked => { const obj = { height: this.height, number: 1, translateXValue: this.translateXValue }; if (!checked) { obj.height = 2; obj.number = 0; obj.translateXValue = 2; } Animated.parallel([Animated.sequence([Animated.spring(this.state.borderValue, { toValue: obj.height, overshootClamping: true, useNativeDriver: false }), Animated.spring(this.state.bgOpacity, { toValue: obj.number, overshootClamping: true, useNativeDriver: false })]), Animated.spring(this.state.translateXValue, { toValue: obj.translateXValue, overshootClamping: true, useNativeDriver: false })]).start(); }; this.state = { checked: !!this.props.checked, containerSize: { width: 0, height: 0 }, borderValue: new Animated.Value(0), translateXValue: new Animated.Value(2), bgOpacity: new Animated.Value(props.value ? 1 : 0), control: 'state', animatedStart: this.animatedStart }; this.animatedStart(!!this.props.checked); } static getDerivedStateFromProps(props, state) { if (state.control === 'state') { return { control: 'props' }; } if (props.checked !== state.checked) { state.animatedStart(!!props.checked); return { checked: !!props.checked, control: 'props' }; } return null; } onPress = () => { const checked = !this.state.checked; this.setState({ checked, control: 'state' }, () => { this.animatedStart(checked); this.props.onValueChange(checked); }); }; measureContainer = event => { const { checked } = this.state; const { translateXValue } = this.state; const { height: layoutHeight, width: layoutWidth } = event.nativeEvent.layout; const height = layoutHeight - 4; const width = height; const size = { width, height }; this.height = height / 2; const state = { containerSize: size }; this.translateXValue = layoutWidth - 2 - width; translateXValue.setValue(checked ? layoutWidth - 2 - width : 2); this.setState({ ...state, control: 'state' }, () => { this.animatedStart(!!this.props.checked); }); }; render() { const { style, size, // eslint-disable-next-line @typescript-eslint/no-unused-vars checked, color, disabled, thumbColor, trackStyle, thumbStyle, ...otherProps } = this.props; const { containerSize } = this.state; const bgBorder = this.state.borderValue.interpolate({ inputRange: [2, this.height], outputRange: [2, this.height] // extrapolate: 'clamp', }); const sizeStyl = {}; switch (size) { case 'small': sizeStyl.height = 20; sizeStyl.width = 30; break; case 'large': sizeStyl.height = 30; sizeStyl.width = 48; break; default: sizeStyl.height = 26; sizeStyl.width = 38; break; } return <View {...otherProps} onLayout={this.measureContainer} style={[styles.warpper, sizeStyl, style]}> {disabled && <View style={[styles.position, styles.disabled]} />} <Animated.View style={[styles.bg, styles.position, trackStyle, { borderWidth: bgBorder }]} /> <TouchableOpacity // eslint-disable-next-line react-native/no-inline-styles style={[styles.position, { zIndex: 1 }]} onPress={this.onPress} /> <Animated.View style={[styles.position, // eslint-disable-next-line react-native/no-inline-styles { backgroundColor: this.state.checked ? color : '', borderRadius: 16, opacity: this.state.bgOpacity }]} /> <Animated.View style={[styles.grip, thumbStyle, disabled ? styles.shadowDisable : styles.shadow, { backgroundColor: thumbColor, width: containerSize.width, height: containerSize.height }, { transform: [{ translateX: this.state.translateXValue }] }]} /> </View>; } } const styles = StyleSheet.create({ warpper: { position: 'relative', borderRadius: 16, backgroundColor: '#E6E6E6' }, disabled: { backgroundColor: 'rgba(255, 255, 255, 0.6)', borderRadius: 16, zIndex: 22 }, bg: { borderRadius: 16, borderWidth: 2, borderColor: '#E6E6E6' }, position: { position: 'absolute', backgroundColor: 'transparent', top: 0, bottom: 0, left: 0, right: 0 }, grip: { top: 2, borderRadius: 16 }, shadowDisable: { shadowColor: '#000', shadowOffset: { width: 10, height: 10 }, shadowOpacity: 0.3, shadowRadius: 2 }, shadow: { shadowColor: '#000', shadowOffset: { width: 4, height: 4 }, shadowOpacity: 0.2, shadowRadius: 3 } });