about summary refs log tree commit diff
path: root/src/view/com/util/Selector.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util/Selector.tsx')
-rw-r--r--src/view/com/util/Selector.tsx111
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,
+  },
 })