Build the RTL Next.js experience with live catalog, journal, customer interactions, and SEO-ready canonical routes. Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { Phone } from "lucide-react";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import {
|
|
productImage,
|
|
productUrl,
|
|
type Product,
|
|
} from "@/lib/meshkee";
|
|
|
|
function getPageSize() {
|
|
if (typeof window === "undefined") return 5;
|
|
if (window.innerWidth < 620) return 1;
|
|
if (window.innerWidth < 900) return 2;
|
|
return 5;
|
|
}
|
|
|
|
export function ProductCarousel({ products }: { products: Product[] }) {
|
|
const [pageSize, setPageSize] = useState(5);
|
|
const [page, setPage] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const update = () => {
|
|
setPageSize(getPageSize());
|
|
setPage(0);
|
|
};
|
|
update();
|
|
window.addEventListener("resize", update);
|
|
return () => window.removeEventListener("resize", update);
|
|
}, []);
|
|
|
|
const pages = Math.max(1, products.length - pageSize + 1);
|
|
const visibleProducts = useMemo(() => {
|
|
const start = page;
|
|
return products.slice(start, start + pageSize);
|
|
}, [page, pageSize]);
|
|
|
|
return (
|
|
<div className="product-carousel">
|
|
<div
|
|
key={`${page}-${pageSize}`}
|
|
className="source-products-grid"
|
|
style={{ "--visible-products": pageSize } as React.CSSProperties}
|
|
aria-live="polite"
|
|
>
|
|
{visibleProducts.map((product) => (
|
|
<article className="source-product-card" key={product.id}>
|
|
<div className="source-product-image">
|
|
<Link href={productUrl(product)} aria-label={product.nameFa || product.title}>
|
|
<Image
|
|
src={productImage(product) || "/images/hero.jpg"}
|
|
alt={product.nameFa || product.title}
|
|
fill
|
|
sizes="(max-width: 620px) 100vw, (max-width: 900px) 50vw, (max-width: 1200px) 33vw, 20vw"
|
|
/>
|
|
</Link>
|
|
</div>
|
|
<div className="source-product-copy">
|
|
<div>
|
|
<h3>
|
|
<Link href={productUrl(product)}>{product.nameFa || product.title}</Link>
|
|
</h3>
|
|
<a href="tel:+989301293902">تماس بگیرید</a>
|
|
</div>
|
|
<a
|
|
className="product-phone"
|
|
href="tel:+989301293902"
|
|
aria-label={`تماس برای ${product.title}`}
|
|
>
|
|
<Phone />
|
|
</a>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
|
|
{pages > 1 && (
|
|
<div className="carousel-dots" aria-label="صفحات محصولات">
|
|
{Array.from({ length: pages }, (_, index) => (
|
|
<button
|
|
key={index}
|
|
type="button"
|
|
className={index === page ? "active" : ""}
|
|
onClick={() => setPage(index)}
|
|
aria-label={`صفحه ${index + 1}`}
|
|
aria-current={index === page ? "true" : undefined}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|