243 lines
8.1 KiB
TypeScript
243 lines
8.1 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import { useMemo, useState } from "react";
|
|
import type { ProductDetail, ProductVariantOption } from "@/data/product-detail";
|
|
|
|
type ProductBuyBoxProps = {
|
|
product: ProductDetail;
|
|
};
|
|
|
|
function OptionRow({
|
|
labelFa,
|
|
labelEn,
|
|
options,
|
|
value,
|
|
onChange,
|
|
kind,
|
|
}: {
|
|
labelFa: string;
|
|
labelEn: string;
|
|
options: ProductVariantOption[];
|
|
value: string;
|
|
onChange: (id: string) => void;
|
|
kind: "color" | "size" | "package";
|
|
}) {
|
|
if (!options.length) return null;
|
|
|
|
return (
|
|
<div className="space-y-2.5">
|
|
<div className="flex items-baseline justify-between gap-3">
|
|
<p className="text-sm text-ink">{labelFa}</p>
|
|
<p dir="ltr" className="font-montserrat text-[10px] tracking-[0.14em] text-muted uppercase">
|
|
{labelEn}
|
|
</p>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{options.map((opt) => {
|
|
const selected = value === opt.id;
|
|
const disabled = opt.available === false;
|
|
const tip = opt.labelEn || opt.labelFa;
|
|
if (kind === "color") {
|
|
return (
|
|
<button
|
|
key={opt.id}
|
|
type="button"
|
|
disabled={disabled}
|
|
title={tip}
|
|
aria-label={tip}
|
|
onClick={() => onChange(opt.id)}
|
|
className={`group/swatch relative h-9 w-9 rounded-full border transition-transform ${
|
|
selected ? "border-ink scale-105" : "border-line"
|
|
} ${disabled ? "opacity-35 cursor-not-allowed" : "hover:border-ink"}`}
|
|
aria-pressed={selected}
|
|
>
|
|
<span
|
|
className="absolute inset-1 rounded-full border border-black/5"
|
|
style={{ background: opt.swatch || "#ccc" }}
|
|
/>
|
|
<span
|
|
role="tooltip"
|
|
className="pointer-events-none absolute bottom-[calc(100%+6px)] left-1/2 z-20 -translate-x-1/2 whitespace-nowrap rounded-[3px] bg-ink px-2 py-1 font-montserrat text-[10px] uppercase tracking-[0.08em] text-white opacity-0 transition-opacity group-hover/swatch:opacity-100"
|
|
>
|
|
{tip}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|
|
return (
|
|
<button
|
|
key={opt.id}
|
|
type="button"
|
|
disabled={disabled}
|
|
title={tip}
|
|
aria-label={tip}
|
|
onClick={() => onChange(opt.id)}
|
|
className={`min-w-[2.75rem] border px-3 py-2 text-sm uppercase tracking-[0.04em] transition-colors ${
|
|
selected
|
|
? "border-ink bg-ink text-white"
|
|
: "border-line bg-white text-ink hover:border-ink"
|
|
} ${disabled ? "opacity-35 cursor-not-allowed line-through" : ""}`}
|
|
aria-pressed={selected}
|
|
>
|
|
{kind === "size" ? opt.labelEn.toUpperCase() : opt.labelFa}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function ProductBuyBox({ product }: ProductBuyBoxProps) {
|
|
const firstColor =
|
|
product.colors.find((c) => c.available !== false)?.id ??
|
|
product.colors[0]?.id ??
|
|
"";
|
|
const firstSize =
|
|
product.sizes.find((s) => s.available !== false)?.id ??
|
|
product.sizes[0]?.id ??
|
|
"";
|
|
const firstPackage =
|
|
product.packages.find((p) => p.available !== false)?.id ??
|
|
product.packages[0]?.id ??
|
|
"";
|
|
|
|
const [color, setColor] = useState(firstColor);
|
|
const [size, setSize] = useState(firstSize);
|
|
const [pkg, setPkg] = useState(firstPackage);
|
|
|
|
const selectedColor = useMemo(
|
|
() => product.colors.find((c) => c.id === color),
|
|
[color, product.colors],
|
|
);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 lg:gap-8">
|
|
<div className="text-right">
|
|
<h2 className="font-doran text-[clamp(1.5rem,2.5vw,2rem)] font-medium text-ink">
|
|
{product.nameFa}
|
|
</h2>
|
|
<p
|
|
dir="ltr"
|
|
className="mt-2 text-right font-montserrat text-xs font-light uppercase tracking-[0.18em] text-muted"
|
|
>
|
|
{product.nameEn}
|
|
</p>
|
|
<p className="mt-4 text-sm leading-7 text-ink/75">{product.summary}</p>
|
|
</div>
|
|
|
|
<div className="space-y-5">
|
|
<OptionRow
|
|
labelFa={`رنگ${selectedColor ? ` · ${selectedColor.labelFa}` : ""}`}
|
|
labelEn="Color"
|
|
options={product.colors}
|
|
value={color}
|
|
onChange={setColor}
|
|
kind="color"
|
|
/>
|
|
<OptionRow
|
|
labelFa="سایز"
|
|
labelEn="Size"
|
|
options={product.sizes}
|
|
value={size}
|
|
onChange={setSize}
|
|
kind="size"
|
|
/>
|
|
<OptionRow
|
|
labelFa="بستهبندی"
|
|
labelEn="Package"
|
|
options={product.packages}
|
|
value={pkg}
|
|
onChange={setPkg}
|
|
kind="package"
|
|
/>
|
|
</div>
|
|
|
|
<div dir="ltr" className="flex items-baseline gap-2 border-y border-line py-4">
|
|
<p className="font-montserrat text-2xl text-ink sm:text-3xl">
|
|
{product.price}
|
|
</p>
|
|
<p className="text-xs text-muted">IRT</p>
|
|
</div>
|
|
|
|
{/* Sizing guide frame */}
|
|
<div className="relative overflow-hidden rounded-[5px] border border-dashed border-ink/25 bg-[#f7f7f5]">
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-0 opacity-60"
|
|
style={{
|
|
backgroundImage:
|
|
"radial-gradient(circle, rgba(10,10,10,0.12) 0.7px, transparent 1px)",
|
|
backgroundSize: "12px 12px",
|
|
}}
|
|
/>
|
|
<div className="relative grid gap-4 p-4 sm:grid-cols-[minmax(0,1fr)_140px] sm:items-center">
|
|
<div>
|
|
<p className="font-doran text-lg text-ink">راهنمای سایز</p>
|
|
<p
|
|
dir="ltr"
|
|
className="mt-1 font-montserrat text-[10px] uppercase tracking-[0.16em] text-muted"
|
|
>
|
|
Sizing guide
|
|
</p>
|
|
<p className="mt-2 text-xs leading-6 text-ink/70">
|
|
{product.sizeGuideNote}
|
|
</p>
|
|
<a
|
|
href="#product-tabs"
|
|
className="mt-3 inline-flex text-sm text-ink underline underline-offset-4 hover:opacity-70"
|
|
>
|
|
مشاهده جدول اندازهها
|
|
</a>
|
|
</div>
|
|
<div className="relative mx-auto aspect-[3/4] w-full max-w-[140px] overflow-hidden rounded-[5px] border border-line bg-white">
|
|
{product.sizeGuideImage ? (
|
|
<Image
|
|
src={product.sizeGuideImage}
|
|
alt=""
|
|
fill
|
|
sizes="140px"
|
|
className="object-cover object-top opacity-90"
|
|
/>
|
|
) : (
|
|
<span className="absolute inset-0 flex items-center justify-center text-[11px] text-muted">
|
|
Size chart
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2 sm:flex-row">
|
|
<button
|
|
type="button"
|
|
className="group relative inline-flex h-12 flex-1 items-center justify-center overflow-hidden rounded-[5px] border border-ink bg-ink px-4 text-white"
|
|
>
|
|
<span
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-0 origin-bottom scale-y-0 bg-white transition-transform duration-[400ms] ease-out group-hover:scale-y-100"
|
|
/>
|
|
<span className="relative z-10 text-sm transition-colors duration-[400ms] group-hover:text-ink sm:text-base">
|
|
افزودن به سبد
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="inline-flex h-12 items-center justify-center gap-2 rounded-[5px] border border-line px-4 text-sm text-ink transition-colors hover:border-ink"
|
|
aria-label="افزودن به علاقهمندی"
|
|
>
|
|
<Image
|
|
src="/images/home/icons/bookmark.svg"
|
|
alt=""
|
|
width={20}
|
|
height={20}
|
|
unoptimized
|
|
/>
|
|
علاقهمندی
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|