All files / web/src/components PageWithNav.tsx

0% Statements 0/152
0% Branches 0/1
0% Functions 0/1
0% Lines 0/152

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>
    </>
  )
}