mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {createRef, useState, useMemo, useRef} from 'react'
2import {Animated, Pressable, StyleSheet, View} from 'react-native'
3import {Text} from './text/Text'
4import {usePalette} from 'lib/hooks/usePalette'
5import {useLingui} from '@lingui/react'
6import {msg} from '@lingui/macro'
7
8interface Layout {
9 x: number
10 width: number
11}
12
13export function Selector({
14 selectedIndex,
15 items,
16 panX,
17 onSelect,
18}: {
19 selectedIndex: number
20 items: string[]
21 panX: Animated.Value
22 onSelect?: (index: number) => void
23}) {
24 const {_} = useLingui()
25 const containerRef = useRef<View>(null)
26 const pal = usePalette('default')
27 const [itemLayouts, setItemLayouts] = useState<undefined | Layout[]>(
28 undefined,
29 )
30 const itemRefs = useMemo(
31 () => Array.from({length: items.length}).map(() => createRef<View>()),
32 [items.length],
33 )
34
35 const currentLayouts = useMemo(() => {
36 const left = itemLayouts?.[selectedIndex - 1] || {x: 0, width: 0}
37 const middle = itemLayouts?.[selectedIndex] || {x: 0, width: 0}
38 const right = itemLayouts?.[selectedIndex + 1] || {
39 x: middle.x + 20,
40 width: middle.width,
41 }
42 return [left, middle, right]
43 }, [selectedIndex, itemLayouts])
44
45 const underlineStyle = {
46 backgroundColor: pal.colors.text,
47 left: panX.interpolate({
48 inputRange: [-1, 0, 1],
49 outputRange: [
50 currentLayouts[0].x,
51 currentLayouts[1].x,
52 currentLayouts[2].x,
53 ],
54 }),
55 width: panX.interpolate({
56 inputRange: [-1, 0, 1],
57 outputRange: [
58 currentLayouts[0].width,
59 currentLayouts[1].width,
60 currentLayouts[2].width,
61 ],
62 }),
63 }
64
65 const onLayout = () => {
66 const promises = []
67 for (let i = 0; i < items.length; i++) {
68 promises.push(
69 new Promise<Layout>(resolve => {
70 if (!containerRef.current || !itemRefs[i].current) {
71 return resolve({x: 0, width: 0})
72 }
73 itemRefs[i].current?.measureLayout(
74 containerRef.current,
75 (x: number, _y: number, width: number) => {
76 resolve({x, width})
77 },
78 )
79 }),
80 )
81 }
82 Promise.all(promises).then((layouts: Layout[]) => {
83 setItemLayouts(layouts)
84 })
85 }
86
87 const onPressItem = (index: number) => {
88 onSelect?.(index)
89 }
90
91 const numItems = items.length
92
93 return (
94 <View
95 style={[pal.view, styles.outer]}
96 onLayout={onLayout}
97 ref={containerRef}>
98 <Animated.View style={[styles.underline, underlineStyle]} />
99 {items.map((item, i) => {
100 const selected = i === selectedIndex
101 return (
102 <Pressable
103 testID={`selector-${i}`}
104 key={item}
105 onPress={() => onPressItem(i)}
106 accessibilityLabel={_(msg`Select ${item}`)}
107 accessibilityHint={_(msg`Select option ${i} of ${numItems}`)}>
108 <View style={styles.item} ref={itemRefs[i]}>
109 <Text
110 style={
111 selected
112 ? [styles.labelSelected, pal.text]
113 : [styles.label, pal.textLight]
114 }>
115 {item}
116 </Text>
117 </View>
118 </Pressable>
119 )
120 })}
121 </View>
122 )
123}
124
125const styles = StyleSheet.create({
126 outer: {
127 flexDirection: 'row',
128 paddingTop: 8,
129 paddingBottom: 12,
130 paddingHorizontal: 14,
131 },
132 item: {
133 marginRight: 14,
134 paddingHorizontal: 10,
135 },
136 label: {
137 fontWeight: '600',
138 },
139 labelSelected: {
140 fontWeight: '600',
141 },
142 underline: {
143 position: 'absolute',
144 height: 4,
145 bottom: 0,
146 },
147})