feat: use CSS transitions for smooth fullscreen player selection collapse

Replace conditional rendering with always-mounted component that uses CSS transitions for height, width, and opacity. Prevents height from snapping when transitioning between fullscreen and prominent nav modes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-09-29 16:57:36 -05:00
parent 98cfa5645b
commit 31898328a3
2 changed files with 38 additions and 13 deletions

View File

@ -9,18 +9,23 @@ interface Player {
interface FullscreenPlayerSelectionProps { interface FullscreenPlayerSelectionProps {
inactivePlayers: Player[] inactivePlayers: Player[]
onSelectPlayer: (playerId: number) => void onSelectPlayer: (playerId: number) => void
isVisible: boolean
} }
export function FullscreenPlayerSelection({ inactivePlayers, onSelectPlayer }: FullscreenPlayerSelectionProps) { export function FullscreenPlayerSelection({ inactivePlayers, onSelectPlayer, isVisible }: FullscreenPlayerSelectionProps) {
return ( return (
<div style={{ <div style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
gap: '32px', gap: '32px',
width: '100%', width: isVisible ? '100%' : '0',
padding: '40px 20px', padding: isVisible ? '40px 20px' : '0',
animation: 'expandIn 0.4s ease' maxHeight: isVisible ? '800px' : '0',
opacity: isVisible ? 1 : 0,
overflow: 'hidden',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
pointerEvents: isVisible ? 'auto' : 'none'
}}> }}>
<div style={{ <div style={{
textAlign: 'center', textAlign: 'center',

View File

@ -35,14 +35,35 @@ export function GameContextNav({
onAddPlayer, onAddPlayer,
onRemovePlayer onRemovePlayer
}: GameContextNavProps) { }: 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 ( return (
<div style={{ <div style={{
display: 'flex', display: 'flex',
flexDirection: showFullscreenSelection ? 'column' : 'row', flexDirection: layoutMode,
alignItems: showFullscreenSelection ? 'stretch' : 'center', alignItems: showFullscreenSelection ? 'stretch' : 'center',
gap: shouldEmphasize ? '16px' : '12px', gap: shouldEmphasize ? '16px' : '12px',
width: showFullscreenSelection ? '100%' : 'auto', width: containerWidth,
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)' transition: 'gap 0.4s cubic-bezier(0.4, 0, 0.2, 1)'
}}> }}>
{/* Header row */} {/* Header row */}
<div style={{ <div style={{
@ -102,12 +123,11 @@ export function GameContextNav({
</div> </div>
{/* Fullscreen player selection grid */} {/* Fullscreen player selection grid */}
{showFullscreenSelection && ( <FullscreenPlayerSelection
<FullscreenPlayerSelection inactivePlayers={inactivePlayers}
inactivePlayers={inactivePlayers} onSelectPlayer={onAddPlayer}
onSelectPlayer={onAddPlayer} isVisible={showFullscreenSelection}
/> />
)}
{/* Add keyframes for animations */} {/* Add keyframes for animations */}
<style dangerouslySetInnerHTML={{ __html: ` <style dangerouslySetInnerHTML={{ __html: `