/** * Pluggable navigation surface used by entropy-ui's auth screens. * * The shared screens used to call `useRouter()` from `expo-router` * directly, which made them un-usable in non-Expo bundles (the browser- * extension popup, future Electron / web targets without the Expo * router runtime). Routing through this context lets every consumer * supply its own router implementation — expo-router on mobile / web, * a stack-reducer adapter inside the extension popup, etc. * * When no consumer is wrapped in ``, `useNavigation` * falls back to `expo-router`'s `useRouter()`, so the existing mobile * client keeps working unchanged. */ import React, { createContext, useContext, useMemo } from "react"; import { type Href, useRouter } from "expo-router"; export interface RouteTarget { pathname: Href | string; params?: Record; } export interface AppRouter { push(target: Href | string | RouteTarget): void; replace(target: Href | string | RouteTarget): void; back(): void; } const NavigationContext = createContext(null); export interface NavigationProviderProps { router: AppRouter; children: React.ReactNode; } /** * Override the default expo-router-backed navigation for everything * rendered below this provider. Apps without expo-router supply this * with their own stack-reducer adapter; everyone else can ignore it. */ export function NavigationProvider({ router, children }: NavigationProviderProps) { const value = useMemo(() => router, [router]); return {children}; } /** * Hook used by entropy-ui screens to navigate. Returns the override * router supplied by `NavigationProvider` when present; otherwise * delegates to `expo-router`'s `useRouter()`. */ export function useNavigation(): AppRouter { const override = useContext(NavigationContext); const expoRouter = useRouter(); // Memoize the expo-router adapter so consumers using the returned // value as a `useEffect`/`useCallback` dep don't see a fresh // reference every render. `useRouter()` itself returns a stable // object across renders, so an empty-dep memo would also work, but // depending on `expoRouter` is defensive against future changes. const expoAdapter = useMemo( () => ({ push(target) { expoRouter.push(target as Href); }, replace(target) { expoRouter.replace(target as Href); }, back() { expoRouter.back(); }, }), [expoRouter] ); return override ?? expoAdapter; }