Files
aragrp/components/featured-posts-grid.tsx
ar.badieiandCursor c2376b3b9e
Build & Push Docker Image / build (push) Has been cancelled
feat: redesign landing page with brand system and section blocks
Refresh the homepage around new hero/services/roadmap sections, shared design tokens, and updated brand assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 17:00:55 +03:30

183 lines
6.3 KiB
TypeScript

"use client";
import Link from "next/link";
import Image from "next/image";
import { ChevronLeft, ImageIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Container } from "@/components/container";
import { getFeaturedImageUrl, type WpPost } from "@/lib/wp-api";
import { formatDateShamsi } from "@/lib/format-date";
import { layout } from "@/lib/layout";
import { cn } from "@/lib/utils";
import { type } from "@/lib/typography";
function stripHtml(html: string): string {
return html.replace(/<[^>]*>/g, "").trim();
}
/** کارت با تصویر به‌صورت پس‌زمینه و عنوان + توضیح در پایین با overlay */
function PostCard({
post,
size = "default",
}: {
post: WpPost;
size?: "large" | "default";
}) {
const excerpt =
stripHtml(post.excerpt.rendered) ||
stripHtml(post.content.rendered).slice(0, size === "large" ? 280 : 120);
const date = post.date ? formatDateShamsi(post.date) : null;
const authorName = post._embedded?.author?.[0]?.name ?? null;
const thumbnailUrl = getFeaturedImageUrl(post, size === "large" ? "medium_large" : "medium");
const href = "#";
return (
<Link
href={href}
className={cn(
"group relative flex overflow-hidden rounded-xl border border-border shadow-sm transition-all duration-300 hover:shadow-lg hover:border-border/80",
size === "large" ? "min-h-[420px]" : "min-h-[200px]"
)}
>
{/* پس‌زمینه: تصویر تمام کارت */}
{thumbnailUrl ? (
<Image
src={thumbnailUrl}
alt=""
fill
className="object-cover transition-transform duration-500 ease-out group-hover:scale-105"
sizes={size === "large" ? "50vw, 600px" : "25vw, 300px"}
/>
) : (
<div className="absolute inset-0 flex items-center justify-center bg-muted" aria-hidden>
<ImageIcon className="size-12 text-muted-foreground/40" />
</div>
)}
{/* گرادیان شفاف و متن در پایین */}
<div className="absolute inset-0 bg-gradient-to-t from-black/85 via-black/30 to-transparent" />
<div
className="absolute bottom-0 left-0 right-0 p-4 text-white transition-all duration-300 md:-bottom-16 md:p-5 md:[mask-image:linear-gradient(to_bottom,black_0%,transparent)] md:[mask-size:100%_100%] group-hover:bottom-0 group-hover:[mask-image:none]"
>
<h3
className={cn(
"font-bold leading-tight line-clamp-2 drop-shadow-sm",
size === "large" ? cn(type.h3, "text-white") : cn(type["card-title"], "text-white")
)}
dangerouslySetInnerHTML={{ __html: post.title.rendered }}
/>
<p
className={cn(
"mt-2 text-white/90 leading-relaxed line-clamp-2",
size === "large" ? cn(type.body, "line-clamp-3 text-white/90") : cn(type["body-sm"], "text-white/90")
)}
>
{excerpt}
</p>
</div>
</Link>
);
}
/** کارت ردیفی: تصویر کنار عنوان، بدون توضیح */
function PostCardRow({ post }: { post: WpPost }) {
const date = post.date ? formatDateShamsi(post.date) : null;
const authorName = post._embedded?.author?.[0]?.name ?? null;
const thumbnailUrl = getFeaturedImageUrl(post, "medium");
const href = "#";
return (
<Link
href={href}
className="group flex min-h-[70px] overflow-hidden rounded-xl transition-all duration-300"
>
{/* نیمه عنوان + نویسنده */}
<div className="flex flex-1 flex-col justify-center p-4">
<h3
className={cn(type["card-title"], "line-clamp-2 text-foreground")}
dangerouslySetInnerHTML={{ __html: post.title.rendered }}
/>
</div>
</Link>
);
}
const DEFAULT_TITLE = "از دانشنامه";
const DEFAULT_CAPTION =
"از داده تا بینش؛ یادداشت‌ها و تجربه‌هایی که مسیر شما را روشن‌تر می‌کنند.";
const DEFAULT_CTA_LABEL = "مشاهده همه مقالات";
const DEFAULT_CTA_HREF = "#";
interface FeaturedPostsGridProps {
posts: WpPost[];
/** عنوان بخش */
title?: string;
/** کپشن زیر عنوان */
caption?: string;
/** متن دکمه CTA */
ctaLabel?: string;
/** لینک CTA (پیش‌فرض: # — آزمایشی) */
ctaHref?: string;
}
export function FeaturedPostsGrid({
posts,
title = DEFAULT_TITLE,
caption = DEFAULT_CAPTION,
ctaLabel = DEFAULT_CTA_LABEL,
ctaHref = DEFAULT_CTA_HREF,
}: FeaturedPostsGridProps) {
if (!posts.length) return null;
const [first, second, third, fourth] = posts;
return (
<section
className={cn("section-shell bg-section-canvas", layout.sectionY)}
aria-label={title}
>
<Container>
<header className="mb-10 md:mb-12">
<h2 className={cn(type.h2, "text-center")}>
{title}
</h2>
<p className={cn(type["section-intro"], "mx-auto mt-3 max-w-2xl")}>
{caption}
</p>
</header>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3 lg:gap-5">
{/* کارت بزرگ: در RTL سمت چپ، در LTR سمت راست */}
{first && (
<div className="lg:col-span-2 lg:min-h-0">
<PostCard post={first} size="large" />
</div>
)}
{/* سه کارت کوچک: یک بالا، دو پایین */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:order-1 lg:grid-cols-1 lg:gap-5">
{second && (
<div className="min-h-[200px]">
<PostCard post={second} size="default" />
</div>
)}
{third && (
<div className="min-h-[120px]">
<PostCard post={third} size="default" />
</div>
)}
</div>
</div>
<div className="mt-12 flex flex-col items-center justify-center gap-3 text-center md:mt-16">
<p className={type.caption}>
مطالب بیشتر در دانشنامه
</p>
<Button asChild size="lg">
<Link href={ctaHref}>
{ctaLabel}
<ChevronLeft aria-hidden />
</Link>
</Button>
</div>
</Container>
</section>
);
}