one 1.21.7 → 1.21.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 (209) hide show
  1. package/dist/cjs/cli/dev.native.js.map +1 -1
  2. package/dist/cjs/cli.cjs +0 -3
  3. package/dist/cjs/cli.native.js +0 -3
  4. package/dist/cjs/cli.native.js.map +1 -1
  5. package/dist/cjs/image/getImageData.cjs +1 -1
  6. package/dist/cjs/image/getImageData.native.js +1 -1
  7. package/dist/cjs/image/getImageData.native.js.map +1 -1
  8. package/dist/cjs/index.native.js.map +1 -1
  9. package/dist/cjs/layouts/Stack.cjs +5 -5
  10. package/dist/cjs/layouts/Stack.native.js +5 -5
  11. package/dist/cjs/layouts/Stack.native.js.map +1 -1
  12. package/dist/cjs/layouts/stack-utils/StackScreen.cjs +3 -0
  13. package/dist/cjs/layouts/stack-utils/StackScreen.native.js +3 -0
  14. package/dist/cjs/layouts/stack-utils/StackScreen.native.js.map +1 -1
  15. package/dist/cjs/layouts/stack-utils/StackToolbar.cjs +419 -0
  16. package/dist/cjs/layouts/stack-utils/StackToolbar.native.js +453 -0
  17. package/dist/cjs/layouts/stack-utils/StackToolbar.native.js.map +1 -0
  18. package/dist/cjs/layouts/stack-utils/StackToolbar.types.cjs +18 -0
  19. package/dist/cjs/layouts/stack-utils/StackToolbar.types.native.js +21 -0
  20. package/dist/cjs/layouts/stack-utils/StackToolbar.types.native.js.map +1 -0
  21. package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.runtime.test.cjs +63 -0
  22. package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js +73 -0
  23. package/dist/cjs/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js.map +1 -0
  24. package/dist/cjs/layouts/stack-utils/__tests__/composition.test.cjs +205 -0
  25. package/dist/cjs/layouts/stack-utils/__tests__/composition.test.native.js +220 -0
  26. package/dist/cjs/layouts/stack-utils/__tests__/composition.test.native.js.map +1 -1
  27. package/dist/cjs/layouts/stack-utils/index.cjs +2 -0
  28. package/dist/cjs/layouts/stack-utils/index.native.js +2 -0
  29. package/dist/cjs/layouts/stack-utils/index.native.js.map +1 -1
  30. package/dist/cjs/layouts/withLayoutContext.cjs +39 -8
  31. package/dist/cjs/layouts/withLayoutContext.native.js +43 -8
  32. package/dist/cjs/layouts/withLayoutContext.native.js.map +1 -1
  33. package/dist/cjs/layouts/withLayoutContext.test.cjs +96 -0
  34. package/dist/cjs/layouts/withLayoutContext.test.native.js +100 -0
  35. package/dist/cjs/layouts/withLayoutContext.test.native.js.map +1 -0
  36. package/dist/cjs/router/Route.cjs +3 -0
  37. package/dist/cjs/router/Route.native.js +3 -0
  38. package/dist/cjs/router/Route.native.js.map +1 -1
  39. package/dist/cjs/router/protectedRoutes.test.cjs +22 -0
  40. package/dist/cjs/router/protectedRoutes.test.native.js +25 -0
  41. package/dist/cjs/router/protectedRoutes.test.native.js.map +1 -0
  42. package/dist/cjs/router/router.cjs +26 -8
  43. package/dist/cjs/router/router.native.js +26 -8
  44. package/dist/cjs/router/router.native.js.map +1 -1
  45. package/dist/cjs/router/useScreens.cjs +38 -11
  46. package/dist/cjs/router/useScreens.native.js +39 -11
  47. package/dist/cjs/router/useScreens.native.js.map +1 -1
  48. package/dist/cjs/router/useScreens.test.cjs +134 -0
  49. package/dist/cjs/router/useScreens.test.native.js +163 -0
  50. package/dist/cjs/router/useScreens.test.native.js.map +1 -0
  51. package/dist/cjs/views/Navigator.cjs +14 -6
  52. package/dist/cjs/views/Navigator.native.js +14 -6
  53. package/dist/cjs/views/Navigator.native.js.map +1 -1
  54. package/dist/cjs/views/Protected.native.js.map +1 -1
  55. package/dist/cjs/views/SuspenseFallback.cjs +18 -0
  56. package/dist/cjs/views/SuspenseFallback.native.js +21 -0
  57. package/dist/cjs/views/SuspenseFallback.native.js.map +1 -0
  58. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +47 -7
  59. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +47 -7
  60. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  61. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.cjs +15 -6
  62. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.native.js +15 -6
  63. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.test.native.js.map +1 -1
  64. package/dist/esm/cli/dev.mjs.map +1 -1
  65. package/dist/esm/cli/dev.native.js.map +1 -1
  66. package/dist/esm/cli.mjs +0 -3
  67. package/dist/esm/cli.mjs.map +1 -1
  68. package/dist/esm/cli.native.js +0 -3
  69. package/dist/esm/cli.native.js.map +1 -1
  70. package/dist/esm/image/getImageData.mjs +1 -1
  71. package/dist/esm/image/getImageData.mjs.map +1 -1
  72. package/dist/esm/image/getImageData.native.js +1 -1
  73. package/dist/esm/image/getImageData.native.js.map +1 -1
  74. package/dist/esm/index.js.map +1 -1
  75. package/dist/esm/index.mjs.map +1 -1
  76. package/dist/esm/index.native.js.map +1 -1
  77. package/dist/esm/layouts/Stack.mjs +6 -6
  78. package/dist/esm/layouts/Stack.mjs.map +1 -1
  79. package/dist/esm/layouts/Stack.native.js +6 -6
  80. package/dist/esm/layouts/Stack.native.js.map +1 -1
  81. package/dist/esm/layouts/stack-utils/StackScreen.mjs +3 -0
  82. package/dist/esm/layouts/stack-utils/StackScreen.mjs.map +1 -1
  83. package/dist/esm/layouts/stack-utils/StackScreen.native.js +3 -0
  84. package/dist/esm/layouts/stack-utils/StackScreen.native.js.map +1 -1
  85. package/dist/esm/layouts/stack-utils/StackToolbar.mjs +384 -0
  86. package/dist/esm/layouts/stack-utils/StackToolbar.mjs.map +1 -0
  87. package/dist/esm/layouts/stack-utils/StackToolbar.native.js +416 -0
  88. package/dist/esm/layouts/stack-utils/StackToolbar.native.js.map +1 -0
  89. package/dist/esm/layouts/stack-utils/StackToolbar.types.mjs +2 -0
  90. package/dist/esm/layouts/stack-utils/StackToolbar.types.mjs.map +1 -0
  91. package/dist/esm/layouts/stack-utils/StackToolbar.types.native.js +2 -0
  92. package/dist/esm/layouts/stack-utils/StackToolbar.types.native.js.map +1 -0
  93. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.mjs +40 -0
  94. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.mjs.map +1 -0
  95. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js +47 -0
  96. package/dist/esm/layouts/stack-utils/__tests__/StackToolbar.runtime.test.native.js.map +1 -0
  97. package/dist/esm/layouts/stack-utils/__tests__/composition.test.mjs +205 -0
  98. package/dist/esm/layouts/stack-utils/__tests__/composition.test.mjs.map +1 -1
  99. package/dist/esm/layouts/stack-utils/__tests__/composition.test.native.js +220 -0
  100. package/dist/esm/layouts/stack-utils/__tests__/composition.test.native.js.map +1 -1
  101. package/dist/esm/layouts/stack-utils/index.mjs +2 -1
  102. package/dist/esm/layouts/stack-utils/index.mjs.map +1 -1
  103. package/dist/esm/layouts/stack-utils/index.native.js +2 -1
  104. package/dist/esm/layouts/stack-utils/index.native.js.map +1 -1
  105. package/dist/esm/layouts/withLayoutContext.mjs +41 -11
  106. package/dist/esm/layouts/withLayoutContext.mjs.map +1 -1
  107. package/dist/esm/layouts/withLayoutContext.native.js +45 -11
  108. package/dist/esm/layouts/withLayoutContext.native.js.map +1 -1
  109. package/dist/esm/layouts/withLayoutContext.test.mjs +73 -0
  110. package/dist/esm/layouts/withLayoutContext.test.mjs.map +1 -0
  111. package/dist/esm/layouts/withLayoutContext.test.native.js +74 -0
  112. package/dist/esm/layouts/withLayoutContext.test.native.js.map +1 -0
  113. package/dist/esm/router/Route.mjs +3 -1
  114. package/dist/esm/router/Route.mjs.map +1 -1
  115. package/dist/esm/router/Route.native.js +3 -1
  116. package/dist/esm/router/Route.native.js.map +1 -1
  117. package/dist/esm/router/protectedRoutes.test.mjs +23 -0
  118. package/dist/esm/router/protectedRoutes.test.mjs.map +1 -0
  119. package/dist/esm/router/protectedRoutes.test.native.js +23 -0
  120. package/dist/esm/router/protectedRoutes.test.native.js.map +1 -0
  121. package/dist/esm/router/router.mjs +26 -9
  122. package/dist/esm/router/router.mjs.map +1 -1
  123. package/dist/esm/router/router.native.js +26 -9
  124. package/dist/esm/router/router.native.js.map +1 -1
  125. package/dist/esm/router/useScreens.mjs +39 -11
  126. package/dist/esm/router/useScreens.mjs.map +1 -1
  127. package/dist/esm/router/useScreens.native.js +40 -11
  128. package/dist/esm/router/useScreens.native.js.map +1 -1
  129. package/dist/esm/router/useScreens.test.mjs +105 -0
  130. package/dist/esm/router/useScreens.test.mjs.map +1 -0
  131. package/dist/esm/router/useScreens.test.native.js +130 -0
  132. package/dist/esm/router/useScreens.test.native.js.map +1 -0
  133. package/dist/esm/views/Navigator.mjs +15 -7
  134. package/dist/esm/views/Navigator.mjs.map +1 -1
  135. package/dist/esm/views/Navigator.native.js +15 -7
  136. package/dist/esm/views/Navigator.native.js.map +1 -1
  137. package/dist/esm/views/Protected.mjs.map +1 -1
  138. package/dist/esm/views/Protected.native.js.map +1 -1
  139. package/dist/esm/views/SuspenseFallback.mjs +2 -0
  140. package/dist/esm/views/SuspenseFallback.mjs.map +1 -0
  141. package/dist/esm/views/SuspenseFallback.native.js +2 -0
  142. package/dist/esm/views/SuspenseFallback.native.js.map +1 -0
  143. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +47 -7
  144. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
  145. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +47 -7
  146. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  147. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.mjs +15 -6
  148. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.mjs.map +1 -1
  149. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.native.js +15 -6
  150. package/dist/esm/vite/plugins/fileSystemRouterPlugin.test.native.js.map +1 -1
  151. package/package.json +12 -11
  152. package/src/cli/dev.ts +0 -1
  153. package/src/cli.ts +0 -3
  154. package/src/image/getImageData.ts +2 -2
  155. package/src/index.ts +12 -0
  156. package/src/layouts/Stack.tsx +6 -4
  157. package/src/layouts/stack-utils/README.md +1 -0
  158. package/src/layouts/stack-utils/StackScreen.tsx +7 -0
  159. package/src/layouts/stack-utils/StackToolbar.tsx +550 -0
  160. package/src/layouts/stack-utils/StackToolbar.types.ts +98 -0
  161. package/src/layouts/stack-utils/__tests__/StackToolbar.runtime.test.tsx +46 -0
  162. package/src/layouts/stack-utils/__tests__/composition.test.tsx +176 -0
  163. package/src/layouts/stack-utils/index.tsx +16 -0
  164. package/src/layouts/withLayoutContext.test.tsx +74 -0
  165. package/src/layouts/withLayoutContext.tsx +71 -12
  166. package/src/router/Route.tsx +8 -0
  167. package/src/router/protectedRoutes.test.ts +35 -0
  168. package/src/router/router.ts +48 -20
  169. package/src/router/useScreens.test.tsx +139 -0
  170. package/src/router/useScreens.tsx +59 -13
  171. package/src/views/Navigator.tsx +25 -5
  172. package/src/views/Protected.tsx +3 -0
  173. package/src/views/SuspenseFallback.ts +7 -0
  174. package/src/vite/plugins/fileSystemRouterPlugin.test.ts +15 -6
  175. package/src/vite/plugins/fileSystemRouterPlugin.tsx +52 -7
  176. package/types/cli/dev.d.ts +0 -1
  177. package/types/cli/dev.d.ts.map +1 -1
  178. package/types/image/getImageData.d.ts +1 -1
  179. package/types/image/getImageData.d.ts.map +1 -1
  180. package/types/index.d.ts +2 -1
  181. package/types/index.d.ts.map +1 -1
  182. package/types/layouts/Stack.d.ts +11 -0
  183. package/types/layouts/Stack.d.ts.map +1 -1
  184. package/types/layouts/stack-utils/StackScreen.d.ts.map +1 -1
  185. package/types/layouts/stack-utils/StackToolbar.d.ts +26 -0
  186. package/types/layouts/stack-utils/StackToolbar.d.ts.map +1 -0
  187. package/types/layouts/stack-utils/StackToolbar.types.d.ts +82 -0
  188. package/types/layouts/stack-utils/StackToolbar.types.d.ts.map +1 -0
  189. package/types/layouts/stack-utils/index.d.ts +3 -1
  190. package/types/layouts/stack-utils/index.d.ts.map +1 -1
  191. package/types/layouts/withLayoutContext.d.ts +7 -0
  192. package/types/layouts/withLayoutContext.d.ts.map +1 -1
  193. package/types/layouts/withLayoutContext.test.d.ts +2 -0
  194. package/types/layouts/withLayoutContext.test.d.ts.map +1 -0
  195. package/types/router/Route.d.ts +4 -0
  196. package/types/router/Route.d.ts.map +1 -1
  197. package/types/router/protectedRoutes.test.d.ts +2 -0
  198. package/types/router/protectedRoutes.test.d.ts.map +1 -0
  199. package/types/router/router.d.ts +4 -6
  200. package/types/router/router.d.ts.map +1 -1
  201. package/types/router/useScreens.d.ts.map +1 -1
  202. package/types/router/useScreens.test.d.ts +2 -0
  203. package/types/router/useScreens.test.d.ts.map +1 -0
  204. package/types/views/Navigator.d.ts.map +1 -1
  205. package/types/views/Protected.d.ts +3 -0
  206. package/types/views/Protected.d.ts.map +1 -1
  207. package/types/views/SuspenseFallback.d.ts +8 -0
  208. package/types/views/SuspenseFallback.d.ts.map +1 -0
  209. package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
