feat : improve preview for export dialog
This commit is contained in:
@@ -43,7 +43,10 @@ export function ConstellationImage({
|
||||
: "text-[76px]",
|
||||
)}
|
||||
>
|
||||
{`${guide.character.name}’s Constellations`}
|
||||
<ExportText
|
||||
th={`กลุ่มดาว ${guide.character.name}`}
|
||||
en={`${guide.character.name}’s Constellations`}
|
||||
/>
|
||||
</h2>
|
||||
<p className="mt-3 line-clamp-2 max-w-275 whitespace-pre-line text-[42px] leading-tight font-bold text-[#f1badd]">
|
||||
<ExportText th="ดาเมจทั้งทีม" en="Team Damage" />
|
||||
|
||||
@@ -180,6 +180,7 @@ export function GuideImageExport({
|
||||
modeSelection = "dialog",
|
||||
additionalOptions,
|
||||
previewPosition = "top",
|
||||
compactDialog = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
fileName: string;
|
||||
@@ -195,9 +196,10 @@ export function GuideImageExport({
|
||||
showLanguageSelector?: boolean;
|
||||
promptForLanguage?: boolean;
|
||||
localizeFileName?: boolean;
|
||||
modeSelection?: "dialog" | "inline";
|
||||
modeSelection?: "dialog" | "inline" | "direct";
|
||||
additionalOptions?: ReactNode;
|
||||
previewPosition?: "top" | "right";
|
||||
compactDialog?: boolean;
|
||||
}) {
|
||||
const captureRef = useRef<HTMLDivElement>(null);
|
||||
const [captureMode, setCaptureMode] = useState<
|
||||
@@ -214,6 +216,7 @@ export function GuideImageExport({
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [previewing, setPreviewing] = useState(false);
|
||||
const previewEnabled = modeSelection === "inline" || modeDialogOpen;
|
||||
const squarePreview = height !== undefined && width === height;
|
||||
|
||||
function requestCapture() {
|
||||
setModeDialogOpen(false);
|
||||
@@ -228,7 +231,10 @@ export function GuideImageExport({
|
||||
size="sm"
|
||||
className={mobileIconOnly ? "max-sm:size-7 max-sm:px-0 max-sm:has-data-[icon=inline-start]:pl-0" : undefined}
|
||||
disabled={capturing || disabled}
|
||||
onClick={() => setModeDialogOpen(true)}
|
||||
onClick={() => {
|
||||
if (modeSelection === "direct") setCaptureMode("with-notes");
|
||||
else setModeDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
{capturing ? (
|
||||
<Spinner data-icon="inline-start" />
|
||||
@@ -302,16 +308,26 @@ export function GuideImageExport({
|
||||
const exportPreview = (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-sm font-medium">ตัวอย่างภาพส่งออก</p>
|
||||
<div className="flex min-h-32 items-center justify-center overflow-hidden rounded-lg border bg-muted/30 p-2">
|
||||
{previewing ? (
|
||||
<Spinner className="size-6" />
|
||||
) : previewUrl ? (
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex h-[clamp(8rem,50svh,32rem)] items-center justify-center overflow-hidden rounded-lg border bg-muted/30 p-2",
|
||||
modeSelection === "dialog" && squarePreview && "lg:h-80",
|
||||
)}
|
||||
>
|
||||
{previewUrl ? (
|
||||
// This is a temporary client-generated blob, not an optimizable remote asset.
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={previewUrl} alt="ตัวอย่างภาพที่จะส่งออก" className="max-h-[50svh] max-w-full object-contain" />
|
||||
<img src={previewUrl} alt="Preview image" className="size-full object-contain" />
|
||||
) : previewing ? (
|
||||
<Spinner className="size-6" />
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">ไม่สามารถสร้างตัวอย่างได้</p>
|
||||
<p className="text-sm text-muted-foreground">เกิดข้อผิดพลาดในการโชว์ Preview</p>
|
||||
)}
|
||||
{previewing && previewUrl ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Spinner className="size-6" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -444,7 +460,8 @@ export function GuideImageExport({
|
||||
<DialogContent
|
||||
className={cn(
|
||||
"max-h-[90svh] overflow-y-auto sm:max-w-3xl",
|
||||
previewPosition === "right" && "sm:w-fit sm:max-w-[calc(100%-2rem)]",
|
||||
previewPosition === "right" && "lg:max-w-[calc(100%-2rem)]",
|
||||
previewPosition === "right" && (squarePreview || compactDialog ? "lg:w-156" : "lg:w-236"),
|
||||
)}
|
||||
>
|
||||
<DialogHeader>
|
||||
@@ -452,12 +469,12 @@ export function GuideImageExport({
|
||||
<DialogDescription>เลือกตัวเลือกที่ต้องการก่อนส่งออกภาพ</DialogDescription>
|
||||
</DialogHeader>
|
||||
{previewPosition === "right" ? (
|
||||
<div className="grid min-h-0 gap-4 lg:grid-cols-[16rem_fit-content(42rem)]">
|
||||
<div className="grid min-h-0 gap-4 lg:grid-cols-[16rem_minmax(0,1fr)]">
|
||||
<div className="flex min-w-0 flex-col gap-4">
|
||||
{additionalOptions}
|
||||
{exportOptions}
|
||||
</div>
|
||||
<div className="min-w-0">{exportPreview}</div>
|
||||
<div className="w-full min-w-0">{exportPreview}</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Image from "next/image";
|
||||
import localFont from "next/font/local";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
@@ -64,6 +65,11 @@ import {
|
||||
import { publicGuideSections } from "@/lib/guides/public-sections";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const kanitSemiBold = localFont({
|
||||
src: "../../public/Kanit-SemiBold.ttf",
|
||||
weight: "600",
|
||||
});
|
||||
|
||||
export function publicSections(guide: PublicGuide) {
|
||||
return publicGuideSections(guide);
|
||||
}
|
||||
@@ -252,6 +258,7 @@ function GuideOverview({
|
||||
showLanguageSelector={false}
|
||||
localizeFileName={false}
|
||||
captureClassName="p-0"
|
||||
modeSelection="direct"
|
||||
>
|
||||
<MaterialPoster
|
||||
character={guide.character}
|
||||
@@ -416,7 +423,7 @@ function WeaponSectionContent({
|
||||
) : null}
|
||||
<header className="relative z-10 flex items-center justify-center text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<p className="text-sm font-medium text-primary">
|
||||
<p className={cn("text-sm font-medium text-primary", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
|
||||
อาวุธที่แนะนำ
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
@@ -432,7 +439,7 @@ function WeaponSectionContent({
|
||||
"drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]",
|
||||
)}
|
||||
/>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">
|
||||
<h1 className={cn("whitespace-nowrap font-heading text-4xl font-semibold", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
|
||||
Weapons
|
||||
</h1>
|
||||
</div>
|
||||
@@ -649,7 +656,7 @@ function ArtifactSectionContent({
|
||||
const recommendedConditions = profile?.recommendedConditions ?? [];
|
||||
const recommendedCards = recommendedConditions.map((condition, conditionIndex) => (
|
||||
<div key={conditionIndex} data-artifact-export-section="best-stats" data-recommended-condition={conditionIndex} className="flex flex-col gap-5">
|
||||
{exporting && recommendedConditions.length > 1 ? <p className="text-sm font-medium">{condition.name || <ExportText th={`เงื่อนไข ${conditionIndex + 1}`} en={`Condition ${conditionIndex + 1}`} />}</p> : null}
|
||||
{exporting && recommendedConditions.length > 1 ? <p className="text-center text-sm font-medium">{condition.name || <ExportText th={`เงื่อนไข ${conditionIndex + 1}`} en={`Condition ${conditionIndex + 1}`} />}</p> : null}
|
||||
{condition.note ? <p data-export-note className="whitespace-pre-wrap text-sm text-muted-foreground">{condition.note}</p> : null}
|
||||
{profile ? (
|
||||
<Card data-artifact-export-section="best-stats" className="artifact-export-main-stats overflow-hidden pt-0">
|
||||
@@ -706,7 +713,7 @@ function ArtifactSectionContent({
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"flex flex-col gap-6",
|
||||
"artifact-export-root flex flex-col gap-6",
|
||||
exporting &&
|
||||
"relative isolate overflow-hidden bg-[#070b20] p-10 text-white",
|
||||
)}
|
||||
@@ -716,9 +723,9 @@ function ArtifactSectionContent({
|
||||
backgroundUrl={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
/>
|
||||
) : null}
|
||||
<header className="relative z-10 flex items-center justify-center text-center">
|
||||
<header className={cn("relative z-10 flex items-center justify-center text-center", exporting && kanitSemiBold.className)}>
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<p className="text-sm font-medium text-primary">
|
||||
<p className={cn("text-sm font-medium text-primary", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
|
||||
อาร์ติแฟกต์และสแตทที่แนะนำ
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
@@ -734,7 +741,7 @@ function ArtifactSectionContent({
|
||||
"drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]",
|
||||
)}
|
||||
/>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">
|
||||
<h1 className={cn("whitespace-nowrap text-4xl font-semibold", !exporting && "font-heading", exporting && "drop-shadow-[0_3px_2px_rgba(0,0,0,0.9)]")}>
|
||||
Artifacts & Stats
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -499,6 +499,7 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
|
||||
showLanguageSelector={false}
|
||||
promptForLanguage
|
||||
previewPosition="right"
|
||||
compactDialog
|
||||
>
|
||||
<TeamExportImage team={team} />
|
||||
</GuideImageExport>
|
||||
|
||||
@@ -54,7 +54,10 @@ export function WeaponComparisonImage({
|
||||
<GuideExportBranding backgroundUrl={guide.character.splashImageUrl ?? guide.coverUrl} />
|
||||
<header className="relative z-10 flex flex-col items-center px-48 text-center drop-shadow-[0_4px_3px_rgba(0,0,0,0.9)]">
|
||||
<h2 className="max-w-full text-5xl leading-tight font-bold text-balance">
|
||||
{`${guide.character.name}’s Weapon Comparison`}
|
||||
<ExportText
|
||||
th={`เปรียบเทียบอาวุธ ${guide.character.name}`}
|
||||
en={`${guide.character.name}’s Weapon Comparison`}
|
||||
/>
|
||||
</h2>
|
||||
<p className="mt-1 text-4xl font-bold text-white">
|
||||
{metric === "overallPercent" ? (
|
||||
|
||||
Reference in New Issue
Block a user