import { useNavigation, type ParamListBase } from "@react-navigation/native";import type { NativeStackHeaderItem, NativeStackHeaderItemMenu, NativeStackNavigationOptions, NativeStackNavigationProp,} from "@react-navigation/native-stack";import { Children, isValidElement, useEffect, useLayoutEffect, useMemo, useRef, type ReactElement, type ReactNode,} from "react";import type { ColorValue } from "react-native"; export { nativeHeaderScrollEdgeEffects, nativeTopScrollEdgeEffect, type NativeHeaderScrollEdgeEffects, type NativeTopScrollEdgeEffect,} from "./scrollEdgeEffects"; export type AppNativeStackNavigationOptions = Omit< NativeStackNavigationOptions, "headerTintColor" | "unstable_headerLeftItems" | "unstable_headerRightItems"> & { readonly headerTintColor?: string | ColorValue; readonly unstable_headerCenterItems?: unknown; readonly unstable_headerLeftItems?: unknown; readonly unstable_headerRightItems?: unknown; readonly unstable_headerSubtitle?: unknown; readonly unstable_headerToolbarItems?: unknown; readonly unstable_navigationItemStyle?: unknown;}; function useNativeStackNavigation(): NativeStackNavigationProp<ParamListBase> | null { return useNavigation<NativeStackNavigationProp<ParamListBase>>();} function normalizeScreenOptions( options: AppNativeStackNavigationOptions | undefined,): NativeStackNavigationOptions | undefined { if (!options) { return options; } const normalized = { ...options } as NativeStackNavigationOptions & { unstable_navigationItemStyle?: unknown; unstable_headerCenterItems?: unknown; unstable_headerSubtitle?: unknown; unstable_headerToolbarItems?: unknown; }; if (normalized.headerTintColor !== undefined) { normalized.headerTintColor = String(normalized.headerTintColor); } return normalized as NativeStackNavigationOptions;} function optionsSignature(value: unknown, seen = new WeakSet<object>()): string { if (value === null) return "null"; switch (typeof value) { case "boolean": case "number": case "string": return JSON.stringify(value); case "undefined": return "undefined"; case "function": // Header factories are frequently recreated inline. Their source is // stable across equivalent renders, while a reference comparison would // make navigation.setOptions re-enter the navigator indefinitely. return `function:${Function.prototype.toString.call(value)}`; case "symbol": return `symbol:${String(value)}`; case "bigint": return `bigint:${String(value)}`; case "object": { const object = value as object; if (seen.has(object)) return "[circular]"; seen.add(object); if (Array.isArray(value)) { return `[${value.map((entry) => optionsSignature(entry, seen)).join(",")}]`; } // React refs carry mutable native instances that must not make static // screen options appear different after every render. if ("current" in object) return "[ref]"; return `{${Object.keys(value as Record<string, unknown>) .sort() .map( (key) => `${JSON.stringify(key)}:${optionsSignature((value as Record<string, unknown>)[key], seen)}`, ) .join(",")}}`; } } return String(value);} function stabilizeOptionFunctions( value: unknown, path: string, latestFunctions: Map<string, (...args: unknown[]) => unknown>, wrappers: Map<string, (...args: unknown[]) => unknown>, seen = new WeakSet<object>(),): unknown { if (typeof value === "function") { latestFunctions.set(path, value as (...args: unknown[]) => unknown); let wrapper = wrappers.get(path); if (!wrapper) { wrapper = (...args: unknown[]) => { return latestFunctions.get(path)?.(...args); }; wrappers.set(path, wrapper); } return wrapper; } if (Array.isArray(value)) { if (seen.has(value)) return value; seen.add(value); return value.map((entry, index) => stabilizeOptionFunctions(entry, `${path}[${index}]`, latestFunctions, wrappers, seen), ); } if (value !== null && typeof value === "object") { if (seen.has(value) || "current" in value) return value; seen.add(value); return Object.fromEntries( Object.entries(value as Record<string, unknown>).map(([key, entry]) => [ key, stabilizeOptionFunctions(entry, `${path}.${key}`, latestFunctions, wrappers, seen), ]), ); } return value;} export function NativeStackScreenOptions(props: { readonly options?: AppNativeStackNavigationOptions; /** * Causes dynamic native header factories to be reapplied when their closed-over * menu content changes. Factory functions are intentionally stabilized, so * their source alone cannot capture a menu that was initially empty while * asynchronous data was loading. */ readonly optionsVersion?: unknown; readonly listeners?: Record<string, (event: never) => void>; readonly name?: string;}) { const navigation = useNativeStackNavigation(); const lastAppliedOptionsSignatureRef = useRef<string | undefined>(undefined); const latestOptionFunctionsRef = useRef(new Map<string, (...args: unknown[]) => unknown>()); const optionFunctionWrappersRef = useRef(new Map<string, (...args: unknown[]) => unknown>()); const normalizedOptions = useMemo(() => normalizeScreenOptions(props.options), [props.options]); const stableOptions = normalizedOptions ? (stabilizeOptionFunctions( normalizedOptions, "options", latestOptionFunctionsRef.current, optionFunctionWrappersRef.current, ) as NativeStackNavigationOptions) : undefined; useLayoutEffect(() => { if (!navigation || !stableOptions) { return; } const signature = optionsSignature([stableOptions, props.optionsVersion]); // Avoid re-entering navigation state when semantically equal options are // reapplied every layout (common when callers pass unstable object literals). if (lastAppliedOptionsSignatureRef.current === signature) { return; } lastAppliedOptionsSignatureRef.current = signature; navigation.setOptions(stableOptions); }, [navigation, props.optionsVersion, stableOptions]); useEffect(() => { if (!navigation || !props.listeners) { return; } const subscriptions = Object.entries(props.listeners).map(([eventName, listener]) => navigation.addListener(eventName as never, listener as never), ); return () => { for (const unsubscribe of subscriptions) { unsubscribe(); } }; }, [navigation, props.listeners]); return null;} function labelFromChildren(children: ReactNode): string { const parts: string[] = []; Children.forEach(children, (child) => { if (typeof child === "string" || typeof child === "number") { parts.push(String(child)); } else if (isValidElement<{ children?: ReactNode }>(child)) { parts.push(labelFromChildren(child.props.children)); } }); return parts.join("");} type NativeStackHeaderIcon = NonNullable< Extract<NativeStackHeaderItem, { type: "button" }>["icon"]>;type NativeStackOptionsWithToolbar = NativeStackNavigationOptions & { unstable_headerToolbarItems?: () => NativeStackHeaderItem[];}; function iconFromProp(icon: unknown): NativeStackHeaderIcon | undefined { if (typeof icon !== "string") { return undefined; } return { type: "sfSymbol", name: icon as never };} type ToolbarElementProps = Record<string, unknown> & { readonly children?: ReactNode }; function elementTypeName(element: ReactElement): string | undefined { const type = element.type; if (typeof type === "function") { return (type as { displayName?: string; name?: string }).displayName ?? type.name; } return undefined;} function convertMenuAction( element: ReactElement<ToolbarElementProps>,): NativeStackHeaderItemMenu["menu"]["items"][number] | null { const typeName = elementTypeName(element); if (typeName === "NativeHeaderToolbarMenuAction") { const label = labelFromChildren(element.props.children); return { type: "action", label, description: typeof element.props.subtitle === "string" ? element.props.subtitle : undefined, disabled: Boolean(element.props.disabled), icon: iconFromProp(element.props.icon), onPress: typeof element.props.onPress === "function" ? (element.props.onPress as () => void) : () => undefined, state: element.props.isOn === true ? "on" : undefined, destructive: Boolean(element.props.destructive), discoverabilityLabel: typeof element.props.discoverabilityLabel === "string" ? element.props.discoverabilityLabel : undefined, }; } if (typeName === "NativeHeaderToolbarMenu") { return { type: "submenu", label: typeof element.props.title === "string" ? element.props.title : labelFromChildren(element.props.children), icon: iconFromProp(element.props.icon), inline: Boolean(element.props.inline), items: collectMenuItems(element.props.children), }; } return null;} function collectMenuItems(children: ReactNode): NativeStackHeaderItemMenu["menu"]["items"] { const items: NativeStackHeaderItemMenu["menu"]["items"] = []; Children.forEach(children, (child) => { if (!isValidElement<ToolbarElementProps>(child)) { return; } const item = convertMenuAction(child); if (item) { items.push(item); return; } items.push(...collectMenuItems(child.props.children)); }); return items;} function convertToolbarChild(child: ReactNode): NativeStackHeaderItem | null { if (!isValidElement<ToolbarElementProps>(child)) { return null; } const typeName = elementTypeName(child); if (typeName === "NativeHeaderToolbarButton") { return { type: "button", label: typeof child.props.label === "string" ? child.props.label : "", accessibilityLabel: typeof child.props.accessibilityLabel === "string" ? child.props.accessibilityLabel : undefined, disabled: Boolean(child.props.disabled), icon: iconFromProp(child.props.icon), onPress: typeof child.props.onPress === "function" ? (child.props.onPress as () => void) : () => undefined, sharesBackground: !child.props.separateBackground, tintColor: child.props.tintColor as ColorValue | undefined, variant: "plain", }; } if (typeName === "NativeHeaderToolbarMenu") { return { type: "menu", label: typeof child.props.title === "string" ? child.props.title : "", accessibilityLabel: typeof child.props.accessibilityLabel === "string" ? child.props.accessibilityLabel : undefined, disabled: Boolean(child.props.disabled), icon: iconFromProp(child.props.icon), menu: { title: typeof child.props.title === "string" ? child.props.title : undefined, items: collectMenuItems(child.props.children), }, sharesBackground: !child.props.separateBackground, tintColor: child.props.tintColor as ColorValue | undefined, variant: "plain", }; } if (typeName === "NativeHeaderToolbarSpacer") { return { type: "spacing", spacing: typeof child.props.width === "number" ? child.props.width : 8, flexible: Boolean(child.props.flexible), } as NativeStackHeaderItem; } return null;} function collectToolbarItems(children: ReactNode): NativeStackHeaderItem[] { const items: NativeStackHeaderItem[] = []; Children.forEach(children, (child) => { const item = convertToolbarChild(child); if (item) { if (item.type === "spacing") { // Native inserts spacing items at `index`, treating a missing index // as 0 — which would move the spacer in front of earlier siblings. (item as { index?: number }).index = items.length; } items.push(item); } }); return items;} function NativeHeaderToolbarRoot(props: { readonly placement?: "left" | "right" | "bottom"; readonly children?: ReactNode;}) { const navigation = useNativeStackNavigation(); const items = useMemo(() => collectToolbarItems(props.children), [props.children]); // Swap toolbar owners before paint so split and compact headers cannot clear each other. useLayoutEffect(() => { if (!navigation) { return; } if (props.placement === "bottom") { navigation.setOptions({ unstable_headerToolbarItems: () => items, } as NativeStackOptionsWithToolbar); return () => { navigation.setOptions({ unstable_headerToolbarItems: () => [], } as NativeStackOptionsWithToolbar); }; } if (props.placement === "left") { navigation.setOptions({ unstable_headerLeftItems: () => items }); return () => { navigation.setOptions({ unstable_headerLeftItems: () => [] }); }; } navigation.setOptions({ unstable_headerRightItems: () => items }); return () => { navigation.setOptions({ unstable_headerRightItems: () => [] }); }; }, [items, navigation, props.placement]); return null;} function NativeHeaderToolbarButton(_props: { readonly accessibilityLabel?: string; readonly disabled?: boolean; readonly icon?: string; readonly label?: string; readonly onPress?: () => void; readonly separateBackground?: boolean; readonly tintColor?: ColorValue;}) { return null;}NativeHeaderToolbarButton.displayName = "NativeHeaderToolbarButton"; function NativeHeaderToolbarMenu(_props: { readonly accessibilityLabel?: string; readonly children?: ReactNode; readonly disabled?: boolean; readonly icon?: string; readonly inline?: boolean; readonly separateBackground?: boolean; readonly tintColor?: ColorValue; readonly title?: string;}) { return null;}NativeHeaderToolbarMenu.displayName = "NativeHeaderToolbarMenu"; function NativeHeaderToolbarMenuAction(_props: { readonly children?: ReactNode; readonly destructive?: boolean; readonly disabled?: boolean; readonly discoverabilityLabel?: string; readonly icon?: string; readonly isOn?: boolean; readonly onPress?: () => void; readonly subtitle?: string;}) { return null;}NativeHeaderToolbarMenuAction.displayName = "NativeHeaderToolbarMenuAction"; function NativeHeaderToolbarLabel(_props: { readonly children?: ReactNode }) { return null;}NativeHeaderToolbarLabel.displayName = "NativeHeaderToolbarLabel"; function NativeHeaderToolbarSpacer(_props: { readonly flexible?: boolean; readonly sharesBackground?: boolean; readonly width?: number;}) { return null;}NativeHeaderToolbarSpacer.displayName = "NativeHeaderToolbarSpacer"; function NativeHeaderToolbarSearchBarSlot() { return null;}NativeHeaderToolbarSearchBarSlot.displayName = "NativeHeaderToolbarSearchBarSlot"; export const NativeHeaderToolbar = Object.assign(NativeHeaderToolbarRoot, { Button: NativeHeaderToolbarButton, Label: NativeHeaderToolbarLabel, Menu: Object.assign(NativeHeaderToolbarMenu, { Action: NativeHeaderToolbarMenuAction, }), MenuAction: NativeHeaderToolbarMenuAction, SearchBarSlot: NativeHeaderToolbarSearchBarSlot, Spacer: NativeHeaderToolbarSpacer,});
import { useNavigation, type ParamListBase } from "@react-navigation/native";
import type {
NativeStackHeaderItem,
NativeStackHeaderItemMenu,
NativeStackNavigationOptions,
NativeStackNavigationProp,
} from "@react-navigation/native-stack";
import {
Children,
isValidElement,
useEffect,
useLayoutEffect,
useMemo,
useRef,
type ReactElement,
type ReactNode,
} from "react";
import type { ColorValue } from "react-native";
export {
nativeHeaderScrollEdgeEffects,
nativeTopScrollEdgeEffect,
type NativeHeaderScrollEdgeEffects,
type NativeTopScrollEdgeEffect,
} from "./scrollEdgeEffects";
export type AppNativeStackNavigationOptions = Omit<
NativeStackNavigationOptions,
"headerTintColor" | "unstable_headerLeftItems" | "unstable_headerRightItems"
> & {
readonly headerTintColor?: string | ColorValue;
readonly unstable_headerCenterItems?: unknown;
readonly unstable_headerLeftItems?: unknown;
readonly unstable_headerRightItems?: unknown;
readonly unstable_headerSubtitle?: unknown;
readonly unstable_headerToolbarItems?: unknown;
readonly unstable_navigationItemStyle?: unknown;
};
function useNativeStackNavigation(): NativeStackNavigationProp<ParamListBase> | null {
return useNavigation<NativeStackNavigationProp<ParamListBase>>();
}
function normalizeScreenOptions(
options: AppNativeStackNavigationOptions | undefined,
): NativeStackNavigationOptions | undefined {
if (!options) {
return options;
}
const normalized = { ...options } as NativeStackNavigationOptions & {
unstable_navigationItemStyle?: unknown;
unstable_headerCenterItems?: unknown;
unstable_headerSubtitle?: unknown;
unstable_headerToolbarItems?: unknown;
};
if (normalized.headerTintColor !== undefined) {
normalized.headerTintColor = String(normalized.headerTintColor);
}
return normalized as NativeStackNavigationOptions;
}
function optionsSignature(value: unknown, seen = new WeakSet<object>()): string {
if (value === null) return "null";
switch (typeof value) {
case "boolean":
case "number":
case "string":
return JSON.stringify(value);
case "undefined":
return "undefined";
case "function":
// Header factories are frequently recreated inline. Their source is
// stable across equivalent renders, while a reference comparison would
// make navigation.setOptions re-enter the navigator indefinitely.
return `function:${Function.prototype.toString.call(value)}`;
case "symbol":
return `symbol:${String(value)}`;
case "bigint":
return `bigint:${String(value)}`;
case "object": {
const object = value as object;
if (seen.has(object)) return "[circular]";
seen.add(object);
if (Array.isArray(value)) {
return `[${value.map((entry) => optionsSignature(entry, seen)).join(",")}]`;
}
// React refs carry mutable native instances that must not make static
// screen options appear different after every render.
if ("current" in object) return "[ref]";
return `{${Object.keys(value as Record<string, unknown>)
.sort()
.map(
(key) =>
`${JSON.stringify(key)}:${optionsSignature((value as Record<string, unknown>)[key], seen)}`,
)
.join(",")}}`;
}
}
return String(value);
}
function stabilizeOptionFunctions(
value: unknown,
path: string,
latestFunctions: Map<string, (...args: unknown[]) => unknown>,
wrappers: Map<string, (...args: unknown[]) => unknown>,
seen = new WeakSet<object>(),
): unknown {
if (typeof value === "function") {
latestFunctions.set(path, value as (...args: unknown[]) => unknown);
let wrapper = wrappers.get(path);
if (!wrapper) {
wrapper = (...args: unknown[]) => {
return latestFunctions.get(path)?.(...args);
};
wrappers.set(path, wrapper);
}
return wrapper;
}
if (Array.isArray(value)) {
if (seen.has(value)) return value;
seen.add(value);
return value.map((entry, index) =>
stabilizeOptionFunctions(entry, `${path}[${index}]`, latestFunctions, wrappers, seen),
);
}
if (value !== null && typeof value === "object") {
if (seen.has(value) || "current" in value) return value;
seen.add(value);
return Object.fromEntries(
Object.entries(value as Record<string, unknown>).map(([key, entry]) => [
key,
stabilizeOptionFunctions(entry, `${path}.${key}`, latestFunctions, wrappers, seen),
]),
);
}
return value;
}
export function NativeStackScreenOptions(props: {
readonly options?: AppNativeStackNavigationOptions;
/**
* Causes dynamic native header factories to be reapplied when their closed-over
* menu content changes. Factory functions are intentionally stabilized, so
* their source alone cannot capture a menu that was initially empty while
* asynchronous data was loading.
*/
readonly optionsVersion?: unknown;
readonly listeners?: Record<string, (event: never) => void>;
readonly name?: string;
}) {
const navigation = useNativeStackNavigation();
const lastAppliedOptionsSignatureRef = useRef<string | undefined>(undefined);
const latestOptionFunctionsRef = useRef(new Map<string, (...args: unknown[]) => unknown>());
const optionFunctionWrappersRef = useRef(new Map<string, (...args: unknown[]) => unknown>());
const normalizedOptions = useMemo(() => normalizeScreenOptions(props.options), [props.options]);
const stableOptions = normalizedOptions
? (stabilizeOptionFunctions(
normalizedOptions,
"options",
latestOptionFunctionsRef.current,
optionFunctionWrappersRef.current,
) as NativeStackNavigationOptions)
: undefined;
useLayoutEffect(() => {
if (!navigation || !stableOptions) {
return;
}
const signature = optionsSignature([stableOptions, props.optionsVersion]);
// Avoid re-entering navigation state when semantically equal options are
// reapplied every layout (common when callers pass unstable object literals).
if (lastAppliedOptionsSignatureRef.current === signature) {
return;
}
lastAppliedOptionsSignatureRef.current = signature;
navigation.setOptions(stableOptions);
}, [navigation, props.optionsVersion, stableOptions]);
useEffect(() => {
if (!navigation || !props.listeners) {
return;
}
const subscriptions = Object.entries(props.listeners).map(([eventName, listener]) =>
navigation.addListener(eventName as never, listener as never),
);
return () => {
for (const unsubscribe of subscriptions) {
unsubscribe();
}
};
}, [navigation, props.listeners]);
return null;
}
function labelFromChildren(children: ReactNode): string {
const parts: string[] = [];
Children.forEach(children, (child) => {
if (typeof child === "string" || typeof child === "number") {
parts.push(String(child));
} else if (isValidElement<{ children?: ReactNode }>(child)) {
parts.push(labelFromChildren(child.props.children));
}
});
return parts.join("");
}
type NativeStackHeaderIcon = NonNullable<
Extract<NativeStackHeaderItem, { type: "button" }>["icon"]
>;
type NativeStackOptionsWithToolbar = NativeStackNavigationOptions & {
unstable_headerToolbarItems?: () => NativeStackHeaderItem[];
};
function iconFromProp(icon: unknown): NativeStackHeaderIcon | undefined {
if (typeof icon !== "string") {
return undefined;
}
return { type: "sfSymbol", name: icon as never };
}
type ToolbarElementProps = Record<string, unknown> & { readonly children?: ReactNode };
function elementTypeName(element: ReactElement): string | undefined {
const type = element.type;
if (typeof type === "function") {
return (type as { displayName?: string; name?: string }).displayName ?? type.name;
}
return undefined;
}
function convertMenuAction(
element: ReactElement<ToolbarElementProps>,
): NativeStackHeaderItemMenu["menu"]["items"][number] | null {
const typeName = elementTypeName(element);
if (typeName === "NativeHeaderToolbarMenuAction") {
const label = labelFromChildren(element.props.children);
return {
type: "action",
label,
description: typeof element.props.subtitle === "string" ? element.props.subtitle : undefined,
disabled: Boolean(element.props.disabled),
icon: iconFromProp(element.props.icon),
onPress:
typeof element.props.onPress === "function"
? (element.props.onPress as () => void)
: () => undefined,
state: element.props.isOn === true ? "on" : undefined,
destructive: Boolean(element.props.destructive),
discoverabilityLabel:
typeof element.props.discoverabilityLabel === "string"
? element.props.discoverabilityLabel
: undefined,
};
}
if (typeName === "NativeHeaderToolbarMenu") {
return {
type: "submenu",
label:
typeof element.props.title === "string"
? element.props.title
: labelFromChildren(element.props.children),
icon: iconFromProp(element.props.icon),
inline: Boolean(element.props.inline),
items: collectMenuItems(element.props.children),
};
}
return null;
}
function collectMenuItems(children: ReactNode): NativeStackHeaderItemMenu["menu"]["items"] {
const items: NativeStackHeaderItemMenu["menu"]["items"] = [];
Children.forEach(children, (child) => {
if (!isValidElement<ToolbarElementProps>(child)) {
return;
}
const item = convertMenuAction(child);
if (item) {
items.push(item);
return;
}
items.push(...collectMenuItems(child.props.children));
});
return items;
}
function convertToolbarChild(child: ReactNode): NativeStackHeaderItem | null {
if (!isValidElement<ToolbarElementProps>(child)) {
return null;
}
const typeName = elementTypeName(child);
if (typeName === "NativeHeaderToolbarButton") {
return {
type: "button",
label: typeof child.props.label === "string" ? child.props.label : "",
accessibilityLabel:
typeof child.props.accessibilityLabel === "string"
? child.props.accessibilityLabel
: undefined,
disabled: Boolean(child.props.disabled),
icon: iconFromProp(child.props.icon),
onPress:
typeof child.props.onPress === "function"
? (child.props.onPress as () => void)
: () => undefined,
sharesBackground: !child.props.separateBackground,
tintColor: child.props.tintColor as ColorValue | undefined,
variant: "plain",
};
}
if (typeName === "NativeHeaderToolbarMenu") {
return {
type: "menu",
label: typeof child.props.title === "string" ? child.props.title : "",
accessibilityLabel:
typeof child.props.accessibilityLabel === "string"
? child.props.accessibilityLabel
: undefined,
disabled: Boolean(child.props.disabled),
icon: iconFromProp(child.props.icon),
menu: {
title: typeof child.props.title === "string" ? child.props.title : undefined,
items: collectMenuItems(child.props.children),
},
sharesBackground: !child.props.separateBackground,
tintColor: child.props.tintColor as ColorValue | undefined,
variant: "plain",
};
}
if (typeName === "NativeHeaderToolbarSpacer") {
return {
type: "spacing",
spacing: typeof child.props.width === "number" ? child.props.width : 8,
flexible: Boolean(child.props.flexible),
} as NativeStackHeaderItem;
}
return null;
}
function collectToolbarItems(children: ReactNode): NativeStackHeaderItem[] {
const items: NativeStackHeaderItem[] = [];
Children.forEach(children, (child) => {
const item = convertToolbarChild(child);
if (item) {
if (item.type === "spacing") {
// Native inserts spacing items at `index`, treating a missing index
// as 0 — which would move the spacer in front of earlier siblings.
(item as { index?: number }).index = items.length;
}
items.push(item);
}
});
return items;
}
function NativeHeaderToolbarRoot(props: {
readonly placement?: "left" | "right" | "bottom";
readonly children?: ReactNode;
}) {
const navigation = useNativeStackNavigation();
const items = useMemo(() => collectToolbarItems(props.children), [props.children]);
// Swap toolbar owners before paint so split and compact headers cannot clear each other.
useLayoutEffect(() => {
if (!navigation) {
return;
}
if (props.placement === "bottom") {
navigation.setOptions({
unstable_headerToolbarItems: () => items,
} as NativeStackOptionsWithToolbar);
return () => {
navigation.setOptions({
unstable_headerToolbarItems: () => [],
} as NativeStackOptionsWithToolbar);
};
}
if (props.placement === "left") {
navigation.setOptions({ unstable_headerLeftItems: () => items });
return () => {
navigation.setOptions({ unstable_headerLeftItems: () => [] });
};
}
navigation.setOptions({ unstable_headerRightItems: () => items });
return () => {
navigation.setOptions({ unstable_headerRightItems: () => [] });
};
}, [items, navigation, props.placement]);
return null;
}
function NativeHeaderToolbarButton(_props: {
readonly accessibilityLabel?: string;
readonly disabled?: boolean;
readonly icon?: string;
readonly label?: string;
readonly onPress?: () => void;
readonly separateBackground?: boolean;
readonly tintColor?: ColorValue;
}) {
return null;
}
NativeHeaderToolbarButton.displayName = "NativeHeaderToolbarButton";
function NativeHeaderToolbarMenu(_props: {
readonly accessibilityLabel?: string;
readonly children?: ReactNode;
readonly disabled?: boolean;
readonly icon?: string;
readonly inline?: boolean;
readonly separateBackground?: boolean;
readonly tintColor?: ColorValue;
readonly title?: string;
}) {
return null;
}
NativeHeaderToolbarMenu.displayName = "NativeHeaderToolbarMenu";
function NativeHeaderToolbarMenuAction(_props: {
readonly children?: ReactNode;
readonly destructive?: boolean;
readonly disabled?: boolean;
readonly discoverabilityLabel?: string;
readonly icon?: string;
readonly isOn?: boolean;
readonly onPress?: () => void;
readonly subtitle?: string;
}) {
return null;
}
NativeHeaderToolbarMenuAction.displayName = "NativeHeaderToolbarMenuAction";
function NativeHeaderToolbarLabel(_props: { readonly children?: ReactNode }) {
return null;
}
NativeHeaderToolbarLabel.displayName = "NativeHeaderToolbarLabel";
function NativeHeaderToolbarSpacer(_props: {
readonly flexible?: boolean;
readonly sharesBackground?: boolean;
readonly width?: number;
}) {
return null;
}
NativeHeaderToolbarSpacer.displayName = "NativeHeaderToolbarSpacer";
function NativeHeaderToolbarSearchBarSlot() {
return null;
}
NativeHeaderToolbarSearchBarSlot.displayName = "NativeHeaderToolbarSearchBarSlot";
export const NativeHeaderToolbar = Object.assign(NativeHeaderToolbarRoot, {
Button: NativeHeaderToolbarButton,
Label: NativeHeaderToolbarLabel,
Menu: Object.assign(NativeHeaderToolbarMenu, {
Action: NativeHeaderToolbarMenuAction,
}),
MenuAction: NativeHeaderToolbarMenuAction,
SearchBarSlot: NativeHeaderToolbarSearchBarSlot,
Spacer: NativeHeaderToolbarSpacer,
});