Files
reui-blocks/blocks/application-shell7.tsx
T

1075 lines
33 KiB
TypeScript
Raw Normal View History

"use client";
import { motion } from "framer-motion";
import {
BadgeCheck,
Bell,
Bookmark,
ChevronDown,
ClipboardCheck,
Clock,
CreditCard,
FileText,
Folder,
Hash,
Inbox,
Layers,
LayoutGrid,
LogOut,
MessageSquare,
MessageSquareText,
MessagesSquare,
PhoneCall,
Pin,
Settings,
Sparkles,
UserPlus,
Users,
} from "lucide-react";
import * as React from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Button } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
} from "@/components/ui/drawer";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
type ModuleId = "messages" | "spaces" | "threads" | "calls" | "files";
type NavItem = {
label: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
href: string;
badge?: string;
active?: boolean;
};
type NavSection = {
title?: string;
items: NavItem[];
};
type NavModule = {
id: ModuleId;
label: string;
icon: NavItem["icon"];
sections: NavSection[];
};
function getItemKey(moduleId: ModuleId, item: NavItem) {
return `${moduleId}:${item.label}`;
}
function getDefaultItemKey(module: NavModule) {
const items = module.sections.flatMap((section) => section.items);
const activeItem = items.find((item) => item.active) ?? items[0];
return activeItem ? getItemKey(module.id, activeItem) : `${module.id}:`;
}
function getActiveItemLabel(module: NavModule, activeItemKey: string) {
const items = module.sections.flatMap((section) => section.items);
const match = items.find(
(item) => getItemKey(module.id, item) === activeItemKey,
);
return match?.label ?? module.sections[0]?.items[0]?.label ?? module.label;
}
function getInitials(name: string) {
return (
name
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((part) => part[0]?.toUpperCase())
.join("") || "U"
);
}
const navModules: NavModule[] = [
{
id: "messages",
label: "پیام‌ها",
icon: MessagesSquare,
sections: [
{
title: "صندوق ورودی",
items: [
{ label: "همه پیام‌ها", icon: Inbox, href: "#", active: true },
{ label: "ارجاع‌ها", icon: UserPlus, href: "#", badge: "۴" },
{ label: "اعلان‌ها", icon: Bell, href: "#", badge: "۱۲" },
{ label: "ذخیره‌شده", icon: Bookmark, href: "#" },
],
},
{
title: "نماها",
items: [
{ label: "خوانده‌نشده", icon: Bell, href: "#" },
{ label: "زمان‌بندی‌شده", icon: Clock, href: "#" },
],
},
],
},
{
id: "spaces",
label: "فضاها",
icon: LayoutGrid,
sections: [
{
title: "فضاها",
items: [
{ label: "به‌روزرسانی محصول", icon: Hash, href: "#", active: true },
{ label: "موفقیت مشتری", icon: Users, href: "#", badge: "۲" },
{ label: "عملیات انتشار", icon: Layers, href: "#" },
],
},
{
title: "مستقیم",
items: [
{ label: "Kai", icon: Users, href: "#", badge: "۱" },
{ label: "Mina", icon: Users, href: "#" },
{ label: "Diego", icon: Users, href: "#" },
],
},
],
},
{
id: "threads",
label: "گفتگوها",
icon: MessageSquareText,
sections: [
{
title: "گفتگوها",
items: [
{ label: "دنبال‌شده", icon: Bookmark, href: "#", active: true },
{ label: "محول‌شده", icon: Users, href: "#" },
{ label: "بسته‌شده", icon: MessageSquareText, href: "#" },
],
},
{
title: "برجسته‌ها",
items: [
{ label: "ارجاع‌ها", icon: UserPlus, href: "#", badge: "۳" },
{ label: "سنجاق‌شده", icon: Bookmark, href: "#" },
],
},
],
},
{
id: "calls",
label: "تماس‌ها",
icon: PhoneCall,
sections: [
{
title: "جلسات",
items: [
{ label: "پیش‌رو", icon: Clock, href: "#", active: true },
{ label: "ضبط‌ها", icon: FileText, href: "#" },
],
},
{
title: "دستگاه‌ها",
items: [
{ label: "صدا و تصویر", icon: PhoneCall, href: "#" },
{ label: "اعلان‌ها", icon: Bell, href: "#" },
],
},
],
},
{
id: "files",
label: "فایل‌ها",
icon: Folder,
sections: [
{
title: "مرور",
items: [
{ label: "اخیر", icon: Clock, href: "#", active: true },
{ label: "اشتراکی با من", icon: Users, href: "#" },
{ label: "بارگذاری‌ها", icon: Folder, href: "#" },
{ label: "مجموعه‌ها", icon: Layers, href: "#" },
],
},
{
title: "فیلترهای سریع",
items: [
{ label: "اسناد", icon: FileText, href: "#" },
{ label: "نظرات خوانده‌نشده", icon: Bell, href: "#", badge: "۵" },
],
},
],
},
];
const utilities: NavItem[] = [
{ label: "اعلان‌ها", icon: Bell, href: "#" },
{ label: "تنظیمات", icon: Settings, href: "#" },
];
const data = {
user: {
name: "علی رضایی",
email: "ali@example.com",
avatar:
"/block-media/shadcnblocks/block/avatar/avatar3.webp",
},
participants: [
{
id: "you",
name: "علی رضایی",
title: "شما",
status: "online",
},
{
id: "riley",
name: "ریحانه Park",
title: "محصول",
status: "online",
},
{
id: "sam",
name: "Sam Rivera",
title: "طراحی",
status: "away",
},
{
id: "noah",
name: "Noah Patel",
title: "مهندسی",
status: "offline",
},
{
id: "maya",
name: "نگار Singh",
title: "پشتیبانی",
status: "online",
},
{
id: "jules",
name: "Jules Tan",
title: "عملیات",
status: "offline",
},
] as const,
activity: [
{
id: "a1",
icon: MessageSquare,
title: "گفتگوی جدید شروع شد",
detail: "Riley در به‌روزرسانی محصول پست گذاشت.",
time: "۲ دقیقه",
},
{
id: "a2",
icon: UserPlus,
title: "ارجاع جدید",
detail: "سام شما را به تیکت پشتیبانی مشتری ارجاع داد.",
time: "۱۸ دقیقه",
},
{
id: "a3",
icon: Pin,
title: "پیام سنجاق شد",
detail: "Maya «چک‌لیست انتشار نسخه ۳» را سنجاق کرد.",
time: "۱ ساعت",
},
{
id: "a4",
icon: ClipboardCheck,
title: "وظیفه تکمیل شد",
detail: "Noah «پاکسازی API» را انجام‌شده علامت زد.",
time: "امروز",
},
{
id: "a5",
icon: PhoneCall,
title: "تماس پایان یافت",
detail: "همگام‌سازی هفتگی • ۲۴ دقیقه",
time: "دیروز",
},
{
id: "a6",
icon: FileText,
title: "فایل بارگذاری شد",
detail: "Jules «incident-notes.pdf» را به اشتراک گذاشت.",
time: "دیروز",
},
] as const,
};
interface ShellBreadcrumbProps {
className?: string;
moduleLabel: string;
itemLabel: string;
}
function ShellBreadcrumb({
className,
moduleLabel,
itemLabel,
}: ShellBreadcrumbProps) {
return (
<Breadcrumb className={cn(className)}>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">{moduleLabel}</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>{itemLabel}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
);
}
interface ModulePanelProps {
className?: string;
module: NavModule;
activeItemKey: string;
onSelectItem: (key: string) => void;
}
function ModulePanel({
className,
module,
activeItemKey,
onSelectItem,
}: ModulePanelProps) {
return (
<div className={cn("space-y-4 px-4 py-3 text-start text-sm", className)}>
{module.sections.map((section, index) => (
<div className="space-y-2" key={section.title ?? index}>
{section.title && (
<SidebarGroupLabel className="mb-1 text-xs font-medium tracking-wide text-muted-foreground">
{section.title}
</SidebarGroupLabel>
)}
<SidebarMenu>
{section.items.map((item) => {
const Icon = item.icon;
const itemKey = getItemKey(module.id, item);
return (
<SidebarMenuItem
key={item.label}
className="text-muted-foreground hover:text-foreground"
>
<SidebarMenuButton
asChild
isActive={itemKey === activeItemKey}
className="px-2"
>
<a
href={item.href}
onClick={(e) => {
e.preventDefault();
onSelectItem(itemKey);
}}
>
<Icon className="size-4" />
{item.badge && (
<SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
)}
<span>{item.label}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</div>
))}
<div className="border-t pt-3">
<SidebarGroupLabel className="mb-2 text-xs font-medium tracking-wide text-muted-foreground">
ترجیحات
</SidebarGroupLabel>
<SidebarMenu className="text-sm">
{utilities.map((item) => {
const Icon = item.icon;
return (
<SidebarMenuItem key={item.label}>
<SidebarMenuButton
asChild
className="gap-2.5 px-2 text-muted-foreground hover:text-foreground"
>
<a href={item.href}>
<Icon className="size-4" />
<span>{item.label}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</div>
</div>
);
}
interface AppSidebarProps extends Omit<
React.ComponentProps<typeof Sidebar>,
"className"
> {
className?: string;
activeKey: ModuleId;
activeItemKey: string;
onNavChange: (key: ModuleId) => void;
onItemChange: (key: string) => void;
}
function AppSidebar({
className,
activeKey,
activeItemKey,
onNavChange,
onItemChange,
...props
}: AppSidebarProps) {
const { state, setOpen, isMobile } = useSidebar();
const isCollapsed = state === "collapsed";
const showFooter = isCollapsed || isMobile;
const activeModule =
navModules.find((module) => module.id === activeKey) ?? navModules[0];
return (
<Sidebar
side="right"
collapsible="icon"
className={cn(
"overflow-hidden *:data-[sidebar=sidebar]:flex-row",
className,
)}
{...props}
>
<Sidebar
collapsible="none"
className="w-[calc(var(--sidebar-width-icon)+1px)]! border-e"
>
<SidebarHeader className="h-14 border-b">
<SidebarMenu className="my-auto">
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild className="md:h-8 md:p-0">
<a href="#">
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-primary">
<img
src="/block-media/shadcnblocks/block/logos/NovaSales-logo.svg"
alt="شاد‌سی‌ان بلاکس"
className="size-6 text-primary-foreground invert dark:invert-0"
/>
</div>
<span className="text-lg font-semibold tracking-tighter">
ری یو آی
</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent className="px-1.5 md:px-0">
<SidebarMenu>
{" "}
<SidebarMenuItem>
<motion.div
key={isCollapsed ? "h-collapsed" : "h-expanded"}
className={cn(
isCollapsed
? "hidden items-center justify-center sm:flex"
: "hidden",
)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
<SidebarTrigger />
</motion.div>
</SidebarMenuItem>
{navModules.map((module) => {
const Icon = module.icon;
return (
<SidebarMenuItem key={module.id}>
<SidebarMenuButton
tooltip={{
children: module.label,
hidden: isMobile,
}}
onClick={() => {
onNavChange(module.id);
setOpen(true);
}}
isActive={activeKey === module.id}
aria-label={module.label}
className="px-2.5 md:px-2"
>
<Icon className="size-4" />
<span>{module.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter className={cn(showFooter ? "flex" : "hidden")}>
<SidebarGroup className="p-0">
<SidebarGroupContent className="px-1.5 md:px-0">
<SidebarMenu>
{utilities.map((item) => {
const Icon = item.icon;
return (
<SidebarMenuItem key={item.label}>
<SidebarMenuButton
asChild
tooltip={{
children: item.label,
hidden: isMobile,
}}
className="px-2.5 md:px-2"
aria-label={item.label}
>
<a href={item.href}>
<Icon className="size-4" />
<span>{item.label}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>{" "}
</SidebarGroupContent>
</SidebarGroup>
</SidebarFooter>
</Sidebar>
<Sidebar collapsible="none" className="hidden flex-1 md:flex">
<SidebarHeader
className={cn(
"flex h-14 border-b px-2",
isCollapsed
? "flex-row items-center justify-between gap-y-4 md:flex-col md:items-center md:justify-start"
: "flex-row items-center justify-between",
)}
>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<div className="flex w-full items-center px-2 group-data-[collapsible=icon]:justify-center">
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="flex h-auto items-center gap-1.5 p-0! hover:bg-transparent"
>
<span className="font-semibold">ری یو آی</span>
<ChevronDown className="size-3 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
</div>
<DropdownMenuContent
className="min-w-60 rounded-lg"
side="bottom"
align="center"
>
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<a href="#">
تنظیمات فضای کار
<DropdownMenuShortcut>⌥⇧S</DropdownMenuShortcut>
</a>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
دعوت هم‌تیمی‌ها
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent className="min-w-64 rounded-lg">
<DropdownMenuLabel>دعوت به Pulse</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem className="flex items-center gap-2">
<div className="flex aspect-square size-6 items-center justify-center rounded bg-blue-500 text-[10px] font-semibold text-white">
EM
</div>
دعوت با ایمیل
</DropdownMenuItem>
<DropdownMenuItem className="flex items-center gap-2">
<div className="flex aspect-square size-6 items-center justify-center rounded bg-emerald-500 text-[10px] font-semibold text-white">
LK
</div>
کپی لینک دعوت
<DropdownMenuShortcut>⌘C</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>دعوت‌های در انتظار</DropdownMenuItem>
<DropdownMenuItem>نقش‌ها و دسترسی</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
خروج
<DropdownMenuShortcut>⌥⇧Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<motion.div
key={isCollapsed ? "h-collapsed" : "h-expanded"}
className={cn(
"flex items-center gap-2",
isCollapsed ? "flex-row md:flex-col-reverse" : "flex-row",
)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8 }}
>
<SidebarTrigger />
</motion.div>
</SidebarHeader>
<SidebarContent className="overflow-hidden">
<ScrollArea className="min-h-0 flex-1">
<SidebarGroup className="px-0">
<SidebarGroupContent>
<ModulePanel
module={activeModule}
activeItemKey={activeItemKey}
onSelectItem={onItemChange}
/>
</SidebarGroupContent>
</SidebarGroup>
</ScrollArea>
</SidebarContent>
</Sidebar>
</Sidebar>
);
}
interface NavUserProps {
className?: string;
user: {
name: string;
email: string;
avatar: string;
};
}
function NavUser({ className, user }: NavUserProps) {
const { isMobile } = useSidebar();
const initials = getInitials(user.name);
return (
<SidebarMenu className={cn("w-full", className)}>
<SidebarMenuItem className="w-full">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="default"
className="h-10 w-full data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-7 w-7">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="bg-sidebar-primary text-xs font-medium text-sidebar-primary-foreground">
{initials}
</AvatarFallback>
</Avatar>
<span className="min-w-0 flex-1 truncate text-sm font-medium">
{user.name}
</span>
<ChevronDown className="ms-auto size-4 text-muted-foreground" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "left"}
align="start"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="bg-sidebar-primary font-medium text-sidebar-primary-foreground">
{initials}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-start text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs text-muted-foreground">
{user.email}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles className="me-2 size-4" />
ارتقا به حرفه‌ای
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck className="me-2 size-4" />
حساب کاربری
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard className="me-2 size-4" />
صورتحساب
</DropdownMenuItem>
<DropdownMenuItem>
<Bell className="me-2 size-4" />
اعلان‌ها
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut className="me-2 size-4" />
خروج
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
interface SidebarRightProps extends Omit<
React.ComponentProps<typeof Sidebar>,
"className"
> {
className?: string;
}
function RightPanelContent() {
return (
<div className="space-y-6 p-4 text-start">
<section className="space-y-3">
<div className="flex items-center justify-between gap-2">
<SidebarGroupLabel className="text-xs font-medium tracking-wide text-muted-foreground">
شرکت‌کنندگان
</SidebarGroupLabel>
<Button variant="outline" size="sm" className="h-8 gap-2">
<UserPlus className="size-4" />
دعوت
</Button>
</div>
<div className="space-y-1">
{data.participants.map((participant) => {
const statusClassName =
participant.status === "online"
? "bg-emerald-500"
: participant.status === "away"
? "bg-amber-500"
: "bg-muted-foreground/50";
const initials = getInitials(participant.name);
const isYou = participant.id === "you";
return (
<button
key={participant.id}
type="button"
className="flex w-full items-center gap-3 rounded-md px-2 py-1.5 text-start hover:bg-accent"
>
<span
className={cn(
"mt-0.5 size-2 shrink-0 rounded-full",
statusClassName,
)}
aria-hidden="true"
/>
<Avatar className="h-7 w-7">
<AvatarFallback className="bg-primary text-xs font-medium text-primary-foreground">
{initials}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="truncate text-sm font-medium">
{participant.name}
</span>
{isYou && (
<Badge
variant="secondary"
className="h-5 px-1.5 text-[10px]"
>
شما
</Badge>
)}
</div>
<div className="truncate text-xs text-muted-foreground">
{participant.title}
</div>
</div>
</button>
);
})}
</div>
</section>
<section className="space-y-3">
<div className="flex items-center justify-between">
<SidebarGroupLabel className="text-xs font-medium tracking-wide text-muted-foreground">
فعالیت تیم‌ها
</SidebarGroupLabel>
<Button
variant="ghost"
size="sm"
className="h-8 px-2 text-muted-foreground hover:text-foreground"
>
مشاهده همه
</Button>
</div>
<div className="space-y-2">
{data.activity.map((event) => {
const Icon = event.icon;
return (
<div
key={event.id}
className="flex gap-3 rounded-md p-2 hover:bg-accent/50"
>
<div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md bg-accent/50">
<Icon className="size-4 text-foreground" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<div className="truncate text-sm font-medium">
{event.title}
</div>
<div className="shrink-0 text-xs text-muted-foreground">
{event.time}
</div>
</div>
<div className="mt-0.5 text-xs text-muted-foreground">
{event.detail}
</div>
</div>
</div>
);
})}
</div>
</section>
</div>
);
}
export function SidebarRight({ className, ...props }: SidebarRightProps) {
return (
<Sidebar
collapsible="none"
side="left"
className={cn("sticky top-0 hidden h-svh border-s lg:flex", className)}
{...props}
>
<SidebarHeader className="flex h-14 items-center border-b px-2">
<NavUser user={data.user} />
</SidebarHeader>
<SidebarContent className="p-0">
<ScrollArea className="h-[calc(100svh-3.5rem)]">
<RightPanelContent />
</ScrollArea>
</SidebarContent>
</Sidebar>
);
}
interface ApplicationShell7Props {
className?: string;
}
export function ApplicationShell7({ className }: ApplicationShell7Props) {
const [activeKey, setActiveKey] = React.useState<ModuleId>(navModules[0].id);
const [isMobilePanelOpen, setIsMobilePanelOpen] = React.useState(false);
const [activeItemKey, setActiveItemKey] = React.useState(() =>
getDefaultItemKey(navModules[0]),
);
const activeModule =
navModules.find((module) => module.id === activeKey) ?? navModules[0];
const activeItemLabel = getActiveItemLabel(activeModule, activeItemKey);
React.useEffect(() => {
setActiveItemKey(getDefaultItemKey(activeModule));
}, [activeModule]);
return (
<SidebarProvider
dir="rtl"
lang="fa"
style={
{
"--sidebar-width": "350px",
} as React.CSSProperties
}
className={cn(className)}
>
<div className="hidden md:block">
<AppSidebar
activeKey={activeKey}
activeItemKey={activeItemKey}
onNavChange={setActiveKey}
onItemChange={setActiveItemKey}
/>
</div>
<SidebarInset>
<header className="sticky top-0 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-3">
<a href="#" className="md:hidden">
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-primary">
<img
src="/block-media/shadcnblocks/block/logos/NovaSales-logo.svg"
alt="شاد‌سی‌ان بلاکس"
className="size-6 text-primary-foreground invert dark:invert-0"
/>
</div>
</a>
<div className="flex flex-1 items-center gap-2">
<ShellBreadcrumb
moduleLabel={activeModule.label}
itemLabel={activeItemLabel}
/>
</div>
<Sheet>
<SheetTrigger asChild>
<Button
variant="ghost"
size="icon"
className="lg:hidden"
aria-label="تیم"
>
<Users className="size-5" />
</Button>
</SheetTrigger>
<SheetContent side="left" dir="rtl" className="w-80 p-0 text-start">
<SheetHeader className="px-4 pt-4">
<SheetTitle className="text-start">تیم</SheetTitle>
</SheetHeader>
<ScrollArea className="min-h-0 flex-1">
<RightPanelContent />
</ScrollArea>
</SheetContent>
</Sheet>
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="h-[50vh] w-full rounded-xl bg-muted/50" />
<div className="h-[100vh] w-full rounded-xl bg-muted/50" />
</div>
</SidebarInset>
<SidebarRight />
<Drawer
open={isMobilePanelOpen}
onOpenChange={setIsMobilePanelOpen}
dismissible
>
<DrawerContent dir="rtl" className="text-start md:hidden">
<DrawerHeader>
<DrawerTitle className="text-start">{activeModule.label}</DrawerTitle>
</DrawerHeader>
<div className="pb-6">
<ModulePanel
module={activeModule}
activeItemKey={activeItemKey}
onSelectItem={(key) => {
setActiveItemKey(key);
setIsMobilePanelOpen(false);
}}
/>
</div>
</DrawerContent>
</Drawer>
<nav
aria-label="ناوبری پایین موبایل"
className="fixed inset-x-0 bottom-0 z-40 border-t bg-background/95 backdrop-blur md:hidden"
>
<div className="grid grid-cols-5">
{navModules.map((module) => {
const Icon = module.icon;
const isActive = module.id === activeKey;
return (
<button
key={module.id}
type="button"
onClick={() => {
setActiveKey(module.id);
setIsMobilePanelOpen(true);
}}
className={`flex flex-col items-center justify-center py-3 text-xs ${isActive
? "text-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
aria-label={module.label}
>
<Icon className="h-5 w-5" />
<span className="sr-only">{module.label}</span>
</button>
);
})}
</div>
</nav>
</SidebarProvider>
);
}