import React, { useRef } from 'react'; import { Animated, Easing, Platform, TouchableOpacity, View, } from 'react-native'; import { Text } from '../core-ui'; import { makeStyles, useTheme } from '../theme'; type Props<T> = { values: Array<T>; selectedIndex: number; width: number; labelExtractor: (item: T) => string; onItemPress: (item: T, index: number) => void; }; const ios = Platform.OS === 'ios'; export function SegmentedControl<T>(props: Props<T>) { const styles = useStyles(); const { spacing } = useTheme(); const { values, selectedIndex, width, labelExtractor, onItemPress } = props; const positionValue = useRef(new Animated.Value(0)).current; const onSegmentSelected = (value: T, index: number) => { onItemPress(value, index); Animated.timing(positionValue, { toValue: index, duration: 100, easing: Easing.ease, useNativeDriver: false, }).start(() => {}); }; // 20 is coming from the text line height (medium) // SPACING.s (4) is coming from the segment padding const IOS_SEGMENT_HEIGHT = 20 + spacing.s * 2; // SPACING.xxl (24) is coming from the padding inside the Home Screen (Left and Right) // SPACING.s (4) is coming from the initial (left) padding of the Segmented Control container const IOS_SEGMENT_WIDTH = (width - (spacing.xxl * 2 + spacing.s)) / values.length; return ( <View style={styles.container}> {ios && ( <Animated.View style={[ { transform: [ { translateX: positionValue.interpolate({ inputRange: [0, values.length - 1], // 4 is coming from initial padding of the container outputRange: [4, (values.length - 1) * IOS_SEGMENT_WIDTH], }), }, ], }, styles.animatedSegment, { height: IOS_SEGMENT_HEIGHT, width: IOS_SEGMENT_WIDTH, }, ]} /> )} {values.map((value, index) => ( <TouchableOpacity key={index} style={[styles.item, selectedIndex === index && styles.itemChosen]} onPress={() => onSegmentSelected(value, index)} disabled={selectedIndex === index} > <Text style={[styles.text, selectedIndex === index && styles.textChosen]} > {labelExtractor(value)} </Text> </TouchableOpacity> ))} </View> ); } const useStyles = makeStyles(({ colors, fontVariants, shadow, spacing }) => ios ? { container: { backgroundColor: colors.backgroundDarker, borderRadius: 4, padding: spacing.s, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginVertical: spacing.m, }, item: { flex: 1, padding: spacing.s, textAlign: 'center', }, animatedSegment: { borderRadius: 4, backgroundColor: colors.background, position: 'absolute', ...shadow, }, text: { textAlign: 'center', color: colors.textLight, }, textChosen: { color: colors.textNormal, }, } : { container: { flexDirection: 'row', justifyContent: 'space-between', }, item: { flex: 1, padding: spacing.xl, textAlign: 'center', }, itemChosen: { borderBottomColor: colors.primary, borderBottomWidth: 2, }, text: { textAlign: 'center', color: colors.textLight, }, textChosen: { color: colors.textNormal, ...fontVariants.semiBold, }, }, );