140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
Animated,
|
|
Easing,
|
|
Dimensions
|
|
} from 'react-native';
|
|
|
|
import { themeColor } from '../utils/ThemeUtils';
|
|
|
|
interface ToggleOption {
|
|
key: string;
|
|
label: string;
|
|
}
|
|
|
|
interface ToggleButtonProps {
|
|
options: ToggleOption[];
|
|
value: string;
|
|
onToggle: (key: string) => void;
|
|
}
|
|
|
|
export default class ToggleButton extends React.Component<ToggleButtonProps> {
|
|
animation = new Animated.Value(
|
|
this.props.options.findIndex((opt) => opt.key === this.props.value)
|
|
);
|
|
|
|
componentDidUpdate(prevProps: ToggleButtonProps) {
|
|
if (prevProps.value !== this.props.value) {
|
|
const idx = this.props.options.findIndex(
|
|
(opt) => opt.key === this.props.value
|
|
);
|
|
Animated.timing(this.animation, {
|
|
toValue: idx,
|
|
duration: 200,
|
|
easing: Easing.out(Easing.cubic),
|
|
useNativeDriver: true
|
|
}).start();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const { options, value, onToggle } = this.props;
|
|
const screenWidth = Dimensions.get('window').width;
|
|
const horizontalPadding = 32;
|
|
const toggleWidth = screenWidth - horizontalPadding;
|
|
const thumbWidth = toggleWidth / options.length;
|
|
const styles = getStyles(toggleWidth, thumbWidth);
|
|
|
|
const translateX = this.animation.interpolate({
|
|
inputRange: options.map((_, i) => i),
|
|
outputRange: options.map((_, i) => i * thumbWidth + 2)
|
|
});
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.toggleButton}>
|
|
<Animated.View
|
|
style={[
|
|
styles.thumb,
|
|
{ width: thumbWidth, transform: [{ translateX }] }
|
|
]}
|
|
/>
|
|
<View style={styles.labelContainer}>
|
|
{options.map((opt, _idx) => (
|
|
<TouchableOpacity
|
|
key={opt.key}
|
|
style={styles.label}
|
|
activeOpacity={0.8}
|
|
onPress={() => onToggle(opt.key)}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.toggleText,
|
|
value === opt.key && styles.activeText
|
|
]}
|
|
>
|
|
{opt.label}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const getStyles = (toggleWidth: number, thumbWidth: number) =>
|
|
StyleSheet.create({
|
|
container: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingHorizontal: 16
|
|
},
|
|
toggleButton: {
|
|
width: toggleWidth,
|
|
height: 40,
|
|
borderRadius: 8,
|
|
backgroundColor: themeColor('secondary'),
|
|
position: 'relative',
|
|
justifyContent: 'center',
|
|
overflow: 'hidden'
|
|
},
|
|
thumb: {
|
|
position: 'absolute',
|
|
height: 36,
|
|
borderRadius: 8,
|
|
backgroundColor: themeColor('text'),
|
|
top: 2,
|
|
left: 0,
|
|
elevation: 2,
|
|
shadowColor: themeColor('background'),
|
|
shadowOffset: { width: 0, height: 1 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 1
|
|
},
|
|
labelContainer: {
|
|
flexDirection: 'row',
|
|
zIndex: 2
|
|
},
|
|
label: {
|
|
width: thumbWidth,
|
|
justifyContent: 'center',
|
|
alignItems: 'center'
|
|
},
|
|
toggleText: {
|
|
fontSize: 16,
|
|
fontFamily: 'PPNeueMontreal-Book',
|
|
color: themeColor('secondaryText'),
|
|
textAlign: 'center'
|
|
},
|
|
activeText: {
|
|
color: themeColor('background'),
|
|
fontFamily: 'PPNeueMontreal-Medium'
|
|
}
|
|
});
|