refactor: restructure GameContextNav to 2x2 grid layout

Reorganizes navigation into clearer visual hierarchy:
- Row 1: Title (left) | Mode indicator + Room info (right)
- Row 2: Control buttons (left) | Network players + Your players (right)

Changes:
- Separate fullscreen mode into early return for clarity
- Remove unused state management (isTransitioning, layoutMode, containerWidth)
- Use space-between justification for horizontal alignment
- Group network players with local players (separated from control buttons)

🤖 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-11 07:31:59 -05:00
parent 6b3a440369
commit 9c9270f931

View File

@@ -66,55 +66,21 @@ export function GameContextNav({
networkPlayers = [],
roomInfo,
}: GameContextNavProps) {
const [_isTransitioning, setIsTransitioning] = React.useState(false)
const [layoutMode, setLayoutMode] = React.useState<'column' | 'row'>(
showFullscreenSelection ? 'column' : 'row'
)
const [containerWidth, setContainerWidth] = React.useState<string>(
showFullscreenSelection ? '100%' : 'auto'
)
React.useEffect(() => {
if (showFullscreenSelection) {
// Switching to fullscreen - change layout and width immediately
setLayoutMode('column')
setContainerWidth('100%')
} else {
// Switching away from fullscreen - delay layout change until transition completes
setIsTransitioning(true)
setContainerWidth('auto')
const timer = setTimeout(() => {
setLayoutMode('row')
setIsTransitioning(false)
}, 400) // Match transition duration
return () => clearTimeout(timer)
}
}, [showFullscreenSelection])
return (
<div
style={{
display: 'flex',
flexDirection: layoutMode,
alignItems: showFullscreenSelection ? 'stretch' : 'center',
gap: shouldEmphasize ? '16px' : '12px',
width: containerWidth,
transition: 'gap 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
{/* Header row */}
// 2x2 grid layout for normal mode, column for fullscreen
if (showFullscreenSelection) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: shouldEmphasize ? '16px' : '12px',
justifyContent: showFullscreenSelection ? 'center' : 'flex-start',
width: showFullscreenSelection ? '100%' : 'auto',
gap: '16px',
width: '100%',
}}
>
<h1
style={{
fontSize: showFullscreenSelection ? '32px' : '18px',
fontSize: '32px',
fontWeight: 'bold',
background: 'linear-gradient(135deg, #60a5fa, #a78bfa, #f472b6)',
backgroundClip: 'text',
@@ -133,91 +99,180 @@ export function GameContextNav({
showFullscreenSelection={showFullscreenSelection}
/>
{/* Room Info - show when in arcade session */}
{roomInfo && !showFullscreenSelection && (
<RoomInfo
roomName={roomInfo.roomName}
gameName={roomInfo.gameName}
playerCount={roomInfo.playerCount}
joinCode={roomInfo.joinCode}
shouldEmphasize={shouldEmphasize}
/>
)}
<FullscreenPlayerSelection
inactivePlayers={inactivePlayers}
onSelectPlayer={onAddPlayer}
onConfigurePlayer={onConfigurePlayer}
isVisible={showFullscreenSelection}
/>
{/* Game Control Buttons - only show during active game */}
{!showFullscreenSelection && !canModifyPlayers && (
<GameControlButtons onSetup={onSetup} onNewGame={onNewGame} onQuit={onExitSession} />
)}
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes expandIn {
from {
opacity: 0;
transform: scaleY(0.8);
}
to {
opacity: 1;
transform: scaleY(1);
}
}
`,
}}
/>
</div>
)
}
{/* Network Players - show other players in the room */}
{networkPlayers.length > 0 && !showFullscreenSelection && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: shouldEmphasize ? '12px' : '6px',
}}
>
{networkPlayers.map((player) => (
<NetworkPlayerIndicator
key={player.id}
player={player}
shouldEmphasize={shouldEmphasize}
/>
))}
</div>
)}
// Normal 2x2 grid layout
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
width: 'auto',
}}
>
{/* Row 1: Title | Mode + Room */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
justifyContent: 'space-between',
}}
>
{/* Left: Title */}
<h1
style={{
fontSize: '18px',
fontWeight: 'bold',
background: 'linear-gradient(135deg, #60a5fa, #a78bfa, #f472b6)',
backgroundClip: 'text',
color: 'transparent',
margin: 0,
whiteSpace: 'nowrap',
}}
>
{navEmoji && `${navEmoji} `}
{navTitle}
</h1>
{/* Active Players + Add Button */}
{(activePlayers.length > 0 ||
(shouldEmphasize && inactivePlayers.length > 0 && canModifyPlayers)) && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: shouldEmphasize ? '12px' : '2px',
padding: shouldEmphasize ? '12px 20px' : '0',
background: shouldEmphasize
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.08))'
: 'transparent',
borderRadius: shouldEmphasize ? '16px' : '0',
border: shouldEmphasize ? '3px solid rgba(255, 255, 255, 0.25)' : 'none',
boxShadow: shouldEmphasize
? '0 6px 20px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255,255,255,0.3)'
: 'none',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
transform: shouldEmphasize ? 'scale(1.05)' : 'scale(1)',
opacity: canModifyPlayers ? 1 : 0.6,
pointerEvents: canModifyPlayers ? 'auto' : 'none',
}}
>
<ActivePlayersList
activePlayers={activePlayers}
{/* Right: Mode + Room */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
<GameModeIndicator gameMode={gameMode} shouldEmphasize={shouldEmphasize} showFullscreenSelection={false} />
{roomInfo && (
<RoomInfo
roomName={roomInfo.roomName}
gameName={roomInfo.gameName}
playerCount={roomInfo.playerCount}
joinCode={roomInfo.joinCode}
shouldEmphasize={shouldEmphasize}
onRemovePlayer={onRemovePlayer}
onConfigurePlayer={onConfigurePlayer}
/>
{canModifyPlayers && (
<AddPlayerButton
inactivePlayers={inactivePlayers}
shouldEmphasize={shouldEmphasize}
onAddPlayer={onAddPlayer}
/>
)}
</div>
)}
)}
</div>
</div>
{/* Fullscreen player selection grid */}
<FullscreenPlayerSelection
inactivePlayers={inactivePlayers}
onSelectPlayer={onAddPlayer}
onConfigurePlayer={onConfigurePlayer}
isVisible={showFullscreenSelection}
/>
{/* Row 2: Controls | Players */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
justifyContent: 'space-between',
}}
>
{/* Left: Control buttons */}
<div>
{!canModifyPlayers && (
<GameControlButtons onSetup={onSetup} onNewGame={onNewGame} onQuit={onExitSession} />
)}
</div>
{/* Right: Network players + Your players */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: shouldEmphasize ? '12px' : '8px',
}}
>
{/* Network Players */}
{networkPlayers.length > 0 && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
}}
>
{networkPlayers.map((player) => (
<NetworkPlayerIndicator key={player.id} player={player} shouldEmphasize={shouldEmphasize} />
))}
</div>
)}
{/* Active Players + Add Button */}
{(activePlayers.length > 0 || (shouldEmphasize && inactivePlayers.length > 0 && canModifyPlayers)) && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: shouldEmphasize ? '12px' : '2px',
padding: shouldEmphasize ? '12px 20px' : '0',
background: shouldEmphasize
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.08))'
: 'transparent',
borderRadius: shouldEmphasize ? '16px' : '0',
border: shouldEmphasize ? '3px solid rgba(255, 255, 255, 0.25)' : 'none',
boxShadow: shouldEmphasize
? '0 6px 20px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255,255,255,0.3)'
: 'none',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
transform: shouldEmphasize ? 'scale(1.05)' : 'scale(1)',
opacity: canModifyPlayers ? 1 : 0.6,
pointerEvents: canModifyPlayers ? 'auto' : 'none',
}}
>
<ActivePlayersList
activePlayers={activePlayers}
shouldEmphasize={shouldEmphasize}
onRemovePlayer={onRemovePlayer}
onConfigurePlayer={onConfigurePlayer}
/>
{canModifyPlayers && (
<AddPlayerButton
inactivePlayers={inactivePlayers}
shouldEmphasize={shouldEmphasize}
onAddPlayer={onAddPlayer}
/>
)}
</div>
)}
</div>
</div>
{/* Add keyframes for animations */}
<style
dangerouslySetInnerHTML={{
__html: `