-
Notifications
You must be signed in to change notification settings - Fork 591
/
Copy pathRouterLink.tsx
125 lines (107 loc) · 3.58 KB
/
RouterLink.tsx
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
123
124
125
import { Flex, Touchable, TouchableProps } from "@artsy/palette-mobile"
import { navigate } from "app/system/navigation/navigate"
import { Sentinel } from "app/utils/Sentinel"
import { useDevToggle } from "app/utils/hooks/useDevToggle"
import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { usePrefetch } from "app/utils/queryPrefetching"
import React, { useState } from "react"
import { GestureResponderEvent } from "react-native"
import { Variables } from "relay-runtime"
export interface RouterLinkProps {
disablePrefetch?: boolean
navigationProps?: Object
to?: string | null | undefined
// Indicates whether the child component is a touchable element, preventing duplicate touch handlers
hasChildTouchable?: boolean
prefetchVariables?: Variables
children: React.ReactNode
}
type PrefetchState = "started" | "complete" | null
/**
* Wrapper component that enables navigation when pressed, using the `to` prop.
* It supports optional prefetching and ensures proper touch handling for nested touchable elements.
*/
export const RouterLink: React.FC<RouterLinkProps & TouchableProps> = ({
disablePrefetch,
to,
prefetchVariables,
onPress,
navigationProps,
children,
hasChildTouchable,
...restProps
}) => {
const enablePrefetchingIndicator = useDevToggle("DTShowPrefetchingIndicator")
const prefetchUrl = usePrefetch()
const enableViewPortPrefetching = useFeatureFlag("AREnableViewPortPrefetching")
const [prefetchState, setPrefetchState] = useState<PrefetchState>(null)
const isPrefetchingEnabled = !disablePrefetch && enableViewPortPrefetching && to
const handlePress = (event: GestureResponderEvent) => {
onPress?.(event)
if (!to) return
if (navigationProps) {
navigate(to, { passProps: navigationProps })
} else {
navigate(to)
}
}
const handleVisible = (isVisible: boolean) => {
if (isPrefetchingEnabled && isVisible) {
if (enablePrefetchingIndicator) setPrefetchState("started")
prefetchUrl(to, prefetchVariables, () => {
if (enablePrefetchingIndicator) setPrefetchState("complete")
})
}
}
const touchableProps = {
activeOpacity: 0.65,
onPress: handlePress,
...restProps,
}
const cloneProps = {
...restProps,
onPress: handlePress,
}
// If the child component is a touchable element, we don't add another touchable wrapper
if (hasChildTouchable && isPrefetchingEnabled) {
return (
<Sentinel onChange={handleVisible} threshold={0}>
<Border prefetchState={prefetchState}>
{React.Children.map(children, (child) => {
return React.isValidElement(child) ? React.cloneElement(child, cloneProps) : child
})}
</Border>
</Sentinel>
)
}
if (hasChildTouchable && !isPrefetchingEnabled) {
return (
<>
{React.Children.map(children, (child) =>
React.isValidElement(child) ? React.cloneElement(child, cloneProps) : child
)}
</>
)
}
if (!isPrefetchingEnabled) {
return <Touchable {...touchableProps} children={children} />
}
return (
<Sentinel onChange={handleVisible} threshold={0}>
<Border prefetchState={prefetchState}>
<Touchable {...touchableProps}>{children}</Touchable>
</Border>
</Sentinel>
)
}
const Border: React.FC<{ prefetchState: PrefetchState }> = ({ children, prefetchState }) => {
if (!prefetchState) {
return <>{children}</>
}
const borderColor = prefetchState === "complete" ? "green" : "yellow"
return (
<Flex border={`1px dotted ${borderColor}`} display="inline">
{children}
</Flex>
)
}