136 lines
5.5 KiB
TypeScript
136 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
|
import { Reveal } from "@/components/Reveal";
|
|
import type { DisplayNewsItem } from "@/lib/api";
|
|
|
|
/** Horizontal snap-scroll rail of news/blog cards, matching the site's other carousels. */
|
|
export function NewsCarousel({
|
|
items,
|
|
basePath,
|
|
}: {
|
|
items: DisplayNewsItem[];
|
|
basePath: string;
|
|
}) {
|
|
const trackRef = useRef<HTMLUListElement>(null);
|
|
const [atStart, setAtStart] = useState(true);
|
|
const [atEnd, setAtEnd] = useState(false);
|
|
|
|
const syncEdges = useCallback(() => {
|
|
const el = trackRef.current;
|
|
if (!el) return;
|
|
const travelled = Math.abs(el.scrollLeft);
|
|
const max = el.scrollWidth - el.clientWidth;
|
|
setAtStart(travelled < 8);
|
|
setAtEnd(travelled >= max - 8);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
syncEdges();
|
|
const el = trackRef.current;
|
|
if (!el) return;
|
|
el.addEventListener("scroll", syncEdges, { passive: true });
|
|
window.addEventListener("resize", syncEdges);
|
|
return () => {
|
|
el.removeEventListener("scroll", syncEdges);
|
|
window.removeEventListener("resize", syncEdges);
|
|
};
|
|
}, [syncEdges]);
|
|
|
|
const step = (direction: "next" | "prev") => {
|
|
const el = trackRef.current;
|
|
if (!el) return;
|
|
const card = el.querySelector("li");
|
|
const amount = card ? card.getBoundingClientRect().width + 24 : el.clientWidth * 0.8;
|
|
el.scrollBy({ left: direction === "next" ? -amount : amount, behavior: "smooth" });
|
|
};
|
|
|
|
const showArrows = items.length > 3;
|
|
|
|
return (
|
|
<div>
|
|
{showArrows && (
|
|
<div className="mx-auto mb-6 flex max-w-[1400px] justify-end gap-2 px-6 lg:px-12">
|
|
<button
|
|
type="button"
|
|
onClick={() => step("prev")}
|
|
disabled={atStart}
|
|
aria-label="اخبار قبلی"
|
|
className="flex h-11 w-11 items-center justify-center border-0 text-ink transition-colors duration-300 hover:text-gold disabled:pointer-events-none disabled:opacity-30"
|
|
>
|
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-4 w-4">
|
|
<path d="M5 12h14m0 0-6-6m6 6-6 6" stroke="currentColor" strokeWidth="1.6" />
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => step("next")}
|
|
disabled={atEnd}
|
|
aria-label="اخبار بعدی"
|
|
className="flex h-11 w-11 items-center justify-center border-0 text-ink transition-colors duration-300 hover:text-gold disabled:pointer-events-none disabled:opacity-30"
|
|
>
|
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-4 w-4">
|
|
<path d="M19 12H5m0 0 6-6m-6 6 6 6" stroke="currentColor" strokeWidth="1.6" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
<ul
|
|
ref={trackRef}
|
|
className="no-scrollbar flex snap-x snap-mandatory gap-6 overflow-x-auto scroll-smooth px-6 pb-2 lg:gap-8 lg:px-12"
|
|
>
|
|
{items.map((item, i) => (
|
|
<li
|
|
key={item.slug}
|
|
className="w-[86vw] shrink-0 snap-start sm:w-[62vw] lg:w-[48vw] xl:w-[42rem]"
|
|
>
|
|
<Reveal delay={(i % 4) * 80}>
|
|
<Link href={`${basePath}/${item.slug}`} className="group block h-full">
|
|
<article className="grid h-full bg-paper lg:grid-cols-2">
|
|
<div className="relative aspect-square overflow-hidden">
|
|
<Image
|
|
src={item.image}
|
|
alt={item.title}
|
|
fill
|
|
quality={80}
|
|
sizes="(max-width: 640px) 86vw, (max-width: 1024px) 62vw, 22rem"
|
|
loading={i < 2 ? "eager" : "lazy"}
|
|
className="object-cover transition-transform duration-[1100ms] ease-out-soft group-hover:scale-[1.06]"
|
|
/>
|
|
<div className="absolute inset-0 bg-ink/0 transition-colors duration-500 group-hover:bg-ink/20" />
|
|
</div>
|
|
|
|
<div className="flex flex-1 flex-col p-7 lg:p-8">
|
|
<time className="flex items-center gap-2.5 text-[0.8rem] text-muted">
|
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-3.5 w-3.5 text-gold">
|
|
<rect x="3.5" y="5" width="17" height="15.5" rx="2" stroke="currentColor" strokeWidth="1.4" />
|
|
<path d="M3.5 10h17M8 3.5V6M16 3.5V6" stroke="currentColor" strokeWidth="1.4" />
|
|
</svg>
|
|
{item.date}
|
|
</time>
|
|
|
|
<h3 className="mt-4 text-[1.1rem] leading-[1.75] font-bold text-ink transition-colors duration-300 group-hover:text-gold">
|
|
{item.title}
|
|
</h3>
|
|
|
|
<span className="mt-auto flex w-max items-center gap-2 border-b border-ink/45 pt-7 pb-1 text-sm text-muted transition-colors duration-300 group-hover:border-gold group-hover:text-gold">
|
|
ادامه مطلب
|
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-3.5 w-3.5 transition-transform duration-300 group-hover:-translate-x-1">
|
|
<path d="M19 12H5m0 0 6-6m-6 6 6 6" stroke="currentColor" strokeWidth="1.8" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
</article>
|
|
</Link>
|
|
</Reveal>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|