1
0
Fork 0
polaris-task-force/src/components/frontend/locker/Wardrobe.tsx
Z8MB1E 775b61e1f4 style(locker): increase wardrobe tooltip text size and use dark theme
- Change tooltip background from bg-foreground to bg-popover (dark)
- Increase text size from text-xs to text-sm
- Add border border-border for subtle definition
- Use font-semibold for slot name label
2026-08-02 18:53:47 -04:00

131 lines
4.1 KiB
TypeScript

"use client";
import { useMemo } from "react";
import {
Backpack,
Crosshair,
Eye,
Glasses,
Rocket,
Shield,
Shirt,
Target,
User,
} from "lucide-react";
import type { Asset, Loadout } from "@/payload-types";
import {
WARDROBE_SLOT_LAYOUT,
getLoadoutSlot,
type WardrobeSlotIcon,
type LoadoutSlots,
type LockerGridItem,
} from "@/lib/locker";
import { cn } from "@/lib/utils";
import { getAssetIcon } from "./LockerGrid";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
function slotIcon(icon: WardrobeSlotIcon, filled: boolean) {
const cls = filled ? "size-3.5" : "size-3";
switch (icon) {
case "head":
return <User className={cls} />;
case "eye":
return <Eye className={cls} />;
case "face":
return <Glasses className={cls} />;
case "chest":
return <Shield className={cls} />;
case "torso":
return <Shirt className={cls} />;
case "back":
return <Backpack className={cls} />;
case "weapon":
return <Crosshair className={cls} />;
case "sidearm":
return <Target className={cls} />;
case "heavy":
return <Rocket className={cls} />;
}
}
function resolveSlotAsset(
value: LoadoutSlots[keyof LoadoutSlots],
items: LockerGridItem[],
): Asset | null {
if (value == null) return null;
if (typeof value === "object" && "id" in value) return value as Asset;
const id = value as number;
return items.find((i) => i.assetId === id)?.asset ?? null;
}
export function Wardrobe({
loadout,
items,
onSlotClick,
}: {
loadout: Loadout;
items: LockerGridItem[];
onSlotClick: (slotName: string) => void;
}) {
const slots = (loadout.slots ?? {}) as LoadoutSlots;
const filled = useMemo(() => {
const set = new Set<string>();
for (const layout of WARDROBE_SLOT_LAYOUT) {
const value = slots[layout.name];
if (value != null) set.add(layout.zone);
}
return set;
}, [slots]);
return (
<div className="relative mx-auto w-full max-w-100">
<div className="relative" style={{ aspectRatio: "260 / 380" }}>
<img
src="/images/soldier-pose-alpha.png"
alt="Character wardrobe reflecting the equipped loadout"
className="h-full w-full object-contain pointer-events-none select-none"
draggable={false}
/>
{WARDROBE_SLOT_LAYOUT.map((layout) => {
const value = slots[layout.name];
const asset = resolveSlotAsset(value, items);
return (
<Tooltip key={layout.name}>
<TooltipTrigger asChild>
<button
type="button"
aria-label={`${layout.label} slot: ${asset ? asset.name : "empty"}`}
onClick={() => onSlotClick(layout.name)}
className={cn(
"absolute flex items-center justify-center rounded-full border transition-colors",
"-translate-x-1/2 -translate-y-1/2 cursor-pointer",
asset
? "size-7 border-amber-400/70 bg-amber-500/25 text-amber-200 hover:bg-amber-500/40"
: "size-6 border-border bg-background/80 text-muted-foreground hover:border-primary hover:text-foreground",
)}
style={{ left: `${layout.x}%`, top: `${layout.y}%` }}
>
{asset ? getAssetIcon(asset.assetType, "size-3.5") : slotIcon(layout.icon, false)}
</button>
</TooltipTrigger>
<TooltipContent
side="right"
sideOffset={8}
className="bg-popover text-popover-foreground border border-border text-sm px-3.5 py-2"
>
<div className="flex flex-col gap-0.5">
<span className="font-semibold">{layout.label}</span>
<span className="text-muted-foreground">
{asset ? asset.name : "Empty"}
</span>
</div>
</TooltipContent>
</Tooltip>
);
})}
</div>
</div>
);
}