39948-vm/frontend/src/context/PageNavigationContext.tsx

164 lines
4.9 KiB
TypeScript

/**
* PageNavigationContext
*
* Context provider for sharing page navigation state with child components.
* Wraps the usePageNavigationState hook to provide state machine access
* throughout the component tree without prop drilling.
*
* Usage:
* ```tsx
* // In parent (constructor.tsx or RuntimePresentation.tsx)
* <PageNavigationProvider preloadCache={preloadOrchestrator} transitionSettings={transitionSettings}>
* <CanvasBackground />
* <Elements />
* </PageNavigationProvider>
*
* // In child components
* const { phase, onBackgroundReady, isSwitching } = usePageNavigationContext();
* ```
*/
import React, { createContext, useContext, ReactNode, useMemo } from 'react';
import {
usePageNavigationState,
UsePageNavigationStateOptions,
UsePageNavigationStateResult,
} from '../hooks/usePageNavigationState';
// ============================================================================
// Context
// ============================================================================
const PageNavigationContext =
createContext<UsePageNavigationStateResult | null>(null);
// ============================================================================
// Provider
// ============================================================================
export interface PageNavigationProviderProps
extends UsePageNavigationStateOptions {
children: ReactNode;
}
/**
* Provider component that wraps usePageNavigationState and exposes it via context.
*
* @example
* ```tsx
* <PageNavigationProvider
* preloadCache={preloadOrchestrator}
* transitionSettings={transitionSettings}
* >
* <CanvasBackground ... />
* <PageElements ... />
* </PageNavigationProvider>
* ```
*/
export function PageNavigationProvider({
children,
...options
}: PageNavigationProviderProps) {
const navState = usePageNavigationState(options);
return (
<PageNavigationContext.Provider value={navState}>
{children}
</PageNavigationContext.Provider>
);
}
// ============================================================================
// Hooks
// ============================================================================
/**
* Hook to access page navigation state from context.
* Must be used within a PageNavigationProvider.
*
* @throws Error if used outside of PageNavigationProvider
*
* @example
* ```tsx
* function CanvasBackground() {
* const { onBackgroundReady, isSwitching, isNewBgReady } = usePageNavigationContext();
*
* return (
* <img
* onLoad={onBackgroundReady}
* style={{ opacity: isNewBgReady ? 1 : 0 }}
* />
* );
* }
* ```
*/
export function usePageNavigationContext(): UsePageNavigationStateResult {
const ctx = useContext(PageNavigationContext);
if (!ctx) {
throw new Error(
'usePageNavigationContext must be used within a PageNavigationProvider',
);
}
return ctx;
}
/**
* Hook to optionally access page navigation state from context.
* Returns null if used outside of PageNavigationProvider.
* Useful for components that may be used both inside and outside the provider.
*
* @example
* ```tsx
* function FlexibleComponent() {
* const navState = usePageNavigationContextOptional();
*
* if (navState) {
* // Inside provider - use navigation state
* return <div>Phase: {navState.phase}</div>;
* }
*
* // Outside provider - render without navigation state
* return <div>Standalone mode</div>;
* }
* ```
*/
export function usePageNavigationContextOptional(): UsePageNavigationStateResult | null {
return useContext(PageNavigationContext);
}
// ============================================================================
// Selector Hook
// ============================================================================
/**
* Hook to select specific parts of the navigation state.
* Helps with performance by allowing components to subscribe to only what they need.
*
* @example
* ```tsx
* // Only re-render when phase changes
* const phase = usePageNavigationSelector(state => state.phase);
*
* // Get multiple values
* const { isSwitching, isNewBgReady } = usePageNavigationSelector(state => ({
* isSwitching: state.isSwitching,
* isNewBgReady: state.isNewBgReady,
* }));
* ```
*/
export function usePageNavigationSelector<T>(
selector: (state: UsePageNavigationStateResult) => T,
): T {
const ctx = usePageNavigationContext();
// Note: This doesn't prevent re-renders on its own since the context value changes.
// For true selector optimization, consider using useSyncExternalStore or a state management library.
// This is primarily for code organization/readability.
return useMemo(() => selector(ctx), [ctx, selector]);
}
// ============================================================================
// Exports
// ============================================================================
export type { UsePageNavigationStateResult } from '../hooks/usePageNavigationState';