one 1.23.0 → 1.24.0

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 (164) hide show
  1. package/dist/cjs/headless/__tests__/useStack.test.cjs +79 -0
  2. package/dist/cjs/headless/__tests__/useStack.test.native.js +85 -0
  3. package/dist/cjs/headless/__tests__/useStack.test.native.js.map +1 -0
  4. package/dist/cjs/headless/useStack.cjs +2 -1
  5. package/dist/cjs/headless/useStack.native.js +2 -1
  6. package/dist/cjs/headless/useStack.native.js.map +1 -1
  7. package/dist/cjs/index.native.js.map +1 -1
  8. package/dist/cjs/layouts/Stack.cjs +12 -9
  9. package/dist/cjs/layouts/Stack.native.js +13 -10
  10. package/dist/cjs/layouts/Stack.native.js.map +1 -1
  11. package/dist/cjs/layouts/stack-utils/StackScreen.cjs +6 -3
  12. package/dist/cjs/layouts/stack-utils/StackScreen.native.js +7 -4
  13. package/dist/cjs/layouts/stack-utils/StackScreen.native.js.map +1 -1
  14. package/dist/cjs/layouts/stack-utils/StackToolbar.cjs +124 -0
  15. package/dist/cjs/layouts/stack-utils/StackToolbar.native.js +129 -0
  16. package/dist/cjs/layouts/stack-utils/StackToolbar.native.js.map +1 -0
  17. package/dist/cjs/layouts/stack-utils/StackToolbar.shared.cjs +57 -0
  18. package/dist/cjs/layouts/stack-utils/StackToolbar.shared.native.js +61 -0
  19. package/dist/cjs/layouts/stack-utils/StackToolbar.shared.native.js.map +1 -0
  20. package/dist/cjs/layouts/stack-utils/StackToolbar.types.cjs +18 -0
  21. package/dist/cjs/layouts/stack-utils/StackToolbar.types.native.js +21 -0
  22. package/dist/cjs/layouts/stack-utils/StackToolbar.types.native.js.map +1 -0
  23. package/dist/cjs/layouts/stack-utils/StackToolbarImplementation.cjs +39 -0
  24. package/dist/cjs/layouts/stack-utils/StackToolbarImplementation.native.js +41 -0
  25. package/dist/cjs/layouts/stack-utils/StackToolbarImplementation.native.js.map +1 -0
  26. package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.test.cjs +66 -0
  27. package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.test.native.js +99 -0
  28. package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.test.native.js.map +1 -0
  29. package/dist/cjs/layouts/stack-utils/index.cjs +2 -0
  30. package/dist/cjs/layouts/stack-utils/index.native.js +2 -0
  31. package/dist/cjs/layouts/stack-utils/index.native.js.map +1 -1
  32. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.cjs +9 -3
  33. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.js +9 -4
  34. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.js.map +1 -1
  35. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.test.cjs +84 -0
  36. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.test.native.js +91 -0
  37. package/dist/cjs/metro-config/buildOneMetroResolverOverrides.test.native.js.map +1 -0
  38. package/dist/cjs/router/router.native.js.map +1 -1
  39. package/dist/cjs/router/useInitializeOneRouter.cjs +2 -2
  40. package/dist/cjs/router/useInitializeOneRouter.native.js +2 -2
  41. package/dist/cjs/router/useInitializeOneRouter.native.js.map +1 -1
  42. package/dist/cjs/router/useInitializeOneRouter.preview-lifecycle.test.cjs +1 -1
  43. package/dist/cjs/router/useInitializeOneRouter.preview-lifecycle.test.native.js +1 -1
  44. package/dist/cjs/router/useInitializeOneRouter.preview-lifecycle.test.native.js.map +1 -1
  45. package/dist/cjs/stack-toolbar-implementation.cjs +30 -0
  46. package/dist/cjs/stack-toolbar-implementation.native.js +33 -0
  47. package/dist/cjs/stack-toolbar-implementation.native.js.map +1 -0
  48. package/dist/esm/headless/__tests__/useStack.test.mjs +56 -0
  49. package/dist/esm/headless/__tests__/useStack.test.mjs.map +1 -0
  50. package/dist/esm/headless/__tests__/useStack.test.native.js +59 -0
  51. package/dist/esm/headless/__tests__/useStack.test.native.js.map +1 -0
  52. package/dist/esm/headless/useStack.mjs +2 -1
  53. package/dist/esm/headless/useStack.mjs.map +1 -1
  54. package/dist/esm/headless/useStack.native.js +2 -1
  55. package/dist/esm/headless/useStack.native.js.map +1 -1
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/index.mjs.map +1 -1
  58. package/dist/esm/index.native.js.map +1 -1
  59. package/dist/esm/layouts/Stack.mjs +13 -10
  60. package/dist/esm/layouts/Stack.mjs.map +1 -1
  61. package/dist/esm/layouts/Stack.native.js +14 -11
  62. package/dist/esm/layouts/Stack.native.js.map +1 -1
  63. package/dist/esm/layouts/stack-utils/StackScreen.mjs +6 -3
  64. package/dist/esm/layouts/stack-utils/StackScreen.mjs.map +1 -1
  65. package/dist/esm/layouts/stack-utils/StackScreen.native.js +7 -4
  66. package/dist/esm/layouts/stack-utils/StackScreen.native.js.map +1 -1
  67. package/dist/esm/layouts/stack-utils/StackToolbar.mjs +89 -0
  68. package/dist/esm/layouts/stack-utils/StackToolbar.mjs.map +1 -0
  69. package/dist/esm/layouts/stack-utils/StackToolbar.native.js +92 -0
  70. package/dist/esm/layouts/stack-utils/StackToolbar.native.js.map +1 -0
  71. package/dist/esm/layouts/stack-utils/StackToolbar.shared.mjs +31 -0
  72. package/dist/esm/layouts/stack-utils/StackToolbar.shared.mjs.map +1 -0
  73. package/dist/esm/layouts/stack-utils/StackToolbar.shared.native.js +32 -0
  74. package/dist/esm/layouts/stack-utils/StackToolbar.shared.native.js.map +1 -0
  75. package/dist/esm/layouts/stack-utils/StackToolbar.types.mjs +2 -0
  76. package/dist/esm/layouts/stack-utils/StackToolbar.types.mjs.map +1 -0
  77. package/dist/esm/layouts/stack-utils/StackToolbar.types.native.js +2 -0
  78. package/dist/esm/layouts/stack-utils/StackToolbar.types.native.js.map +1 -0
  79. package/dist/esm/layouts/stack-utils/StackToolbarImplementation.mjs +12 -0
  80. package/dist/esm/layouts/stack-utils/StackToolbarImplementation.mjs.map +1 -0
  81. package/dist/esm/layouts/stack-utils/StackToolbarImplementation.native.js +12 -0
  82. package/dist/esm/layouts/stack-utils/StackToolbarImplementation.native.js.map +1 -0
  83. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.test.mjs +67 -0
  84. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.test.mjs.map +1 -0
  85. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.test.native.js +73 -0
  86. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.test.native.js.map +1 -0
  87. package/dist/esm/layouts/stack-utils/index.mjs +2 -1
  88. package/dist/esm/layouts/stack-utils/index.mjs.map +1 -1
  89. package/dist/esm/layouts/stack-utils/index.native.js +2 -1
  90. package/dist/esm/layouts/stack-utils/index.native.js.map +1 -1
  91. package/dist/esm/metro-config/buildOneMetroResolverOverrides.mjs +9 -3
  92. package/dist/esm/metro-config/buildOneMetroResolverOverrides.mjs.map +1 -1
  93. package/dist/esm/metro-config/buildOneMetroResolverOverrides.native.js +9 -4
  94. package/dist/esm/metro-config/buildOneMetroResolverOverrides.native.js.map +1 -1
  95. package/dist/esm/metro-config/buildOneMetroResolverOverrides.test.mjs +61 -0
  96. package/dist/esm/metro-config/buildOneMetroResolverOverrides.test.mjs.map +1 -0
  97. package/dist/esm/metro-config/buildOneMetroResolverOverrides.test.native.js +65 -0
  98. package/dist/esm/metro-config/buildOneMetroResolverOverrides.test.native.js.map +1 -0
  99. package/dist/esm/router/useInitializeOneRouter.mjs +1 -1
  100. package/dist/esm/router/useInitializeOneRouter.mjs.map +1 -1
  101. package/dist/esm/router/useInitializeOneRouter.native.js +1 -1
  102. package/dist/esm/router/useInitializeOneRouter.preview-lifecycle.test.mjs +1 -1
  103. package/dist/esm/router/useInitializeOneRouter.preview-lifecycle.test.mjs.map +1 -1
  104. package/dist/esm/router/useInitializeOneRouter.preview-lifecycle.test.native.js +1 -1
  105. package/dist/esm/router/useInitializeOneRouter.preview-lifecycle.test.native.js.map +1 -1
  106. package/dist/esm/stack-toolbar-implementation.mjs +3 -0
  107. package/dist/esm/stack-toolbar-implementation.mjs.map +1 -0
  108. package/dist/esm/stack-toolbar-implementation.native.js +3 -0
  109. package/dist/esm/stack-toolbar-implementation.native.js.map +1 -0
  110. package/package.json +26 -20
  111. package/src/headless/__tests__/useStack.test.tsx +54 -0
  112. package/src/headless/types.ts +2 -1
  113. package/src/headless/useStack.ts +2 -1
  114. package/src/index.ts +12 -0
  115. package/src/layouts/Stack.tsx +33 -8
  116. package/src/layouts/stack-utils/README.md +1 -0
  117. package/src/layouts/stack-utils/StackScreen.tsx +26 -5
  118. package/src/layouts/stack-utils/StackToolbar.shared.ts +44 -0
  119. package/src/layouts/stack-utils/StackToolbar.tsx +134 -0
  120. package/src/layouts/stack-utils/StackToolbar.types.ts +100 -0
  121. package/src/layouts/stack-utils/StackToolbarImplementation.tsx +39 -0
  122. package/src/layouts/stack-utils/__tests__/StackToolbar.test.tsx +70 -0
  123. package/src/layouts/stack-utils/index.tsx +17 -0
  124. package/src/metro-config/buildOneMetroResolverOverrides.test.ts +88 -0
  125. package/src/metro-config/buildOneMetroResolverOverrides.ts +20 -8
  126. package/src/router/router.ts +5 -5
  127. package/src/router/useInitializeOneRouter.preview-lifecycle.test.ts +1 -1
  128. package/src/router/useInitializeOneRouter.ts +2 -2
  129. package/src/stack-toolbar-implementation.ts +8 -0
  130. package/types/fork/useLinking.native.d.ts +4 -4
  131. package/types/headless/types.d.ts +4 -1
  132. package/types/headless/types.d.ts.map +1 -1
  133. package/types/headless/useStack.d.ts.map +1 -1
  134. package/types/index.d.ts +1 -1
  135. package/types/index.d.ts.map +1 -1
  136. package/types/layouts/Drawer.d.ts +54 -54
  137. package/types/layouts/Stack.d.ts +58 -46
  138. package/types/layouts/Stack.d.ts.map +1 -1
  139. package/types/layouts/Tabs.d.ts +46 -46
  140. package/types/layouts/stack-utils/StackScreen.d.ts +2 -1
  141. package/types/layouts/stack-utils/StackScreen.d.ts.map +1 -1
  142. package/types/layouts/stack-utils/StackToolbar.d.ts +28 -0
  143. package/types/layouts/stack-utils/StackToolbar.d.ts.map +1 -0
  144. package/types/layouts/stack-utils/StackToolbar.shared.d.ts +5 -0
  145. package/types/layouts/stack-utils/StackToolbar.shared.d.ts.map +1 -0
  146. package/types/layouts/stack-utils/StackToolbar.types.d.ts +83 -0
  147. package/types/layouts/stack-utils/StackToolbar.types.d.ts.map +1 -0
  148. package/types/layouts/stack-utils/StackToolbarImplementation.d.ts +12 -0
  149. package/types/layouts/stack-utils/StackToolbarImplementation.d.ts.map +1 -0
  150. package/types/layouts/stack-utils/index.d.ts +3 -1
  151. package/types/layouts/stack-utils/index.d.ts.map +1 -1
  152. package/types/metro-config/buildOneMetroResolverOverrides.d.ts +2 -1
  153. package/types/metro-config/buildOneMetroResolverOverrides.d.ts.map +1 -1
  154. package/types/metro-config/buildOneMetroResolverOverrides.test.d.ts +2 -0
  155. package/types/metro-config/buildOneMetroResolverOverrides.test.d.ts.map +1 -0
  156. package/types/router/router.d.ts +1 -1
  157. package/types/router/router.d.ts.map +1 -1
  158. package/types/stack-toolbar-implementation.d.ts +3 -0
  159. package/types/stack-toolbar-implementation.d.ts.map +1 -0
  160. package/types/ui/TabContext.d.ts +15 -23
  161. package/types/ui/TabContext.d.ts.map +1 -1
  162. package/types/ui/TabRouter.d.ts +5 -5
  163. package/types/ui/Tabs.shared.d.ts +22 -22
  164. package/types/ui/Tabs.shared.d.ts.map +1 -1
