about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-03-16 19:54:32 -0500
committerPaul Frazee <pfrazee@gmail.com>2023-03-16 19:54:32 -0500
commitad9da82612a33a796bcb2c679dbff357f4829dc8 (patch)
treec4e444aacb439eaa8bf0122e9884fdd5f15653f1 /src
parentf01d43f9e8107160088296fe6b0a9bb753d61032 (diff)
downloadvoidsky-ad9da82612a33a796bcb2c679dbff357f4829dc8.tar.zst
Add tab bar to pager
Diffstat (limited to 'src')
-rw-r--r--src/view/com/util/Pager.tsx72
-rw-r--r--src/view/com/util/TabBar.tsx119
-rw-r--r--src/view/screens/Home.tsx14
3 files changed, 198 insertions, 7 deletions
diff --git a/src/view/com/util/Pager.tsx b/src/view/com/util/Pager.tsx
new file mode 100644
index 000000000..1a3ff642c
--- /dev/null
+++ b/src/view/com/util/Pager.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import {Animated, StyleSheet, View} from 'react-native'
+import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view'
+import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+import {TabBar} from './TabBar'
+
+export type PageSelectedEvent = PagerViewOnPageSelectedEvent
+const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
+
+interface Props {
+  onPageSelected?: (e: PageSelectedEvent) => void
+}
+export const Pager = ({
+  children,
+  onPageSelected,
+}: React.PropsWithChildren<Props>) => {
+  const [selectedPage, setSelectedPage] = React.useState(0)
+  const position = useAnimatedValue(0)
+  const offset = useAnimatedValue(0)
+  const pagerView = React.useRef<PagerView>()
+
+  const onPageSelectedInner = React.useCallback(
+    (e: PageSelectedEvent) => {
+      setSelectedPage(e.nativeEvent.position)
+      onPageSelected?.(e)
+    },
+    [setSelectedPage, onPageSelected],
+  )
+
+  const onTabBarSelect = React.useCallback(
+    (index: number) => {
+      pagerView.current?.setPage(index)
+    },
+    [pagerView],
+  )
+
+  return (
+    <View>
+      <TabBar
+        position={position}
+        offset={offset}
+        items={['One', 'Two', 'Three']}
+        selectedPage={selectedPage}
+        onSelect={onTabBarSelect}
+      />
+      <AnimatedPagerView
+        ref={pagerView}
+        style={{height: '100%'}}
+        initialPage={0}
+        onPageSelected={onPageSelectedInner}
+        onPageScroll={Animated.event(
+          [
+            {
+              nativeEvent: {
+                position: position,
+                offset: offset,
+              },
+            },
+          ],
+          {useNativeDriver: false},
+        )}>
+        {children}
+      </AnimatedPagerView>
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  tabBar: {
+    flexDirection: 'row',
+  },
+})
diff --git a/src/view/com/util/TabBar.tsx b/src/view/com/util/TabBar.tsx
new file mode 100644
index 000000000..3a823e42c
--- /dev/null
+++ b/src/view/com/util/TabBar.tsx
@@ -0,0 +1,119 @@
+import React, {createRef, useState, useMemo} from 'react'
+import {
+  Animated,
+  StyleSheet,
+  TouchableWithoutFeedback,
+  View,
+} from 'react-native'
+import {Text} from './text/Text'
+import {usePalette} from 'lib/hooks/usePalette'
+
+interface Layout {
+  x: number
+  width: number
+}
+
+export function TabBar({
+  selectedPage,
+  items,
+  position,
+  offset,
+  onSelect,
+}: {
+  selectedPage: number
+  items: string[]
+  position: Animated.Value
+  offset: Animated.Value
+  onSelect?: (index: number) => void
+}) {
+  const pal = usePalette('default')
+  const [itemLayouts, setItemLayouts] = useState<Layout[]>(
+    items.map(() => ({x: 0, width: 0})),
+  )
+  const itemRefs = useMemo(
+    () => Array.from({length: items.length}).map(() => createRef<View>()),
+    [items.length],
+  )
+  const panX = Animated.add(position, offset)
+
+  const underlineStyle = {
+    backgroundColor: pal.colors.text,
+    left: panX.interpolate({
+      inputRange: items.map((_item, i) => i),
+      outputRange: itemLayouts.map(l => l.x),
+    }),
+    width: panX.interpolate({
+      inputRange: items.map((_item, i) => i),
+      outputRange: itemLayouts.map(l => l.width),
+    }),
+  }
+
+  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) => {
+    onSelect?.(index)
+  }
+
+  return (
+    <View style={[pal.view, styles.outer]} onLayout={onLayout}>
+      <Animated.View style={[styles.underline, underlineStyle]} />
+      {items.map((item, i) => {
+        const selected = i === selectedPage
+        return (
+          <TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
+            <View style={styles.item} ref={itemRefs[i]}>
+              <Text
+                style={
+                  selected
+                    ? [styles.labelSelected, pal.text]
+                    : [styles.label, pal.textLight]
+                }>
+                {item}
+              </Text>
+            </View>
+          </TouchableWithoutFeedback>
+        )
+      })}
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  outer: {
+    flexDirection: 'row',
+    paddingHorizontal: 14,
+  },
+  item: {
+    paddingTop: 8,
+    paddingBottom: 12,
+    marginRight: 14,
+    paddingHorizontal: 10,
+  },
+  label: {
+    fontWeight: '600',
+  },
+  labelSelected: {
+    fontWeight: '600',
+  },
+  underline: {
+    position: 'absolute',
+    height: 4,
+    bottom: 0,
+  },
+})
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 49915cd04..6c708e2fd 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -18,7 +18,7 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics'
 import {ComposeIcon2} from 'lib/icons'
 
-import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view'
+import {Pager, PageSelectedEvent} from 'view/com/util/Pager'
 import {Text} from 'view/com/util/text/Text'
 
 const HEADER_HEIGHT = 42
@@ -27,7 +27,7 @@ type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
 export const HomeScreen = withAuthRequired((_opts: Props) => {
   const store = useStores()
   const onPageSelected = React.useCallback(
-    (e: PagerViewOnPageSelectedEvent) => {
+    (e: PageSelectedEvent) => {
       store.shell.setIsDrawerSwipeDisabled(e.nativeEvent.position > 0)
     },
     [store],
@@ -42,17 +42,17 @@ export const HomeScreen = withAuthRequired((_opts: Props) => {
   )
 
   return (
-    <PagerView
-      style={{height: '100%'}}
-      initialPage={0}
-      onPageSelected={onPageSelected}>
+    <Pager onPageSelected={onPageSelected}>
       <View key="1">
         <MyPage>First page</MyPage>
       </View>
       <View key="2">
         <MyPage>Second page</MyPage>
       </View>
-    </PagerView>
+      <View key="3">
+        <MyPage>Third page</MyPage>
+      </View>
+    </Pager>
   )
 })
 function MyPage({children}) {