feat(P5B.T4): TicTacToeBoard + ConnectFourBoard render-only components

This commit is contained in:
byGalax
2026-05-16 21:51:06 +02:00
parent dbf90504b4
commit e56e22631b
2 changed files with 114 additions and 0 deletions
@@ -0,0 +1,53 @@
import { type Cell, tttWinningLine } from '@chat-app/shared/chat';
interface Props {
board: Cell[];
myPlayerIdx: 0 | 1 | null;
disabled: boolean;
onMove: (cell: number) => void;
}
const MARK = ['×', '○'] as const;
export function TicTacToeBoard({ board, myPlayerIdx, disabled, onMove }: Props) {
const winLine = tttWinningLine(board);
const winSet = new Set<number>(winLine ?? []);
return (
<div
className="mx-auto grid w-full max-w-md grid-cols-3 gap-2 p-4"
style={{ aspectRatio: '1 / 1' }}
role="grid"
aria-label="Tic-Tac-Toe"
>
{board.map((cell, i) => {
const filled = cell !== null;
const inWin = winSet.has(i);
const canClick = !disabled && !filled && myPlayerIdx !== null;
return (
<button
key={i}
type="button"
onClick={() => canClick && onMove(i)}
disabled={!canClick}
aria-label={'Feld ' + (i + 1) + (filled ? ' belegt' : ' frei')}
className={
'flex aspect-square items-center justify-center rounded-xl border-2 text-5xl font-bold transition ' +
(inWin
? 'border-emerald-400 bg-emerald-400/20 text-emerald-200'
: filled
? cell === 0
? 'border-rose-500/60 bg-rose-500/10 text-rose-300'
: 'border-sky-500/60 bg-sky-500/10 text-sky-300'
: canClick
? 'cursor-pointer border-line bg-surface-2 text-fg-muted hover:bg-surface-3 hover:text-fg'
: 'cursor-not-allowed border-line bg-surface-2 text-fg-muted opacity-60')
}
>
{filled ? MARK[cell as 0 | 1] : ''}
</button>
);
})}
</div>
);
}