@@ -14,9 +14,14 @@ import {
14
14
  StackHeaderComponent,
15
15
  StackHeaderSearchBar,
16
16
  StackScreen,
17
+ StackToolbar,
17
18
  type StackScreenProps,
18
19
  } from './stack-utils'
19
20
  import { withLayoutContext } from './withLayoutContext'
21
+ import {
22
+ getStackToolbarImplementation,
23
+ type StackToolbarImplementation,
24
+ } from './stack-utils/StackToolbarImplementation'
20
25
 
21
26
  const NativeStackNavigator = createStackNavigator().Navigator
22
27
 
@@ -31,12 +36,19 @@ const RNStack = withLayoutContext<
31
36
  * Pre-process children to convert StackScreen (with Header children) to Screen (with options).
32
37
  * This allows the Header Composition API to work in layout files.
33
38
  */
34
- function mapChildren(children: React.ReactNode): React.ReactNode {
39
+ function mapChildren(
40
+ children: React.ReactNode,
41
+ toolbarImplementation: StackToolbarImplementation | null
42
+ ): React.ReactNode {
35
43
  return Children.toArray(children)
36
44
  .map((child, index) => {
37
45
  if (isChildOfType(child, StackScreen)) {
38
46
  // convert StackScreen to Screen with options extracted from Header children
39
- const options = appendScreenStackPropsToOptions({}, child.props)
47
+ const options = appendScreenStackPropsToOptions(
48
+ {},
49
+ child.props,
50
+ toolbarImplementation
51
+ )
40
52
  const { children: _, ...rest } = child.props
41
53
  return <Screen key={child.props.name ?? index} {...rest} options={options} />
42
54
  }
@@ -46,7 +58,7 @@ function mapChildren(children: React.ReactNode): React.ReactNode {
46
58
  return React.cloneElement(
47
59
  child,
48
60
  { key: `protected-${index}` },
49
- mapChildren(child.props.children)
61
+ mapChildren(child.props.children, toolbarImplementation)
50
62
  )
51
63
  }
52
64
 
@@ -71,6 +83,7 @@ function mapChildren(children: React.ReactNode): React.ReactNode {
71
83
  const StackWithComposition = React.forwardRef<unknown, ComponentProps<typeof RNStack>>(
72
84
  (props, ref) => {
73
85
  const { children, screenOptions, ...rest } = props
86
+ const toolbarImplementation = getStackToolbarImplementation()
74
87
 
75
88
  // extract Stack.Header from children for screenOptions
76
89
  const screenOptionsWithHeader = useMemo(() => {
@@ -84,19 +97,30 @@ const StackWithComposition = React.forwardRef<unknown, ComponentProps<typeof RNS
84
97
  if (typeof screenOptions === 'function') {
85
98
  return (...args: Parameters<typeof screenOptions>) => {
86
99
  const opts = screenOptions(...args)
87
- return appendScreenStackPropsToOptions(opts, headerProps)
100
+ return appendScreenStackPropsToOptions(
101
+ opts,
102
+ headerProps,
103
+ toolbarImplementation
104
+ )
88
105
  }
89
106
  }
90
- return appendScreenStackPropsToOptions(screenOptions, headerProps)
107
+ return appendScreenStackPropsToOptions(
108
+ screenOptions,
109
+ headerProps,
110
+ toolbarImplementation
111
+ )
91
112
  }
92
- return appendScreenStackPropsToOptions({}, headerProps)
113
+ return appendScreenStackPropsToOptions({}, headerProps, toolbarImplementation)
93
114
  }
94
115
 
95
116
  return screenOptions
96
- }, [children, screenOptions])
117
+ }, [children, screenOptions, toolbarImplementation])
97
118
 
98
119
  // pre-process children to convert StackScreen to Screen
99
- const processedChildren = useMemo(() => mapChildren(children), [children])
120
+ const processedChildren = useMemo(
121
+ () => mapChildren(children, toolbarImplementation),
122
+ [children, toolbarImplementation]
123
+ )
100
124
  const navigatorProps = useMemo(() => getStackNavigatorProps(children), [children])
101
125
 
102
126
  return (
@@ -119,6 +143,7 @@ export const Stack = Object.assign(StackWithComposition, {
119
143
  Header: StackHeader,
120
144
  Protected,
121
145
  SearchBar: StackHeaderSearchBar,
146
+ Toolbar: StackToolbar,
122
147
  })
123
148
 
124
149
  export default Stack
@@ -25,6 +25,7 @@ import { Stack } from 'one'
25
25
  | `Stack.Header.Right` | Custom right header content |
26
26
  | `Stack.Header.BackButton` | Back button configuration |
27
27
  | `Stack.Header.SearchBar` | Search bar configuration |
28
+ | `Stack.Toolbar` | Native iOS header and bottom toolbar items |
28
29
 
29
30
  ## Basic Usage
30
31
 
@@ -9,6 +9,15 @@ import {
9
9
  appendStackHeaderPropsToOptions,
10
10
  type StackHeaderProps,
11
11
  } from './StackHeaderComponent'
12
+ import {
13
+ StackToolbar,
14
+ appendStackToolbarPropsToOptions,
15
+ type StackToolbarProps,
16
+ } from './StackToolbar'
17
+ import {
18
+ getStackToolbarImplementation,
19
+ type StackToolbarImplementation,
20
+ } from './StackToolbarImplementation'
12
21
  import { Screen } from '../../views/Screen'
13
22
 
14
23
  export type StackScreenOptions = Omit<NativeStackNavigationOptions, 'presentation'> & {
@@ -47,12 +56,17 @@ export interface StackScreenProps extends PropsWithChildren {
47
56
  * ```
48
57
  */
49
58
  export function StackScreen({ children, options, ...rest }: StackScreenProps) {
59
+ const toolbarImplementation = getStackToolbarImplementation()
50
60
  const updatedOptions = useMemo(
51
61
  () =>
52
- appendScreenStackPropsToOptions(options ?? {}, {
53
- children,
54
- }),
55
- [options, children]
62
+ appendScreenStackPropsToOptions(
63
+ options ?? {},
64
+ {
65
+ children,
66
+ },
67
+ toolbarImplementation
68
+ ),
69
+ [options, children, toolbarImplementation]
56
70
  )
57
71
  return <Screen {...rest} options={updatedOptions as NativeStackNavigationOptions} />
58
72
  }
@@ -101,7 +115,8 @@ export function validateStackPresentation(
101
115
 
102
116
  export function appendScreenStackPropsToOptions(
103
117
  options: StackScreenOptions,
104
- props: StackScreenProps
118
+ props: StackScreenProps,
119
+ toolbarImplementation?: StackToolbarImplementation | null
105
120
  ): StackScreenOptions {
106
121
  let updatedOptions: StackScreenOptions = { ...options, ...props.options }
107
122
 
@@ -114,6 +129,12 @@ export function appendScreenStackPropsToOptions(
114
129
  options as NativeStackNavigationOptions,
115
130
  child.props as StackHeaderProps
116
131
  ) as StackScreenOptions
132
+ } else if (child.type === StackToolbar) {
133
+ return appendStackToolbarPropsToOptions(
134
+ options as NativeStackNavigationOptions,
135
+ child.props as StackToolbarProps,
136
+ toolbarImplementation
137
+ ) as StackScreenOptions
117
138
  } else {
118
139
  console.warn(
119
140
  `Warning: Unknown child element passed to Stack.Screen: ${(child.type as { name: string }).name ?? child.type}`
@@ -0,0 +1,44 @@
1
+ import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'
2
+
3
+ import type {
4
+ StackToolbarConfig,
5
+ StackToolbarPlacement,
6
+ StackToolbarProps,
7
+ } from './StackToolbar.types'
8
+
9
+ const optionKeys = {
10
+ left: '__oneStackToolbarLeft',
11
+ right: '__oneStackToolbarRight',
12
+ bottom: '__oneStackToolbarBottom',
13
+ } as const satisfies Record<StackToolbarPlacement, string>
14
+
15
+ export function appendStackToolbarConfig(
16
+ options: NativeStackNavigationOptions,
17
+ props: StackToolbarProps
18
+ ): NativeStackNavigationOptions {
19
+ return {
20
+ ...options,
21
+ [optionKeys[props.placement ?? 'bottom']]: props,
22
+ }
23
+ }
24
+
25
+ export function exposeStackToolbarConfig(
26
+ options: Record<string, any>
27
+ ): Record<string, any> {
28
+ const toolbar: StackToolbarConfig = {
29
+ left: options[optionKeys.left],
30
+ right: options[optionKeys.right],
31
+ bottom: options[optionKeys.bottom],
32
+ }
33
+
34
+ if (!toolbar.left && !toolbar.right && !toolbar.bottom) return options
35
+
36
+ const {
37
+ [optionKeys.left]: _left,
38
+ [optionKeys.right]: _right,
39
+ [optionKeys.bottom]: _bottom,
40
+ ...publicOptions
41
+ } = options
42
+
43
+ return { ...publicOptions, toolbar }
44
+ }
@@ -0,0 +1,134 @@
1
+ 'use client'
2
+
3
+ import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'
4
+ import { useMemo } from 'react'
5
+
6
+ import { useIsomorphicLayoutEffect } from '@vxrn/use-isomorphic-layout-effect'
7
+ import { NAVIGATOR_CONFIG } from '../../headless/children'
8
+ import { useNavigation } from '../../router/useNavigation'
9
+ import {
10
+ STACK_TOOLBAR_CHILD,
11
+ getStackToolbarImplementation,
12
+ type StackToolbarImplementation,
13
+ } from './StackToolbarImplementation'
14
+ import { appendStackToolbarConfig } from './StackToolbar.shared'
15
+ import type {
16
+ StackToolbarBadgeProps,
17
+ StackToolbarButtonProps,
18
+ StackToolbarIconProps,
19
+ StackToolbarLabelProps,
20
+ StackToolbarMenuActionProps,
21
+ StackToolbarMenuProps,
22
+ StackToolbarProps,
23
+ StackToolbarSearchBarSlotProps,
24
+ StackToolbarSpacerProps,
25
+ StackToolbarViewProps,
26
+ } from './StackToolbar.types'
27
+
28
+ export function appendStackToolbarPropsToOptions(
29
+ options: NativeStackNavigationOptions,
30
+ props: StackToolbarProps,
31
+ implementation?: StackToolbarImplementation | null
32
+ ) {
33
+ const optionsWithConfig = appendStackToolbarConfig(options, props)
34
+ return implementation
35
+ ? implementation.appendPropsToOptions(optionsWithConfig, props)
36
+ : optionsWithConfig
37
+ }
38
+
39
+ function StackToolbarComponent(props: StackToolbarProps) {
40
+ const navigation = useNavigation()
41
+ const implementation = getStackToolbarImplementation()
42
+ const options = useMemo(
43
+ () => appendStackToolbarPropsToOptions({}, props, implementation),
44
+ [
45
+ implementation,
46
+ props.placement,
47
+ props.asChild,
48
+ props.children,
49
+ props.disableImePadding,
50
+ props.tintColor,
51
+ props.backgroundColor,
52
+ ]
53
+ )
54
+
55
+ useIsomorphicLayoutEffect(() => {
56
+ navigation.setOptions(options)
57
+ }, [navigation, options])
58
+
59
+ return implementation?.render(props) ?? null
60
+ }
61
+
62
+ export function StackToolbarButton(_props: StackToolbarButtonProps) {
63
+ return null
64
+ }
65
+
66
+ export function StackToolbarMenu(_props: StackToolbarMenuProps) {
67
+ return null
68
+ }
69
+
70
+ export function StackToolbarMenuAction(_props: StackToolbarMenuActionProps) {
71
+ return null
72
+ }
73
+
74
+ export function StackToolbarSpacer(_props: StackToolbarSpacerProps) {
75
+ return null
76
+ }
77
+
78
+ export function StackToolbarSearchBarSlot(_props: StackToolbarSearchBarSlotProps) {
79
+ return null
80
+ }
81
+
82
+ export function StackToolbarView(_props: StackToolbarViewProps) {
83
+ return null
84
+ }
85
+
86
+ export function StackToolbarLabel(_props: StackToolbarLabelProps) {
87
+ return null
88
+ }
89
+
90
+ export function StackToolbarIcon(_props: StackToolbarIconProps) {
91
+ return null
92
+ }
93
+
94
+ export function StackToolbarBadge(_props: StackToolbarBadgeProps) {
95
+ return null
96
+ }
97
+
98
+ Object.assign(StackToolbarButton, { [STACK_TOOLBAR_CHILD]: 'button' })
99
+ Object.assign(StackToolbarMenu, { [STACK_TOOLBAR_CHILD]: 'menu' })
100
+ Object.assign(StackToolbarMenuAction, { [STACK_TOOLBAR_CHILD]: 'menuAction' })
101
+ Object.assign(StackToolbarSpacer, { [STACK_TOOLBAR_CHILD]: 'spacer' })
102
+ Object.assign(StackToolbarSearchBarSlot, { [STACK_TOOLBAR_CHILD]: 'searchBarSlot' })
103
+ Object.assign(StackToolbarView, { [STACK_TOOLBAR_CHILD]: 'view' })
104
+ Object.assign(StackToolbarLabel, { [STACK_TOOLBAR_CHILD]: 'label' })
105
+ Object.assign(StackToolbarIcon, { [STACK_TOOLBAR_CHILD]: 'icon' })
106
+ Object.assign(StackToolbarBadge, { [STACK_TOOLBAR_CHILD]: 'badge' })
107
+
108
+ export const StackToolbar = Object.assign(StackToolbarComponent, {
109
+ [NAVIGATOR_CONFIG]: true,
110
+ Button: StackToolbarButton,
111
+ Menu: StackToolbarMenu,
112
+ MenuAction: StackToolbarMenuAction,
113
+ Spacer: StackToolbarSpacer,
114
+ SearchBarSlot: StackToolbarSearchBarSlot,
115
+ View: StackToolbarView,
116
+ Label: StackToolbarLabel,
117
+ Icon: StackToolbarIcon,
118
+ Badge: StackToolbarBadge,
119
+ })
120
+
121
+ export type {
122
+ StackToolbarBadgeProps,
123
+ StackToolbarButtonProps,
124
+ StackToolbarConfig,
125
+ StackToolbarIconProps,
126
+ StackToolbarLabelProps,
127
+ StackToolbarMenuActionProps,
128
+ StackToolbarMenuProps,
129
+ StackToolbarPlacement,
130
+ StackToolbarProps,
131
+ StackToolbarSearchBarSlotProps,
132
+ StackToolbarSpacerProps,
133
+ StackToolbarViewProps,
134
+ } from './StackToolbar.types'
@@ -0,0 +1,100 @@
1
+ import type { ReactNode } from 'react'
2
+ import type { ColorValue, ImageSourcePropType, StyleProp, TextStyle } from 'react-native'
3
+
4
+ export type StackToolbarPlacement = 'left' | 'right' | 'bottom'
5
+
6
+ export interface StackToolbarProps {
7
+ children?: ReactNode
8
+ placement?: StackToolbarPlacement
9
+ asChild?: boolean
10
+ /** Android-only. The Android toolbar host is not implemented yet. */
11
+ disableImePadding?: boolean
12
+ /** Android-only. The Android toolbar host is not implemented yet. */
13
+ tintColor?: ColorValue
14
+ /** Android-only. The Android toolbar host is not implemented yet. */
15
+ backgroundColor?: ColorValue
16
+ }
17
+
18
+ export type StackToolbarConfig = Partial<Record<StackToolbarPlacement, StackToolbarProps>>
19
+
20
+ export interface StackToolbarIconProps {
21
+ src?: ImageSourcePropType
22
+ sf?: string
23
+ xcasset?: string
24
+ renderingMode?: 'template' | 'original'
25
+ }
26
+
27
+ export interface StackToolbarLabelProps {
28
+ children?: string
29
+ }
30
+
31
+ export interface StackToolbarBadgeProps {
32
+ children?: string
33
+ style?: StyleProp<
34
+ Pick<
35
+ TextStyle,
36
+ 'fontFamily' | 'fontSize' | 'fontWeight' | 'color' | 'backgroundColor'
37
+ >
38
+ >
39
+ }
40
+
41
+ export interface StackToolbarItemProps {
42
+ children?: ReactNode
43
+ icon?: string | ImageSourcePropType
44
+ iconRenderingMode?: 'template' | 'original'
45
+ tintColor?: ColorValue
46
+ hidden?: boolean
47
+ disabled?: boolean
48
+ accessibilityLabel?: string
49
+ accessibilityHint?: string
50
+ hidesSharedBackground?: boolean
51
+ separateBackground?: boolean
52
+ style?: StyleProp<TextStyle>
53
+ }
54
+
55
+ export interface StackToolbarButtonProps extends StackToolbarItemProps {
56
+ image?: ImageSourcePropType
57
+ onPress?: () => void
58
+ selected?: boolean
59
+ variant?: 'plain' | 'done' | 'prominent'
60
+ }
61
+
62
+ export interface StackToolbarMenuProps extends StackToolbarItemProps {
63
+ image?: ImageSourcePropType
64
+ destructive?: boolean
65
+ title?: string
66
+ inline?: boolean
67
+ palette?: boolean
68
+ singleSelection?: boolean
69
+ elementSize?: 'auto' | 'small' | 'medium' | 'large'
70
+ variant?: 'plain' | 'done' | 'prominent'
71
+ }
72
+
73
+ export interface StackToolbarMenuActionProps extends StackToolbarItemProps {
74
+ image?: ImageSourcePropType
75
+ destructive?: boolean
76
+ discoverabilityLabel?: string
77
+ subtitle?: string
78
+ isOn?: boolean
79
+ onPress?: () => void
80
+ unstable_keepPresented?: boolean
81
+ }
82
+
83
+ export interface StackToolbarSpacerProps {
84
+ width?: number
85
+ hidden?: boolean
86
+ sharesBackground?: boolean
87
+ }
88
+
89
+ export interface StackToolbarSearchBarSlotProps {
90
+ hidden?: boolean
91
+ hidesSharedBackground?: boolean
92
+ separateBackground?: boolean
93
+ }
94
+
95
+ export interface StackToolbarViewProps {
96
+ children?: ReactNode
97
+ hidden?: boolean
98
+ hidesSharedBackground?: boolean
99
+ separateBackground?: boolean
100
+ }
@@ -0,0 +1,39 @@
1
+ 'use client'
2
+
3
+ import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'
4
+ import type { ReactNode } from 'react'
5
+
6
+ import type { StackToolbarProps } from './StackToolbar.types'
7
+
8
+ export const STACK_TOOLBAR_CHILD = Symbol.for('one.stack-toolbar.child')
9
+
10
+ export type StackToolbarChildType =
11
+ | 'button'
12
+ | 'menu'
13
+ | 'menuAction'
14
+ | 'spacer'
15
+ | 'searchBarSlot'
16
+ | 'view'
17
+ | 'label'
18
+ | 'icon'
19
+ | 'badge'
20
+
21
+ export type StackToolbarImplementation = {
22
+ appendPropsToOptions: (
23
+ options: NativeStackNavigationOptions,
24
+ props: StackToolbarProps
25
+ ) => NativeStackNavigationOptions
26
+ render: (props: StackToolbarProps) => ReactNode
27
+ }
28
+
29
+ let stackToolbarImplementation: StackToolbarImplementation | null = null
30
+
31
+ export function registerStackToolbarImplementation(
32
+ implementation: StackToolbarImplementation
33
+ ) {
34
+ stackToolbarImplementation = implementation
35
+ }
36
+
37
+ export function getStackToolbarImplementation() {
38
+ return stackToolbarImplementation
39
+ }
@@ -0,0 +1,70 @@
1
+ import React from 'react'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import type { StackToolbarImplementation } from '../StackToolbarImplementation'
4
+
5
+ import { StackToolbar } from '../StackToolbar'
6
+ import { STACK_TOOLBAR_CHILD } from '../StackToolbarImplementation'
7
+ import { exposeStackToolbarConfig } from '../StackToolbar.shared'
8
+ import { appendScreenStackPropsToOptions } from '../StackScreen'
9
+
10
+ describe('Stack.Toolbar headless config', () => {
11
+ it('marks declaration children for installed implementations', () => {
12
+ expect(
13
+ (StackToolbar.Button as unknown as Record<symbol, string>)[STACK_TOOLBAR_CHILD]
14
+ ).toBe('button')
15
+ expect(
16
+ (StackToolbar.MenuAction as unknown as Record<symbol, string>)[STACK_TOOLBAR_CHILD]
17
+ ).toBe('menuAction')
18
+ })
19
+
20
+ it('preserves every placement for the headless stack implementation', () => {
21
+ const options = appendScreenStackPropsToOptions(
22
+ { title: 'Inbox' },
23
+ {
24
+ children: [
25
+ <StackToolbar key="left" placement="left">
26
+ <StackToolbar.Button icon="sidebar.left" />
27
+ </StackToolbar>,
28
+ <StackToolbar key="right" placement="right">
29
+ <StackToolbar.Button icon="square.and.arrow.up" />
30
+ </StackToolbar>,
31
+ ],
32
+ }
33
+ )
34
+
35
+ expect(exposeStackToolbarConfig(options)).toMatchObject({
36
+ title: 'Inbox',
37
+ toolbar: {
38
+ left: { placement: 'left' },
39
+ right: { placement: 'right' },
40
+ },
41
+ })
42
+ })
43
+
44
+ it('hands toolbar declarations to an installed implementation', () => {
45
+ const appendPropsToOptions = vi.fn((options) => ({
46
+ ...options,
47
+ headerShown: true,
48
+ }))
49
+ const implementation: StackToolbarImplementation = {
50
+ appendPropsToOptions,
51
+ render: () => null,
52
+ }
53
+ const toolbar = (
54
+ <StackToolbar placement="right">
55
+ <StackToolbar.Button icon="square.and.arrow.up">Share</StackToolbar.Button>
56
+ </StackToolbar>
57
+ )
58
+
59
+ const options = appendScreenStackPropsToOptions(
60
+ { title: 'Inbox' },
61
+ { children: toolbar },
62
+ implementation
63
+ )
64
+
65
+ expect(options.headerShown).toBe(true)
66
+ expect(appendPropsToOptions).toHaveBeenCalledOnce()
67
+ expect(appendPropsToOptions.mock.calls[0]?.[1]).toBe(toolbar.props)
68
+ expect(exposeStackToolbarConfig(options).toolbar.right).toBe(toolbar.props)
69
+ })
70
+ })
@@ -10,6 +10,7 @@ import {
10
10
  type StackHeaderSearchBarProps,
11
11
  } from './StackHeaderSearchBar'
12
12
  import { StackHeaderTitle, type StackHeaderTitleProps } from './StackHeaderTitle'
13
+ import { StackToolbar } from './StackToolbar'
13
14
  import { NAVIGATOR_CONFIG } from '../../headless/children'
14
15
 
15
16
  /**
@@ -38,8 +39,24 @@ export {
38
39
  type StackHeaderSearchBarProps,
39
40
  StackHeaderTitle,
40
41
  type StackHeaderTitleProps,
42
+ StackToolbar,
41
43
  }
42
44
 
45
+ export type {
46
+ StackToolbarBadgeProps,
47
+ StackToolbarButtonProps,
48
+ StackToolbarConfig,
49
+ StackToolbarIconProps,
50
+ StackToolbarLabelProps,
51
+ StackToolbarMenuActionProps,
52
+ StackToolbarMenuProps,
53
+ StackToolbarPlacement,
54
+ StackToolbarProps,
55
+ StackToolbarSearchBarSlotProps,
56
+ StackToolbarSpacerProps,
57
+ StackToolbarViewProps,
58
+ } from './StackToolbar'
59
+
43
60
  export {
44
61
  StackScreen,
45
62
  appendScreenStackPropsToOptions,
@@ -0,0 +1,88 @@
1
+ import path from 'node:path'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { buildOneMetroResolverOverrides } from './buildOneMetroResolverOverrides'
4
+
5
+ describe('buildOneMetroResolverOverrides', () => {
6
+ it('resolves every react-native-worklets import from the app root', () => {
7
+ const projectRoot = path.resolve(__dirname, '../../')
8
+ const nestedOrigin = path.join(
9
+ projectRoot,
10
+ 'node_modules/react-native-reanimated/src/index.ts'
11
+ )
12
+ const resolvedOrigins: string[] = []
13
+ const config = buildOneMetroResolverOverrides({ projectRoot })({
14
+ resolver: {
15
+ resolveRequest: (
16
+ context: { originModulePath: string },
17
+ moduleName: string,
18
+ _platform: string
19
+ ) => {
20
+ resolvedOrigins.push(`${moduleName}:${context.originModulePath}`)
21
+ return { type: 'sourceFile', filePath: moduleName }
22
+ },
23
+ },
24
+ })
25
+ const resolveRequest = config.resolver?.resolveRequest
26
+
27
+ if (!resolveRequest) {
28
+ throw new Error('expected a Metro resolver override')
29
+ }
30
+
31
+ resolveRequest({ originModulePath: nestedOrigin }, 'react-native-worklets', 'ios')
32
+ resolveRequest(
33
+ { originModulePath: nestedOrigin },
34
+ 'react-native-worklets/package.json',
35
+ 'ios'
36
+ )
37
+ resolveRequest(
38
+ { originModulePath: nestedOrigin },
39
+ 'react-native-safe-area-context',
40
+ 'ios'
41
+ )
42
+
43
+ expect(resolvedOrigins).toEqual([
44
+ `react-native-worklets:${path.join(projectRoot, 'package.json')}`,
45
+ `react-native-worklets/package.json:${path.join(projectRoot, 'package.json')}`,
46
+ `react-native-safe-area-context:${nestedOrigin}`,
47
+ ])
48
+ })
49
+
50
+ it('keeps the compiled react-native-svg native entry point', () => {
51
+ const projectRoot = path.resolve(__dirname, '../../')
52
+ const config = buildOneMetroResolverOverrides({ projectRoot })({
53
+ resolver: {
54
+ resolveRequest: (
55
+ _context: { originModulePath: string },
56
+ moduleName: string,
57
+ _platform: string
58
+ ) => ({
59
+ type: 'sourceFile',
60
+ filePath: path.join(projectRoot, 'node_modules', moduleName, 'src', 'index.ts'),
61
+ }),
62
+ },
63
+ })
64
+ const resolveRequest = config.resolver?.resolveRequest
65
+
66
+ if (!resolveRequest) {
67
+ throw new Error('expected a Metro resolver override')
68
+ }
69
+
70
+ expect(
71
+ resolveRequest(
72
+ { originModulePath: path.join(projectRoot, 'app.tsx') },
73
+ 'react-native-svg',
74
+ 'ios'
75
+ )
76
+ ).toEqual({
77
+ type: 'sourceFile',
78
+ filePath: path.join(
79
+ projectRoot,
80
+ 'node_modules',
81
+ 'react-native-svg',
82
+ 'lib',
83
+ 'commonjs',
84
+ 'index.js'
85
+ ),
86
+ })
87
+ })
88
+ })