Files
aragrp/components/featured-posts-grid.tsx
T
ar.azizan f7a163c4aa feat: add featured posts grid and update blog terminology
- Introduced a new FeaturedPostsGrid component to display featured posts on the homepage.
- Updated the Home component to fetch and render featured posts from the WordPress API.
- Changed instances of "بلاگ" to "دانشنامه" across various components for consistent terminology.
2026-02-08 20:33:07 +03:30

177 lines
6.4 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 { getFeaturedImageUrl, type WpPost } from "@/lib/wp-api";
import { formatDateShamsi } from "@/lib/format-date";
import { cn } from "@/lib/utils";
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 = `/blog/${encodeURIComponent(post.slug)}`;
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" ? "text-lg md:text-xl" : "text-base"
)}
dangerouslySetInnerHTML={{ __html: post.title.rendered }}
/>
<p
className={cn(
"mt-2 text-white/90 leading-relaxed line-clamp-2",
size === "large" ? "text-sm md:text-base line-clamp-3" : "text-sm"
)}
>
{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 = `/blog/${encodeURIComponent(post.slug)}`;
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="font-bold leading-tight text-foreground line-clamp-2 text-base"
dangerouslySetInnerHTML={{ __html: post.title.rendered }}
/>
</div>
</Link>
);
}
const DEFAULT_TITLE = "از دانشنامه";
const DEFAULT_CAPTION =
"از داده تا بینش؛ یادداشت‌ها و تجربه‌هایی که مسیر شما را روشن‌تر می‌کنند.";
const DEFAULT_CTA_LABEL = "مشاهده همه مقالات";
const DEFAULT_CTA_HREF = "/blog";
interface FeaturedPostsGridProps {
posts: WpPost[];
/** عنوان بخش */
title?: string;
/** کپشن زیر عنوان */
caption?: string;
/** متن دکمه CTA */
ctaLabel?: string;
/** لینک CTA (پیش‌فرض: /blog) */
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="bg-background py-16 md:py-24" aria-label={title}>
<div className="mx-auto max-w-6xl px-4 sm:px-6">
<header className="mb-10 text-center md:mb-12">
<h2 className="text-2xl font-bold text-foreground md:text-3xl">
{title}
</h2>
<p className="mt-3 max-w-2xl mx-auto text-muted-foreground text-sm leading-relaxed md:text-base">
{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="text-sm text-muted-foreground">
مطالب بیشتر در دانشنامه
</p>
<Button asChild size="lg" variant="outline" className="rounded-full px-6 font-medium shadow-sm transition-all hover:shadow-md">
<Link href={ctaHref} className="inline-flex items-center gap-2">
{ctaLabel}
<ChevronLeft className="size-4" aria-hidden />
</Link>
</Button>
</div>
</div>
</section>
);
}