98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
|
|
import React from 'react';
|
||
|
|
import type { Player } from '../types';
|
||
|
|
|
||
|
|
interface PlayerListProps {
|
||
|
|
players: Player[];
|
||
|
|
selectedPlayer: Player | null;
|
||
|
|
onSelectPlayer: (player: Player) => void;
|
||
|
|
onRemovePlayer: (id: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const PlayerList: React.FC<PlayerListProps> = ({
|
||
|
|
players,
|
||
|
|
selectedPlayer,
|
||
|
|
onSelectPlayer,
|
||
|
|
onRemovePlayer,
|
||
|
|
}) => {
|
||
|
|
return (
|
||
|
|
<div className="flex flex-col h-full bg-slate-900 border-l border-slate-800 w-80">
|
||
|
|
{/* Header */}
|
||
|
|
<div className="p-4 border-b border-slate-800 flex items-center justify-between">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<span className="w-2.5 h-2.5 rounded-full bg-sky-400" />
|
||
|
|
<h3 className="font-semibold text-slate-100 text-sm">Active Bots</h3>
|
||
|
|
</div>
|
||
|
|
<span className="text-xs font-mono px-2 py-0.5 rounded-full bg-slate-800 text-slate-300 border border-slate-700">
|
||
|
|
{players.length} / 4096
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Players List */}
|
||
|
|
<div className="flex-1 overflow-y-auto p-3 space-y-2">
|
||
|
|
{players.length === 0 ? (
|
||
|
|
<div className="text-center py-12 px-4 text-slate-500 text-xs">
|
||
|
|
<div className="text-2xl mb-2">🤖</div>
|
||
|
|
No players registered yet.
|
||
|
|
<br />
|
||
|
|
Register a player or spawn a random bot to place them on the grid!
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
players.map((player) => {
|
||
|
|
const isSelected = selectedPlayer?.id === player.id;
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
key={player.id}
|
||
|
|
onClick={() => onSelectPlayer(player)}
|
||
|
|
className={`group flex items-center justify-between p-2.5 rounded-xl border transition-all cursor-pointer ${
|
||
|
|
isSelected
|
||
|
|
? 'bg-sky-950/40 border-sky-500 shadow-md shadow-sky-500/10'
|
||
|
|
: 'bg-slate-950/60 border-slate-800 hover:border-slate-700 hover:bg-slate-800/40'
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
<div className="flex items-center gap-3 min-w-0">
|
||
|
|
<div
|
||
|
|
className="w-8 h-8 rounded-full flex-shrink-0 flex items-center justify-center text-white font-bold text-xs shadow"
|
||
|
|
style={{
|
||
|
|
backgroundColor: player.color,
|
||
|
|
boxShadow: `0 0 10px ${player.color}55`,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{player.name.slice(0, 2).toUpperCase()}
|
||
|
|
</div>
|
||
|
|
<div className="min-w-0">
|
||
|
|
<div className="text-xs font-semibold text-slate-200 truncate">
|
||
|
|
{player.name}
|
||
|
|
</div>
|
||
|
|
<div className="text-[11px] font-mono text-slate-400">
|
||
|
|
pos: <span className="text-emerald-400">({player.x}, {player.y})</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-1.5 opacity-60 group-hover:opacity-100 transition-opacity">
|
||
|
|
<button
|
||
|
|
onClick={(e) => {
|
||
|
|
e.stopPropagation();
|
||
|
|
onRemovePlayer(player.id);
|
||
|
|
}}
|
||
|
|
title="Remove Bot"
|
||
|
|
className="p-1 rounded text-slate-500 hover:text-red-400 hover:bg-red-950/40 transition-colors text-xs"
|
||
|
|
>
|
||
|
|
✕
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Footer Info */}
|
||
|
|
<div className="p-3 border-t border-slate-800 bg-slate-950/40 text-[11px] font-mono text-slate-400 flex justify-between items-center">
|
||
|
|
<span>Coordinate range:</span>
|
||
|
|
<span className="text-sky-400 font-semibold">[0,0] → [64,64]</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|