package/src/cli.ts CHANGED
@@ -79,9 +79,6 @@ const dev = defineCommand({
79
79
  port: {
80
80
  type: 'string',
81
81
  },
82
- https: {
83
- type: 'boolean',
84
- },
85
82
  mode: {
86
83
  type: 'string',
87
84
  description:
@@ -16,10 +16,10 @@ export type ImageMeta = {
16
16
 
17
17
  let sharpWarned = false
18
18
 
19
- export async function getSharp(): Promise<typeof import('sharp') | null> {
19
+ export async function getSharp(): Promise<(typeof import('sharp'))['default'] | null> {
20
20
  try {
21
21
  const sharpModule = await import('sharp')
22
- return (sharpModule as any).default || sharpModule
22
+ return sharpModule.default
23
23
  } catch (e) {
24
24
  if (!sharpWarned) {
25
25
  sharpWarned = true
package/src/index.ts CHANGED
@@ -94,6 +94,17 @@ export type {
94
94
  StackHeaderTitleProps,
95
95
  StackScreenOptions,
96
96
  StackScreenProps,
97
+ StackToolbarBadgeProps,
98
+ StackToolbarButtonProps,
99
+ StackToolbarIconProps,
100
+ StackToolbarLabelProps,
101
+ StackToolbarMenuActionProps,
102
+ StackToolbarMenuProps,
103
+ StackToolbarPlacement,
104
+ StackToolbarProps,
105
+ StackToolbarSearchBarSlotProps,
106
+ StackToolbarSpacerProps,
107
+ StackToolbarViewProps,
97
108
  } from './layouts/stack-utils'
98
109
  // Stack render API (for custom web overlay rendering)
99
110
  export type {
@@ -187,6 +198,7 @@ export { LoadProgressBar } from './views/LoadProgressBar'
187
198
  export { Navigator, Slot } from './views/Navigator'
188
199
  export { ScrollBehavior } from './views/ScrollBehavior'
189
200
  export { SourceInspector, type SourceInspectorProps } from './views/SourceInspector'
201
+ export type { SuspenseFallbackProps } from './views/SuspenseFallback'
190
202
  export type { ErrorBoundaryProps, ErrorRouteInfo } from './views/Try'
191
203
  // scroll position groups
192
204
  export { useScrollGroup } from './useScrollGroup'
@@ -10,6 +10,7 @@ import {
10
10
  StackHeader,
11
11
  StackHeaderComponent,
12
12
  StackHeaderSearchBar,
13
+ StackToolbar,
13
14
  appendScreenStackPropsToOptions,
14
15
  type StackScreenProps,
15
16
  } from './stack-utils'
@@ -46,10 +47,10 @@ function mapChildren(children: React.ReactNode): React.ReactNode {
46
47
 
47
48
  if (isChildOfType(child, Protected)) {
48
49
  // recursively process Protected children
49
- return (
50
- <Protected key={`protected-${index}`} guard={child.props.guard}>
51
- {mapChildren(child.props.children)}
52
- </Protected>
50
+ return React.cloneElement(
51
+ child,
52
+ { key: `protected-${index}` },
53
+ mapChildren(child.props.children)
53
54
  )
54
55
  }
55
56
 
@@ -127,6 +128,7 @@ export const Stack = Object.assign(StackWithComposition, {
127
128
  Header: StackHeader,
128
129
  Protected,
129
130
  SearchBar: StackHeaderSearchBar,
131
+ Toolbar: StackToolbar,
130
132
  })
131
133
 
132
134
  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
 
@@ -8,6 +8,11 @@ import {
8
8
  appendStackHeaderPropsToOptions,
9
9
  type StackHeaderProps,
10
10
  } from './StackHeaderComponent'
11
+ import {
12
+ StackToolbar,
13
+ appendStackToolbarPropsToOptions,
14
+ type StackToolbarProps,
15
+ } from './StackToolbar'
11
16
  import { Screen } from '../../views/Screen'
12
17
  import type { StackRender } from '../../router/web/ScreenRenderContext'
13
18
 
@@ -119,6 +124,8 @@ export function appendScreenStackPropsToOptions(
119
124
  function appendChildOptions(child: React.ReactElement, options: StackScreenOptions) {
120
125
  if (child.type === StackHeaderComponent) {
121
126
  return appendStackHeaderPropsToOptions(options, child.props as StackHeaderProps)
127
+ } else if (child.type === StackToolbar) {
128
+ return appendStackToolbarPropsToOptions(options, child.props as StackToolbarProps)
122
129
  } else {
123
130
  console.warn(
124
131
  `Warning: Unknown child element passed to Stack.Screen: ${(child.type as { name: string }).name ?? child.type}`
@@ -0,0 +1,550 @@
1
+ 'use client'
2
+
3
+ import type {
4
+ NativeStackHeaderItem,
5
+ NativeStackHeaderItemButton,
6
+ NativeStackHeaderItemMenu,
7
+ NativeStackHeaderItemMenuAction,
8
+ NativeStackHeaderItemMenuSubmenu,
9
+ NativeStackNavigationOptions,
10
+ } from '@react-navigation/native-stack'
11
+ import { MenuAction, ToolbarHost, ToolbarItem } from '@vxrn/native'
12
+ import {
13
+ Children,
14
+ createContext,
15
+ Fragment,
16
+ useContext,
17
+ useId,
18
+ useMemo,
19
+ type ReactNode,
20
+ } from 'react'
21
+ import { Platform, StyleSheet, type ImageSourcePropType } from 'react-native'
22
+
23
+ import type {
24
+ StackToolbarBadgeProps,
25
+ StackToolbarButtonProps,
26
+ StackToolbarIconProps,
27
+ StackToolbarItemProps,
28
+ StackToolbarLabelProps,
29
+ StackToolbarMenuActionProps,
30
+ StackToolbarMenuProps,
31
+ StackToolbarPlacement,
32
+ StackToolbarProps,
33
+ StackToolbarSearchBarSlotProps,
34
+ StackToolbarSpacerProps,
35
+ StackToolbarViewProps,
36
+ } from './StackToolbar.types'
37
+ import { useIsomorphicLayoutEffect } from '@vxrn/use-isomorphic-layout-effect'
38
+ import { useNavigation } from '../../router/useNavigation'
39
+ import { isChildOfType } from '../../utils/children'
40
+
41
+ const StackToolbarPlacementContext = createContext<StackToolbarPlacement | null>(null)
42
+
43
+ export function StackToolbarLabel(_props: StackToolbarLabelProps) {
44
+ return null
45
+ }
46
+
47
+ export function StackToolbarIcon(_props: StackToolbarIconProps) {
48
+ return null
49
+ }
50
+
51
+ export function StackToolbarBadge(_props: StackToolbarBadgeProps) {
52
+ return null
53
+ }
54
+
55
+ type ToolbarContent = {
56
+ label: string
57
+ icon?: StackToolbarIconProps
58
+ badge?: StackToolbarBadgeProps
59
+ }
60
+
61
+ function getToolbarContent(
62
+ children: ReactNode,
63
+ icon?: string | ImageSourcePropType,
64
+ iconRenderingMode?: 'template' | 'original'
65
+ ): ToolbarContent {
66
+ let label = ''
67
+ let iconProps: StackToolbarIconProps | undefined =
68
+ typeof icon === 'string'
69
+ ? { sf: icon }
70
+ : icon
71
+ ? { src: icon, renderingMode: iconRenderingMode }
72
+ : undefined
73
+ let badge: StackToolbarBadgeProps | undefined
74
+
75
+ Children.forEach(children, (child) => {
76
+ if (typeof child === 'string' || typeof child === 'number') {
77
+ label += String(child)
78
+ } else if (isChildOfType(child, StackToolbarLabel)) {
79
+ label = child.props.children ?? ''
80
+ } else if (isChildOfType(child, StackToolbarIcon)) {
81
+ iconProps = child.props
82
+ } else if (isChildOfType(child, StackToolbarBadge)) {
83
+ badge = child.props
84
+ }
85
+ })
86
+
87
+ return { label, icon: iconProps, badge }
88
+ }
89
+
90
+ function getHeaderIcon(
91
+ content: ToolbarContent,
92
+ tintColor?: StackToolbarItemProps['tintColor']
93
+ ) {
94
+ if (content.icon?.sf) {
95
+ return { type: 'sfSymbol' as const, name: content.icon.sf }
96
+ }
97
+ if (content.icon?.src) {
98
+ return {
99
+ type: 'image' as const,
100
+ source: content.icon.src,
101
+ tinted:
102
+ (content.icon.renderingMode ?? (tintColor ? 'template' : 'original')) ===
103
+ 'template',
104
+ }
105
+ }
106
+ if (content.icon?.xcasset) {
107
+ return {
108
+ type: 'image' as const,
109
+ source: { uri: content.icon.xcasset },
110
+ tinted:
111
+ (content.icon.renderingMode ?? (tintColor ? 'template' : 'original')) ===
112
+ 'template',
113
+ }
114
+ }
115
+ return undefined
116
+ }
117
+
118
+ function getHeaderItemSharedProps(
119
+ props: StackToolbarButtonProps | StackToolbarMenuProps,
120
+ content: ToolbarContent
121
+ ) {
122
+ const labelStyle = StyleSheet.flatten(props.style)
123
+ const badgeStyle = StyleSheet.flatten(content.badge?.style)
124
+
125
+ return {
126
+ label: content.label,
127
+ labelStyle,
128
+ icon: getHeaderIcon(content, props.tintColor),
129
+ variant: props.variant,
130
+ tintColor: props.tintColor,
131
+ disabled: props.disabled,
132
+ hidesSharedBackground: props.hidesSharedBackground,
133
+ sharesBackground: !props.separateBackground,
134
+ accessibilityLabel: props.accessibilityLabel ?? content.label,
135
+ accessibilityHint: props.accessibilityHint,
136
+ badge: content.badge
137
+ ? {
138
+ value: content.badge.children ?? '',
139
+ style: badgeStyle,
140
+ }
141
+ : undefined,
142
+ }
143
+ }
144
+
145
+ function getMenuItems(children: ReactNode): NativeStackHeaderItemMenu['menu']['items'] {
146
+ return Children.toArray(children)
147
+ .map(
148
+ (
149
+ child
150
+ ): NativeStackHeaderItemMenuAction | NativeStackHeaderItemMenuSubmenu | null => {
151
+ if (isChildOfType(child, StackToolbarMenuAction)) {
152
+ const content = getToolbarContent(
153
+ child.props.children,
154
+ child.props.icon,
155
+ child.props.iconRenderingMode
156
+ )
157
+ return {
158
+ type: 'action',
159
+ label: content.label,
160
+ description: child.props.subtitle,
161
+ icon: getHeaderIcon(content, child.props.tintColor),
162
+ onPress: child.props.onPress ?? (() => {}),
163
+ state: child.props.isOn ? 'on' : 'off',
164
+ disabled: child.props.disabled,
165
+ destructive: child.props.destructive,
166
+ hidden: child.props.hidden,
167
+ keepsMenuPresented: child.props.unstable_keepPresented,
168
+ discoverabilityLabel: child.props.discoverabilityLabel,
169
+ } as NativeStackHeaderItemMenuAction
170
+ }
171
+
172
+ if (isChildOfType(child, StackToolbarMenu)) {
173
+ const content = getToolbarContent(
174
+ child.props.children,
175
+ child.props.icon,
176
+ child.props.iconRenderingMode
177
+ )
178
+ return {
179
+ type: 'submenu',
180
+ label: content.label || child.props.title || '',
181
+ icon: getHeaderIcon(content, child.props.tintColor),
182
+ inline: child.props.inline,
183
+ layout: child.props.palette ? 'palette' : 'default',
184
+ destructive: child.props.destructive,
185
+ multiselectable: child.props.singleSelection === false,
186
+ items: getMenuItems(child.props.children),
187
+ } as NativeStackHeaderItemMenuSubmenu
188
+ }
189
+
190
+ return null
191
+ }
192
+ )
193
+ .filter(
194
+ (
195
+ item
196
+ ): item is NativeStackHeaderItemMenuAction | NativeStackHeaderItemMenuSubmenu =>
197
+ Boolean(item)
198
+ )
199
+ }
200
+
201
+ function convertToolbarChildToHeaderItem(child: ReactNode): NativeStackHeaderItem | null {
202
+ if (isChildOfType(child, StackToolbarButton)) {
203
+ if (child.props.hidden) return null
204
+ const content = getToolbarContent(
205
+ child.props.children,
206
+ child.props.icon,
207
+ child.props.iconRenderingMode
208
+ )
209
+ return {
210
+ ...getHeaderItemSharedProps(child.props, content),
211
+ type: 'button',
212
+ onPress: child.props.onPress ?? (() => {}),
213
+ selected: child.props.selected,
214
+ } as NativeStackHeaderItemButton
215
+ }
216
+
217
+ if (isChildOfType(child, StackToolbarMenu)) {
218
+ if (child.props.hidden) return null
219
+ const content = getToolbarContent(
220
+ child.props.children,
221
+ child.props.icon,
222
+ child.props.iconRenderingMode
223
+ )
224
+ const contentWithTitle = {
225
+ ...content,
226
+ label: content.label || child.props.title || '',
227
+ }
228
+ return {
229
+ ...getHeaderItemSharedProps(child.props, contentWithTitle),
230
+ type: 'menu',
231
+ menu: {
232
+ title: child.props.title,
233
+ multiselectable: child.props.singleSelection === false,
234
+ layout: child.props.palette ? 'palette' : 'default',
235
+ items: getMenuItems(child.props.children),
236
+ },
237
+ } as NativeStackHeaderItemMenu
238
+ }
239
+
240
+ if (isChildOfType(child, StackToolbarSpacer)) {
241
+ if (child.props.hidden) return null
242
+ if (child.props.width === undefined) {
243
+ if (process.env.NODE_ENV !== 'production') {
244
+ console.warn('Stack.Toolbar.Spacer requires a width in header placements.')
245
+ }
246
+ return null
247
+ }
248
+ return { type: 'spacing', spacing: child.props.width }
249
+ }
250
+
251
+ if (isChildOfType(child, StackToolbarView)) {
252
+ if (child.props.hidden) return null
253
+ return {
254
+ type: 'custom',
255
+ element: <Fragment>{child.props.children}</Fragment>,
256
+ hidesSharedBackground: child.props.hidesSharedBackground,
257
+ }
258
+ }
259
+
260
+ return null
261
+ }
262
+
263
+ export function appendStackToolbarPropsToOptions(
264
+ options: NativeStackNavigationOptions,
265
+ props: StackToolbarProps
266
+ ): NativeStackNavigationOptions {
267
+ if (Platform.OS !== 'ios') return options
268
+
269
+ const placement = props.placement ?? 'bottom'
270
+ if (placement === 'bottom') {
271
+ if (process.env.NODE_ENV !== 'production') {
272
+ console.warn(
273
+ 'Stack.Toolbar with bottom placement must be rendered in a page component, not inside Stack.Screen.'
274
+ )
275
+ }
276
+ return options
277
+ }
278
+
279
+ if (placement !== 'left' && placement !== 'right') {
280
+ throw new Error(
281
+ `Invalid placement "${placement}" for Stack.Toolbar. Expected "left", "right", or "bottom".`
282
+ )
283
+ }
284
+
285
+ if (props.asChild) {
286
+ return placement === 'left'
287
+ ? { ...options, headerShown: true, headerLeft: () => props.children }
288
+ : { ...options, headerShown: true, headerRight: () => props.children }
289
+ }
290
+
291
+ const allChildren = Children.toArray(props.children)
292
+ const items = allChildren
293
+ .map(convertToolbarChildToHeaderItem)
294
+ .filter((item): item is NativeStackHeaderItem => Boolean(item))
295
+
296
+ if (process.env.NODE_ENV !== 'production' && items.length !== allChildren.length) {
297
+ const invalidChildren = allChildren.filter(
298
+ (child) =>
299
+ !isChildOfType(child, StackToolbarButton) &&
300
+ !isChildOfType(child, StackToolbarMenu) &&
301
+ !isChildOfType(child, StackToolbarSpacer) &&
302
+ !isChildOfType(child, StackToolbarView)
303
+ )
304
+ if (invalidChildren.length) {
305
+ console.warn(
306
+ `Stack.Toolbar with placement="${placement}" only accepts Stack.Toolbar.Button, Menu, Spacer, and View children.`
307
+ )
308
+ }
309
+ }
310
+
311
+ const getItems = () => items
312
+ return placement === 'left'
313
+ ? { ...options, headerShown: true, unstable_headerLeftItems: getItems }
314
+ : { ...options, headerShown: true, unstable_headerRightItems: getItems }
315
+ }
316
+
317
+ function StackToolbarComponent(props: StackToolbarProps) {
318
+ const parentPlacement = useContext(StackToolbarPlacementContext)
319
+ const navigation = useNavigation()
320
+ const placement = props.placement ?? 'bottom'
321
+ const options = useMemo(
322
+ () => (placement === 'bottom' ? {} : appendStackToolbarPropsToOptions({}, props)),
323
+ [placement, props.asChild, props.children]
324
+ )
325
+
326
+ if (parentPlacement) {
327
+ throw new Error('Stack.Toolbar cannot be nested inside another Stack.Toolbar.')
328
+ }
329
+
330
+ useIsomorphicLayoutEffect(() => {
331
+ if (Platform.OS !== 'ios' || placement === 'bottom') return
332
+
333
+ navigation.setOptions(options)
334
+ }, [navigation, options, placement])
335
+
336
+ if (Platform.OS !== 'ios' || placement !== 'bottom') return null
337
+
338
+ return (
339
+ <StackToolbarPlacementContext.Provider value="bottom">
340
+ <ToolbarHost>{props.children}</ToolbarHost>
341
+ </StackToolbarPlacementContext.Provider>
342
+ )
343
+ }
344
+
345
+ export function StackToolbarButton(props: StackToolbarButtonProps) {
346
+ const placement = useContext(StackToolbarPlacementContext)
347
+ const identifier = useId()
348
+ if (Platform.OS !== 'ios') return null
349
+ if (!placement)
350
+ throw new Error('Stack.Toolbar.Button must be used inside Stack.Toolbar.')
351
+
352
+ const content = getToolbarContent(props.children, props.icon, props.iconRenderingMode)
353
+ const badgeStyle = StyleSheet.flatten(content.badge?.style)
354
+ return (
355
+ <ToolbarItem
356
+ identifier={identifier}
357
+ title={content.label}
358
+ systemImageName={content.icon?.sf}
359
+ xcassetName={content.icon?.xcasset}
360
+ image={props.image ?? content.icon?.src}
361
+ imageRenderingMode={
362
+ content.icon?.renderingMode ??
363
+ props.iconRenderingMode ??
364
+ (props.tintColor ? 'template' : 'original')
365
+ }
366
+ tintColor={props.tintColor}
367
+ hidesSharedBackground={props.hidesSharedBackground}
368
+ sharesBackground={!props.separateBackground}
369
+ barButtonItemStyle={props.variant === 'done' ? 'prominent' : props.variant}
370
+ hidden={props.hidden}
371
+ selected={props.selected}
372
+ badgeConfiguration={
373
+ content.badge
374
+ ? {
375
+ value: content.badge.children,
376
+ backgroundColor: badgeStyle?.backgroundColor,
377
+ color: badgeStyle?.color,
378
+ fontFamily: badgeStyle?.fontFamily,
379
+ fontSize: badgeStyle?.fontSize,
380
+ fontWeight: badgeStyle?.fontWeight,
381
+ }
382
+ : undefined
383
+ }
384
+ titleStyle={StyleSheet.flatten(props.style)}
385
+ accessibilityLabel={props.accessibilityLabel ?? content.label}
386
+ accessibilityHint={props.accessibilityHint}
387
+ disabled={props.disabled}
388
+ onSelected={props.onPress}
389
+ />
390
+ )
391
+ }
392
+
393
+ export function StackToolbarMenu(props: StackToolbarMenuProps) {
394
+ const placement = useContext(StackToolbarPlacementContext)
395
+ const identifier = useId()
396
+ if (Platform.OS !== 'ios') return null
397
+ if (!placement) throw new Error('Stack.Toolbar.Menu must be used inside Stack.Toolbar.')
398
+
399
+ const content = getToolbarContent(props.children, props.icon, props.iconRenderingMode)
400
+ const actions = Children.toArray(props.children).filter(
401
+ (child) =>
402
+ isChildOfType(child, StackToolbarMenu) ||
403
+ isChildOfType(child, StackToolbarMenuAction)
404
+ )
405
+ return (
406
+ <MenuAction
407
+ identifier={identifier}
408
+ title={props.title ?? ''}
409
+ label={content.label || props.title}
410
+ icon={content.icon?.sf}
411
+ xcassetName={content.icon?.xcasset}
412
+ image={props.image ?? content.icon?.src}
413
+ imageRenderingMode={
414
+ content.icon?.renderingMode ??
415
+ props.iconRenderingMode ??
416
+ (props.tintColor ? 'template' : 'original')
417
+ }
418
+ disabled={props.disabled}
419
+ destructive={props.destructive}
420
+ hidden={props.hidden}
421
+ accessibilityLabel={props.accessibilityLabel ?? (content.label || props.title)}
422
+ accessibilityHint={props.accessibilityHint}
423
+ displayInline={props.inline}
424
+ displayAsPalette={props.palette}
425
+ singleSelection={props.singleSelection}
426
+ preferredElementSize={props.elementSize}
427
+ tintColor={props.tintColor}
428
+ barButtonItemStyle={props.variant === 'done' ? 'prominent' : props.variant}
429
+ sharesBackground={!props.separateBackground}
430
+ hidesSharedBackground={props.hidesSharedBackground}
431
+ titleStyle={StyleSheet.flatten(props.style)}
432
+ >
433
+ {actions}
434
+ </MenuAction>
435
+ )
436
+ }
437
+
438
+ export function StackToolbarMenuAction(props: StackToolbarMenuActionProps) {
439
+ const placement = useContext(StackToolbarPlacementContext)
440
+ const identifier = useId()
441
+ if (Platform.OS !== 'ios') return null
442
+ if (!placement)
443
+ throw new Error('Stack.Toolbar.MenuAction must be used inside Stack.Toolbar.Menu.')
444
+
445
+ const content = getToolbarContent(props.children, props.icon, props.iconRenderingMode)
446
+ return (
447
+ <MenuAction
448
+ identifier={identifier}
449
+ title={content.label}
450
+ icon={content.icon?.sf}
451
+ xcassetName={content.icon?.xcasset}
452
+ image={props.image ?? content.icon?.src}
453
+ imageRenderingMode={
454
+ content.icon?.renderingMode ??
455
+ props.iconRenderingMode ??
456
+ (props.tintColor ? 'template' : 'original')
457
+ }
458
+ disabled={props.disabled}
459
+ destructive={props.destructive}
460
+ discoverabilityLabel={props.discoverabilityLabel}
461
+ subtitle={props.subtitle}
462
+ accessibilityLabel={props.accessibilityLabel ?? content.label}
463
+ accessibilityHint={props.accessibilityHint}
464
+ isOn={props.isOn}
465
+ keepPresented={props.unstable_keepPresented}
466
+ hidden={props.hidden}
467
+ onSelected={props.onPress}
468
+ />
469
+ )
470
+ }
471
+
472
+ export function StackToolbarSpacer(props: StackToolbarSpacerProps) {
473
+ const placement = useContext(StackToolbarPlacementContext)
474
+ const identifier = useId()
475
+ if (Platform.OS !== 'ios') return null
476
+ if (!placement)
477
+ throw new Error('Stack.Toolbar.Spacer must be used inside Stack.Toolbar.')
478
+
479
+ return (
480
+ <ToolbarItem
481
+ identifier={identifier}
482
+ type={props.width === undefined ? 'fluidSpacer' : 'fixedSpacer'}
483
+ width={props.width}
484
+ hidden={props.hidden}
485
+ sharesBackground={props.sharesBackground}
486
+ />
487
+ )
488
+ }
489
+
490
+ export function StackToolbarSearchBarSlot(props: StackToolbarSearchBarSlotProps) {
491
+ const placement = useContext(StackToolbarPlacementContext)
492
+ const identifier = useId()
493
+ if (Platform.OS !== 'ios') return null
494
+ if (!placement)
495
+ throw new Error('Stack.Toolbar.SearchBarSlot must be used inside Stack.Toolbar.')
496
+
497
+ return (
498
+ <ToolbarItem
499
+ identifier={identifier}
500
+ type="searchBar"
501
+ hidden={props.hidden}
502
+ hidesSharedBackground={props.hidesSharedBackground}
503
+ sharesBackground={!props.separateBackground}
504
+ />
505
+ )
506
+ }
507
+
508
+ export function StackToolbarView(props: StackToolbarViewProps) {
509
+ const placement = useContext(StackToolbarPlacementContext)
510
+ const identifier = useId()
511
+ if (Platform.OS !== 'ios') return null
512
+ if (!placement) throw new Error('Stack.Toolbar.View must be used inside Stack.Toolbar.')
513
+ if (props.hidden) return null
514
+
515
+ return (
516
+ <ToolbarItem
517
+ identifier={identifier}
518
+ hidesSharedBackground={props.hidesSharedBackground}
519
+ sharesBackground={!props.separateBackground}
520
+ >
521
+ {props.children}
522
+ </ToolbarItem>
523
+ )
524
+ }
525
+
526
+ export const StackToolbar = Object.assign(StackToolbarComponent, {
527
+ Button: StackToolbarButton,
528
+ Menu: StackToolbarMenu,
529
+ MenuAction: StackToolbarMenuAction,
530
+ Spacer: StackToolbarSpacer,
531
+ SearchBarSlot: StackToolbarSearchBarSlot,
532
+ View: StackToolbarView,
533
+ Label: StackToolbarLabel,
534
+ Icon: StackToolbarIcon,
535
+ Badge: StackToolbarBadge,
536
+ })
537
+
538
+ export type {
539
+ StackToolbarBadgeProps,
540
+ StackToolbarButtonProps,
541
+ StackToolbarIconProps,
542
+ StackToolbarLabelProps,
543
+ StackToolbarMenuActionProps,
544
+ StackToolbarMenuProps,
545
+ StackToolbarPlacement,
546
+ StackToolbarProps,
547
+ StackToolbarSearchBarSlotProps,
548
+ StackToolbarSpacerProps,
549
+ StackToolbarViewProps,
550
+ } from './StackToolbar.types'