Files
initiative/apps/web/src/components/player-management.tsx
Lukas 85acb5c185
All checks were successful
CI / check (push) Successful in 48s
CI / build-image (push) Successful in 18s
Migrate icon buttons to Button component and simplify size variants
Replace raw <button> elements with Button variant="ghost" in stat-block
panel, toast, player modals. Add icon-sm size variant (h-6 w-6) for
compact contexts. Consolidate text button sizes into a single default
(h-8 px-3), removing the redundant sm variant. Add size prop to
ConfirmButton for consistent sizing.

Button now has three sizes: default (text), icon, icon-sm.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:22:00 +01:00

127 lines
3.5 KiB
TypeScript

import type {
PlayerCharacter,
PlayerCharacterId,
PlayerIcon,
} from "@initiative/domain";
import { Pencil, Plus, Trash2, X } from "lucide-react";
import { useEffect } from "react";
import { PLAYER_COLOR_HEX, PLAYER_ICON_MAP } from "./player-icon-map";
import { Button } from "./ui/button";
import { ConfirmButton } from "./ui/confirm-button";
interface PlayerManagementProps {
open: boolean;
onClose: () => void;
characters: readonly PlayerCharacter[];
onEdit: (pc: PlayerCharacter) => void;
onDelete: (id: PlayerCharacterId) => void;
onCreate: () => void;
}
export function PlayerManagement({
open,
onClose,
characters,
onEdit,
onDelete,
onCreate,
}: PlayerManagementProps) {
useEffect(() => {
if (!open) return;
function handleKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") onClose();
}
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [open, onClose]);
if (!open) return null;
return (
// biome-ignore lint/a11y/noStaticElementInteractions: backdrop click to close
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onMouseDown={onClose}
>
{/* biome-ignore lint/a11y/noStaticElementInteractions: prevent close when clicking modal content */}
<div
className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl"
onMouseDown={(e) => e.stopPropagation()}
>
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-foreground">
Player Characters
</h2>
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="text-muted-foreground"
>
<X size={20} />
</Button>
</div>
{characters.length === 0 ? (
<div className="flex flex-col items-center gap-3 py-8 text-center">
<p className="text-muted-foreground">No player characters yet</p>
<Button onClick={onCreate}>
<Plus size={16} />
Create your first player character
</Button>
</div>
) : (
<div className="flex flex-col gap-1">
{characters.map((pc) => {
const Icon = PLAYER_ICON_MAP[pc.icon as PlayerIcon];
const color =
PLAYER_COLOR_HEX[pc.color as keyof typeof PLAYER_COLOR_HEX];
return (
<div
key={pc.id}
className="group flex items-center gap-3 rounded-md px-3 py-2 hover:bg-hover-neutral-bg"
>
{Icon && (
<Icon size={18} style={{ color }} className="shrink-0" />
)}
<span className="flex-1 truncate text-sm text-foreground">
{pc.name}
</span>
<span className="text-xs tabular-nums text-muted-foreground">
AC {pc.ac}
</span>
<span className="text-xs tabular-nums text-muted-foreground">
HP {pc.maxHp}
</span>
<Button
variant="ghost"
size="icon-sm"
onClick={() => onEdit(pc)}
className="text-muted-foreground"
title="Edit"
>
<Pencil size={14} />
</Button>
<ConfirmButton
icon={<Trash2 size={14} />}
label="Delete player character"
onConfirm={() => onDelete(pc.id)}
size="icon-sm"
className="text-muted-foreground"
/>
</div>
);
})}
<div className="mt-2 flex justify-end">
<Button onClick={onCreate} variant="ghost">
<Plus size={16} />
Add
</Button>
</div>
</div>
)}
</div>
</div>
);
}