109 lines
4.4 KiB
TypeScript
109 lines
4.4 KiB
TypeScript
import type { ReactNode } from "react";
|
|
|
|
import { CatalogItemImage, type CatalogItemImageData } from "@/components/catalog-item-image";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type DamageMetric = "overallPercent" | "personalPercent";
|
|
|
|
export function weaponComparisonPercent(value: string | null, exporting = false) {
|
|
if (value === null) return null;
|
|
return Number(value) === 0
|
|
? exporting ? "-%" : "-"
|
|
: `${Number(value).toLocaleString(undefined, { maximumFractionDigits: 2 })}%`;
|
|
}
|
|
|
|
export function WeaponComparisonCard({
|
|
weapon, rank, refinement, note, overallPercent, personalPercent,
|
|
exporting = false, compactExportGrid = false, weaponHref,
|
|
imageControl, refinementControl, noteControl, actions, renderMetric,
|
|
}: {
|
|
weapon: CatalogItemImageData & { name: string };
|
|
rank: number;
|
|
refinement: string;
|
|
note: string | null;
|
|
overallPercent: string | null;
|
|
personalPercent: string | null;
|
|
exporting?: boolean;
|
|
compactExportGrid?: boolean;
|
|
weaponHref?: string;
|
|
imageControl?: ReactNode;
|
|
refinementControl?: ReactNode;
|
|
noteControl?: ReactNode;
|
|
actions?: ReactNode;
|
|
renderMetric?: (metric: DamageMetric) => ReactNode;
|
|
}) {
|
|
const Panel = weaponHref ? "a" : "div";
|
|
const title = <p className={cn(
|
|
"truncate font-semibold",
|
|
compactExportGrid ? "text-sm" : exporting ? "text-lg" : "text-base sm:text-lg",
|
|
)}>
|
|
{rank}. {weapon.name}{refinementControl ? null : ` R${refinement}`}
|
|
</p>;
|
|
|
|
return <Card size="sm" className="bg-white py-0">
|
|
<CardContent className={cn(
|
|
"grid p-0",
|
|
compactExportGrid
|
|
? "grid-cols-[minmax(0,3fr)_minmax(0,2fr)]"
|
|
: exporting
|
|
? "grid-cols-[minmax(0,3fr)_minmax(0,1fr)]"
|
|
: renderMetric
|
|
? "md:grid-cols-[minmax(0,5fr)_minmax(0,2fr)]"
|
|
: "md:grid-cols-[minmax(0,3fr)_minmax(0,1fr)]",
|
|
)}>
|
|
<Panel
|
|
href={weaponHref}
|
|
target={weaponHref ? "_blank" : undefined}
|
|
rel={weaponHref ? "noreferrer" : undefined}
|
|
tabIndex={exporting ? -1 : undefined}
|
|
aria-label={weaponHref ? `เปิดข้อมูลอาวุธ ${weapon.name} บน Lunaris` : undefined}
|
|
className={cn(
|
|
"flex min-h-28 min-w-0 items-center gap-4 rounded-br-[4rem] p-4 pr-10 text-white",
|
|
compactExportGrid
|
|
? "rounded-br-[8rem] pr-10"
|
|
: exporting ? "rounded-br-[8rem] pr-16" : "md:rounded-br-[8rem] md:pr-16",
|
|
rarityGradientClass(weapon.rarity),
|
|
actions && "relative",
|
|
)}
|
|
>
|
|
{imageControl ?? <CatalogItemImage
|
|
item={weapon} kind="weapon" alt={weapon.name} rarityBackground={false}
|
|
className="size-20 shrink-0" loading={exporting ? "eager" : undefined}
|
|
sizes="(min-width: 768px) 5rem, 4rem"
|
|
/>}
|
|
<div className="flex min-w-0 flex-col gap-1">
|
|
{refinementControl ? <div className="flex min-w-0 flex-wrap items-center gap-2">{title}{refinementControl}</div> : title}
|
|
{noteControl ?? (note ? <p className={cn("whitespace-pre-wrap", compactExportGrid ? "text-xs" : "text-sm")}>{note}</p> : null)}
|
|
</div>
|
|
{actions}
|
|
</Panel>
|
|
<div className={cn(
|
|
"flex items-center justify-around bg-white py-5 text-black",
|
|
compactExportGrid ? "gap-1 px-2" : "gap-4 px-4",
|
|
)}>
|
|
{(["overallPercent", "personalPercent"] as const).map((metric) => {
|
|
const value = metric === "overallPercent" ? overallPercent : personalPercent;
|
|
if (!renderMetric && !value) return null;
|
|
return <div key={metric} className="min-w-0 flex-1 text-center">
|
|
<p className={cn(
|
|
"whitespace-nowrap leading-none font-bold",
|
|
compactExportGrid ? "text-2xl" : exporting ? "text-5xl" : "text-4xl sm:text-5xl",
|
|
rarityTextClass(weapon.rarity),
|
|
)}>
|
|
{renderMetric ? renderMetric(metric) : weaponComparisonPercent(value, exporting)}
|
|
</p>
|
|
<p className={cn(
|
|
"mt-1 whitespace-nowrap",
|
|
compactExportGrid ? "text-[10px]" : exporting ? "text-base" : "text-sm sm:text-base",
|
|
)}>
|
|
{metric === "overallPercent" ? "ดาเมจทั้งทีม" : "ดาเมจส่วนตัว"}
|
|
</p>
|
|
</div>;
|
|
})}
|
|
</div>
|
|
</CardContent>
|
|
</Card>;
|
|
}
|