107 lines
3.9 KiB
TypeScript
107 lines
3.9 KiB
TypeScript
import { POSITIONS } from "@/lib/game-engine";
|
|
import type { Dictionary } from "@/lib/i18n";
|
|
import type { ReactNode } from "react";
|
|
import type { Feedback, Guess, Position } from "@/lib/types";
|
|
import PlayerSlot from "./PlayerSlot";
|
|
|
|
type PitchProps = {
|
|
actions: ReactNode;
|
|
collapsed: boolean;
|
|
draft: Record<Position, string | null>;
|
|
fixedPosition: Position;
|
|
lastGuess?: Guess;
|
|
onToggleCollapsed: () => void;
|
|
selectedPosition: Position;
|
|
solutionRevealed?: boolean;
|
|
t: Dictionary;
|
|
onSelectPosition: (position: Position) => void;
|
|
};
|
|
|
|
const layout: Record<Position, string> = {
|
|
ST: "col-start-2 row-start-1",
|
|
WING: "col-start-1 row-start-2",
|
|
MID: "col-start-3 row-start-2",
|
|
DEF: "col-start-2 row-start-3",
|
|
GK: "col-start-2 row-start-4",
|
|
};
|
|
|
|
export default function Pitch({
|
|
actions,
|
|
collapsed,
|
|
draft,
|
|
fixedPosition,
|
|
lastGuess,
|
|
onToggleCollapsed,
|
|
selectedPosition,
|
|
solutionRevealed = false,
|
|
t,
|
|
onSelectPosition,
|
|
}: PitchProps) {
|
|
function feedbackFor(position: Position): Feedback {
|
|
if (solutionRevealed) return "correct";
|
|
if (position === fixedPosition) return "correct";
|
|
return lastGuess?.slots.find((slot) => slot.position === position)?.feedback ?? "empty";
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-hidden rounded-lg border border-white/15 bg-pitch-800 shadow-2xl">
|
|
<div className="flex items-center justify-between gap-3 border-b border-white/10 bg-pitch-950/35 px-3 py-2">
|
|
<button
|
|
type="button"
|
|
onClick={onToggleCollapsed}
|
|
className="flex min-w-0 flex-1 items-center justify-between gap-3 text-left"
|
|
aria-expanded={!collapsed}
|
|
>
|
|
<span className="text-xs font-black uppercase text-white">{t.field}</span>
|
|
<span className="text-xs font-bold text-white/60">{collapsed ? t.show : t.hide}</span>
|
|
</button>
|
|
{actions}
|
|
</div>
|
|
|
|
{!collapsed ? (
|
|
<>
|
|
<div className="relative min-h-[560px] p-4">
|
|
<div className="absolute inset-4 rounded-lg border-2 border-white/70" />
|
|
<div className="absolute left-1/2 top-1/2 h-32 w-32 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white/55" />
|
|
<div className="absolute left-1/2 top-4 h-20 w-36 -translate-x-1/2 rounded-b-lg border-x-2 border-b-2 border-white/55" />
|
|
<div className="absolute bottom-4 left-1/2 h-20 w-36 -translate-x-1/2 rounded-t-lg border-x-2 border-t-2 border-white/55" />
|
|
|
|
<div className="relative z-10 grid min-h-[528px] grid-cols-3 grid-rows-4 place-items-center gap-3">
|
|
{POSITIONS.map((position) => (
|
|
<div key={position} className={layout[position]}>
|
|
<PlayerSlot
|
|
position={position}
|
|
playerId={draft[position]}
|
|
feedback={feedbackFor(position)}
|
|
fixed={position === fixedPosition}
|
|
selected={position === selectedPosition}
|
|
t={t}
|
|
onClick={() => {
|
|
if (position !== fixedPosition) onSelectPosition(position);
|
|
}}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-end gap-3 border-t border-white/10 bg-pitch-950/45 px-4 py-2 text-[10px] font-black uppercase text-white/70">
|
|
<span className="flex items-center gap-1.5">
|
|
<span className="h-2 w-2 rounded-sm bg-emerald-400" />
|
|
{t.exact}
|
|
</span>
|
|
<span className="flex items-center gap-1.5">
|
|
<span className="h-2 w-2 rounded-sm bg-yellow-400" />
|
|
{t.close}
|
|
</span>
|
|
<span className="flex items-center gap-1.5">
|
|
<span className="h-2 w-2 rounded-sm bg-slate-500" />
|
|
{t.miss}
|
|
</span>
|
|
</div>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|