1014 lines
28 KiB
TypeScript
1014 lines
28 KiB
TypeScript
"use client";
|
||||
|
|
import {
|
|||
|
|
Bell,
|
|||
|
|
Bookmark,
|
|||
|
|
Bug,
|
|||
|
|
ChevronDown,
|
|||
|
|
CreditCard,
|
|||
|
|
FileQuestion,
|
|||
|
|
Heart,
|
|||
|
|
HeartHandshake,
|
|||
|
|
HelpCircle,
|
|||
|
|
History,
|
|||
|
|
LayoutGrid,
|
|||
|
|
LifeBuoy,
|
|||
|
|
LogOut,
|
|||
|
|
type LucideIcon,
|
|||
|
|
Menu,
|
|||
|
|
MessageCircleHeart,
|
|||
|
|
Search,
|
|||
|
|
Settings,
|
|||
|
|
ShoppingCart,
|
|||
|
|
Star,
|
|||
|
|
ThumbsUp,
|
|||
|
|
User,
|
|||
|
|
} from "lucide-react";
|
|||
|
|
import {
|
|||
|
|
type CSSProperties,
|
|||
|
|
Fragment,
|
|||
|
|
useEffect,
|
|||
|
|
useMemo,
|
|||
|
|
useState,
|
|||
|
|
} from "react";
|
|||
|
|
|
|||
|
|
import {
|
|||
|
|
Logo,
|
|||
|
|
LogoImage,
|
|||
|
|
LogoTextDesktop,
|
|||
|
|
} from "@/components/shadcnblocks/logo";
|
|||
|
|
import {
|
|||
|
|
Accordion,
|
|||
|
|
AccordionContent,
|
|||
|
|
AccordionItem,
|
|||
|
|
AccordionTrigger,
|
|||
|
|
} from "@/components/ui/accordion";
|
|||
|
|
import { Badge } from "@/components/ui/badge";
|
|||
|
|
import { Button } from "@/components/ui/button";
|
|||
|
|
import {
|
|||
|
|
DropdownMenu,
|
|||
|
|
DropdownMenuContent,
|
|||
|
|
DropdownMenuItem,
|
|||
|
|
DropdownMenuPortal,
|
|||
|
|
DropdownMenuSub,
|
|||
|
|
DropdownMenuSubContent,
|
|||
|
|
DropdownMenuSubTrigger,
|
|||
|
|
DropdownMenuTrigger,
|
|||
|
|
} from "@/components/ui/dropdown-menu";
|
|||
|
|
import {
|
|||
|
|
NavigationMenu,
|
|||
|
|
NavigationMenuContent,
|
|||
|
|
NavigationMenuItem,
|
|||
|
|
NavigationMenuLink,
|
|||
|
|
NavigationMenuList,
|
|||
|
|
NavigationMenuTrigger,
|
|||
|
|
navigationMenuTriggerStyle,
|
|||
|
|
} from "@/components/ui/navigation-menu";
|
|||
|
|
import {
|
|||
|
|
Sheet,
|
|||
|
|
SheetContent,
|
|||
|
|
SheetHeader,
|
|||
|
|
SheetTrigger,
|
|||
|
|
} from "@/components/ui/sheet";
|
|||
|
|
import { cn } from "@/lib/utils";
|
|||
|
|
|
|||
|
|
type HomeLink = {
|
|||
|
|
href: string;
|
|||
|
|
logo: {
|
|||
|
|
src: string;
|
|||
|
|
alt: string;
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type Link = {
|
|||
|
|
label: string;
|
|||
|
|
href: string;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
interface SideMenuItem extends Link {
|
|||
|
|
icon: LucideIcon;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type HelpfullLinkGroup = {
|
|||
|
|
group: string;
|
|||
|
|
links: Link[];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type SideMenuType = Array<SideMenuItem[]>;
|
|||
|
|
|
|||
|
|
type MegaMenuSectionType = {
|
|||
|
|
label: string;
|
|||
|
|
href: string;
|
|||
|
|
id: string;
|
|||
|
|
items: {
|
|||
|
|
label: string;
|
|||
|
|
href: string;
|
|||
|
|
}[];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type MenuItemType = {
|
|||
|
|
label: string;
|
|||
|
|
href?: string;
|
|||
|
|
id?: string;
|
|||
|
|
sections?: MegaMenuSectionType[];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type MenuType = MenuItemType[];
|
|||
|
|
|
|||
|
|
interface HelpfullLinksDropdownProps {
|
|||
|
|
groups?: HelpfullLinkGroup[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface SecondaryMenuProps {
|
|||
|
|
menu: MenuType;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface SecondaryMenuMobileProps {
|
|||
|
|
menu: MenuType;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type DesktopMenuDropdownItemProps = MenuItemType;
|
|||
|
|
|
|||
|
|
interface MobileMenuProps {
|
|||
|
|
menu: SideMenuType;
|
|||
|
|
homeLink: HomeLink;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface EcommerceNavbar2Props {
|
|||
|
|
home: HomeLink;
|
|||
|
|
helpfullLinks?: HelpfullLinkGroup[];
|
|||
|
|
menu: MenuType;
|
|||
|
|
sideMenu: SideMenuType;
|
|||
|
|
className?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const HOME = {
|
|||
|
|
href: "https://ShadcnBlocks",
|
|||
|
|
logo: {
|
|||
|
|
src: "/block-media/shadcnblocks/block/logos/ShadcnBlockscom-icon-white.svg",
|
|||
|
|
alt: "لوگو",
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const HELPFUL_LINKS = [
|
|||
|
|
{
|
|||
|
|
group: "پشتیبانی",
|
|||
|
|
links: [
|
|||
|
|
{ label: "پشتیبانی مشتریان", href: "#" },
|
|||
|
|
{ label: "تماس با ما", href: "#" },
|
|||
|
|
{ label: "سؤالات متداول", href: "#" },
|
|||
|
|
{ label: "راهنمای محصول", href: "#" },
|
|||
|
|
{ label: "گارانتی و تعمیر", href: "#" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
group: "سفارش و ارسال",
|
|||
|
|
links: [
|
|||
|
|
{ label: "پیگیری سفارش", href: "#" },
|
|||
|
|
{ label: "اطلاعات ارسال", href: "#" },
|
|||
|
|
{ label: "مرجوعی و بازپرداخت", href: "#" },
|
|||
|
|
{ label: "لغو سفارش", href: "#" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
group: "ابزارهای خرید",
|
|||
|
|
links: [
|
|||
|
|
{ label: "یافتن فروشگاه", href: "#" },
|
|||
|
|
{ label: "کارت هدیه", href: "#" },
|
|||
|
|
{ label: "تخفیف دانشجویی", href: "#" },
|
|||
|
|
{ label: "پیشنهادها و تخفیفها", href: "#" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
group: "شرکت و قوانین",
|
|||
|
|
links: [
|
|||
|
|
{ label: "درباره ما", href: "#" },
|
|||
|
|
{ label: "فرصتهای شغلی", href: "#" },
|
|||
|
|
{ label: "حریم خصوصی", href: "#" },
|
|||
|
|
{ label: "شرایط و ضوابط", href: "#" },
|
|||
|
|
{ label: "دسترسیپذیری", href: "#" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const MENU = [
|
|||
|
|
{
|
|||
|
|
id: "shop",
|
|||
|
|
label: "فروشگاه",
|
|||
|
|
href: "#",
|
|||
|
|
sections: [
|
|||
|
|
{
|
|||
|
|
label: "صورت",
|
|||
|
|
id: "face",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"پاککنندهها",
|
|||
|
|
"پاککنندههای روغنی",
|
|||
|
|
"پاککنندههای فومی",
|
|||
|
|
"تونرها",
|
|||
|
|
"اسنسها",
|
|||
|
|
"سرمها",
|
|||
|
|
"مرطوبکنندهها",
|
|||
|
|
"کرم دور چشم",
|
|||
|
|
"روغنهای صورت",
|
|||
|
|
"ماسک صورت",
|
|||
|
|
"لایهبردارها",
|
|||
|
|
"درمان لک",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "بدن",
|
|||
|
|
id: "body",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"شوینده بدن",
|
|||
|
|
"اسکراب بدن",
|
|||
|
|
"لوسیون بدن",
|
|||
|
|
"کرم بدن",
|
|||
|
|
"روغن بدن",
|
|||
|
|
"کرم دست",
|
|||
|
|
"مراقبت پا",
|
|||
|
|
"درمان سفتکننده",
|
|||
|
|
"ملزومات حمام",
|
|||
|
|
"مراقبت پس از آفتاب",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "محافظت در برابر آفتاب",
|
|||
|
|
id: "sun",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"SPF روزانه",
|
|||
|
|
"ضدآفتاب معدنی",
|
|||
|
|
"ضدآفتاب شیمیایی",
|
|||
|
|
"SPF رنگی",
|
|||
|
|
"استیک SPF",
|
|||
|
|
"ضدآفتاب صورت",
|
|||
|
|
"ضدآفتاب بدن",
|
|||
|
|
"SPF لب",
|
|||
|
|
"مراقبت پس از آفتاب",
|
|||
|
|
"اسپری ضدآفتاب",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "پاکسازی",
|
|||
|
|
id: "cleansing",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"میسلار واتر",
|
|||
|
|
"پاککننده ژلی",
|
|||
|
|
"پاککننده کرمی",
|
|||
|
|
"مرهم پاککننده",
|
|||
|
|
"روغن پاککننده",
|
|||
|
|
"پاککننده آرایش",
|
|||
|
|
"پاککننده لایهبردار",
|
|||
|
|
"پاککننده پوست حساس",
|
|||
|
|
"سایز مسافرتی",
|
|||
|
|
"بستههای شارژ",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "درمانها",
|
|||
|
|
id: "treatments",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"درمان آکنه",
|
|||
|
|
"درمان روشنکننده",
|
|||
|
|
"درمان ضدپیری",
|
|||
|
|
"تقویتکننده آبرسانی",
|
|||
|
|
"پیلینگ",
|
|||
|
|
"ماسک شبانه",
|
|||
|
|
"آمپول",
|
|||
|
|
"سرم اصلاحی",
|
|||
|
|
"درمان آرامبخش",
|
|||
|
|
"درمان ترمیمی",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "ابزار و دستگاه",
|
|||
|
|
id: "tools",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"غلتک صورت",
|
|||
|
|
"گua شا",
|
|||
|
|
"ماسک LED",
|
|||
|
|
"برس پاکسازی",
|
|||
|
|
"ماساژور صورت",
|
|||
|
|
"غلتک یخی",
|
|||
|
|
"ابزار منافذ",
|
|||
|
|
"دستگاه میکروجریان",
|
|||
|
|
"سرهای یدکی",
|
|||
|
|
"ابزار مسافرتی",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "ماسکها",
|
|||
|
|
id: "masks",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"ماسک ورقهای",
|
|||
|
|
"ماسک رس",
|
|||
|
|
"ماسک خواب",
|
|||
|
|
"ماسک لایهای",
|
|||
|
|
"ماسک آبرسان",
|
|||
|
|
"ماسک پاککننده",
|
|||
|
|
"ماسک آرامبخش",
|
|||
|
|
"ماسک روشنکننده",
|
|||
|
|
"ماسک لایهبردار",
|
|||
|
|
"ست مولتیماسک",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "مراقبت چشم و لب",
|
|||
|
|
id: "eye-lip",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"کرم دور چشم",
|
|||
|
|
"سرم دور چشم",
|
|||
|
|
"ماسک چشم",
|
|||
|
|
"مرهم لب",
|
|||
|
|
"ماسک لب",
|
|||
|
|
"اسکراب لب",
|
|||
|
|
"درمان پف چشم",
|
|||
|
|
"مراقبت تیرگی دور چشم",
|
|||
|
|
"SPF لب",
|
|||
|
|
"مراقبت شبانه لب",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "مو و پوست سر",
|
|||
|
|
id: "hair",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"درمان پوست سر",
|
|||
|
|
"اسکراب پوست سر",
|
|||
|
|
"سرم مو",
|
|||
|
|
"روغن مو",
|
|||
|
|
"ماسک مو",
|
|||
|
|
"ضدآفتاب مو",
|
|||
|
|
"مراقبت شوره",
|
|||
|
|
"درمان رشد مو",
|
|||
|
|
"مراقبت بدون آبکشی",
|
|||
|
|
"ابزار مو",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "حمام و سلامتی",
|
|||
|
|
id: "wellness",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"روغن حمام",
|
|||
|
|
"نمک حمام",
|
|||
|
|
"محلولهای حمام",
|
|||
|
|
"رایحهدرمانی",
|
|||
|
|
"آرامبخش بدن",
|
|||
|
|
"کاهش استرس",
|
|||
|
|
"مراقبت خواب",
|
|||
|
|
"روغن ماساژ",
|
|||
|
|
"ست مراقبت شخصی",
|
|||
|
|
"هدایای سلامتی",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "مراقبت آقایان",
|
|||
|
|
id: "men",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"شوینده صورت",
|
|||
|
|
"مرطوبکننده",
|
|||
|
|
"پس از اصلاح",
|
|||
|
|
"مراقبت ریش",
|
|||
|
|
"کرم دور چشم",
|
|||
|
|
"ضدپیری",
|
|||
|
|
"کنترل چربی",
|
|||
|
|
"مراقبت بدن",
|
|||
|
|
"کیت مسافرتی",
|
|||
|
|
"ست اصلاح",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "مراقبت پوست نوجوان",
|
|||
|
|
id: "teen",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"پاککننده ملایم",
|
|||
|
|
"مراقبت آکنه",
|
|||
|
|
"کنترل چربی",
|
|||
|
|
"مرطوبکننده آرامبخش",
|
|||
|
|
"درمان لک",
|
|||
|
|
"SPF",
|
|||
|
|
"کیت شروع",
|
|||
|
|
"پوست حساس",
|
|||
|
|
"سایز مسافرتی",
|
|||
|
|
"ملزومات روزانه",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "سایز مسافرتی",
|
|||
|
|
id: "travel",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"پاککننده کوچک",
|
|||
|
|
"سرم کوچک",
|
|||
|
|
"مرطوبکننده کوچک",
|
|||
|
|
"ماسک کوچک",
|
|||
|
|
"کیت مسافرتی",
|
|||
|
|
"ظرف قابل شارژ",
|
|||
|
|
"مناسب کابین",
|
|||
|
|
"ست نمونه",
|
|||
|
|
"کیت آخر هفته",
|
|||
|
|
"مراقبت در مسیر",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "هدیه و ستها",
|
|||
|
|
id: "gifts",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"ست هدیه",
|
|||
|
|
"کیت شروع",
|
|||
|
|
"بسته روتین",
|
|||
|
|
"پرفروشترینها",
|
|||
|
|
"ست مناسبتی",
|
|||
|
|
"نسخه محدود",
|
|||
|
|
"ست کوچک",
|
|||
|
|
"هدایای لوکس",
|
|||
|
|
"زیر ۵۰ دلار",
|
|||
|
|
"زیر ۱۰۰ دلار",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
id: "collections",
|
|||
|
|
label: "مجموعهها",
|
|||
|
|
href: "#",
|
|||
|
|
sections: [
|
|||
|
|
{
|
|||
|
|
label: "بر اساس مشکل پوست",
|
|||
|
|
id: "concerns",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"آکنه",
|
|||
|
|
"خشکی",
|
|||
|
|
"حساسیت",
|
|||
|
|
"خطوط ریز",
|
|||
|
|
"چین و چروک",
|
|||
|
|
"لک",
|
|||
|
|
"قرمزی",
|
|||
|
|
"کدری",
|
|||
|
|
"بافت ناهمگن",
|
|||
|
|
"منافذ باز",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "بر اساس نوع پوست",
|
|||
|
|
id: "skin-type",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"نرمال",
|
|||
|
|
"خشک",
|
|||
|
|
"چرب",
|
|||
|
|
"مختلط",
|
|||
|
|
"حساس",
|
|||
|
|
"بالغ",
|
|||
|
|
"آکنهای",
|
|||
|
|
"کمآب",
|
|||
|
|
"واکنشپذیر",
|
|||
|
|
"همه انواع پوست",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "بر اساس ماده فعال",
|
|||
|
|
id: "ingredients",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"هیالورونیک اسید",
|
|||
|
|
"ویتامین C",
|
|||
|
|
"رتینول",
|
|||
|
|
"نیاسینامید",
|
|||
|
|
"سرامید",
|
|||
|
|
"پپتید",
|
|||
|
|
"سالیسیلیک اسید",
|
|||
|
|
"AHA و BHA",
|
|||
|
|
"باکوچیول",
|
|||
|
|
"سنتلا آسیاتica",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "بر اساس روتین",
|
|||
|
|
id: "routine",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"روتین صبح",
|
|||
|
|
"روتین شب",
|
|||
|
|
"روتین مینیمال",
|
|||
|
|
"روتین پیشرفته",
|
|||
|
|
"درمانهای هفتگی",
|
|||
|
|
"روتین مسافرتی",
|
|||
|
|
"مراقبت پس از درمان",
|
|||
|
|
"مراقبت فصلی",
|
|||
|
|
"آیینهای مراقبت شخصی",
|
|||
|
|
"الهامگرفته از حرفهایها",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "زیبایی پاک و مسئولانه",
|
|||
|
|
id: "clean",
|
|||
|
|
href: "#",
|
|||
|
|
items: [
|
|||
|
|
"زیبایی پاک",
|
|||
|
|
"وگان",
|
|||
|
|
"بدون تست حیوانی",
|
|||
|
|
"بدون بو",
|
|||
|
|
"تستشده توسط متخصص پوست",
|
|||
|
|
"مناسب پوست حساس",
|
|||
|
|
"بستهبندی زیستمحیطی",
|
|||
|
|
"قابل بازیافت",
|
|||
|
|
"قابل شارژ",
|
|||
|
|
"برندهای پایدار",
|
|||
|
|
].map((label) => ({ label, href: "#" })),
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
{ label: "آزمون پوست", href: "#" },
|
|||
|
|
{ label: "درباره ما", href: "#" },
|
|||
|
|
{ label: "وبلاگ", href: "#" },
|
|||
|
|
{ label: "حراج", href: "#" },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const SIDE_MENU = [
|
|||
|
|
[
|
|||
|
|
{
|
|||
|
|
label: "حساب کاربری",
|
|||
|
|
href: "#",
|
|||
|
|
icon: User,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "سوابق خرید",
|
|||
|
|
href: "#",
|
|||
|
|
icon: History,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "روشهای پرداخت",
|
|||
|
|
href: "#",
|
|||
|
|
icon: CreditCard,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "تنظیمات حساب",
|
|||
|
|
href: "#",
|
|||
|
|
icon: Settings,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "خروج",
|
|||
|
|
href: "#",
|
|||
|
|
icon: LogOut,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
[
|
|||
|
|
{
|
|||
|
|
label: "راهنما",
|
|||
|
|
href: "#",
|
|||
|
|
icon: HeartHandshake,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "راهنمای کاربران",
|
|||
|
|
href: "#",
|
|||
|
|
icon: HelpCircle,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "سؤالات متداول",
|
|||
|
|
href: "#",
|
|||
|
|
icon: FileQuestion,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "تیکت پشتیبانی",
|
|||
|
|
href: "#",
|
|||
|
|
icon: LifeBuoy,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
[
|
|||
|
|
{
|
|||
|
|
label: "علاقهمندیها",
|
|||
|
|
href: "#",
|
|||
|
|
icon: Heart,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "ذخیرهشدهها",
|
|||
|
|
href: "#",
|
|||
|
|
icon: Bookmark,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "هشدار موجود شدن",
|
|||
|
|
href: "#",
|
|||
|
|
icon: Bell,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "اخیراً مشاهدهشده",
|
|||
|
|
href: "#",
|
|||
|
|
icon: Star,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
[
|
|||
|
|
{
|
|||
|
|
label: "بازخورد",
|
|||
|
|
href: "#",
|
|||
|
|
icon: MessageCircleHeart,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "امتیازدهی به تجربه",
|
|||
|
|
href: "#",
|
|||
|
|
icon: ThumbsUp,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "گزارش مشکل",
|
|||
|
|
href: "#",
|
|||
|
|
icon: Bug,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const EcommerceNavbar2 = ({
|
|||
|
|
home = HOME,
|
|||
|
|
helpfullLinks = HELPFUL_LINKS,
|
|||
|
|
menu = MENU,
|
|||
|
|
sideMenu = SIDE_MENU,
|
|||
|
|
className,
|
|||
|
|
}: EcommerceNavbar2Props) => {
|
|||
|
|
useEffect(() => {
|
|||
|
|
document.documentElement.style.setProperty(
|
|||
|
|
"--primary-nav-height",
|
|||
|
|
"5.25rem",
|
|||
|
|
);
|
|||
|
|
document.documentElement.style.setProperty(
|
|||
|
|
"--secondary-nav-height",
|
|||
|
|
"3.25rem",
|
|||
|
|
);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<header
|
|||
|
|
dir="rtl"
|
|||
|
|
lang="fa"
|
|||
|
|
className={cn("fixed inset-x-0 top-0 isolate z-50 text-start", className)}
|
|||
|
|
>
|
|||
|
|
<div className="flex h-[var(--primary-nav-height)] items-center gap-3 bg-primary px-6 py-4 text-primary-foreground">
|
|||
|
|
<Logo url={home.href} className="shrink-0 p-2">
|
|||
|
|
<LogoImage
|
|||
|
|
src={home.logo.src}
|
|||
|
|
alt={home.logo.alt}
|
|||
|
|
className="size-8"
|
|||
|
|
/>
|
|||
|
|
<LogoTextDesktop className="font-medium text-primary-foreground">
|
|||
|
|
ری یو آی
|
|||
|
|
</LogoTextDesktop>
|
|||
|
|
</Logo>
|
|||
|
|
<div className="ms-auto flex items-center gap-3">
|
|||
|
|
<div className="hidden lg:contents">
|
|||
|
|
<HelpfullLinksDropdown groups={helpfullLinks} />
|
|||
|
|
<Button
|
|||
|
|
variant="ghost"
|
|||
|
|
className="text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground"
|
|||
|
|
>
|
|||
|
|
<Heart />
|
|||
|
|
علاقهمندیها
|
|||
|
|
</Button>
|
|||
|
|
<Button
|
|||
|
|
variant="ghost"
|
|||
|
|
className="text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground"
|
|||
|
|
>
|
|||
|
|
<User />
|
|||
|
|
حساب کاربری
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
<SearchForm />
|
|||
|
|
<div className="relative size-fit">
|
|||
|
|
<Button
|
|||
|
|
size="icon"
|
|||
|
|
variant="ghost"
|
|||
|
|
className="text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground"
|
|||
|
|
aria-label="سبد خرید"
|
|||
|
|
>
|
|||
|
|
<ShoppingCart />
|
|||
|
|
</Button>
|
|||
|
|
<Badge className="absolute top-0 end-0 translate-x-1/2 -translate-y-1/2 bg-amber-500 text-[0.625rem] font-medium text-foreground">
|
|||
|
|
۰
|
|||
|
|
</Badge>
|
|||
|
|
</div>
|
|||
|
|
<div className="contents lg:hidden">
|
|||
|
|
<MobileMenu menu={sideMenu} homeLink={home} />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="relative h-[var(--secondary-nav-height)] overflow-visible">
|
|||
|
|
<SecondaryMenu menu={menu} />
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const SearchForm = () => {
|
|||
|
|
return (
|
|||
|
|
<Button
|
|||
|
|
size="icon"
|
|||
|
|
variant="ghost"
|
|||
|
|
className="text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground"
|
|||
|
|
aria-label="جستجو"
|
|||
|
|
>
|
|||
|
|
<Search />
|
|||
|
|
</Button>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const HelpfullLinksDropdown = ({ groups }: HelpfullLinksDropdownProps) => {
|
|||
|
|
if (!groups) return;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<DropdownMenu>
|
|||
|
|
<DropdownMenuTrigger asChild>
|
|||
|
|
<Button
|
|||
|
|
variant="ghost"
|
|||
|
|
className="text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground data-[state=open]:bg-primary-foreground/10 data-[state=open]:text-primary-foreground"
|
|||
|
|
>
|
|||
|
|
<HeartHandshake className="size-4" />
|
|||
|
|
راهنما و پشتیبانی
|
|||
|
|
<ChevronDown className="rtl:rotate-180" />
|
|||
|
|
</Button>
|
|||
|
|
</DropdownMenuTrigger>
|
|||
|
|
<DropdownMenuContent
|
|||
|
|
className="w-56 !border-primary-foreground/20 !bg-primary !text-primary-foreground"
|
|||
|
|
align="start"
|
|||
|
|
>
|
|||
|
|
{groups.map(({ group, links }, index) => (
|
|||
|
|
<DropdownMenuSub key={index}>
|
|||
|
|
<DropdownMenuSubTrigger className="**:!text-primary-foreground focus:bg-primary-foreground/10 focus:!text-primary-foreground data-[state=open]:bg-primary-foreground/10 data-[state=open]:!text-primary-foreground">
|
|||
|
|
{group}
|
|||
|
|
</DropdownMenuSubTrigger>
|
|||
|
|
<DropdownMenuPortal>
|
|||
|
|
<DropdownMenuSubContent className="!border-primary-foreground/20 !bg-primary !text-primary-foreground">
|
|||
|
|
{links.map(({ label, href }, index) => (
|
|||
|
|
<DropdownMenuItem
|
|||
|
|
asChild
|
|||
|
|
key={`${label}-${index}`}
|
|||
|
|
className="focus:bg-primary-foreground/10 focus:text-primary-foreground"
|
|||
|
|
>
|
|||
|
|
<a href={href}>{label}</a>
|
|||
|
|
</DropdownMenuItem>
|
|||
|
|
))}
|
|||
|
|
</DropdownMenuSubContent>
|
|||
|
|
</DropdownMenuPortal>
|
|||
|
|
</DropdownMenuSub>
|
|||
|
|
))}
|
|||
|
|
</DropdownMenuContent>
|
|||
|
|
</DropdownMenu>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const MobileMenu = ({ menu, homeLink }: MobileMenuProps) => {
|
|||
|
|
return (
|
|||
|
|
<Sheet>
|
|||
|
|
<SheetTrigger asChild>
|
|||
|
|
<Button
|
|||
|
|
size="icon"
|
|||
|
|
variant="ghost"
|
|||
|
|
className="text-primary-foreground hover:bg-primary-foreground/10 hover:text-primary-foreground focus:bg-primary-foreground/10 focus:text-primary-foreground data-[state=open]:bg-primary-foreground/10 data-[state=open]:text-primary-foreground"
|
|||
|
|
aria-label="باز کردن منو"
|
|||
|
|
>
|
|||
|
|
<Menu />
|
|||
|
|
</Button>
|
|||
|
|
</SheetTrigger>
|
|||
|
|
<SheetContent side="right" className="hide-scrollbar overflow-auto">
|
|||
|
|
<div className="px-4">
|
|||
|
|
<SheetHeader>
|
|||
|
|
<Logo url={homeLink.href} className="max-h-fit shrink-0 basis-11">
|
|||
|
|
<LogoImage
|
|||
|
|
src={homeLink.logo.src}
|
|||
|
|
alt={homeLink.logo.alt}
|
|||
|
|
className="size-11 shrink-0 invert dark:invert-0"
|
|||
|
|
/>
|
|||
|
|
</Logo>
|
|||
|
|
</SheetHeader>
|
|||
|
|
{menu.map((group, index) => (
|
|||
|
|
<div key={index} className="py-6 not-last:border-b">
|
|||
|
|
{group.map(({ href, label, icon: Icon }, index) => (
|
|||
|
|
<Button
|
|||
|
|
asChild
|
|||
|
|
variant="ghost"
|
|||
|
|
className="w-full justify-start text-start"
|
|||
|
|
key={`side-menu-item-${index}`}
|
|||
|
|
>
|
|||
|
|
<a href={href}>
|
|||
|
|
<Icon />
|
|||
|
|
{label}
|
|||
|
|
</a>
|
|||
|
|
</Button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</SheetContent>
|
|||
|
|
</Sheet>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const SecondaryMenu = ({ menu }: SecondaryMenuProps) => {
|
|||
|
|
return (
|
|||
|
|
<div className="relative overflow-visible bg-accent px-6 py-2">
|
|||
|
|
<div className="hidden lg:contents">
|
|||
|
|
<NavigationMenu
|
|||
|
|
dir="rtl"
|
|||
|
|
viewport={false}
|
|||
|
|
className="relative z-50 justify-start overflow-visible text-start [&_a]:bg-transparent [&_a]:hover:bg-accent-foreground/10 [&_button]:bg-transparent [&_button]:hover:bg-accent-foreground/10"
|
|||
|
|
>
|
|||
|
|
<NavigationMenuList className="gap-3.5">
|
|||
|
|
{menu.map(({ sections, ...item }, index) => (
|
|||
|
|
<NavigationMenuItem value={`${index}`} key={index} className="static">
|
|||
|
|
{sections ? (
|
|||
|
|
<DesktopMenuDropdownItem sections={sections} {...item} />
|
|||
|
|
) : (
|
|||
|
|
<NavigationMenuLink
|
|||
|
|
asChild
|
|||
|
|
className={cn(
|
|||
|
|
navigationMenuTriggerStyle(),
|
|||
|
|
"bg-transparent hover:bg-accent-foreground/10",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
<a href={item.href}>{item.label}</a>
|
|||
|
|
</NavigationMenuLink>
|
|||
|
|
)}
|
|||
|
|
</NavigationMenuItem>
|
|||
|
|
))}
|
|||
|
|
</NavigationMenuList>
|
|||
|
|
</NavigationMenu>
|
|||
|
|
</div>
|
|||
|
|
<div className="lg:hidden">
|
|||
|
|
<SecondaryMenuMobile menu={menu} />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const SecondaryMenuMobile = ({ menu }: SecondaryMenuMobileProps) => {
|
|||
|
|
if (!menu) return;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Sheet modal={false}>
|
|||
|
|
<SheetTrigger asChild>
|
|||
|
|
<Button variant="secondary">
|
|||
|
|
<LayoutGrid />
|
|||
|
|
دستهبندیها
|
|||
|
|
</Button>
|
|||
|
|
</SheetTrigger>
|
|||
|
|
<SheetContent
|
|||
|
|
side="top"
|
|||
|
|
className="!top-[calc(var(--primary-nav-height)+var(--secondary-nav-height))] z-40 !h-[calc(100dvh-var(--primary-nav-height)-var(--secondary-nav-height))] overflow-hidden [&>button]:hidden"
|
|||
|
|
>
|
|||
|
|
<div className="min-h-0 flex-1 touch-pan-y overflow-y-auto overscroll-contain pb-10">
|
|||
|
|
<div className="p-6">
|
|||
|
|
{menu.map(({ sections, label, href }, index) => (
|
|||
|
|
<div key={index}>
|
|||
|
|
<h2 className="border-b py-4 text-lg leading-normal font-bold">
|
|||
|
|
{href ? <a href={href}>{label}</a> : <span>{label}</span>}
|
|||
|
|
</h2>
|
|||
|
|
{sections && (
|
|||
|
|
<Accordion type="multiple">
|
|||
|
|
{sections.map(({ id, label, items }) => (
|
|||
|
|
<AccordionItem value={id} key={`menu-section-${id}`}>
|
|||
|
|
<AccordionTrigger>{label}</AccordionTrigger>
|
|||
|
|
<AccordionContent>
|
|||
|
|
<div className="flex flex-col">
|
|||
|
|
{items.map(({ href, label }, index) => (
|
|||
|
|
<Button
|
|||
|
|
className="w-full justify-start"
|
|||
|
|
asChild
|
|||
|
|
variant="ghost"
|
|||
|
|
size="sm"
|
|||
|
|
key={`menu-item-${index}`}
|
|||
|
|
>
|
|||
|
|
<a href={href}>{label}</a>
|
|||
|
|
</Button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</AccordionContent>
|
|||
|
|
</AccordionItem>
|
|||
|
|
))}
|
|||
|
|
</Accordion>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</SheetContent>
|
|||
|
|
</Sheet>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const DesktopMenuDropdownItem = ({
|
|||
|
|
sections,
|
|||
|
|
label,
|
|||
|
|
href,
|
|||
|
|
}: DesktopMenuDropdownItemProps) => {
|
|||
|
|
const [activeSectionId, setActiveSectionId] = useState<string>();
|
|||
|
|
|
|||
|
|
const sharedClasses = "h-full space-y-2 overflow-auto pt-6 pb-2";
|
|||
|
|
|
|||
|
|
const MenuItemClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|||
|
|
const selectedSectionId =
|
|||
|
|
e.currentTarget.getAttribute("data-id") ?? undefined;
|
|||
|
|
|
|||
|
|
setActiveSectionId(selectedSectionId);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const MenuItemMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|||
|
|
const selectedSectionId =
|
|||
|
|
e.currentTarget.getAttribute("data-id") ?? undefined;
|
|||
|
|
|
|||
|
|
setActiveSectionId(selectedSectionId);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const activeSectionData = useMemo(
|
|||
|
|
() => sections?.find((section) => section.id === activeSectionId),
|
|||
|
|
[sections, activeSectionId],
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!sections) return;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Fragment>
|
|||
|
|
<NavigationMenuTrigger>{label}</NavigationMenuTrigger>
|
|||
|
|
<NavigationMenuContent
|
|||
|
|
dir="rtl"
|
|||
|
|
className="start-0 end-auto z-50 !overflow-visible p-0"
|
|||
|
|
>
|
|||
|
|
<div
|
|||
|
|
style={
|
|||
|
|
{
|
|||
|
|
"--menu-margin-bottom": "5rem",
|
|||
|
|
} as CSSProperties
|
|||
|
|
}
|
|||
|
|
className="relative flex min-w-max items-stretch overflow-visible max-h-[calc(100dvh-var(--secondary-nav-height)-var(--primary-nav-height)-var(--menu-margin-bottom))]"
|
|||
|
|
>
|
|||
|
|
<div className="relative z-0 shrink-0">
|
|||
|
|
<div className={cn("w-60", sharedClasses)}>
|
|||
|
|
<h2 className="px-6 text-sm font-bold">
|
|||
|
|
<a href={href}>{label}</a>
|
|||
|
|
</h2>
|
|||
|
|
<ol>
|
|||
|
|
{sections.map((section, index) => (
|
|||
|
|
<li key={index}>
|
|||
|
|
<Button
|
|||
|
|
variant="ghost"
|
|||
|
|
data-id={section.id}
|
|||
|
|
data-state={
|
|||
|
|
section.id === activeSectionId ? "active" : "inactive"
|
|||
|
|
}
|
|||
|
|
onClick={MenuItemClick}
|
|||
|
|
onMouseEnter={MenuItemMouseEnter}
|
|||
|
|
className={cn(
|
|||
|
|
"relative w-full justify-start rounded-none px-6 text-start font-normal",
|
|||
|
|
"data-[state=active]:bg-accent",
|
|||
|
|
"transition-opacity after:absolute after:inset-y-0 after:start-0 after:h-full after:w-1 after:bg-primary data-[state=inactive]:after:opacity-0",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{section.label}
|
|||
|
|
</Button>
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ol>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{activeSectionData && (
|
|||
|
|
<div className="relative z-10 shrink-0">
|
|||
|
|
<div
|
|||
|
|
className={cn(
|
|||
|
|
"w-50 border-s bg-popover shadow-sm",
|
|||
|
|
sharedClasses,
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
<h2 className="px-6 text-sm font-bold">
|
|||
|
|
{activeSectionData.label}
|
|||
|
|
</h2>
|
|||
|
|
<ol>
|
|||
|
|
{activeSectionData.items.map((item, index) => (
|
|||
|
|
<li key={index}>
|
|||
|
|
<NavigationMenuLink
|
|||
|
|
className="px-6 hover:underline"
|
|||
|
|
asChild
|
|||
|
|
>
|
|||
|
|
<a href={item.href}>{item.label}</a>
|
|||
|
|
</NavigationMenuLink>
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ol>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</NavigationMenuContent>
|
|||
|
|
</Fragment>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export { EcommerceNavbar2 };
|