feat: add Setup button to exit arcade sessions

- Add onExitSession prop to PageWithNav and GameContextNav
- Display Setup button (⚙️) in nav bar during games
- Call exitSession() and reload page to return to setup
- Provides consistent exit UI across all arcade games

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-06 11:04:22 -05:00
parent abc2ea50d0
commit ae1318e8bf
3 changed files with 111 additions and 2 deletions

View File

@@ -0,0 +1,70 @@
'use client'
import { useEffect, useRef } from 'react'
import { useArcadeMemoryPairs } from '../context/ArcadeMemoryPairsContext'
import { useFullscreen } from '../../../../contexts/FullscreenContext'
import { SetupPhase } from './SetupPhase'
import { GamePhase } from './GamePhase'
import { ResultsPhase } from './ResultsPhase'
import { StandardGameLayout } from '../../../../components/StandardGameLayout'
import { PageWithNav } from '@/components/PageWithNav'
import { css } from '../../../../../styled-system/css'
export function MemoryPairsGame() {
const { state, exitSession } = useArcadeMemoryPairs()
const { setFullscreenElement } = useFullscreen()
const gameRef = useRef<HTMLDivElement>(null)
useEffect(() => {
// Register this component's main div as the fullscreen element
if (gameRef.current) {
console.log('🎯 MemoryPairsGame: Registering fullscreen element:', gameRef.current)
setFullscreenElement(gameRef.current)
}
}, [setFullscreenElement])
return (
<PageWithNav
navTitle="Memory Pairs"
navEmoji="🧩"
emphasizeGameContext={state.gamePhase === 'setup'}
onExitSession={() => {
exitSession()
window.location.reload()
}}
>
<StandardGameLayout>
<div
ref={gameRef}
className={css({
flex: 1,
padding: { base: '12px', sm: '16px', md: '20px' },
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative',
overflow: 'auto'
})}>
{/* Note: Fullscreen restore prompt removed - client-side navigation preserves fullscreen */}
<main className={css({
width: '100%',
maxWidth: '1200px',
background: 'rgba(255,255,255,0.95)',
borderRadius: { base: '12px', md: '20px' },
padding: { base: '12px', sm: '16px', md: '24px', lg: '32px' },
boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden'
})}>
{state.gamePhase === 'setup' && <SetupPhase />}
{state.gamePhase === 'playing' && <GamePhase />}
{state.gamePhase === 'results' && <ResultsPhase />}
</main>
</div>
</StandardGameLayout>
</PageWithNav>
)
}

View File

@@ -10,10 +10,11 @@ interface PageWithNavProps {
navTitle?: string
navEmoji?: string
emphasizeGameContext?: boolean
onExitSession?: () => void
children: React.ReactNode
}
export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false, children }: PageWithNavProps) {
export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false, onExitSession, children }: PageWithNavProps) {
const { players, activePlayers, setActive, activePlayerCount } = useGameMode()
const [mounted, setMounted] = React.useState(false)
const [configurePlayerId, setConfigurePlayerId] = React.useState<string | null>(null)
@@ -68,6 +69,7 @@ export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false,
onAddPlayer={handleAddPlayer}
onRemovePlayer={handleRemovePlayer}
onConfigurePlayer={handleConfigurePlayer}
onExitSession={onExitSession}
/>
) : null

View File

@@ -23,6 +23,7 @@ interface GameContextNavProps {
onAddPlayer: (playerId: string) => void
onRemovePlayer: (playerId: string) => void
onConfigurePlayer: (playerId: string) => void
onExitSession?: () => void
}
export function GameContextNav({
@@ -35,7 +36,8 @@ export function GameContextNav({
showFullscreenSelection,
onAddPlayer,
onRemovePlayer,
onConfigurePlayer
onConfigurePlayer,
onExitSession
}: GameContextNavProps) {
const [isTransitioning, setIsTransitioning] = React.useState(false)
const [layoutMode, setLayoutMode] = React.useState<'column' | 'row'>(showFullscreenSelection ? 'column' : 'row')
@@ -93,6 +95,41 @@ export function GameContextNav({
showFullscreenSelection={showFullscreenSelection}
/>
{/* Exit Session Button */}
{onExitSession && !showFullscreenSelection && (
<button
onClick={onExitSession}
style={{
background: 'linear-gradient(135deg, #dfe6e9, #b2bec3)',
border: 'none',
borderRadius: '8px',
padding: '6px 12px',
fontSize: '13px',
fontWeight: 'bold',
color: 'rgb(51, 51, 51)',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '4px',
transition: 'all 0.2s ease',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'linear-gradient(135deg, #b2bec3, #636e72)'
e.currentTarget.style.transform = 'translateY(-1px)'
e.currentTarget.style.boxShadow = '0 3px 6px rgba(0, 0, 0, 0.15)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'linear-gradient(135deg, #dfe6e9, #b2bec3)'
e.currentTarget.style.transform = 'translateY(0)'
e.currentTarget.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'
}}
>
<span></span>
<span>Setup</span>
</button>
)}
{/* Active Players + Add Button */}
{(activePlayers.length > 0 || (shouldEmphasize && inactivePlayers.length > 0)) && (
<div style={{