import React, { useCallback, useMemo, useState, type ReactNode } from 'react'; import { View, type StyleProp, type ViewStyle } from 'react-native'; import { createThemedStyles, SuperagentThemeContext, syncSuperagentColorScheme, type SuperagentColorScheme, } from '../../theme'; /** * Reusable design-system shell for every Superagent native screen. * * The DS theming layer (theme.ts/tokens.ts + `createThemedStyles`) only resolves * colors correctly when the active color scheme has been published to the * module-level resolver and exposed via context. Rather than re-wire that on each * screen, screens compose these two pieces: * * - `SuperagentThemeProvider` — the theming ROOT. Owns the active scheme, syncs * it during render (so the whole subtree resolves to the right DS scheme), and * exposes it via context. Mount once near the top of a screen tree. * - `SuperagentScreen` — the themed SURFACE. A full-bleed container painted with * the DS canvas token. Nested screens (rendered under an existing provider) * just use this; a standalone screen can pass `colorScheme` (controlled) or * `defaultColorScheme` (uncontrolled) to have it set up the provider too. * * Safe-area insets stay the screen's concern (e.g. SuperagentTopBar pads the * status bar itself), so this surface is intentionally edge-to-edge. */ const shellStyles = createThemedStyles({ // #000000 → surface.canvas (DS): stone-50 in light, neutral-950 in dark. screen: { backgroundColor: '#000000', flex: 1 }, }); interface SuperagentThemeProviderProps { /** * Controlled active scheme: when provided, the host owns it. It's read directly * during render (no stale first paint) and an in-tree `setScheme` only notifies * via `onColorSchemeChange` — it never mutates local state, so context and the * module-level scheme can't diverge from the prop. */ colorScheme?: SuperagentColorScheme; /** Uncontrolled initial scheme (default 'light'); an in-app `setScheme` owns it after. */ defaultColorScheme?: SuperagentColorScheme; /** Notified whenever the scheme changes (in-tree toggle); required for the controlled mode. */ onColorSchemeChange?: (scheme: SuperagentColorScheme) => void; children: ReactNode; } export function SuperagentThemeProvider({ colorScheme, defaultColorScheme, onColorSchemeChange, children, }: SuperagentThemeProviderProps) { const isControlled = colorScheme !== undefined; const [uncontrolledScheme, setUncontrolledScheme] = useState( defaultColorScheme ?? 'light', ); // Controlled → the prop is the source of truth (resolved during render, so the // subtree never paints a stale scheme); uncontrolled → local state. const scheme = isControlled ? colorScheme : uncontrolledScheme; // Sync the module-level scheme during render so themed styles resolve correctly // for the whole subtree rendered after this point. syncSuperagentColorScheme(scheme); const setScheme = useCallback( (next: SuperagentColorScheme) => { // In controlled mode the host owns the value — only notify; never fork local // state (which would let context drift from the prop). if (!isControlled) { setUncontrolledScheme(next); } onColorSchemeChange?.(next); }, [isControlled, onColorSchemeChange], ); const value = useMemo(() => ({ scheme, setScheme }), [scheme, setScheme]); return {children}; } interface SuperagentScreenProps { children: ReactNode; /** Extra surface styles (merged after the DS canvas background). */ style?: StyleProp; /** * Pass `colorScheme` (controlled) or `defaultColorScheme` (uncontrolled) to have * this surface set up its own `SuperagentThemeProvider` (standalone/root screen). * Omit both when rendering inside an existing provider — the surface then * inherits the ambient scheme. */ colorScheme?: SuperagentColorScheme; defaultColorScheme?: SuperagentColorScheme; onColorSchemeChange?: (scheme: SuperagentColorScheme) => void; } // Surface lives in its own component so its themed `shellStyles` read happens // when React renders it — i.e. AFTER an enclosing provider has synced the active // scheme. Reading the proxy inline in `SuperagentScreen` would resolve against // the pre-sync scheme when `SuperagentScreen` also sets up the provider. function ThemedSurface({ style, children }: { style?: StyleProp; children: ReactNode }) { // Plain View (not SafeAreaView): this surface is edge-to-edge by contract, so // safe-area insets are each screen's concern (e.g. SuperagentTopBar pads the // status bar itself). A SafeAreaView here would double-inset such screens. return {children}; } export function SuperagentScreen({ children, style, colorScheme, defaultColorScheme, onColorSchemeChange, }: SuperagentScreenProps) { // Nested usage (no scheme props) inherits the ambient provider. if (colorScheme === undefined && defaultColorScheme === undefined) { return {children}; } return ( {children} ); }