feat : better ui
This commit is contained in:
@@ -49,8 +49,8 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
|
||||
<InputGroupInput
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder="ค้นหา Guide…"
|
||||
aria-label="ค้นหา Guide"
|
||||
placeholder="ค้นหาไกด์…"
|
||||
aria-label="ค้นหาไกด์"
|
||||
/>
|
||||
</InputGroup>
|
||||
{filtered.length ? (
|
||||
@@ -65,7 +65,7 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
|
||||
<div className="relative aspect-square bg-muted">
|
||||
<Image
|
||||
src={guide.coverUrl}
|
||||
alt={`Guide ${guide.name}`}
|
||||
alt={`ไกด์ ${guide.name}`}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
@@ -83,7 +83,7 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
|
||||
) : (
|
||||
<Empty className="border">
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>ไม่พบ Guide</EmptyTitle>
|
||||
<EmptyTitle>ไม่พบไกด์</EmptyTitle>
|
||||
<EmptyDescription>ลองค้นหาด้วยชื่อตัวละครอื่น</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
|
||||
@@ -48,6 +48,14 @@ export function publicSections(guide: PublicGuide) {
|
||||
return guide.sections.filter((section) => hasSection(guide, section.slug));
|
||||
}
|
||||
|
||||
function publicSectionTitle(slug: string, fallback: string): string {
|
||||
if (slug === "weapon") return "อาวุธ";
|
||||
if (slug === "artifact") return "อาร์ติแฟกต์และค่าสถานะ";
|
||||
if (slug === "constellations") return "กลุ่มดาว";
|
||||
if (slug === "team") return "คำนวณทีม";
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function StructuredGuideShell({
|
||||
guide,
|
||||
section,
|
||||
@@ -135,14 +143,14 @@ function GuideOverview({
|
||||
<div className="relative mx-auto aspect-square w-full max-w-3xl overflow-hidden rounded-xl border bg-muted">
|
||||
<Image
|
||||
src={guide.coverUrl}
|
||||
alt={`Guide ${guide.name}`}
|
||||
alt={`ไกด์ ${guide.name}`}
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-auto flex max-w-3xl flex-col gap-3 text-center">
|
||||
<p className="text-sm font-medium text-primary">Build Overview</p>
|
||||
<p className="text-sm font-medium text-primary">ภาพรวมแนวทาง</p>
|
||||
<h1 className="font-heading text-4xl font-semibold">{guide.name}</h1>
|
||||
<p className="text-lg leading-8 text-muted-foreground">
|
||||
{guide.overview}
|
||||
@@ -155,7 +163,7 @@ function GuideOverview({
|
||||
<Card className="h-full">
|
||||
<CardHeader>
|
||||
{navigationIconPath(item.slug) ? <Image src={navigationIconPath(item.slug)!} alt="" width={40} height={40} className="size-10 object-contain" /> : null}
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
<CardTitle>{publicSectionTitle(item.slug, item.title)}</CardTitle>
|
||||
<CardDescription>เปิดดูคำแนะนำฉบับเต็ม</CardDescription>
|
||||
<CardAction>
|
||||
<ArrowUpRightIcon aria-hidden="true" className="size-5 text-primary" />
|
||||
@@ -206,14 +214,14 @@ function WeaponSection({ guide }: { guide: PublicGuide }) {
|
||||
<section className="flex flex-col gap-6">
|
||||
<header className="flex flex-col items-center gap-3 text-center">
|
||||
<p className="text-sm font-medium text-primary">อาวุธที่แนะนำ</p>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">Weapons</h1>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">อาวุธ</h1>
|
||||
<a
|
||||
href={`/api/guides/${guide.id}/weapons/image`}
|
||||
download
|
||||
className={buttonVariants({ variant: "outline", size: "sm" })}
|
||||
>
|
||||
<DownloadIcon data-icon="inline-start" />
|
||||
Export image
|
||||
ส่งออกภาพ
|
||||
</a>
|
||||
</header>
|
||||
<div className="grid gap-3">
|
||||
@@ -286,7 +294,7 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
อาร์ติแฟกต์และสเตตัสที่แนะนำ
|
||||
</p>
|
||||
<h1 className="font-heading text-4xl font-semibold">
|
||||
Artifacts & Stats
|
||||
อาร์ติแฟกต์และค่าสถานะ
|
||||
</h1>
|
||||
<a
|
||||
href={`/api/guides/${guide.id}/artifacts/image`}
|
||||
@@ -294,7 +302,7 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
className={buttonVariants({ variant: "outline", size: "sm" })}
|
||||
>
|
||||
<DownloadIcon data-icon="inline-start" />
|
||||
Export image
|
||||
ส่งออกภาพ
|
||||
</a>
|
||||
</header>
|
||||
<div className="grid gap-5 lg:grid-cols-[1.2fr_1fr]">
|
||||
@@ -303,7 +311,7 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
{guide.artifactGroups.map((group) => (
|
||||
<Card key={group.id}>
|
||||
<CardHeader>
|
||||
<CardTitle>{group.name}</CardTitle>
|
||||
<CardTitle>{group.name === "Late Game" ? "เลทเกม" : group.name === "Early Game" ? "ช่วงต้นเกม" : group.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-3">
|
||||
{group.rows.map((row, index) => (
|
||||
@@ -359,18 +367,18 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
<div className="flex flex-col gap-5">
|
||||
<Card className="overflow-hidden pt-0">
|
||||
<CardHeader className="bg-primary py-4 text-center text-primary-foreground">
|
||||
<CardTitle className="text-2xl">Artifact Stats</CardTitle>
|
||||
<CardTitle className="text-2xl">ออฟหลักที่แนะนำ</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-3 p-0">
|
||||
<ArtifactStat iconSrc="/icon/Icon_Sands_of_Eon.webp" label="Sands" values={profile.sands} />
|
||||
<ArtifactStat iconSrc="/icon/Icon_Goblet_of_Eonothem.webp" label="Goblet" values={profile.goblet} />
|
||||
<ArtifactStat iconSrc="/icon/Icon_Circlet_of_Logos.webp" label="Circlet" values={profile.circlet} />
|
||||
<ArtifactStat iconSrc="/icon/Icon_Sands_of_Eon.webp" label="นาฬิกา" values={profile.sands} />
|
||||
<ArtifactStat iconSrc="/icon/Icon_Goblet_of_Eonothem.webp" label="ถ้วย" values={profile.goblet} />
|
||||
<ArtifactStat iconSrc="/icon/Icon_Circlet_of_Logos.webp" label="หัว" values={profile.circlet} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
{profile.substats.length ? (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center gap-2 py-5 text-center text-sm">
|
||||
<strong>Stat Priority:</strong>
|
||||
<strong>ลำดับค่าสถานะ:</strong>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{profile.substats.map((stat, index) => (
|
||||
<span key={`${stat}-${index}`} className={statPriorityColorClass(index)}>
|
||||
@@ -386,7 +394,7 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
<CardContent className="flex flex-col gap-5">
|
||||
{profile.targets.length ? (
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-medium">Target Stats</p>
|
||||
<p className="mb-2 text-sm font-medium">ค่าสถานะเป้าหมาย</p>
|
||||
<div className="flex flex-col gap-1">
|
||||
{profile.targets.map((target, index) => (
|
||||
<div
|
||||
@@ -398,7 +406,7 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
{targetStatIconName(target.stat) ? <Image src={`/icon/stats/${targetStatIconName(target.stat)}.webp`} alt="" width={28} height={28} className="size-7 shrink-0 object-contain brightness-0 invert" /> : null}
|
||||
<span>{normalizeTargetStat(target.stat) ?? target.stat}</span>
|
||||
<span>{statLabel(normalizeTargetStat(target.stat) ?? target.stat)}</span>
|
||||
</span>
|
||||
<strong className="shrink-0">{target.value}</strong>
|
||||
</div>
|
||||
@@ -420,7 +428,7 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
<div className="relative size-14 overflow-hidden rounded-xl border bg-muted sm:size-20">
|
||||
{catalogTalent?.iconUrl ? <Image src={catalogTalent.iconUrl} alt={talentName} fill className="object-contain" /> : <span className="grid size-full place-items-center font-semibold">{talent.type}</span>}
|
||||
</div>
|
||||
<Badge variant="secondary">{talent.level ? `Lv.${talent.level}+` : "—"}</Badge>
|
||||
<Badge variant="secondary">{talent.level ? `เลเวล ${talent.level}+` : "—"}</Badge>
|
||||
<p className="text-xs font-medium sm:text-sm">{talentName}</p>
|
||||
<p className="text-[11px] text-muted-foreground sm:text-xs">{talentTypeLabel(talent.type)}</p>
|
||||
</div>
|
||||
@@ -453,7 +461,29 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
}
|
||||
|
||||
function statLabel(value: string): string {
|
||||
return value === "EM" ? "Elemental Mastery" : value === "ER" ? "Energy Recharge" : value;
|
||||
const labels: Record<string, string> = {
|
||||
HP: "พลังชีวิต",
|
||||
ATK: "พลังโจมตี",
|
||||
DEF: "พลังป้องกัน",
|
||||
EM: "ความชำนาญธาตุ",
|
||||
ER: "ฟื้นฟูพลังงาน",
|
||||
"CRIT RATE": "อัตราคริ",
|
||||
"CRIT DAMAGE": "ความแรงคริ",
|
||||
"ATK%": "พลังโจมตี%",
|
||||
"HP%": "พลังชีวิต%",
|
||||
"DEF%": "พลังป้องกัน%",
|
||||
"CRIT Rate%/CRIT DMG%": "อัตราคริ/ความแรงคริ",
|
||||
"Pyro DMG%": "ความเสียหายธาตุไฟ%",
|
||||
"Hydro DMG%": "ความเสียหายธาตุน้ำ%",
|
||||
"Cryo DMG%": "ความเสียหายธาตุน้ำแข็ง%",
|
||||
"Electro DMG%": "ความเสียหายธาตุไฟฟ้า%",
|
||||
"Anemo DMG%": "ความเสียหายธาตุลม%",
|
||||
"Geo DMG%": "ความเสียหายธาตุดิน%",
|
||||
"Dendro DMG%": "ความเสียหายธาตุไม้%",
|
||||
"Physical DMG%": "ความเสียหายกายภาพ%",
|
||||
"Healing Bonus%": "โบนัสการรักษา%",
|
||||
};
|
||||
return labels[value] ?? value;
|
||||
}
|
||||
|
||||
function ArtifactStat({ iconSrc, label, values }: { iconSrc: string; label: string; values: string[] }) {
|
||||
@@ -480,7 +510,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
|
||||
<section className="flex flex-col gap-6">
|
||||
<header className="text-center">
|
||||
<p className="text-sm font-medium text-primary">กลุ่มดาว</p>
|
||||
<h1 className="font-heading text-4xl font-semibold">Constellations</h1>
|
||||
<h1 className="font-heading text-4xl font-semibold">กลุ่มดาว</h1>
|
||||
</header>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{guide.constellations.map((row) => (
|
||||
@@ -519,7 +549,7 @@ function TeamSection({ guide }: { guide: PublicGuide }) {
|
||||
<section className="flex flex-col gap-6">
|
||||
<header className="text-center">
|
||||
<p className="text-sm font-medium text-primary">คำนวณความเสียหาย</p>
|
||||
<h1 className="font-heading text-4xl font-semibold">Team Calc</h1>
|
||||
<h1 className="font-heading text-4xl font-semibold">คำนวณทีม</h1>
|
||||
</header>
|
||||
<TeamSearch teams={guide.teams} />
|
||||
</section>
|
||||
@@ -530,7 +560,7 @@ function ExtraSection({ extra }: { extra: PublicGuide["extras"][number] }) {
|
||||
return (
|
||||
<section className="flex flex-col gap-6">
|
||||
<header className="text-center">
|
||||
<p className="text-sm font-medium text-primary">Extra Guide</p>
|
||||
<p className="text-sm font-medium text-primary">คู่มือเสริม</p>
|
||||
<h1 className="font-heading text-4xl font-semibold">{extra.title}</h1>
|
||||
</header>
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
|
||||
@@ -38,9 +38,9 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
<div className="relative">
|
||||
<SearchIcon aria-hidden="true" className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
aria-label="Search teams, characters, weapons, or artifacts"
|
||||
aria-label="ค้นหาทีม ตัวละคร อาวุธ หรืออาร์ติแฟกต์"
|
||||
className="pl-9"
|
||||
placeholder="Search teams, characters, weapons, or artifacts"
|
||||
placeholder="ค้นหาทีม ตัวละคร อาวุธ หรืออาร์ติแฟกต์"
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
/>
|
||||
@@ -57,7 +57,7 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
<Card key={team.id}>
|
||||
<CardHeader>
|
||||
<CardTitle>{team.title}</CardTitle>
|
||||
<CardDescription>{seconds}s rotation</CardDescription>
|
||||
<CardDescription>ระยะเวลาโรเทชั่น {seconds} วิ</CardDescription>
|
||||
<CardAction>
|
||||
<a
|
||||
href={`/api/teams/${team.id}/image`}
|
||||
@@ -68,7 +68,7 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
})}
|
||||
>
|
||||
<DownloadIcon data-icon="inline-start" />
|
||||
Export image
|
||||
ส่งออกภาพ
|
||||
</a>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
@@ -84,6 +84,9 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
<Kbd className="absolute top-0 left-0 bg-transparent text-white">
|
||||
C{member.constellation}
|
||||
</Kbd>
|
||||
</div>
|
||||
<div className="col-span-1 row-span-2 flex min-w-0 flex-col gap-2">
|
||||
{member.weapon ? (
|
||||
@@ -99,7 +102,7 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
fill
|
||||
className="object-contain p-1"
|
||||
/>
|
||||
<Kbd className="absolute top-0 left-0 bg-transparent text-black">
|
||||
<Kbd className="absolute top-0 left-0 bg-transparent text-white">
|
||||
R{member.refinement}
|
||||
</Kbd>
|
||||
</div>
|
||||
@@ -126,7 +129,7 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 truncate text-sm font-medium">
|
||||
C{member.constellation} {member.character.name}
|
||||
{member.character.name}
|
||||
</p>
|
||||
<p className="text-sm font-semibold">
|
||||
{Number(member.damage).toLocaleString(undefined, {
|
||||
@@ -146,12 +149,12 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
<div className="flex items-end justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Total damage
|
||||
ดาเมจรวม
|
||||
</p>
|
||||
<p className="font-semibold">{total.toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm text-muted-foreground">Total DPS</p>
|
||||
<p className="text-sm text-muted-foreground">DPS</p>
|
||||
<p className="text-3xl font-semibold text-primary">
|
||||
{seconds > 0
|
||||
? (total / seconds).toLocaleString(undefined, {
|
||||
@@ -172,7 +175,7 @@ export function TeamSearch({ teams }: { teams: PublicGuide["teams"] }) {
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="py-8 text-center text-sm text-muted-foreground">
|
||||
No teams match your search.
|
||||
ไม่พบทีมที่ตรงกับคำค้นหา
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user