Files
reui-blocks/blocks/navbar11.tsx
T

578 lines
16 KiB
TypeScript

"use client";
import type { LucideIcon } from "lucide-react";
import {
Book,
Cloud,
Code,
Database,
Gift,
Globe,
Heart,
Layout,
MenuIcon,
Monitor,
Paintbrush,
Server,
Settings,
Shield,
ShoppingCart,
Smartphone,
Sparkles,
Terminal,
Users,
X,
Zap,
} from "lucide-react";
import { Fragment, useEffect, useState } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import { cn } from "@/lib/utils";
interface MenuLink {
label: string;
description?: string;
url: string;
icon: LucideIcon;
}
interface MenuGroup {
title: string;
links: MenuLink[];
}
interface MenuItem {
title: string;
url?: string;
groups?: MenuGroup[];
}
interface DesktopMenuItemProps {
item: MenuItem;
index: number;
}
interface MobileNavigationMenuProps {
open: boolean;
}
const LOGO = {
url: "https://www.ShadcnBlocks",
src: "/block-media/shadcnblocks/block/block-1.svg",
alt: "لوگو",
title: "لیدهاب",
};
const NAVIGATION: MenuItem[] = [
{
title: "محصولات",
groups: [
{
title: "وبلاگ",
links: [
{
label: "بینش‌ها",
icon: Book,
description: "اخبار محصول و به‌روزرسانی سامانه مدیریت ارتباط با مشتری",
url: "#",
},
{
label: "مهندسی",
icon: Code,
description: "راهنمای فنی و API",
url: "#",
},
{
label: "فرهنگ",
icon: Users,
description: "ارزش‌ها و نحوه کار تیم لیدهاب",
url: "#",
},
{
label: "مطبوعات",
icon: Globe,
description: "پوشش رسانه‌ای",
url: "#",
},
],
},
{
title: "ابزارهای توسعه",
links: [
{
label: "API",
icon: Monitor,
description: "دسترسی REST API",
url: "#",
},
{
label: "CLI",
icon: Terminal,
description: "ابزارهای CLI",
url: "#",
},
{
label: "SDKها",
icon: Code,
description: "یکپارچه‌سازی سریع با SDK",
url: "#",
},
{
label: "مستندات",
icon: Book,
description: "مستندات کامل محصول",
url: "#",
},
],
},
{
title: "فروش",
links: [
{
label: "پلن‌ها",
icon: ShoppingCart,
description: "انتخاب پلن مناسب تیم",
url: "#",
},
{
label: "اشتراک‌ها",
icon: Database,
description: "گزینه‌های اشتراک تیم",
url: "#",
},
{
label: "اپ موبایل",
icon: Smartphone,
description: "مدیریت پایپ‌لاین در حرکت",
url: "#",
},
{
label: "اعتبار معرفی",
icon: Gift,
description: "دعوت همکار با اعتبار اولیه",
url: "#",
},
],
},
],
},
{
title: "راه‌حل‌ها",
groups: [
{
title: "صنایع",
links: [
{
label: "خدمات B2B",
icon: ShoppingCart,
description: "راه‌حل برای تیم‌های فروش میدانی",
url: "#",
},
{
label: "SaaS",
icon: Cloud,
description: "سامانه مدیریت ارتباط با مشتری برای تیم‌های SaaS",
url: "#",
},
{
label: "فین‌تک",
icon: Shield,
description: "پایپ‌لاین امن برای خدمات فین‌تک",
url: "#",
},
{
label: "سلامت دیجیتال",
icon: Heart,
description: "مدیریت لید برای خدمات سلامت دیجیتال",
url: "#",
},
],
},
{
title: "ماژول‌ها",
links: [
{
label: "ماژول",
icon: Layout,
description: "ماژول‌های قابل استفاده مجدد سامانه مدیریت ارتباط با مشتری",
url: "#",
},
{
label: "فیلدها",
icon: Settings,
description: "مرجع فیلدهای سفارشی",
url: "#",
},
{
label: "ویجت‌ها",
icon: Sparkles,
description: "ویجت‌های داشبورد",
url: "#",
},
{
label: "قالب‌ها",
icon: Paintbrush,
description: "قالب‌های فضای کاری",
url: "#",
},
],
},
],
},
{
title: "پلتفرم",
groups: [
{
title: "قابلیت‌های اصلی",
links: [
{
label: "میز میزبان داده",
icon: Server,
description: "زیرساخت پایدار فرصت‌ها",
url: "#",
},
{
label: "احراز هویت",
icon: Shield,
description: "ورود امن و نقش‌ها",
url: "#",
},
{
label: "مخاطبین",
icon: Database,
description: "ذخیره مقیاس‌پذیر مخاطب و فعالیت",
url: "#",
},
{
label: "اتوماسیون سریع",
icon: Zap,
description: "تریگرهای کم‌تأخیر برای پیگیری",
url: "#",
},
],
},
],
},
{
title: "منابع",
url: "#",
},
{
title: "قیمت‌گذاری",
url: "#",
},
];
const DESKTOP_BUTTONS = [
{
label: "تماس",
isPrimary: false,
url: "#",
},
{
label: "ورود",
isPrimary: false,
url: "#",
},
{
label: "شروع رایگان",
isPrimary: true,
url: "#",
},
];
const MOBILE_BUTTONS = [
{
label: "شروع رایگان",
isPrimary: true,
url: "#",
},
{
label: "ورود",
isPrimary: false,
url: "#",
},
];
const MOBILE_BREAKPOINT = 1024;
interface Navbar11Props {
/** When true, positions inside block preview instead of the viewport. */
contained?: boolean;
className?: string;
}
const Navbar11 = ({ contained = false, className }: Navbar11Props) => {
const [open, setOpen] = useState<boolean>(false);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > MOBILE_BREAKPOINT) {
setOpen(false);
}
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "auto";
}, [open]);
const handleMobileMenu = () => {
const nextOpen = !open;
setOpen(nextOpen);
};
return (
<Fragment>
<section
lang="fa"
className={cn(
"pointer-events-auto z-999 flex h-16 w-full items-center justify-center bg-background text-start",
contained ? "relative" : "fixed top-0",
className,
)}
>
<div className="container">
<div className="flex items-center justify-between gap-8">
<div className="flex items-center gap-8">
<a
href={LOGO.url}
className="flex max-h-8 items-center gap-2 text-lg font-semibold tracking-tighter"
>
<img
src={LOGO.src}
alt={LOGO.alt}
className="inline-block size-6"
/>
<span className="hidden md:inline-block">{LOGO.title}</span>
</a>
<NavigationMenu
dir="rtl"
className="relative z-50 hidden xl:flex"
viewport={false}
>
<NavigationMenuList>
{NAVIGATION.map((item, index) => (
<DesktopMenuItem
key={`desktop-link-${index}`}
item={item}
index={index}
/>
))}
</NavigationMenuList>
</NavigationMenu>
</div>
<div className="hidden items-center gap-3 xl:flex">
{DESKTOP_BUTTONS.map((btn, index) => (
<Button
size="sm"
variant={!btn.isPrimary ? "outline" : "default"}
className={
btn.isPrimary
? "text-primary-foreground"
: "text-foreground"
}
asChild
key={`navbar-btn-${index}`}
>
<a href={btn.url}>{btn.label}</a>
</Button>
))}
</div>
<div className="xl:hidden">
<Button
className="size-11"
variant="ghost"
onClick={handleMobileMenu}
aria-label={open ? "بستن منو" : "باز کردن منو"}
>
{open ? (
<X className="size-5.5 stroke-foreground" />
) : (
<MenuIcon className="size-5.5 stroke-foreground" />
)}
</Button>
</div>
</div>
</div>
</section>
<MobileNavigationMenu open={open} />
</Fragment>
);
};
const DesktopMenuItem = ({ item, index }: DesktopMenuItemProps) => {
if (item.groups) {
return (
<NavigationMenuItem key={`desktop-menu-item-${index}`} value={`${index}`}>
<NavigationMenuTrigger className="h-fit bg-transparent px-2.5 text-start font-normal text-muted-foreground [&_svg]:ms-1 [&_svg]:!ml-0">
{item.title}
</NavigationMenuTrigger>
<NavigationMenuContent
dir="rtl"
className="start-0 end-auto left-auto z-50 !rounded-xl !border !p-0 text-start"
>
<ul
className="flex flex-row-reverse p-2 text-start"
style={{ width: item.groups.length * 248 }}
>
{item.groups.map((group, index1) => (
<li className="flex-1" key={`desktop-group-${index1}`}>
<ul>
<li className="px-3 py-2 text-start text-sm leading-normal text-muted-foreground">
{group.title}
</li>
{group.links.map((link, index2) => (
<li key={`desktop-links-${index1}-${index2}`}>
<NavigationMenuLink
asChild
className="group/link flex-row gap-2 px-3 py-2 text-start transition-colors duration-200"
>
<a href={link.url} className="text-start">
<div className="flex size-8 shrink-0 rounded-lg border duration-400 fade-in group-hover/link:bg-background">
<link.icon className="m-auto size-4 group-hover/link:stroke-black" />
</div>
<div className="flex min-w-0 flex-col gap-[2px] text-start">
<div className="text-sm font-medium">
{link.label}
</div>
<div className="text-xs text-muted-foreground group-hover/link:text-foreground">
{link.description}
</div>
</div>
</a>
</NavigationMenuLink>
</li>
))}
</ul>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
);
}
return (
<NavigationMenuItem key={`desktop-menu-item-${index}`} value={`${index}`}>
<NavigationMenuLink
href={item.url}
className={`${navigationMenuTriggerStyle()} h-fit bg-transparent px-2.5 text-start font-normal text-muted-foreground`}
>
{item.title}
</NavigationMenuLink>
</NavigationMenuItem>
);
};
const MobileNavigationMenu = ({ open }: MobileNavigationMenuProps) => {
return (
<Sheet open={open}>
<SheetContent
aria-describedby={undefined}
side="top"
className="dark inset-0 z-998 h-dvh w-full bg-background pt-[3.9375rem] [&>button]:hidden"
>
<div className="h-full overflow-y-auto pt-10 pb-20">
<div className="container">
<div className="absolute -m-px h-px w-px overflow-hidden border-0 mask-clip-border p-0 text-nowrap whitespace-nowrap">
<SheetTitle className="text-primary">منوی موبایل</SheetTitle>
</div>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
{MOBILE_BUTTONS.map((btn, index) => (
<Button
variant={!btn.isPrimary ? "outline" : "default"}
className={
btn.isPrimary
? "text-primary-foreground"
: "text-foreground"
}
asChild
key={`navbar-btn-${index}`}
>
<a href={btn.url}>{btn.label}</a>
</Button>
))}
</div>
<Accordion
type="multiple"
dir="rtl"
className="w-full text-start"
>
{NAVIGATION.map((item, index) =>
renderMobileMenuItem(item, index),
)}
</Accordion>
</div>
</div>
</div>
</SheetContent>
</Sheet>
);
};
const renderMobileMenuItem = (item: MenuItem, index: number) => {
if (item.groups) {
return (
<AccordionItem
key={item.title}
value={`nav-${index}`}
className="border-b-0"
>
<AccordionTrigger className="h-[3.75rem] items-center justify-start p-0 !px-4 text-start text-base leading-[3.75] font-normal text-muted-foreground hover:bg-muted hover:no-underline [&>svg]:ms-auto [&>svg]:me-0">
{item.title}
</AccordionTrigger>
<AccordionContent className="max-h-[60dvh] overflow-x-auto">
{item.groups.flatMap((group, groupIndex) =>
group.links.map((link, linkIndex) => (
<a
key={`mobile-link-${groupIndex}-${linkIndex}`}
href={link.url}
className="flex h-12 items-center gap-2 rounded-lg px-4 text-start text-muted-foreground transition-colors duration-300 hover:bg-muted hover:text-foreground"
>
<link.icon className="size-4 stroke-muted-foreground" />
{link.label}
</a>
)),
)}
</AccordionContent>
</AccordionItem>
);
}
return (
<a
key={item.title}
href={item.url}
className="flex h-[3.75rem] items-center rounded-md p-0 px-4 text-start text-base leading-[3.75] font-normal text-muted-foreground ring-ring/10 outline-ring/50 transition-all hover:bg-muted focus-visible:ring-4 focus-visible:outline-1 nth-last-1:border-0"
>
{item.title}
</a>
);
};
export { Navbar11 };