forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import * as React from 'react'
2import {type JSX} from 'react'
3import {type ScrollView, View} from 'react-native'
4import {useAnimatedRef} from 'react-native-reanimated'
5
6import {
7 Pager,
8 type PagerRef,
9 type RenderTabBarFnProps,
10} from '#/view/com/pager/Pager'
11import {atoms as a, web} from '#/alf'
12import * as Layout from '#/components/Layout'
13import {type ListMethods} from '../util/List'
14import {TabBar} from './TabBar'
15
16export interface PagerWithHeaderChildParams {
17 headerHeight: number
18 isFocused: boolean
19 scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
20}
21
22export interface PagerWithHeaderProps {
23 testID?: string
24 children:
25 | (((props: PagerWithHeaderChildParams) => JSX.Element) | null)[]
26 | ((props: PagerWithHeaderChildParams) => JSX.Element)
27 items: string[]
28 isHeaderReady: boolean
29 renderHeader?: ({
30 setMinimumHeight,
31 }: {
32 setMinimumHeight: () => void
33 }) => JSX.Element
34 initialPage?: number
35 onPageSelected?: (index: number) => void
36 onCurrentPageSelected?: (index: number) => void
37}
38export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
39 function PageWithHeaderImpl(
40 {
41 children,
42 testID,
43 items,
44 isHeaderReady,
45 renderHeader,
46 initialPage,
47 onPageSelected,
48 onCurrentPageSelected,
49 }: PagerWithHeaderProps,
50 ref,
51 ) {
52 const [currentPage, setCurrentPage] = React.useState(0)
53
54 const renderTabBar = React.useCallback(
55 (props: RenderTabBarFnProps) => {
56 return (
57 <PagerTabBar
58 items={items}
59 renderHeader={renderHeader}
60 isHeaderReady={isHeaderReady}
61 currentPage={currentPage}
62 onCurrentPageSelected={onCurrentPageSelected}
63 onSelect={props.onSelect}
64 tabBarAnchor={props.tabBarAnchor}
65 testID={testID}
66 />
67 )
68 },
69 [
70 items,
71 isHeaderReady,
72 renderHeader,
73 currentPage,
74 onCurrentPageSelected,
75 testID,
76 ],
77 )
78
79 const onPageSelectedInner = React.useCallback(
80 (index: number) => {
81 setCurrentPage(index)
82 onPageSelected?.(index)
83 },
84 [onPageSelected, setCurrentPage],
85 )
86
87 return (
88 <Pager
89 ref={ref}
90 testID={testID}
91 initialPage={initialPage}
92 onPageSelected={onPageSelectedInner}
93 renderTabBar={renderTabBar}>
94 {toArray(children)
95 .filter(Boolean)
96 .map((child, i) => {
97 const isReady = isHeaderReady
98 return (
99 <View
100 key={i}
101 collapsable={false}
102 style={{
103 display: isReady ? undefined : 'none',
104 }}>
105 <PagerItem isFocused={i === currentPage} renderTab={child} />
106 </View>
107 )
108 })}
109 </Pager>
110 )
111 },
112)
113
114let PagerTabBar = ({
115 currentPage,
116 items,
117 isHeaderReady,
118 testID,
119 renderHeader,
120 onCurrentPageSelected,
121 onSelect,
122 tabBarAnchor,
123}: {
124 currentPage: number
125 items: string[]
126 testID?: string
127 renderHeader?: ({
128 setMinimumHeight,
129 }: {
130 setMinimumHeight: () => void
131 }) => JSX.Element
132 isHeaderReady: boolean
133 onCurrentPageSelected?: (index: number) => void
134 onSelect?: (index: number) => void
135 tabBarAnchor?: JSX.Element | null | undefined
136}): React.ReactNode => {
137 return (
138 <>
139 <Layout.Center>{renderHeader?.({setMinimumHeight: noop})}</Layout.Center>
140 {tabBarAnchor}
141 <Layout.Center
142 style={[
143 a.z_10,
144 web([
145 a.sticky,
146 {
147 top: 0,
148 display: isHeaderReady ? undefined : 'none',
149 },
150 ]),
151 ]}>
152 <TabBar
153 testID={testID}
154 items={items}
155 selectedPage={currentPage}
156 onSelect={onSelect}
157 onPressSelected={onCurrentPageSelected}
158 dragProgress={undefined as any /* native-only */}
159 dragState={undefined as any /* native-only */}
160 />
161 </Layout.Center>
162 </>
163 )
164}
165PagerTabBar = React.memo(PagerTabBar)
166
167function PagerItem({
168 isFocused,
169 renderTab,
170}: {
171 isFocused: boolean
172 renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
173}) {
174 const scrollElRef = useAnimatedRef()
175 if (renderTab == null) {
176 return null
177 }
178 return renderTab({
179 headerHeight: 0,
180 isFocused,
181 scrollElRef: scrollElRef as React.MutableRefObject<
182 ListMethods | ScrollView | null
183 >,
184 })
185}
186
187function toArray<T>(v: T | T[]): T[] {
188 if (Array.isArray(v)) {
189 return v
190 }
191 return [v]
192}
193
194function noop() {}