Files
reui-blocks/blocks/blog27.tsx
T

596 lines
21 KiB
TypeScript

"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowRight, Slash } from "lucide-react";
import { Fragment, useCallback, useMemo, useState } from "react";
import type { ControllerRenderProps } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { AspectRatio } from "@/components/ui/aspect-ratio";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Field } from "@/components/ui/field";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
interface BreadcrumbItem {
label: string;
link: string;
}
interface Post {
categoryKey: string;
category: string;
title: string;
summary: string;
link: string;
cta: string;
thumbnail: string;
}
interface Category {
label: string;
value: string;
}
interface FilterFormProps {
categories: Array<Category>;
onCategoryChange: (selectedCategories: string[]) => void;
}
interface BlogsResultProps {
posts: Array<Post>;
categories: Array<Category>;
}
interface BreadcrumbBlogProps {
breadcrumb: Array<BreadcrumbItem>;
}
const POSTS_PER_PAGE = 6;
const BREADCRUMB: Array<BreadcrumbItem> = [
{
label: "منابع",
link: "#",
},
{
label: "وبلاگ",
link: "#",
},
];
const CATEGORIES: Array<Category> = [
{
label: "همه",
value: "all",
},
{
label: "بهره‌وری",
value: "productivity",
},
{
label: "دسترس‌پذیری",
value: "accessibility",
},
{
label: "کارایی",
value: "performance",
},
];
const PRIMARY_POST: Post = {
categoryKey: "all",
category: "ویژه نوآوری",
title: "چگونه هوش مصنوعی توسعه فرانت‌اند را متحول می‌کند",
summary:
"ببینید ابزارهایی مانند GitHub Copilot و دستیارهای هوش مصنوعی چگونه فرایند طراحی رابط کاربری و توسعه نرم‌افزار را متحول می‌کنند.",
link: "#",
cta: "بیشتر بدانید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-1.svg",
};
const POSTS: Array<Post> = [
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "۵ افزونه ویرایشگر کد که ساعت‌ها وقت شما را ذخیره می‌کند",
summary:
"افزونه‌های ضروری برای افزایش بهره‌وری کدنویسی و ساده‌تر کردن گردش کار.",
link: "#",
cta: "ویرایشگر را تقویت کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-2.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "مدیریت زمان برای توسعه‌دهندگان: چه چیزی واقعاً جواب می‌دهد",
summary:
"استراتژی‌های اثبات‌شده برای جلوگیری از فرسودگی و مدیریت وظایف بدون استرس.",
link: "#",
cta: "زمان را مدیریت کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-3.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "خودکارسازی گردش کار با اجرای خودکار وظایفها",
summary:
"از Gulp، اسکریپت‌های npm و GitHub Actions برای خودکار کردن کارهای تکراری توسعه استفاده کنید.",
link: "#",
cta: "همین حالا خودکار کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-4.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "روتین‌های روزانه مؤثر برای توسعه‌دهندگان",
summary:
"روتین‌هایی که توسعه‌دهندگان برتر برای ماندن پرانرژی، خلاق و متمرکز دنبال می‌کنند.",
link: "#",
cta: "جریان کاری خود را بیابید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-5.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "تسلط بر کنترل نسخه با این میانبرها",
summary:
"با استفاده از Aliasها و ترفندهای Git، مدیریت نسخه و فرایند توسعه را سریع‌تر و ساده‌تر کنید.",
link: "#",
cta: "Git را سریع‌تر کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-6.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "کاهش تعویض زمینه در کار توسعه",
summary:
"حواس‌پرتی را کم کنید و با تمرکز عمیق روی کد پیش بروید.",
link: "#",
cta: "متمرکز بمانید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-1.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "راه‌اندازی دورکاری: ابزارهای محیط بدون حواس‌پرتی",
summary:
"فضا و استک نرم‌افزاری را برای حداکثر بهره‌وری در کار از خانه تنظیم کنید.",
link: "#",
cta: "محیط کار را ارتقا دهید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-2.svg",
},
{
categoryKey: "productivity",
category: "بهره‌وری",
title: "پومودورو برای برنامه‌نویسان: واقعاً مؤثر است؟",
summary:
"بررسی عملی تکنیک پومودورو و اثربخشی آن در توسعه نرم‌افزار.",
link: "#",
cta: "روش را امتحان کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-3.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "چرا دسترس‌پذیری باید بخشی از نسخه اولیه محصول شما باشد",
summary:
"فراگیر کردن محصول از روز اول، قابلیت استفاده و دسترسی را بهبود می‌دهد.",
link: "#",
cta: "دلیلش را بدانید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-4.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "استفاده درست از نقش‌های برچسب دسترس‌پذیری در وب‌اپ",
summary:
"یاد بگیرید چگونه با نقش‌ها و Landmarkهای ARIA، دسترسی‌پذیری برای صفحه‌خوان‌ها را بهبود دهید.",
link: "#",
cta: "معناشناسی را بهبود دهید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-5.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "نکات کنتراست رنگ برای خوانایی بهتر",
summary:
"ترکیب‌های رنگی دسترس‌پذیر مطابق استاندارد WCAG را بیاموزید.",
link: "#",
cta: "رنگ‌ها را اصلاح کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-6.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "ناوبری با کیبورد: تجربه‌ای که نادیده گرفته می‌شود",
summary:
"وب‌سایت را کاملاً با کیبورد قابل استفاده کنید برای دسترس‌پذیری و سرعت.",
link: "#",
cta: "ناوبری را تست کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-1.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "فرم‌های دسترس‌پذیر: برچسب، خطا و بازخورد",
summary:
"با دستورالعمل‌های واضح، فرم‌هایی طراحی کنید که هم برای کاربران و هم برای صفحه‌خوان‌ها قابل درک باشند.",
link: "#",
cta: "فرم‌ها را اصلاح کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-2.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "تست با صفحه‌خوان: راهنمای مبتدی",
summary:
"یاد بگیرید چگونه وب‌سایت خود را با صفحه‌خوان‌های رایج آزمایش و مشکلات دسترس‌پذیری را شناسایی کنید.",
link: "#",
cta: "تست را شروع کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-3.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "تفکر طراحی فراگیر در توسعه رابط کاربری",
summary:
"رابط‌هایی طراحی کنید که از ابتدا همه توانایی‌ها را در نظر بگیرند.",
link: "#",
cta: "برای همه طراحی کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-4.svg",
},
{
categoryKey: "accessibility",
category: "دسترس‌پذیری",
title: "ممیزی دسترس‌پذیری: ابزارها و چک‌لیست‌ها",
summary:
"ممیزی کامل با Axe، Lighthouse و چک‌لیست‌های دستی انجام دهید.",
link: "#",
cta: "ممیزی کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-5.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "بارگذاری تنبل تصاویر با استاندارد وب مدرن",
summary:
"زمان بارگذاری را با lazy-loading بومی و استراتژی‌های جایگزین بهبود دهید.",
link: "#",
cta: "تصاویر را بهینه کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-6.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "فشرده‌سازی برنامه‌نویسی وب بدون ایجاد اختلال در برنامه",
summary:
"بهترین روش‌های minify و tree-shaking برای bundleهای سریع‌تر.",
link: "#",
cta: "کد را کوچک کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-1.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "شاخص‌های عملکرد وب توضیح داده شد: جابجایی چیدمان، زمان بارگذاری اصلی، تأخیر اولین تعامل",
summary:
"اندازه‌گیری و بهبود Core Web Vitals برای تجربه کاربری بهتر.",
link: "#",
cta: "شاخص‌ها را بهبود دهید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-2.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "رندر سمت سرور در برابر کلاینت: کدام سریع‌تر است؟",
summary:
"SSR و CSR را مقایسه کنید و بدانید هر کدام چه زمانی برای کارایی بهتر مناسب‌اند.",
link: "#",
cta: "گزینه‌ها را بررسی کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-3.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "بهینه‌سازی فونت برای بارگذاری سریع‌تر",
summary:
"فونت‌ها را بدون مسدود کردن رندر یا ایجاد جابه‌جایی چیدمان بارگذاری کنید.",
link: "#",
cta: "فونت‌ها را سریع کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-4.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "کاهش حجم بسته با تقسیم هوشمند بار",
summary:
"با import پویا و chunking مبتنی بر مسیر، زمان بار اولیه را کم کنید.",
link: "#",
cta: "تقسیم‌بندی کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-5.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "استراتژی‌های کش برای وب‌اپ‌های مدرن",
summary:
"از کش HTTP، service worker و CDN برای سرعت و پشتیبانی آفلاین بهره ببرید.",
link: "#",
cta: "هوشمندانه کش کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-6.svg",
},
{
categoryKey: "performance",
category: "کارایی",
title: "تحلیل گلوگاه‌های کارایی با ابزار تحلیل مرورگر",
summary:
"با تب Performance در DevTools مشکلات زمان اجرا را پیدا و رفع کنید.",
link: "#",
cta: "همین حالا تحلیل کنید",
thumbnail:
"/block-media/shadcnblocks/block/placeholder-1.svg",
},
];
const FilterFormSchema = z.object({
items: z.array(z.string()).refine((value) => value.length > 0, {
message: "حداقل یک دسته باید انتخاب شود.",
}),
});
const FilterForm = ({ categories, onCategoryChange }: FilterFormProps) => {
const form = useForm<z.infer<typeof FilterFormSchema>>({
resolver: zodResolver(FilterFormSchema),
defaultValues: {
items: [CATEGORIES[0].value],
},
});
const handleCheckboxChange = useCallback(
(
checked: boolean | string,
categoryValue: string,
field: ControllerRenderProps<z.infer<typeof FilterFormSchema>, "items">,
) => {
let updatedValues = checked
? [...field.value, categoryValue]
: field.value.filter((value: string) => value !== categoryValue);
if (updatedValues.length === 0) {
form.setValue("items", ["all"]);
onCategoryChange(["all"]);
return;
}
if (updatedValues.includes("all")) {
updatedValues = updatedValues.filter((v: string) => v !== "all");
}
if (JSON.stringify(field.value) !== JSON.stringify(updatedValues)) {
form.setValue("items", updatedValues);
onCategoryChange(updatedValues);
}
},
[form, onCategoryChange],
);
return (
<form>
<Controller
control={form.control}
name="items"
render={({ field }) => (
<Field className="flex w-full flex-wrap items-center gap-2.5">
{categories.map((category) => {
const isChecked = field.value?.includes(category.value);
return (
<Field
key={category.value}
className="flex flex-row items-start gap-3 space-y-0"
>
<Label className="flex cursor-pointer items-center gap-2.5 rounded-full bg-muted px-2.5 py-1.5">
<div>{category.label}</div>
<Checkbox
checked={isChecked}
onCheckedChange={(checked) =>
handleCheckboxChange(checked, category.value, field)
}
/>
</Label>
</Field>
);
})}
</Field>
)}
/>
</form>
);
};
const BlogsResult = ({ posts, categories }: BlogsResultProps) => {
const [visibleCount, setVisibleCount] = useState(POSTS_PER_PAGE);
const [selectedCategories, setSelectedCategories] = useState<string[]>([
CATEGORIES[0].value,
]);
const handleCategoryChange = useCallback((selected: string[]) => {
setSelectedCategories(selected);
setVisibleCount(POSTS_PER_PAGE);
}, []);
const handleLoadMore = useCallback(() => {
setVisibleCount((prev) => prev + POSTS_PER_PAGE);
}, []);
const filteredPosts = useMemo(() => {
return posts.filter(
(post) =>
selectedCategories.includes(post.categoryKey) ||
selectedCategories.includes("all"),
);
}, [posts, selectedCategories]);
const postsToDisplay = filteredPosts.length > 0 ? filteredPosts : posts;
const hasMore = visibleCount < postsToDisplay.length;
return (
<div>
<FilterForm
categories={categories}
onCategoryChange={handleCategoryChange}
/>
<div className="flex w-full flex-col gap-4 py-8 lg:gap-8">
<div className="grid gap-10 md:grid-cols-2 lg:grid-cols-3">
{postsToDisplay.slice(0, visibleCount).map((post) => (
<BlogCard key={post.title} {...post} />
))}
</div>
<div className="flex justify-center">
{hasMore && (
<Button variant="secondary" onClick={handleLoadMore}>
بارگذاری بیشتر
</Button>
)}
</div>
</div>
</div>
);
};
const BreadcrumbBlog = ({ breadcrumb }: BreadcrumbBlogProps) => {
return (
<Breadcrumb>
<BreadcrumbList>
{breadcrumb.map((item, i) => {
return (
<Fragment key={`${item.label}`}>
<BreadcrumbItem>
<BreadcrumbLink href={item.link}>{item.label}</BreadcrumbLink>
</BreadcrumbItem>
{i < breadcrumb.length - 1 ? (
<BreadcrumbSeparator>
<Slash />
</BreadcrumbSeparator>
) : null}
</Fragment>
);
})}
</BreadcrumbList>
</Breadcrumb>
);
};
const BlogCard = ({ category, title, thumbnail, summary, link, cta }: Post) => {
return (
<a href={link} className="block h-full w-full">
<Card className="size-full rounded-lg border py-0">
<CardContent className="p-0">
<div className="border-b p-2.5 text-start text-sm leading-[1.2] font-medium text-muted-foreground">
{category}
</div>
<AspectRatio ratio={1.520833333} className="overflow-hidden">
<img
src={thumbnail}
alt={title}
className="block size-full object-cover object-center"
/>
</AspectRatio>
<div className="flex w-full flex-col gap-5 p-5 text-start">
<h2 className="text-lg leading-tight font-medium md:text-xl">
{title}
</h2>
<div className="w-full max-w-[20rem]">
<p className="text-sm leading-[1.4] font-medium text-muted-foreground">
{summary}
</p>
</div>
<div>
<Button size="sm" variant="outline">
{cta}
<ArrowRight className="rtl:rotate-180" />
</Button>
</div>
</div>
</CardContent>
</Card>
</a>
);
};
interface Blog27Props {
className?: string;
}
const Blog27 = ({ className }: Blog27Props) => {
return (
<section dir="rtl" lang="fa" className={cn("pb-32", className)}>
<div className="bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat">
<div className="container flex flex-col items-start justify-start gap-16 py-20 lg:flex-row lg:items-center lg:justify-between">
<div className="flex w-full flex-col justify-between gap-12">
<div className="flex w-full max-w-[36rem] flex-col gap-8 text-start">
<BreadcrumbBlog breadcrumb={BREADCRUMB} />
<div className="flex w-full flex-col gap-5">
<h1 className="text-[2.5rem] leading-[1.2] font-semibold md:text-2xll lg:text-2xll">
بهترین مقالات وبلاگ
</h1>
<p className="text-xl leading-[1.4] text-muted-foreground">
یک وبلاگ موفق با محتوای ارزشمند، کاربردی و قابل اعتماد، مخاطب را جذب کرده و او را به مطالعه بیشتر ترغیب می‌کند.
</p>
</div>
</div>
</div>
<div className="w-full max-w-[27.5rem]">
<BlogCard {...PRIMARY_POST} />
</div>
</div>
</div>
<div className="py-20">
<div className="container flex flex-col gap-8 text-start">
<h2 className="text-[1.75rem] leading-none font-medium md:text-[2.25rem] lg:text-[2rem]">
همه مطالب
</h2>
<div>
<BlogsResult posts={POSTS} categories={CATEGORIES} />
</div>
</div>
</div>
</section>
);
};
export { Blog27 };