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

512 lines
15 KiB
TypeScript
Raw Normal View History

"use client";
import { motion } from "framer-motion";
import {
Activity,
Calendar,
Clapperboard,
FileText,
Handshake,
Home,
Images,
Inbox,
Kanban,
LayoutDashboard,
ListTodo,
Receipt,
Settings,
Wallet,
} from "lucide-react";
import * as React from "react";
import {
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
} from "@/components/ui/drawer";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
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 =
| "overview"
| "sales"
| "production"
| "deliverables"
| "finance";
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[];
};
const navModules: NavModule[] = [
{
id: "overview",
label: "نمای کلی",
icon: LayoutDashboard,
sections: [
{
title: "نمای کلی",
items: [
{ label: "خانه", icon: Home, href: "#", active: true },
{ label: "داشبورد", icon: Activity, href: "#" },
{ label: "صندوق ورودی", icon: Inbox, href: "#", badge: "۳" },
],
},
{
title: "وظایف",
items: [
{ label: "وظایف من", icon: ListTodo, href: "#", badge: "۵" },
{ label: "تقویم", icon: Calendar, href: "#" },
],
},
],
},
{
id: "sales",
label: "فروش",
icon: Handshake,
sections: [
{
title: "قیف فروش",
items: [
{ label: "سرنخ‌های جدید", icon: Inbox, href: "#", active: true },
{ label: "پیش‌فاکتورها", icon: FileText, href: "#" },
{ label: "رزروها", icon: Calendar, href: "#" },
],
},
{
title: "گزارش‌ها",
items: [{ label: "داشبورد فروش", icon: Activity, href: "#" }],
},
],
},
{
id: "production",
label: "تولید",
icon: Clapperboard,
sections: [
{
title: "مسیر تولید",
items: [
{ label: "تخته فیلمبرداری", icon: Kanban, href: "#", active: true },
{ label: "لیست شات‌ها", icon: ListTodo, href: "#" },
],
},
{
title: "تیم",
items: [{ label: "تخصیص‌ها", icon: Inbox, href: "#" }],
},
],
},
{
id: "deliverables",
label: "تحویل‌ها",
icon: Images,
sections: [
{
title: "گالری‌ها",
items: [
{ label: "همه گالری‌ها", icon: Images, href: "#", active: true },
{ label: "تأییدها", icon: Inbox, href: "#", badge: "۲" },
],
},
{
title: "خروجی‌ها",
items: [{ label: "مرکز دانلود", icon: FileText, href: "#" }],
},
],
},
{
id: "finance",
label: "مالی",
icon: Wallet,
sections: [
{
title: "صورتحساب",
items: [
{ label: "فاکتورها", icon: Receipt, href: "#", active: true },
{ label: "پرداخت‌ها", icon: Wallet, href: "#" },
],
},
{
title: "گزارش‌ها",
items: [{ label: "سود و زیان", icon: Activity, href: "#" }],
},
],
},
];
const utilities: NavItem[] = [{ label: "تنظیمات", icon: Settings, href: "#" }];
interface ModulePanelProps {
className?: string;
module: NavModule;
}
function ModulePanel({ className, module }: 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;
return (
<SidebarMenuItem
key={item.label}
className="text-muted-foreground hover:text-foreground"
>
<SidebarMenuButton
asChild
isActive={item.active}
className="px-2"
>
<a href={item.href}>
<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;
onNavChange: (key: ModuleId) => void;
}
export function AppSidebar({
className,
activeKey,
onNavChange,
...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];
const activeNavLabel = activeModule.label;
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/ShadcnBlocks-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="h-14 gap-3.5 border-b px-4">
<div className="my-auto flex w-full items-center justify-between">
<div className="text-start text-base font-medium text-foreground">
{activeNavLabel}
</div>
<div className="hidden sm:block">
<SidebarTrigger />
</div>
</div>
</SidebarHeader>
<SidebarContent className="overflow-hidden">
<ScrollArea className="min-h-0 flex-1">
<SidebarGroup className="px-0">
<SidebarGroupContent>
<ModulePanel module={activeModule} />
</SidebarGroupContent>
</SidebarGroup>
</ScrollArea>
</SidebarContent>
</Sidebar>
</Sidebar>
);
}
interface ApplicationShell6Props {
className?: string;
}
export function ApplicationShell6({ className }: ApplicationShell6Props) {
const [activeKey, setActiveKey] = React.useState<ModuleId>(navModules[0].id);
const [isMobilePanelOpen, setIsMobilePanelOpen] = React.useState(false);
const activeModule =
navModules.find((module) => module.id === activeKey) ?? navModules[0];
const activeNavLabel = activeModule.label;
return (
<SidebarProvider
dir="rtl"
lang="fa"
style={
{
"--sidebar-width": "350px",
} as React.CSSProperties
}
className={cn(className)}
>
<div className="hidden md:block">
<AppSidebar activeKey={activeKey} onNavChange={setActiveKey} />
</div>
<SidebarInset className="pb-20 md:pb-0">
<div className="flex flex-1 flex-col gap-4">
<div className="sticky top-0 z-40 flex h-14 items-center gap-2 border-b bg-background px-4 md:hidden">
<SidebarTrigger />
<Separator
orientation="vertical"
className="ms-2 data-[orientation=vertical]:h-4"
/>
<a href="#">
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-primary">
<img
src="/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo.svg"
alt="شاد‌سی‌ان بلاکس"
className="size-6 text-primary-foreground invert dark:invert-0"
/>
</div>
</a>
<div className="ms-auto text-sm font-medium text-muted-foreground">
{activeNavLabel}
</div>
</div>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</div>
</SidebarInset>
<Drawer
open={isMobilePanelOpen}
onOpenChange={setIsMobilePanelOpen}
dismissible
>
<DrawerContent dir="rtl" className="text-start md:hidden">
<DrawerHeader>
<DrawerTitle className="text-start">{activeNavLabel}</DrawerTitle>
</DrawerHeader>
<div className="pb-6">
<ModulePanel module={activeModule} />
</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>
);
}