From 9c25f1464c2deffe0ef85d4cd28b031dbfe2a00b Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Wed, 15 Apr 2026 14:19:29 -0400 Subject: [PATCH] feat: animate chips falling into place --- connect4-ui/components/Board.tsx | 111 ++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 17 deletions(-) diff --git a/connect4-ui/components/Board.tsx b/connect4-ui/components/Board.tsx index 3dff576..9ae69a3 100644 --- a/connect4-ui/components/Board.tsx +++ b/connect4-ui/components/Board.tsx @@ -1,6 +1,7 @@ "use client"; -import { useState } from "react"; +import type { CSSProperties } from "react"; +import { useEffect, useRef, useState } from "react"; import type { BoardState } from "@/lib/protocol"; interface BoardProps { @@ -23,7 +24,43 @@ export default function Board({ currentTurnColor, }: BoardProps) { const [hoveredCol, setHoveredCol] = useState(null); + const [animatingMove, setAnimatingMove] = useState<{ + column: number; + row: number; + } | null>(null); + const isFirstRender = useRef(true); + const animationTimeoutRef = useRef(null); const canInteract = !disabled && !!onColumnClick; + const lastMoveColumn = lastMove?.column ?? null; + const lastMoveRow = lastMove?.row ?? null; + + useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } + + if (animationTimeoutRef.current !== null) { + window.clearTimeout(animationTimeoutRef.current); + } + + if (lastMoveColumn === null || lastMoveRow === null) { + setAnimatingMove(null); + return; + } + + setAnimatingMove({ column: lastMoveColumn, row: lastMoveRow }); + animationTimeoutRef.current = window.setTimeout(() => { + setAnimatingMove(null); + animationTimeoutRef.current = null; + }, 450); + + return () => { + if (animationTimeoutRef.current !== null) { + window.clearTimeout(animationTimeoutRef.current); + } + }; + }, [lastMoveColumn, lastMoveRow]); return (
@@ -96,27 +133,44 @@ export default function Board({ const cell = board[col][row]; const isLast = lastMove?.column === col && lastMove?.row === row; + const isAnimatingDrop = + animatingMove?.column === col && animatingMove?.row === row; + const chipScale = isLast ? 1.1 : 1; + const dropDistance = `${(5 - row) * 56 + 16}px`; return (
+ > + {cell !== 0 && ( +
+ )} +
); })}
@@ -135,6 +189,29 @@ export default function Board({ ))}
+ + ); }