93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import type { CSSProperties } from "react";
|
|
import { toCssAspectRatio } from "@/lib/format";
|
|
import type { StaticImageSlot } from "@/lib/types";
|
|
|
|
// Shared renderer for the "one-banner-bg" / "two-banner-bg" / "three-banner-bg"
|
|
// static-image slots: url -> <img src>, linkUrl -> wrap in <a>, slot.aspectRatio
|
|
// drives the frame. An empty or failed slot keeps a flat, labelled placeholder
|
|
// (this site's usual stand-in for undesigned art, see
|
|
// public/images/categories/{id}.svg) instead of a fake photo, sized so the
|
|
// business/designer can see exactly what to fill in.
|
|
function BannerGrid({
|
|
slot,
|
|
count,
|
|
defaultAspect,
|
|
recommendedWidth,
|
|
cols,
|
|
}: {
|
|
slot?: StaticImageSlot;
|
|
count: number;
|
|
defaultAspect: string;
|
|
recommendedWidth: number;
|
|
cols: string;
|
|
}) {
|
|
const images = (slot?.images ?? []).slice(0, count);
|
|
const cssAspect = toCssAspectRatio(slot?.aspectRatio) ?? defaultAspect;
|
|
const aspectStyle = { aspectRatio: cssAspect } as CSSProperties;
|
|
const cells = Array.from({ length: count }, (_, i) => images[i]);
|
|
|
|
return (
|
|
<section className={`boxed mt-10`} aria-label="بنرهای تبلیغاتی">
|
|
<div className={`grid gap-3 ${cols}`}>
|
|
{cells.map((img, i) => {
|
|
if (!img) {
|
|
return (
|
|
<div
|
|
key={i}
|
|
className="flex flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-line bg-surface text-body"
|
|
style={aspectStyle}
|
|
>
|
|
<span className="text-[12px]">بنر تبلیغاتی</span>
|
|
<span className="text-[11px] opacity-70" dir="ltr">
|
|
{recommendedWidth}px · {cssAspect.replace("/", ":")}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const media = (
|
|
<Image
|
|
src={img.url}
|
|
alt={img.titleFa || img.titleEn || "بنر تبلیغاتی"}
|
|
fill
|
|
sizes={`(max-width: 768px) 100vw, ${recommendedWidth}px`}
|
|
className="object-cover"
|
|
/>
|
|
);
|
|
const frameClass = "relative block overflow-hidden rounded-xl bg-surface";
|
|
|
|
// Only wrap in <a> when the slot actually carries a link — an
|
|
// undesignated banner shouldn't become a dead click target.
|
|
return img.linkUrl ? (
|
|
<Link key={img.id} href={img.linkUrl} className={frameClass} style={aspectStyle}>
|
|
{media}
|
|
</Link>
|
|
) : (
|
|
<div key={img.id} className={frameClass} style={aspectStyle}>
|
|
{media}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export function OneBanner({ slot }: { slot?: StaticImageSlot }) {
|
|
return <BannerGrid slot={slot} count={1} defaultAspect="16/5" recommendedWidth={1200} cols="grid-cols-1" />;
|
|
}
|
|
|
|
export function TwoBanners({ slot }: { slot?: StaticImageSlot }) {
|
|
return (
|
|
<BannerGrid slot={slot} count={2} defaultAspect="16/9" recommendedWidth={580} cols="grid-cols-1 md:grid-cols-2" />
|
|
);
|
|
}
|
|
|
|
export function ThreeBanners({ slot }: { slot?: StaticImageSlot }) {
|
|
return (
|
|
<BannerGrid slot={slot} count={3} defaultAspect="4/3" recommendedWidth={380} cols="grid-cols-1 sm:grid-cols-3" />
|
|
);
|
|
}
|