Files
reui-blocks/blocks/changelog8.tsx
T

236 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useRef, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
export type ChangelogEntry = {
version: string;
date: string;
title: string;
description: string;
items?: string[];
image?: string;
};
export interface Changelog8Props {
className?: string;
title?: string;
description?: string;
entries?: ChangelogEntry[];
}
const Changelog8 = ({
title = "تاریخچه نسخه‌ها",
description = "به‌روزرسانی‌های پلتفرم فروش و سامانه مدیریت ارتباط با مشتری را دنبال کنید.",
entries = defaultChangelogData,
className,
}: Changelog8Props) => {
const [activeId, setActiveId] = useState<number | null>(null);
const sectionRefs = useRef<Array<HTMLDivElement | null>>([]);
useEffect(() => {
sectionRefs.current = sectionRefs.current.slice(0, entries.length);
const handleScroll = () => {
const scrollPosition = window.scrollY + 100;
const currentSectionIndex = sectionRefs.current.findIndex(
(ref, index) => {
if (!ref) return false;
const nextRef = sectionRefs.current[index + 1];
const refTop = ref.offsetTop;
const refBottom = nextRef
? nextRef.offsetTop
: document.body.scrollHeight;
return scrollPosition >= refTop && scrollPosition < refBottom;
},
);
if (currentSectionIndex !== -1) {
setActiveId(currentSectionIndex);
} else if (scrollPosition < (sectionRefs.current[0]?.offsetTop || 0)) {
setActiveId(null);
}
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [entries.length]);
const scrollToSection = (index: number) => {
const sectionRef = sectionRefs.current[index];
if (sectionRef) {
window.scrollTo({
top: sectionRef.offsetTop - 80,
behavior: "smooth",
});
}
};
const setRef = (index: number) => (el: HTMLDivElement | null) => {
sectionRefs.current[index] = el;
};
return (
<section
dir="rtl"
lang="fa"
className={cn("bg-background py-24 md:py-32", className)}
>
<div className="relative container flex flex-col lg:flex-row">
<div className="mb-12 lg:hidden">
<h1 className="mb-4 text-3xl font-bold tracking-tight md:text-xl">
{title}
</h1>
<p className="text-base text-muted-foreground md:text-lg">
{description}
</p>
<Separator className="mt-8" />
</div>
<div className="sticky top-24 hidden h-fit w-72 overflow-visible pe-10 lg:block">
<div className="mb-10">
<h1 className="mb-3 text-3xl font-bold tracking-tight">{title}</h1>
<p className="text-base text-muted-foreground">{description}</p>
</div>
<Separator className="mb-6" />
<ScrollArea className="h-[calc(100vh-300px)]">
<div className="pb-6">
<h3 className="mb-5 text-sm font-medium tracking-wider text-muted-foreground uppercase">
در این صفحه
</h3>
<nav className="flex flex-col space-y-2.5">
{entries.map((entry, index) => (
<button
key={index}
onClick={() => scrollToSection(index)}
className={cn(
"group flex flex-col gap-2 rounded-md border border-transparent px-3 py-2 text-start transition-all hover:border-border hover:bg-accent",
activeId === index
? "border-border bg-accent font-medium text-foreground shadow-sm"
: "text-muted-foreground",
)}
>
<span className="text-sm">{entry.title}</span>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span className="font-medium">{entry.version}</span>
<span>•</span>
<span>{entry.date}</span>
</div>
</button>
))}
</nav>
</div>
</ScrollArea>
</div>
<div className="flex-1 lg:ps-10">
<div className="mb-10 hidden lg:block">
<h2 className="text-xl font-semibold">آخرین انتشارها</h2>
</div>
<div className="space-y-16 md:space-y-24">
{entries.map((entry, index) => (
<div
key={index}
ref={setRef(index)}
className="relative flex flex-col gap-6 border-s-4 border-s-border/30 ps-6 transition-colors hover:border-s-border md:gap-8"
id={`entry-${index}`}
>
<div className="flex flex-wrap items-center gap-3">
<Badge
variant="secondary"
className="px-3 py-1 text-xs font-medium"
>
{entry.version}
</Badge>
<span className="text-xs font-medium text-muted-foreground">
{entry.date}
</span>
</div>
<div>
<h2 className="mb-4 text-xl leading-tight font-bold text-foreground md:text-xl">
{entry.title}
</h2>
<p className="mb-6 text-base text-muted-foreground md:text-base">
{entry.description}
</p>
{entry.items && entry.items.length > 0 && (
<div className="mt-6 rounded-lg border bg-muted/30 p-5">
<h3 className="mb-3 text-sm font-medium">
شامل موارد زیر:
</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
{entry.items.map((item, itemIndex) => (
<li key={itemIndex} className="flex items-start">
<span className="mt-1 ms-2 size-1.5 shrink-0 rounded-full bg-primary" />
<span>{item}</span>
</li>
))}
</ul>
</div>
)}
{entry.image && (
<div className="mt-8 overflow-hidden rounded-xl border">
<img
src={entry.image}
alt={`تصویر ${entry.title}`}
className="w-full object-cover transition-transform hover:scale-105"
/>
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export { Changelog8 };
export const defaultChangelogData: ChangelogEntry[] = [
{
version: "نسخه ۱۳.۰",
date: "۲۵ آبان ۱۴۰۳",
title: "داشبورد فروش پیشرفته",
description:
"داشبورد فروش را از نو طراحی کردیم تا وضعیت قیف و عملکرد تیم شفاف‌تر دیده شود.",
items: [
"نمودار تبدیل با به‌روزرسانی نزدیک به لحظه",
"ویجت‌های قابل تنظیم برای داشبورد فروش",
"خروجی گزارش فروش در قالب جدول، PDF و Excel",
"قالب گزارش هفته‌ای و ماهانه برای مدیران فروش",
"فیلتر بهتر بر اساس مرحله قیف، مالک و منبع",
],
image:
"/block-media/shadcnblocks/block/placeholder-aspect-video-1.svg",
},
{
version: "نسخه ۱۲۵",
date: "۱۶ مهر ۱۴۰۳",
title: "انتشار اپلیکیشن موبایل",
description:
"با افتخار انتشار اپلیکیشن موبایل خود را اعلام می‌کنیم؛ اکنون در موبایل اپل و موبایل اندروید در دسترس است.",
items: [
"تجربه بومی موبایل برای بهره‌وری در هر مکان",
"پشتیبانی از حالت آفلاین برای کار بدون اتصال اینترنت",
"اعلان‌های فوری برای به‌روزرسانی‌های مهم",
"احراز هویت بیومتریک برای امنیت بیشتر",
],
image:
"/block-media/shadcnblocks/block/placeholder-aspect-video-1.svg",
},
];