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:
@@ -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: `
|
||||
|
||||
Reference in New Issue
Block a user