one 1.2.6 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/cjs/ui/Slot.cjs +42 -0
  2. package/dist/cjs/ui/Slot.js +27 -0
  3. package/dist/cjs/ui/Slot.js.map +6 -0
  4. package/dist/cjs/ui/Slot.native.js +48 -0
  5. package/dist/cjs/ui/Slot.native.js.map +1 -0
  6. package/dist/cjs/ui/TabContext.cjs +44 -0
  7. package/dist/cjs/ui/TabContext.js +35 -0
  8. package/dist/cjs/ui/TabContext.js.map +6 -0
  9. package/dist/cjs/ui/TabContext.native.js +47 -0
  10. package/dist/cjs/ui/TabContext.native.js.map +1 -0
  11. package/dist/cjs/ui/TabList.cjs +52 -0
  12. package/dist/cjs/ui/TabList.js +38 -0
  13. package/dist/cjs/ui/TabList.js.map +6 -0
  14. package/dist/cjs/ui/TabList.native.js +57 -0
  15. package/dist/cjs/ui/TabList.native.js.map +1 -0
  16. package/dist/cjs/ui/TabRouter.cjs +47 -0
  17. package/dist/cjs/ui/TabRouter.js +41 -0
  18. package/dist/cjs/ui/TabRouter.js.map +6 -0
  19. package/dist/cjs/ui/TabRouter.native.js +57 -0
  20. package/dist/cjs/ui/TabRouter.native.js.map +1 -0
  21. package/dist/cjs/ui/TabSlot.cjs +115 -0
  22. package/dist/cjs/ui/TabSlot.js +91 -0
  23. package/dist/cjs/ui/TabSlot.js.map +6 -0
  24. package/dist/cjs/ui/TabSlot.native.js +120 -0
  25. package/dist/cjs/ui/TabSlot.native.js.map +1 -0
  26. package/dist/cjs/ui/TabTrigger.cjs +151 -0
  27. package/dist/cjs/ui/TabTrigger.js +120 -0
  28. package/dist/cjs/ui/TabTrigger.js.map +6 -0
  29. package/dist/cjs/ui/TabTrigger.native.js +153 -0
  30. package/dist/cjs/ui/TabTrigger.native.js.map +1 -0
  31. package/dist/cjs/ui/Tabs.cjs +175 -0
  32. package/dist/cjs/ui/Tabs.js +121 -0
  33. package/dist/cjs/ui/Tabs.js.map +6 -0
  34. package/dist/cjs/ui/Tabs.native.js +191 -0
  35. package/dist/cjs/ui/Tabs.native.js.map +1 -0
  36. package/dist/cjs/ui/common.cjs +160 -0
  37. package/dist/cjs/ui/common.js +146 -0
  38. package/dist/cjs/ui/common.js.map +6 -0
  39. package/dist/cjs/ui/common.native.js +223 -0
  40. package/dist/cjs/ui/common.native.js.map +1 -0
  41. package/dist/cjs/ui/index.cjs +18 -0
  42. package/dist/cjs/ui/index.js +15 -0
  43. package/dist/cjs/ui/index.js.map +6 -0
  44. package/dist/cjs/ui/index.native.js +21 -0
  45. package/dist/cjs/ui/index.native.js.map +1 -0
  46. package/dist/cjs/ui/useComponent.cjs +46 -0
  47. package/dist/cjs/ui/useComponent.js +37 -0
  48. package/dist/cjs/ui/useComponent.js.map +6 -0
  49. package/dist/cjs/ui/useComponent.native.js +53 -0
  50. package/dist/cjs/ui/useComponent.native.js.map +1 -0
  51. package/dist/esm/ui/Slot.js +17 -0
  52. package/dist/esm/ui/Slot.js.map +6 -0
  53. package/dist/esm/ui/Slot.mjs +19 -0
  54. package/dist/esm/ui/Slot.mjs.map +1 -0
  55. package/dist/esm/ui/Slot.native.js +22 -0
  56. package/dist/esm/ui/Slot.native.js.map +1 -0
  57. package/dist/esm/ui/TabContext.js +19 -0
  58. package/dist/esm/ui/TabContext.js.map +6 -0
  59. package/dist/esm/ui/TabContext.mjs +17 -0
  60. package/dist/esm/ui/TabContext.mjs.map +1 -0
  61. package/dist/esm/ui/TabContext.native.js +17 -0
  62. package/dist/esm/ui/TabContext.native.js.map +1 -0
  63. package/dist/esm/ui/TabList.js +24 -0
  64. package/dist/esm/ui/TabList.js.map +6 -0
  65. package/dist/esm/ui/TabList.mjs +28 -0
  66. package/dist/esm/ui/TabList.mjs.map +1 -0
  67. package/dist/esm/ui/TabList.native.js +30 -0
  68. package/dist/esm/ui/TabList.native.js.map +1 -0
  69. package/dist/esm/ui/TabRouter.js +27 -0
  70. package/dist/esm/ui/TabRouter.js.map +6 -0
  71. package/dist/esm/ui/TabRouter.mjs +24 -0
  72. package/dist/esm/ui/TabRouter.mjs.map +1 -0
  73. package/dist/esm/ui/TabRouter.native.js +31 -0
  74. package/dist/esm/ui/TabRouter.native.js.map +1 -0
  75. package/dist/esm/ui/TabSlot.js +80 -0
  76. package/dist/esm/ui/TabSlot.js.map +6 -0
  77. package/dist/esm/ui/TabSlot.mjs +89 -0
  78. package/dist/esm/ui/TabSlot.mjs.map +1 -0
  79. package/dist/esm/ui/TabSlot.native.js +91 -0
  80. package/dist/esm/ui/TabSlot.native.js.map +1 -0
  81. package/dist/esm/ui/TabTrigger.js +115 -0
  82. package/dist/esm/ui/TabTrigger.js.map +6 -0
  83. package/dist/esm/ui/TabTrigger.mjs +126 -0
  84. package/dist/esm/ui/TabTrigger.mjs.map +1 -0
  85. package/dist/esm/ui/TabTrigger.native.js +125 -0
  86. package/dist/esm/ui/TabTrigger.native.js.map +1 -0
  87. package/dist/esm/ui/Tabs.js +130 -0
  88. package/dist/esm/ui/Tabs.js.map +6 -0
  89. package/dist/esm/ui/Tabs.mjs +149 -0
  90. package/dist/esm/ui/Tabs.mjs.map +1 -0
  91. package/dist/esm/ui/Tabs.native.js +162 -0
  92. package/dist/esm/ui/Tabs.native.js.map +1 -0
  93. package/dist/esm/ui/common.js +133 -0
  94. package/dist/esm/ui/common.js.map +6 -0
  95. package/dist/esm/ui/common.mjs +135 -0
  96. package/dist/esm/ui/common.mjs.map +1 -0
  97. package/dist/esm/ui/common.native.js +195 -0
  98. package/dist/esm/ui/common.native.js.map +1 -0
  99. package/dist/esm/ui/index.js +2 -0
  100. package/dist/esm/ui/index.js.map +6 -0
  101. package/dist/esm/ui/index.mjs +2 -0
  102. package/dist/esm/ui/index.mjs.map +1 -0
  103. package/dist/esm/ui/index.native.js +2 -0
  104. package/dist/esm/ui/index.native.js.map +1 -0
  105. package/dist/esm/ui/useComponent.js +22 -0
  106. package/dist/esm/ui/useComponent.js.map +6 -0
  107. package/dist/esm/ui/useComponent.mjs +23 -0
  108. package/dist/esm/ui/useComponent.mjs.map +1 -0
  109. package/dist/esm/ui/useComponent.native.js +27 -0
  110. package/dist/esm/ui/useComponent.native.js.map +1 -0
  111. package/package.json +18 -9
  112. package/src/ui/README.md +121 -0
  113. package/src/ui/Slot.tsx +34 -0
  114. package/src/ui/TabContext.tsx +115 -0
  115. package/src/ui/TabList.tsx +47 -0
  116. package/src/ui/TabRouter.tsx +79 -0
  117. package/src/ui/TabSlot.tsx +170 -0
  118. package/src/ui/TabTrigger.tsx +282 -0
  119. package/src/ui/Tabs.tsx +313 -0
  120. package/src/ui/common.tsx +277 -0
  121. package/src/ui/index.ts +1 -0
  122. package/src/ui/useComponent.tsx +42 -0
  123. package/types/ui/Slot.d.ts +6 -0
  124. package/types/ui/Slot.d.ts.map +1 -0
  125. package/types/ui/TabContext.d.ts +190 -0
  126. package/types/ui/TabContext.d.ts.map +1 -0
  127. package/types/ui/TabList.d.ts +25 -0
  128. package/types/ui/TabList.d.ts.map +1 -0
  129. package/types/ui/TabRouter.d.ts +103 -0
  130. package/types/ui/TabRouter.d.ts.map +1 -0
  131. package/types/ui/TabSlot.d.ts +73 -0
  132. package/types/ui/TabSlot.d.ts.map +1 -0
  133. package/types/ui/TabTrigger.d.ts +88 -0
  134. package/types/ui/TabTrigger.d.ts.map +1 -0
  135. package/types/ui/Tabs.d.ts +255 -0
  136. package/types/ui/Tabs.d.ts.map +1 -0
  137. package/types/ui/common.d.ts +40 -0
  138. package/types/ui/common.d.ts.map +1 -0
  139. package/types/ui/index.d.ts +2 -0
  140. package/types/ui/index.d.ts.map +1 -0
  141. package/types/ui/useComponent.d.ts +10 -0
  142. package/types/ui/useComponent.d.ts.map +1 -0
