Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 'use client' import dynamic from 'next/dynamic' import type React from 'react' import { useContext } from 'react' import { AppNavBar } from './AppNavBar' import type { RosterWarning } from './nav/GameContextNav' import type { PlayerBadge } from './nav/types' import { PreviewModeContext } from '@/contexts/PreviewModeContext' // Lazy load GameContextNav - it pulls in game mode hooks and arcade room components // Only loaded when navTitle is provided (arcade games, not practice pages) const GameContextNav = dynamic(() => import('./nav/GameContextNav').then((m) => m.GameContextNav), { ssr: false, }) // Lazy load dialogs and notifications - these are rarely visible initially const PlayerConfigDialog = dynamic( () => import('./nav/PlayerConfigDialog').then((m) => m.PlayerConfigDialog), { ssr: false } ) const ModerationNotifications = dynamic( () => import('./nav/ModerationNotifications').then((m) => m.ModerationNotifications), { ssr: false } ) // Lazy load the game nav content component - this contains the hooks // that pull in GameModeContext and useRoomData (100KB+ of arcade dependencies) const GameNavContent = dynamic(() => import('./nav/GameNavContent').then((m) => m.GameNavContent), { ssr: false, }) interface PageWithNavProps { navTitle?: string navEmoji?: string gameName?: 'matching' | 'memory-quiz' | 'complement-race' // Internal game name for API emphasizePlayerSelection?: boolean disableFullscreenSelection?: boolean // Disable "Select Your Champions" overlay onExitSession?: () => void onSetup?: () => void onNewGame?: () => void children: React.ReactNode // Custom nav slot content (for non-game pages like vision-training) navSlot?: React.ReactNode // Game state for turn indicator currentPlayerId?: string playerScores?: Record<string, number> playerStreaks?: Record<string, number> playerBadges?: Record<string, PlayerBadge> // Game-specific roster warnings rosterWarning?: RosterWarning // Side assignments (for 2-player games like Rithmomachia) whitePlayerId?: string | null blackPlayerId?: string | null onAssignWhitePlayer?: (playerId: string | null) => void onAssignBlackPlayer?: (playerId: string | null) => void // Game phase (for showing spectating vs assign) gamePhase?: 'setup' | 'playing' | 'results' // Custom mode display (overrides player-count-based mode) customModeLabel?: string customModeEmoji?: string customModeColor?: string } /** * PageWithNav - Lightweight wrapper that conditionally loads game features * * For non-game pages (no navTitle): renders just AppNavBar + children * For game pages (with navTitle): lazy-loads GameNavContent which contains * the hooks for GameModeContext, useRoomData, etc. * * This reduces the practice session report page bundle by ~100KB by not * loading arcade game dependencies on non-game pages. */ export function PageWithNav({ navTitle, navEmoji, gameName, emphasizePlayerSelection = false, disableFullscreenSelection = false, onExitSession, onSetup, onNewGame, children, currentPlayerId, playerScores, playerStreaks, playerBadges, rosterWarning, whitePlayerId, blackPlayerId, onAssignWhitePlayer, onAssignBlackPlayer, gamePhase, customModeLabel, customModeEmoji, customModeColor, navSlot, }: PageWithNavProps) { // In preview mode, render just the children without navigation const previewMode = useContext(PreviewModeContext) if (previewMode?.isPreview) { return <>{children}</> } // For game pages (with navTitle), render the full GameNavContent // which includes all game-related hooks and components if (navTitle) { return ( <GameNavContent navTitle={navTitle} navEmoji={navEmoji} gameName={gameName} emphasizePlayerSelection={emphasizePlayerSelection} disableFullscreenSelection={disableFullscreenSelection} onExitSession={onExitSession} onSetup={onSetup} onNewGame={onNewGame} currentPlayerId={currentPlayerId} playerScores={playerScores} playerStreaks={playerStreaks} playerBadges={playerBadges} rosterWarning={rosterWarning} whitePlayerId={whitePlayerId} blackPlayerId={blackPlayerId} onAssignWhitePlayer={onAssignWhitePlayer} onAssignBlackPlayer={onAssignBlackPlayer} gamePhase={gamePhase} customModeLabel={customModeLabel} customModeEmoji={customModeEmoji} customModeColor={customModeColor} > {children} </GameNavContent> ) } // Standard path (no navTitle): AppNavBar + children with automatic nav offset. // // The wrapper div applies paddingTop to compensate for the fixed-position AppNavBar. // Pages should NOT add their own nav-height padding — it's handled here. // // The navTitle path above does NOT get this wrapper because those pages use // StandardGameLayout (which dynamically measures the nav height) or the // `with-fixed-nav` CSS class to handle the offset themselves. return ( <> <AppNavBar navSlot={navSlot ?? null} /> <div style={{ paddingTop: 'var(--app-nav-height)' }}>{children}</div> </> ) } |