diff options
Diffstat (limited to 'src/view/com/util/Selector.tsx')
-rw-r--r-- | src/view/com/util/Selector.tsx | 111 |
1 files changed, 87 insertions, 24 deletions
diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx index adc393d89..95e4c66d4 100644 --- a/src/view/com/util/Selector.tsx +++ b/src/view/com/util/Selector.tsx @@ -1,37 +1,98 @@ -import React, {useState} from 'react' -import { - StyleProp, - StyleSheet, - Text, - TouchableWithoutFeedback, - View, - ViewStyle, -} from 'react-native' +import React, {createRef, useState, useMemo} from 'react' +import {StyleSheet, Text, TouchableWithoutFeedback, View} from 'react-native' +import Animated, { + SharedValue, + useAnimatedStyle, + interpolate, +} from 'react-native-reanimated' import {colors} from '../../lib/styles' +interface Layout { + x: number + width: number +} + export function Selector({ - style, + selectedIndex, items, + swipeGestureInterp, onSelect, }: { - style?: StyleProp<ViewStyle> + selectedIndex: number items: string[] + swipeGestureInterp: SharedValue<number> onSelect?: (index: number) => void }) { - const [selectedIndex, setSelectedIndex] = useState<number>(0) + const [itemLayouts, setItemLayouts] = useState<undefined | Layout[]>( + undefined, + ) + const itemRefs = useMemo( + () => Array.from({length: items.length}).map(() => createRef<View>()), + [items.length], + ) + + const currentLayouts = useMemo(() => { + const left = itemLayouts?.[selectedIndex - 1] || {x: 0, width: 0} + const middle = itemLayouts?.[selectedIndex] || {x: 0, width: 0} + const right = itemLayouts?.[selectedIndex + 1] || { + x: middle.x + 20, + width: middle.width, + } + return [left, middle, right] + }, [selectedIndex, itemLayouts]) + + const underlinePos = useAnimatedStyle(() => { + const other = + swipeGestureInterp.value === 0 + ? currentLayouts[1] + : swipeGestureInterp.value < 0 + ? currentLayouts[0] + : currentLayouts[2] + return { + left: interpolate( + Math.abs(swipeGestureInterp.value), + [0, 1], + [currentLayouts[1].x, other.x], + ), + width: interpolate( + Math.abs(swipeGestureInterp.value), + [0, 1], + [currentLayouts[1].width, other.width], + ), + } + }, [currentLayouts, swipeGestureInterp]) + + const onLayout = () => { + const promises = [] + for (let i = 0; i < items.length; i++) { + promises.push( + new Promise<Layout>(resolve => { + itemRefs[i].current?.measure( + (x: number, _y: number, width: number) => { + resolve({x, width}) + }, + ) + }), + ) + } + Promise.all(promises).then((layouts: Layout[]) => { + setItemLayouts(layouts) + }) + } + const onPressItem = (index: number) => { - setSelectedIndex(index) onSelect?.(index) } return ( - <View style={[styles.outer, style]}> + <View style={[styles.outer]} onLayout={onLayout}> + <Animated.View style={[styles.underline, underlinePos]} /> {items.map((item, i) => { const selected = i === selectedIndex return ( <TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}> - <View style={selected ? styles.itemSelected : styles.item}> - <Text style={selected ? styles.labelSelected : styles.label}> + <View style={styles.item} ref={itemRefs[i]}> + <Text style={selected ? styles.labelSelected : styles.itemLabel}> {item} </Text> </View> @@ -45,25 +106,27 @@ export function Selector({ const styles = StyleSheet.create({ outer: { flexDirection: 'row', + paddingTop: 8, + paddingBottom: 12, paddingHorizontal: 14, + backgroundColor: colors.white, }, item: { - paddingBottom: 12, marginRight: 20, }, - label: { + itemLabel: { fontWeight: '600', fontSize: 16, color: colors.gray5, }, - itemSelected: { - paddingBottom: 8, - marginRight: 20, - borderBottomWidth: 4, - borderBottomColor: colors.purple3, - }, labelSelected: { fontWeight: '600', fontSize: 16, }, + underline: { + position: 'absolute', + height: 4, + backgroundColor: colors.purple3, + bottom: 0, + }, }) |