@@ -0,0 +1,282 @@
1
+ import { Slot } from '@radix-ui/react-slot'
2
+ import type { TabNavigationState } from '@react-navigation/native'
3
+ import { type ReactNode, use, type ReactElement, type ComponentProps, useCallback } from 'react'
4
+ import {
5
+ type View,
6
+ StyleSheet,
7
+ Pressable,
8
+ type PressableProps,
9
+ type GestureResponderEvent,
10
+ } from 'react-native'
11
+
12
+ import { TabTriggerMapContext } from './TabContext'
13
+ import type { TriggerMap } from './common'
14
+ import { appendBaseUrl } from '../fork/getPathFromState-mods'
15
+ import { router } from '../router/imperative-api'
16
+ import { stripGroupSegmentsFromPath } from '../router/matchers'
17
+ import type { OneRouter } from '../interfaces/router'
18
+ import { useNavigatorContext } from '../views/Navigator'
19
+
20
+ type PressablePropsWithoutFunctionChildren = Omit<PressableProps, 'children'> & {
21
+ children?: ReactNode | undefined
22
+ }
23
+
24
+ export type TabTriggerProps = PressablePropsWithoutFunctionChildren & {
25
+ /**
26
+ * Name of tab. When used within a `TabList` this sets the name of the tab.
27
+ * Otherwise, this references the name.
28
+ */
29
+ name: string
30
+ /**
31
+ * Name of tab. Required when used within a `TabList`.
32
+ */
33
+ href?: OneRouter.Href
34
+ /**
35
+ * Forward props to child component. Useful for custom wrappers.
36
+ */
37
+ asChild?: boolean
38
+ /**
39
+ * Resets the route when switching to a tab.
40
+ */
41
+ resetOnFocus?: boolean
42
+ }
43
+
44
+ export type TabTriggerOptions = {
45
+ name: string
46
+ href: OneRouter.Href
47
+ }
48
+
49
+ export type TabTriggerSlotProps = PressablePropsWithoutFunctionChildren &
50
+ React.RefAttributes<View> & {
51
+ isFocused?: boolean
52
+ href?: string
53
+ }
54
+
55
+ const TabTriggerSlot = Slot as React.ForwardRefExoticComponent<TabTriggerSlotProps>
56
+
57
+ /**
58
+ * Helper function to determine if a mouse event should be handled
59
+ */
60
+ function shouldHandleMouseEvent(
61
+ e?: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
62
+ ): boolean {
63
+ if (!e) return true
64
+
65
+ // Check if it's a MouseEvent
66
+ if ('button' in e) {
67
+ // Only handle left clicks without modifier keys
68
+ return (
69
+ !e.metaKey &&
70
+ !e.altKey &&
71
+ !e.ctrlKey &&
72
+ !e.shiftKey &&
73
+ (e.button == null || e.button === 0) &&
74
+ [undefined, null, '', 'self'].includes((e.currentTarget as any).target)
75
+ )
76
+ }
77
+
78
+ return true
79
+ }
80
+
81
+ /**
82
+ * Creates a trigger to navigate to a tab. When used as child of `TabList`, its
83
+ * functionality slightly changes since the `href` prop is required,
84
+ * and the trigger also defines what routes are present in the `Tabs`.
85
+ *
86
+ * When used outside of `TabList`, this component no longer requires an `href`.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * <Tabs>
91
+ * <TabSlot />
92
+ * <TabList>
93
+ * <TabTrigger name="home" href="/" />
94
+ * </TabList>
95
+ * </Tabs>
96
+ * ```
97
+ */
98
+ export function TabTrigger({ asChild, name, href, resetOnFocus, ...props }: TabTriggerProps) {
99
+ const { trigger, triggerProps } = useTabTrigger({
100
+ name,
101
+ resetOnFocus,
102
+ ...props,
103
+ })
104
+
105
+ // Pressable doesn't accept the extra props, so only pass them if we are using asChild
106
+ if (asChild) {
107
+ return (
108
+ <TabTriggerSlot
109
+ style={styles.tabTrigger}
110
+ {...props}
111
+ {...triggerProps}
112
+ href={trigger?.resolvedHref}
113
+ >
114
+ {props.children}
115
+ </TabTriggerSlot>
116
+ )
117
+ } else {
118
+ // These props are not typed, but are allowed by React Native Web
119
+ const reactNativeWebProps = { href: trigger?.resolvedHref }
120
+
121
+ return (
122
+ <Pressable style={styles.tabTrigger} {...reactNativeWebProps} {...props} {...triggerProps}>
123
+ {props.children}
124
+ </Pressable>
125
+ )
126
+ }
127
+ }
128
+
129
+ /**
130
+ * @hidden
131
+ */
132
+ export function isTabTrigger(
133
+ child: ReactElement<any>
134
+ ): child is ReactElement<ComponentProps<typeof TabTrigger>> {
135
+ return child.type === TabTrigger
136
+ }
137
+
138
+ /**
139
+ * Options for `switchTab` function.
140
+ */
141
+ export type SwitchToOptions = {
142
+ /**
143
+ * Navigate and reset the history on route focus.
144
+ */
145
+ resetOnFocus?: boolean
146
+ }
147
+
148
+ export type Trigger = TriggerMap[string] & {
149
+ isFocused: boolean
150
+ resolvedHref: string
151
+ route: TabNavigationState<any>['routes'][number]
152
+ }
153
+
154
+ export type UseTabTriggerResult = {
155
+ switchTab: (name: string, options: SwitchToOptions) => void
156
+ getTrigger: (name: string) => Trigger | undefined
157
+ trigger?: Trigger
158
+ triggerProps: TriggerProps
159
+ }
160
+
161
+ export type TriggerProps = {
162
+ isFocused: boolean
163
+ onPress: PressableProps['onPress']
164
+ onLongPress: PressableProps['onLongPress']
165
+ }
166
+
167
+ /**
168
+ * Utility hook creating custom `TabTrigger`.
169
+ */
170
+ export function useTabTrigger(options: TabTriggerProps): UseTabTriggerResult {
171
+ const { state, navigation } = useNavigatorContext()
172
+ const { name, resetOnFocus, onPress, onLongPress } = options
173
+ const triggerMap = use(TabTriggerMapContext)
174
+
175
+ const getTrigger = useCallback(
176
+ (name: string) => {
177
+ const config = triggerMap[name]
178
+
179
+ if (!config) {
180
+ return
181
+ }
182
+
183
+ return {
184
+ isFocused: state.index === config.index,
185
+ route: state.routes[config.index],
186
+ resolvedHref: stripGroupSegmentsFromPath(appendBaseUrl(config.href)),
187
+ ...config,
188
+ }
189
+ },
190
+ [triggerMap, state]
191
+ )
192
+
193
+ const trigger = name !== undefined ? getTrigger(name) : undefined
194
+
195
+ const switchTab = useCallback(
196
+ (name: string, options?: SwitchToOptions) => {
197
+ const config = triggerMap[name]
198
+
199
+ if (config) {
200
+ if (config.type === 'external') {
201
+ return router.navigate(config.href)
202
+ } else {
203
+ return navigation?.dispatch({
204
+ ...config.action,
205
+ type: 'JUMP_TO',
206
+ payload: {
207
+ ...config.action.payload,
208
+ ...options,
209
+ },
210
+ })
211
+ }
212
+ } else {
213
+ return navigation?.dispatch({
214
+ type: 'JUMP_TO',
215
+ payload: {
216
+ name,
217
+ },
218
+ })
219
+ }
220
+ },
221
+ [navigation, triggerMap]
222
+ )
223
+
224
+ const handleOnPress = useCallback<NonNullable<PressableProps['onPress']>>(
225
+ (event) => {
226
+ onPress?.(event)
227
+ if (!trigger) return
228
+ if (event?.isDefaultPrevented()) return
229
+
230
+ navigation?.emit({
231
+ type: 'tabPress',
232
+ target: trigger.type === 'internal' ? trigger.route.key : trigger?.href,
233
+ canPreventDefault: true,
234
+ })
235
+
236
+ if (!shouldHandleMouseEvent(event)) return
237
+
238
+ switchTab(name, { resetOnFocus })
239
+ },
240
+ [onPress, name, resetOnFocus, trigger, navigation, switchTab]
241
+ )
242
+
243
+ const handleOnLongPress = useCallback<NonNullable<PressableProps['onLongPress']>>(
244
+ (event) => {
245
+ onLongPress?.(event)
246
+ if (!trigger) return
247
+ if (event?.isDefaultPrevented()) return
248
+
249
+ navigation?.emit({
250
+ type: 'tabLongPress',
251
+ target: trigger.type === 'internal' ? trigger.route.key : trigger?.href,
252
+ })
253
+
254
+ if (!shouldHandleMouseEvent(event)) return
255
+
256
+ switchTab(name, {
257
+ resetOnFocus,
258
+ })
259
+ },
260
+ [onLongPress, name, resetOnFocus, trigger, navigation, switchTab]
261
+ )
262
+
263
+ const triggerProps = {
264
+ isFocused: Boolean(trigger?.isFocused),
265
+ onPress: handleOnPress,
266
+ onLongPress: handleOnLongPress,
267
+ }
268
+
269
+ return {
270
+ switchTab,
271
+ getTrigger,
272
+ trigger,
273
+ triggerProps,
274
+ }
275
+ }
276
+
277
+ const styles = StyleSheet.create({
278
+ tabTrigger: {
279
+ flexDirection: 'row',
280
+ justifyContent: 'space-between',
281
+ },
282
+ })
@@ -0,0 +1,313 @@
1
+ import {
2
+ type DefaultNavigatorOptions,
3
+ LinkingContext,
4
+ type ParamListBase,
5
+ type TabActionHelpers,
6
+ type TabNavigationState,
7
+ type TabRouterOptions,
8
+ useNavigationBuilder,
9
+ } from '@react-navigation/native'
10
+ import {
11
+ Children,
12
+ type ComponentProps,
13
+ Fragment,
14
+ type ReactElement,
15
+ type ReactNode,
16
+ isValidElement,
17
+ use,
18
+ useMemo,
19
+ type PropsWithChildren,
20
+ } from 'react'
21
+ import { StyleSheet, type ViewProps, View } from 'react-native'
22
+
23
+ import {
24
+ type ExpoTabsScreenOptions,
25
+ type TabNavigationEventMap,
26
+ TabTriggerMapContext,
27
+ type TabsContextValue,
28
+ } from './TabContext'
29
+ import { isTabList } from './TabList'
30
+ import { ExpoTabRouter, type ExpoTabRouterOptions } from './TabRouter'
31
+ import { isTabSlot } from './TabSlot'
32
+ import { isTabTrigger } from './TabTrigger'
33
+ import { ViewSlot, type ScreenTrigger, triggersToScreens } from './common'
34
+ import { useComponent } from './useComponent'
35
+ import { useRouteNode, useContextKey } from '../router/Route'
36
+ import { useRouteInfo } from '../hooks'
37
+ import { resolveHref } from '../link/href'
38
+ import { shouldLinkExternally } from '../utils/url'
39
+ import { NavigatorContext } from '../views/Navigator'
40
+ import type { RouterFactory } from '@react-navigation/native'
41
+
42
+ type NavigatorContextValue = {
43
+ contextKey: string
44
+ state: ReturnType<typeof useNavigationBuilder>['state']
45
+ navigation: ReturnType<typeof useNavigationBuilder>['navigation']
46
+ descriptors: ReturnType<typeof useNavigationBuilder>['descriptors']
47
+ router: RouterFactory<any, any, any>
48
+ }
49
+
50
+ export * from './TabContext'
51
+ export * from './TabList'
52
+ export * from './TabSlot'
53
+ export * from './TabTrigger'
54
+
55
+ /**
56
+ * Options to provide to the Tab Router.
57
+ */
58
+ export type UseTabsOptions = Omit<
59
+ DefaultNavigatorOptions<
60
+ ParamListBase,
61
+ any,
62
+ TabNavigationState<any>,
63
+ ExpoTabsScreenOptions,
64
+ TabNavigationEventMap,
65
+ any
66
+ >,
67
+ 'children'
68
+ > & {
69
+ backBehavior?: TabRouterOptions['backBehavior']
70
+ }
71
+
72
+ export type TabsProps = ViewProps & {
73
+ /** Forward props to child component and removes the extra `<View>`. Useful for custom wrappers. */
74
+ asChild?: boolean
75
+ options?: UseTabsOptions
76
+ }
77
+
78
+ /**
79
+ * Root component for the headless tabs.
80
+ *
81
+ * @see [`useTabsWithChildren`](#usetabswithchildrenoptions) for a hook version of this component.
82
+ * @example
83
+ * ```tsx
84
+ * <Tabs>
85
+ * <TabSlot />
86
+ * <TabList>
87
+ * <TabTrigger name="home" href="/" />
88
+ * </TabList>
89
+ * </Tabs>
90
+ * ```
91
+ */
92
+ export function Tabs(props: TabsProps) {
93
+ const { children, asChild, options, ...rest } = props
94
+ const Comp = asChild ? ViewSlot : View
95
+
96
+ const { NavigationContent } = useTabsWithChildren({
97
+ // asChild adds an extra layer, so we need to process the child's children
98
+ children:
99
+ asChild &&
100
+ isValidElement(children) &&
101
+ children.props &&
102
+ typeof children.props === 'object' &&
103
+ 'children' in children.props
104
+ ? (children.props.children as ReactNode)
105
+ : children,
106
+ ...options,
107
+ })
108
+
109
+ return (
110
+ <Comp style={styles.tabsRoot} {...rest}>
111
+ <NavigationContent>{children}</NavigationContent>
112
+ </Comp>
113
+ )
114
+ }
115
+
116
+ // @docsMissing
117
+ export type UseTabsWithChildrenOptions = PropsWithChildren<UseTabsOptions>
118
+
119
+ // @docsMissing
120
+ export type UseTabsWithTriggersOptions = UseTabsOptions & {
121
+ triggers: ScreenTrigger[]
122
+ }
123
+
124
+ /**
125
+ * Hook version of `Tabs`. The returned NavigationContent component
126
+ * should be rendered. Using the hook requires using the `<TabList />`
127
+ * and `<TabTrigger />` components exported from One.
128
+ *
129
+ * The `useTabsWithTriggers()` hook can be used for custom components.
130
+ *
131
+ * @see [`Tabs`](#tabs) for the component version of this hook.
132
+ * @example
133
+ * ```tsx
134
+ * export function MyTabs({ children }) {
135
+ * const { NavigationContent } = useTabsWithChildren({ children })
136
+ *
137
+ * return <NavigationContent />
138
+ * }
139
+ * ```
140
+ */
141
+ export function useTabsWithChildren(options: UseTabsWithChildrenOptions) {
142
+ const { children, ...rest } = options
143
+ return useTabsWithTriggers({ triggers: parseTriggersFromChildren(children), ...rest })
144
+ }
145
+
146
+ /**
147
+ * Alternative hook version of `Tabs` that uses explicit triggers
148
+ * instead of `children`.
149
+ *
150
+ * @see [`Tabs`](#tabs) for the component version of this hook.
151
+ * @example
152
+ * ```tsx
153
+ * export function MyTabs({ children }) {
154
+ * const { NavigationContent } = useTabsWithChildren({ triggers: [] })
155
+ *
156
+ * return <NavigationContent />
157
+ * }
158
+ * ```
159
+ */
160
+ export function useTabsWithTriggers(options: UseTabsWithTriggersOptions): TabsContextValue {
161
+ const { triggers, ...rest } = options
162
+ // Ensure we extend the parent triggers, so we can trigger them as well
163
+ const parentTriggerMap = use(TabTriggerMapContext)
164
+ const routeNode = useRouteNode()
165
+ const contextKey = useContextKey()
166
+ const linking = use(LinkingContext).options
167
+ const routeInfo = useRouteInfo()
168
+
169
+ if (!routeNode || !linking) {
170
+ throw new Error('No RouteNode. This is likely a bug in one router.')
171
+ }
172
+
173
+ const initialRouteName = routeNode.initialRouteName
174
+
175
+ const { children, triggerMap } = triggersToScreens(
176
+ triggers,
177
+ routeNode,
178
+ linking,
179
+ initialRouteName,
180
+ parentTriggerMap,
181
+ routeInfo,
182
+ contextKey
183
+ )
184
+
185
+ const navigatorContext = useNavigationBuilder<
186
+ TabNavigationState<any>,
187
+ ExpoTabRouterOptions,
188
+ TabActionHelpers<ParamListBase>,
189
+ ExpoTabsScreenOptions,
190
+ TabNavigationEventMap
191
+ >(ExpoTabRouter, {
192
+ children,
193
+ ...rest,
194
+ triggerMap,
195
+ id: contextKey,
196
+ initialRouteName,
197
+ })
198
+
199
+ const {
200
+ state,
201
+ descriptors,
202
+ navigation,
203
+ describe,
204
+ NavigationContent: RNNavigationContent,
205
+ } = navigatorContext
206
+
207
+ const navigatorContextValue = useMemo<NavigatorContextValue>(
208
+ () => ({
209
+ ...(navigatorContext as unknown as ReturnType<typeof useNavigationBuilder>),
210
+ contextKey,
211
+ router: ExpoTabRouter,
212
+ }),
213
+ [navigatorContext, contextKey, ExpoTabRouter]
214
+ )
215
+
216
+ const NavigationContent = useComponent((children: React.ReactNode) => (
217
+ <TabTriggerMapContext.Provider value={triggerMap}>
218
+ <NavigatorContext.Provider value={navigatorContextValue}>
219
+ <RNNavigationContent>{children}</RNNavigationContent>
220
+ </NavigatorContext.Provider>
221
+ </TabTriggerMapContext.Provider>
222
+ )) as TabsContextValue['NavigationContent']
223
+
224
+ return { state, descriptors, navigation, NavigationContent, describe }
225
+ }
226
+
227
+ function parseTriggersFromChildren(
228
+ children: ReactNode,
229
+ screenTriggers: ScreenTrigger[] = [],
230
+ isInTabList = false
231
+ ) {
232
+ Children.forEach(children, (child) => {
233
+ if (!child || !isValidElement(child) || isTabSlot(child)) {
234
+ return
235
+ }
236
+
237
+ if (isFragment(child) && typeof child.props.children !== 'function') {
238
+ return parseTriggersFromChildren(
239
+ child.props.children,
240
+ screenTriggers,
241
+ isInTabList || isTabList(child)
242
+ )
243
+ }
244
+
245
+ if (isTabList(child) && typeof child.props.children !== 'function') {
246
+ let children = child.props.children
247
+
248
+ // <TabList asChild /> adds an extra layer. We need to parse the child's children
249
+ if (
250
+ child.props.asChild &&
251
+ isValidElement(children) &&
252
+ children.props &&
253
+ typeof children.props === 'object' &&
254
+ 'children' in children.props
255
+ ) {
256
+ children = children.props.children as ReactNode
257
+ }
258
+
259
+ return parseTriggersFromChildren(children, screenTriggers, isInTabList || isTabList(child))
260
+ }
261
+
262
+ // We should only process TabTriggers within the TabList. All other components will be ignored
263
+ if (!isInTabList || !isTabTrigger(child)) {
264
+ return
265
+ }
266
+
267
+ const { href, name } = child.props
268
+
269
+ if (!href) {
270
+ if (process.env.NODE_ENV === 'development') {
271
+ console.warn(
272
+ `<TabTrigger name={${name}}> does not have a 'href' prop. TabTriggers within a <TabList /> are required to have an href.`
273
+ )
274
+ }
275
+ return
276
+ }
277
+
278
+ const resolvedHref = resolveHref(href)
279
+
280
+ if (shouldLinkExternally(resolvedHref)) {
281
+ return screenTriggers.push({
282
+ type: 'external',
283
+ name,
284
+ href: resolvedHref,
285
+ })
286
+ }
287
+
288
+ if (!name) {
289
+ if (process.env.NODE_ENV === 'development') {
290
+ console.warn(
291
+ `<TabTrigger> does not have a 'name' prop. TabTriggers within a <TabList /> are required to have a name.`
292
+ )
293
+ }
294
+ return
295
+ }
296
+
297
+ return screenTriggers.push({ type: 'internal', href: resolvedHref, name })
298
+ })
299
+
300
+ return screenTriggers
301
+ }
302
+
303
+ function isFragment(
304
+ child: ReactElement<any>
305
+ ): child is ReactElement<ComponentProps<typeof Fragment>> {
306
+ return child.type === Fragment
307
+ }
308
+
309
+ const styles = StyleSheet.create({
310
+ tabsRoot: {
311
+ flex: 1,
312
+ },
313
+ })