about summary refs log tree commit diff
path: root/src/view/com/util/TabBar.tsx
blob: ac1814685f90c2f96bd979145a308ff8dad2b24f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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,
  indicatorPosition = 'bottom',
  indicatorColor,
  onSelect,
}: {
  selectedPage: number
  items: string[]
  position: Animated.Value
  offset: Animated.Value
  indicatorPosition?: 'top' | 'bottom'
  indicatorColor?: string
  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 indicatorStyle = {
    backgroundColor: indicatorColor || pal.colors.link,
    bottom: indicatorPosition === 'bottom' ? -1 : undefined,
    top: indicatorPosition === 'top' ? -1 : undefined,
    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.indicator, indicatorStyle]} />
      {items.map((item, i) => {
        const selected = i === selectedPage
        return (
          <TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
            <View
              style={
                indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom
              }
              ref={itemRefs[i]}>
              <Text type="xl-bold" style={selected ? pal.text : pal.textLight}>
                {item}
              </Text>
            </View>
          </TouchableWithoutFeedback>
        )
      })}
    </View>
  )
}

const styles = StyleSheet.create({
  outer: {
    flexDirection: 'row',
    paddingHorizontal: 14,
  },
  itemTop: {
    paddingTop: 10,
    paddingBottom: 10,
    marginRight: 24,
  },
  itemBottom: {
    paddingTop: 8,
    paddingBottom: 12,
    marginRight: 24,
  },
  indicator: {
    position: 'absolute',
    height: 3,
    borderRadius: 4,
  },
})