import { useEffect, useSyncExternalStore, type ReactNode, type FC, } from "react"; /** * External store for the page's header title + actions. We use an external * store (not React context) so that pages can inject ReactNode without * subscribing to the header state themselves — subscribing would trigger a * re-render on every update, which in turn creates new JSX, which updates * the store again, which re-renders… an infinite loop. * * Only
reads the store via useSyncExternalStore; pages only write. */ type Listener = () => void; let currentTitle: ReactNode = null; let currentActions: ReactNode = null; const listeners = new Set(); function notify() { for (const l of listeners) l(); } function subscribe(l: Listener): () => void { listeners.add(l); return () => { listeners.delete(l); }; } /** Consumed only by
— returns the current title. */ export function useHeaderTitle(): ReactNode { return useSyncExternalStore( subscribe, () => currentTitle, () => currentTitle, ); } /** Consumed only by
— returns the current actions slot. */ export function useHeaderActions(): ReactNode { return useSyncExternalStore( subscribe, () => currentActions, () => currentActions, ); } /** * Provider is now a no-op wrapper for backwards compatibility — the state * lives in the module-level store above. Kept as a component so callers of * don't need to change. */ export const HeaderActionsProvider: FC<{ children: ReactNode }> = ({ children, }) => <>{children}; /** Mount a custom title into the app header. Cleans up on unmount. */ export function useSetPageTitle(node: ReactNode) { useEffect(() => { currentTitle = node; notify(); return () => { currentTitle = null; notify(); }; }); } /** Mount ReactNode into the header's actions slot. Cleans up on unmount. */ export function useSetHeaderActions(node: ReactNode) { useEffect(() => { currentActions = node; notify(); return () => { currentActions = null; notify(); }; }); }