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,121 @@
1
+ # One Headless UI Components
2
+
3
+ Headless UI components for the One framework.
4
+
5
+ ## Overview
6
+
7
+ The headless tabs system provides complete control over tab navigation UI while maintaining full integration with One's file-based routing system. Unlike traditional tabs that use `@react-navigation/bottom-tabs` with opinionated styling, these components are completely unstyled.
8
+
9
+ ## Installation
10
+
11
+ The UI components are available through the `one/ui` submodule:
12
+
13
+ ```tsx
14
+ import { Tabs, TabList, TabTrigger, TabSlot } from 'one/ui'
15
+ ```
16
+
17
+ ## Core Components
18
+
19
+ | Component | Description |
20
+ |-----------|-------------|
21
+ | `Tabs` | Root container that wraps the entire tab structure and manages navigation state |
22
+ | `TabList` | Container for `TabTrigger` components, typically the tab bar |
23
+ | `TabTrigger` | Pressable element that switches between tabs |
24
+ | `TabSlot` | Renders the currently active tab's content |
25
+
26
+ ## Basic Usage
27
+
28
+ ```tsx
29
+ import { Tabs, TabList, TabTrigger, TabSlot } from 'one/ui'
30
+
31
+ export default function Layout() {
32
+ return (
33
+ <Tabs style={styles.root}>
34
+ <TabSlot />
35
+ <TabList style={styles.tabBar}>
36
+ <TabTrigger
37
+ name="home"
38
+ href="/"
39
+ asChild
40
+ resetOnFocus
41
+ >
42
+ <CustomButton icon="home">Home</CustomButton>
43
+ </TabTrigger>
44
+ <TabTrigger name="profile" href="/profile" asChild>
45
+ <CustomButton icon="user">Profile</CustomButton>
46
+ </TabTrigger>
47
+ </TabList>
48
+ </Tabs>
49
+ )
50
+ }
51
+ ```
52
+
53
+ ## Advanced Usage with Hooks
54
+
55
+ ### useTabsWithChildren()
56
+
57
+ Hook version of `<Tabs>` that allows custom wrapper components:
58
+
59
+ ```tsx
60
+ export function MyTabs({ children }) {
61
+ const { NavigationContent } = useTabsWithChildren({ children })
62
+ return <NavigationContent />
63
+ }
64
+ ```
65
+
66
+ ### useTabsWithTriggers()
67
+
68
+ Explicit trigger array version for advanced custom navigators:
69
+
70
+ ```tsx
71
+ export function MyTabs({ children }) {
72
+ const { NavigationContent } = useTabsWithTriggers({
73
+ triggers: [
74
+ { type: 'internal', name: 'home', href: '/' },
75
+ { type: 'internal', name: 'profile', href: '/profile' },
76
+ ]
77
+ })
78
+ return <NavigationContent />
79
+ }
80
+ ```
81
+
82
+ ### useTabSlot()
83
+
84
+ Returns current tab element for custom slot rendering:
85
+
86
+ ```tsx
87
+ function MyTabSlot() {
88
+ const slot = useTabSlot()
89
+ return slot
90
+ }
91
+ ```
92
+
93
+ ### useTabTrigger()
94
+
95
+ Custom trigger logic for building custom tab buttons:
96
+
97
+ ```tsx
98
+ function CustomTabBar() {
99
+ const home = useTabTrigger({ name: 'home' })
100
+ const profile = useTabTrigger({ name: 'profile' })
101
+
102
+ return (
103
+ <View style={styles.customBar}>
104
+ <Pressable {...home.triggerProps}>
105
+ <Text style={home.trigger?.isFocused && styles.active}>
106
+ Home
107
+ </Text>
108
+ </Pressable>
109
+ <Pressable {...profile.triggerProps}>
110
+ <Text style={profile.trigger?.isFocused && styles.active}>
111
+ Profile
112
+ </Text>
113
+ </Pressable>
114
+ </View>
115
+ )
116
+ }
117
+ ```
118
+
119
+ ## Example
120
+
121
+ See `/examples/one-basic/app/tabs/` for a working example with custom-styled tabs.
@@ -0,0 +1,34 @@
1
+ import { Slot as RUISlot } from '@radix-ui/react-slot'
2
+ import {
3
+ forwardRef,
4
+ useMemo,
5
+ type ForwardRefExoticComponent,
6
+ type Component,
7
+ type RefAttributes,
8
+ } from 'react'
9
+ import { StyleSheet, type ViewProps } from 'react-native'
10
+
11
+ /**
12
+ * RadixUI has special logic to handle the merging of `style` and `className` props.
13
+ * On the web styles are not allowed so Radix does not handle this scenario.
14
+ * This could be fixed upstream (PR open), but it may not as RN is not their target
15
+ * platform.
16
+ *
17
+ * This shim calls `StyleSheet.flatten` on the styles before we render the <Slot />
18
+ *
19
+ * @see https://github.com/expo/expo/issues/31352
20
+ * @see https://github.com/radix-ui/primitives/issues/3107
21
+ * @param Component
22
+ * @returns
23
+ */
24
+ function ShimSlotForReactNative(Component: typeof RUISlot): typeof RUISlot {
25
+ return forwardRef(function RNSlotHOC({ style, ...props }, ref) {
26
+ style = useMemo(() => StyleSheet.flatten(style), [style])
27
+ return <Component ref={ref} {...props} style={style} />
28
+ })
29
+ }
30
+
31
+ export interface Slot<Props = ViewProps, Ref = Component<ViewProps>>
32
+ extends ForwardRefExoticComponent<Props & RefAttributes<Ref>> {}
33
+
34
+ export const Slot: Slot = ShimSlotForReactNative(RUISlot) as Slot
@@ -0,0 +1,115 @@
1
+ import type { BottomTabNavigationOptions } from '@react-navigation/bottom-tabs'
2
+ import type {
3
+ DefaultNavigatorOptions,
4
+ NavigationAction,
5
+ NavigationProp,
6
+ ParamListBase,
7
+ TabActionHelpers,
8
+ TabNavigationState,
9
+ TabRouterOptions,
10
+ useNavigationBuilder,
11
+ } from '@react-navigation/native'
12
+ import { createContext } from 'react'
13
+
14
+ import type { TriggerMap } from './common'
15
+
16
+ export type ExpoTabsProps = ExpoTabsNavigatorOptions
17
+
18
+ export type ExpoTabsNavigatorScreenOptions = {
19
+ detachInactiveScreens?: boolean
20
+ unmountOnBlur?: boolean
21
+ freezeOnBlur?: boolean
22
+ lazy?: boolean
23
+ }
24
+
25
+ export type ExpoTabsNavigatorOptions = DefaultNavigatorOptions<
26
+ ParamListBase,
27
+ string | undefined,
28
+ TabNavigationState<ParamListBase>,
29
+ ExpoTabsScreenOptions,
30
+ TabNavigationEventMap,
31
+ ExpoTabsNavigationProp<ParamListBase>
32
+ > &
33
+ // Should be set through `unstable_settings`
34
+ Omit<TabRouterOptions, 'initialRouteName'> &
35
+ ExpoTabsNavigatorScreenOptions
36
+
37
+ export type ExpoTabsNavigationProp<
38
+ ParamList extends ParamListBase,
39
+ RouteName extends keyof ParamList = keyof ParamList,
40
+ NavigatorID extends string | undefined = undefined,
41
+ > = NavigationProp<
42
+ ParamList,
43
+ RouteName,
44
+ NavigatorID,
45
+ TabNavigationState<ParamListBase>,
46
+ ExpoTabsScreenOptions,
47
+ TabNavigationEventMap
48
+ >
49
+
50
+ export type ExpoTabsScreenOptions = Pick<
51
+ BottomTabNavigationOptions,
52
+ 'title' | 'lazy' | 'freezeOnBlur'
53
+ > & {
54
+ params?: object
55
+ title: string
56
+ action: NavigationAction
57
+ }
58
+
59
+ export type TabNavigationEventMap = {
60
+ /**
61
+ * Event which fires on tapping on the tab in the tab bar.
62
+ */
63
+ tabPress: { data: undefined; canPreventDefault: true }
64
+ /**
65
+ * Event which fires on long press on the tab in the tab bar.
66
+ */
67
+ tabLongPress: { data: undefined }
68
+ }
69
+
70
+ /**
71
+ * The React Navigation custom navigator.
72
+ *
73
+ * @see [`useNavigationBuilder`](https://reactnavigation.org/docs/custom-navigators/#usenavigationbuilder) hook from React Navigation for more information.
74
+ */
75
+ export type TabsContextValue = ReturnType<
76
+ typeof useNavigationBuilder<
77
+ TabNavigationState<any>,
78
+ TabRouterOptions,
79
+ TabActionHelpers<ParamListBase>,
80
+ ExpoTabsNavigatorScreenOptions,
81
+ TabNavigationEventMap
82
+ >
83
+ >
84
+
85
+ export type TabContextValue = TabsDescriptor['options']
86
+
87
+ export const TabContext = createContext<TabContextValue>({})
88
+ /**
89
+ * @hidden
90
+ */
91
+ export const TabTriggerMapContext = createContext<TriggerMap>({})
92
+ /**
93
+ * @hidden
94
+ */
95
+ export const TabsDescriptorsContext = createContext<TabsContextValue['descriptors']>({})
96
+ /**
97
+ * @hidden
98
+ */
99
+ export const TabsNavigatorContext = createContext<TabsContextValue['navigation'] | null>(null)
100
+ /**
101
+ * @hidden
102
+ */
103
+ export const TabsStateContext = createContext<TabsContextValue['state']>({
104
+ type: 'tab',
105
+ preloadedRouteKeys: [],
106
+ history: [],
107
+ index: -1,
108
+ key: '',
109
+ stale: false,
110
+ routeNames: [],
111
+ routes: [],
112
+ })
113
+
114
+ export type Route = TabNavigationState<ParamListBase>['routes'][number]
115
+ export type TabsDescriptor = TabsContextValue['descriptors'][number]
@@ -0,0 +1,47 @@
1
+ import type { ReactElement, ComponentProps } from 'react'
2
+ import { View, StyleSheet, type ViewProps } from 'react-native'
3
+
4
+ import { ViewSlot } from './common'
5
+
6
+ export type TabListProps = ViewProps & {
7
+ /** Forward props to child component and removes the extra `<View>`. Useful for custom wrappers. */
8
+ asChild?: boolean
9
+ }
10
+
11
+ /**
12
+ * Wrapper component for `TabTriggers`. `TabTriggers` within the `TabList` define the tabs.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <Tabs>
17
+ * <TabSlot />
18
+ * <TabList>
19
+ * <TabTrigger name="home" href="/" />
20
+ * </TabList>
21
+ * </Tabs>
22
+ * ```
23
+ */
24
+ export function TabList({ asChild, style, ...props }: TabListProps) {
25
+ const Comp = asChild ? ViewSlot : View
26
+ return <Comp style={[styles.tabList, style]} {...props} />
27
+ }
28
+
29
+ /**
30
+ * @hidden
31
+ */
32
+ export function isTabList(
33
+ child: ReactElement<any>
34
+ ): child is ReactElement<ComponentProps<typeof TabList>> {
35
+ return child.type === TabList
36
+ }
37
+
38
+ const styles = StyleSheet.create({
39
+ tabList: {
40
+ flexDirection: 'row',
41
+ justifyContent: 'space-between',
42
+ },
43
+ tabTrigger: {
44
+ flexDirection: 'row',
45
+ justifyContent: 'space-between',
46
+ },
47
+ })
@@ -0,0 +1,79 @@
1
+ import {
2
+ type CommonNavigationAction,
3
+ type ParamListBase,
4
+ TabRouter as RNTabRouter,
5
+ type Router,
6
+ type TabActionType as RNTabActionType,
7
+ type TabNavigationState,
8
+ type TabRouterOptions as RNTabRouterOptions,
9
+ } from '@react-navigation/native'
10
+
11
+ import type { TriggerMap } from './common'
12
+
13
+ export type ExpoTabRouterOptions = RNTabRouterOptions & {
14
+ triggerMap: TriggerMap
15
+ }
16
+
17
+ export type ExpoTabActionType =
18
+ | RNTabActionType
19
+ | CommonNavigationAction
20
+ | {
21
+ type: 'JUMP_TO'
22
+ source?: string
23
+ target?: string
24
+ payload: {
25
+ name: string
26
+ resetOnFocus?: boolean
27
+ params?: object
28
+ }
29
+ }
30
+
31
+ export function ExpoTabRouter(options: ExpoTabRouterOptions) {
32
+ const rnTabRouter = RNTabRouter(options)
33
+
34
+ const router: Router<
35
+ TabNavigationState<ParamListBase>,
36
+ ExpoTabActionType | CommonNavigationAction
37
+ > = {
38
+ ...rnTabRouter,
39
+ getStateForAction(state, action, options) {
40
+ if (action.type !== 'JUMP_TO') {
41
+ return rnTabRouter.getStateForAction(state, action, options)
42
+ }
43
+
44
+ const route = state.routes.find((route) => route.name === action.payload.name)
45
+
46
+ if (!route) {
47
+ // This shouldn't occur, but lets just hand it off to the next navigator in case.
48
+ return null
49
+ }
50
+
51
+ // We should reset if this is the first time visiting the route
52
+ let shouldReset = !state.history.some((item) => item.key === route?.key) && !route.state
53
+
54
+ if (!shouldReset && 'resetOnFocus' in action.payload && action.payload.resetOnFocus) {
55
+ shouldReset = state.routes[state.index].key !== route.key
56
+ }
57
+
58
+ if (shouldReset) {
59
+ options.routeParamList[route.name] = {
60
+ ...options.routeParamList[route.name],
61
+ }
62
+ state = {
63
+ ...state,
64
+ routes: state.routes.map((r) => {
65
+ if (r.key !== route.key) {
66
+ return r
67
+ }
68
+ return { ...r, state: undefined }
69
+ }),
70
+ }
71
+ return rnTabRouter.getStateForAction(state, action, options)
72
+ } else {
73
+ return rnTabRouter.getStateForRouteFocus(state, route.key)
74
+ }
75
+ },
76
+ }
77
+
78
+ return router
79
+ }
@@ -0,0 +1,170 @@
1
+ import { type ComponentProps, type ReactElement, useState } from 'react'
2
+ import { Platform, StyleSheet } from 'react-native'
3
+ import { ScreenContainer, Screen } from 'react-native-screens'
4
+
5
+ import { TabContext, type TabsDescriptor } from './TabContext'
6
+ import type { TabListProps } from './TabList'
7
+ import { useNavigatorContext } from '../views/Navigator'
8
+
9
+ export type TabSlotProps = ComponentProps<typeof ScreenContainer> & {
10
+ /**
11
+ * Remove inactive screens.
12
+ */
13
+ detachInactiveScreens?: boolean
14
+ /**
15
+ * Override how the `Screen` component is rendered.
16
+ */
17
+ renderFn?: typeof defaultTabsSlotRender
18
+ }
19
+
20
+ /**
21
+ * Options provided to the `UseTabSlotOptions`.
22
+ */
23
+ export type TabsSlotRenderOptions = {
24
+ /**
25
+ * Index of screen.
26
+ */
27
+ index: number
28
+ /**
29
+ * Whether the screen is focused.
30
+ */
31
+ isFocused: boolean
32
+ /**
33
+ * Whether the screen has been loaded.
34
+ */
35
+ loaded: boolean
36
+ /**
37
+ * Should the screen be unloaded when inactive.
38
+ */
39
+ detachInactiveScreens: boolean
40
+ }
41
+
42
+ /**
43
+ * Returns a `ReactElement` of the current tab.
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function MyTabSlot() {
48
+ * const slot = useTabSlot();
49
+ *
50
+ * return slot;
51
+ * }
52
+ * ```
53
+ */
54
+ export function useTabSlot({
55
+ detachInactiveScreens = ['android', 'ios', 'web'].includes(Platform.OS),
56
+ style,
57
+ renderFn = defaultTabsSlotRender,
58
+ }: TabSlotProps = {}) {
59
+ const { state, descriptors } = useNavigatorContext()
60
+ const focusedRouteKey = state.routes[state.index].key
61
+ const [loaded, setLoaded] = useState({ [focusedRouteKey]: true })
62
+
63
+ if (!loaded[focusedRouteKey]) {
64
+ setLoaded({ ...loaded, [focusedRouteKey]: true })
65
+ }
66
+
67
+ return (
68
+ <ScreenContainer
69
+ enabled={detachInactiveScreens}
70
+ hasTwoStates
71
+ style={[styles.screenContainer, style]}
72
+ >
73
+ {state.routes.map((route, index) => {
74
+ const descriptor = descriptors[route.key] as unknown as TabsDescriptor
75
+
76
+ return (
77
+ <TabContext.Provider key={descriptor.route.key} value={descriptor.options}>
78
+ {renderFn(descriptor, {
79
+ index,
80
+ isFocused: state.index === index,
81
+ loaded: loaded[route.key],
82
+ detachInactiveScreens,
83
+ })}
84
+ </TabContext.Provider>
85
+ )
86
+ })}
87
+ </ScreenContainer>
88
+ )
89
+ }
90
+
91
+ /**
92
+ * Renders the current tab.
93
+ *
94
+ * @see [`useTabSlot`](#usetabslot) for a hook version of this component.
95
+ *
96
+ * @example
97
+ * ```tsx
98
+ * <Tabs>
99
+ * <TabSlot />
100
+ * <TabList>
101
+ * <TabTrigger name="home" href="/" />
102
+ * </TabList>
103
+ * </Tabs>
104
+ * ```
105
+ */
106
+ export function TabSlot(props: TabSlotProps) {
107
+ return useTabSlot(props)
108
+ }
109
+
110
+ /**
111
+ * @hidden
112
+ */
113
+ export function defaultTabsSlotRender(
114
+ descriptor: TabsDescriptor,
115
+ { isFocused, loaded, detachInactiveScreens }: TabsSlotRenderOptions
116
+ ) {
117
+ const { lazy = true, unmountOnBlur, freezeOnBlur } = descriptor.options
118
+
119
+ if (unmountOnBlur && !isFocused) {
120
+ return null
121
+ }
122
+
123
+ if (lazy && !loaded && !isFocused) {
124
+ // Don't render a lazy screen if we've never navigated to it
125
+ return null
126
+ }
127
+
128
+ return (
129
+ <Screen
130
+ key={descriptor.route.key}
131
+ enabled={detachInactiveScreens}
132
+ activityState={isFocused ? 2 : 0}
133
+ freezeOnBlur={freezeOnBlur}
134
+ style={[styles.screen, isFocused ? styles.focused : styles.unfocused]}
135
+ >
136
+ {descriptor.render()}
137
+ </Screen>
138
+ )
139
+ }
140
+
141
+ /**
142
+ * @hidden
143
+ */
144
+ export function isTabSlot(child: ReactElement<any>): child is ReactElement<TabListProps> {
145
+ return child.type === TabSlot
146
+ }
147
+
148
+ const styles = StyleSheet.create({
149
+ screen: {
150
+ flex: 1,
151
+ position: 'relative',
152
+ height: '100%',
153
+ },
154
+ screenContainer: {
155
+ flexShrink: 0,
156
+ flexGrow: 1,
157
+ },
158
+ focused: {
159
+ zIndex: 1,
160
+ display: 'flex',
161
+ flexShrink: 0,
162
+ flexGrow: 1,
163
+ },
164
+ unfocused: {
165
+ zIndex: -1,
166
+ display: 'none',
167
+ flexShrink: 1,
168
+ flexGrow: 0,
169
+ },
170
+ })