Files
reui-blocks/blocks/sidebar13.tsx
T

320 lines
9.1 KiB
TypeScript

"use client";
import { ChevronRight, File, Folder, FolderOpen } from "lucide-react";
import * as React from "react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
// Nav group with optional collapsible state
type NavGroup = {
title: string;
items: Array<{
label: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
href: string;
isActive?: boolean;
children?: Array<{
label: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
href: string;
isActive?: boolean;
}>;
}>;
defaultOpen?: boolean;
};
// Complete sidebar data structure
type SidebarData = {
// Logo/branding (all sidebars)
logo: {
src: string;
alt: string;
title: string;
description: string;
};
// Main navigation groups (all sidebars)
navGroups: NavGroup[];
// Footer navigation group (all sidebars)
footerGroup: NavGroup;
// User data for user footer (Sidebar6+)
user?: {
name: string;
email: string;
avatar: string;
};
// Workspaces for switcher (Sidebar7+)
workspaces?: Array<{
id: string;
name: string;
logo: string;
plan: string;
}>;
// Currently active workspace (Sidebar7+)
activeWorkspace?: string;
};
// Shared sidebar data - works with all sidebar variations
const sidebarData: SidebarData = {
logo: {
src: "/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo.svg",
alt: "وندوک",
title: "وندوک",
description: "CRM فروش B2B",
},
navGroups: [],
footerGroup: {
title: "پشتیبانی",
items: [],
},
};
// File tree data structure
type FileTreeItem = {
name: string;
label: string;
type: "file" | "folder";
children?: FileTreeItem[];
};
const fileTreeData: FileTreeItem[] = [
{
name: "src",
label: "فضای کاری",
type: "folder",
children: [
{
name: "components",
label: "ماژول‌ها",
type: "folder",
children: [
{
name: "ui",
label: "فروش",
type: "folder",
children: [
{ name: "button.tsx", label: "داشبورد.tsx", type: "file" },
{ name: "card.tsx", label: "مخاطبین.tsx", type: "file" },
{ name: "dialog.tsx", label: "فرصت‌ها.tsx", type: "file" },
{ name: "input.tsx", label: "فعالیت‌ها.tsx", type: "file" },
{ name: "sidebar.tsx", label: "گزارش‌ها.tsx", type: "file" },
],
},
{ name: "app-sidebar.tsx", label: "تنظیمات تیم.tsx", type: "file" },
{ name: "nav-main.tsx", label: "منوی اصلی.tsx", type: "file" },
{ name: "nav-user.tsx", label: "پروفایل کاربر.tsx", type: "file" },
],
},
{
name: "lib",
label: "کتابخانه داده",
type: "folder",
children: [{ name: "utils.ts", label: "ابزارها.ts", type: "file" }],
},
{
name: "app",
label: "اپلیکیشن",
type: "folder",
children: [
{ name: "layout.tsx", label: "چیدمان.tsx", type: "file" },
{ name: "page.tsx", label: "صفحه.tsx", type: "file" },
{ name: "globals.css", label: "تم سازمانی.css", type: "file" },
],
},
],
},
{
name: "public",
label: "عمومی",
type: "folder",
children: [
{ name: "favicon.ico", label: "آیکن سایت.ico", type: "file" },
{ name: "logo.svg", label: "لوگو.svg", type: "file" },
],
},
{ name: "package.json", label: "پیکربندی بسته.json", type: "file" },
{ name: "tsconfig.json", label: "پیکربندی.tsconfig.json", type: "file" },
{
name: "tailwind.config.ts",
label: "پیکربندی تم.ts",
type: "file",
},
{ name: "README.md", label: "راهنما.md", type: "file" },
];
const SidebarLogo = ({ logo }: { logo: SidebarData["logo"] }) => {
return (
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg">
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-primary">
<img
src={logo.src}
alt={logo.alt}
className="size-6 text-primary-foreground invert dark:invert-0"
/>
</div>
<div className="flex flex-col gap-0.5 text-start leading-none">
<span className="font-medium">{logo.title}</span>
<span className="text-xs text-muted-foreground">
{logo.description}
</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
);
};
const FileTreeNode = ({
item,
depth = 0,
}: {
item: FileTreeItem;
depth?: number;
}) => {
const [isOpen, setIsOpen] = React.useState(depth < 2);
if (item.type === "file") {
return (
<SidebarMenuItem>
<SidebarMenuButton className="text-start data-[active=true]:bg-transparent">
<File className="size-4 shrink-0" />
<span className="truncate">{item.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
);
}
return (
<Collapsible
open={isOpen}
onOpenChange={setIsOpen}
className="group/collapsible [&[data-state=open]>button>svg:last-child]:rotate-90"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton className="text-start">
{isOpen ? (
<FolderOpen className="size-4 shrink-0 text-muted-foreground" />
) : (
<Folder className="size-4 shrink-0 text-muted-foreground" />
)}
<span className="truncate">{item.label}</span>
<ChevronRight className="ms-auto size-4 shrink-0 transition-transform rtl:rotate-180" />
</SidebarMenuButton>
</CollapsibleTrigger>
{item.children && (
<CollapsibleContent>
<SidebarMenuSub className="border-l-0 border-s border-sidebar-border">
{item.children.map((child) => (
<FileTreeNode key={child.name} item={child} depth={depth + 1} />
))}
</SidebarMenuSub>
</CollapsibleContent>
)}
</SidebarMenuItem>
</Collapsible>
);
};
const AppSidebar = ({ ...props }: React.ComponentProps<typeof Sidebar>) => {
return (
<Sidebar side="right" dir="rtl" {...props}>
<SidebarHeader>
<SidebarLogo logo={sidebarData.logo} />
</SidebarHeader>
<SidebarContent className="overflow-hidden">
<ScrollArea className="min-h-0 flex-1">
<SidebarGroup>
<SidebarGroupLabel className="text-start">فایل‌ها</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{fileTreeData.map((item) => (
<FileTreeNode key={item.name} item={item} />
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</ScrollArea>
</SidebarContent>
<SidebarRail />
</Sidebar>
);
};
interface Sidebar13Props {
className?: string;
}
const Sidebar13 = ({ className }: Sidebar13Props) => {
return (
<SidebarProvider
dir="rtl"
lang="fa"
className={cn(className)}
>
<AppSidebar />
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4 text-start">
<SidebarTrigger className="-ms-1" />
<Separator
orientation="vertical"
className="ms-2 data-[orientation=vertical]:h-4"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">ماژول‌ها</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">فروش</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>داشبورد.tsx</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<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>
</SidebarInset>
</SidebarProvider>
);
};
export { Sidebar13 };