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

673 lines
21 KiB
TypeScript

"use client";
import {
Baby,
BadgeCheck,
Bell,
ChevronDown,
ChevronRight,
Clapperboard,
Clock,
CreditCard,
Download,
Film,
Flag,
HelpCircle,
History,
Home,
ListVideo,
LogOut,
type LucideIcon,
Menu,
MessageSquare,
Music,
Music2,
PlaySquare,
Radio,
Search,
Settings,
ShoppingBag,
Sparkles,
ThumbsUp,
} from "lucide-react";
import * as React from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import { Kbd } from "@/components/ui/kbd";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
function getInitials(name: string) {
return (
name
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((part) => part[0]?.toUpperCase())
.join("") || "U"
);
}
const data = {
user: {
name: "علی رضایی",
email: "ali@example.com",
avatar:
"/block-media/shadcnblocks/block/avatar/avatar1.jpg",
},
navPrimary: [
{ title: "خانه", url: "#", icon: Home, isActive: true },
{ title: "ویدیوهای کوتاه", url: "#", icon: Clapperboard },
{ title: "اشتراک‌ها", url: "#", icon: PlaySquare },
{ title: "شما", url: "#", icon: History },
],
subscriptions: [
{ name: "Theo - t3.gg", avatar: "/avatars/theo.jpg", isLive: true },
{ name: "Fireship", avatar: "/avatars/fireship.jpg", hasNew: true },
{ name: "Web Dev Simplified", avatar: "/avatars/webdev.jpg" },
{ name: "Traversy Media", avatar: "/avatars/traversy.jpg", hasNew: true },
{ name: "The Primeagen", avatar: "/avatars/prime.jpg" },
{ name: "Jack Herrington", avatar: "/avatars/jack.jpg", hasNew: true },
{ name: "Coding in Public", avatar: "/avatars/cip.jpg" },
],
navYou: [
{ title: "تاریخچه", url: "#", icon: History },
{ title: "لیست‌های پخش", url: "#", icon: ListVideo },
{ title: "تماشا بعداً", url: "#", icon: Clock },
{ title: "ویدیوهای پسندیده", url: "#", icon: ThumbsUp },
{ title: "ویدیوهای شما", url: "#", icon: PlaySquare },
{ title: "دانلودها", url: "#", icon: Download },
],
navExplore: [
{ title: "خرید", url: "#", icon: ShoppingBag },
{ title: "موسیقی", url: "#", icon: Music },
{ title: "فیلم", url: "#", icon: Film },
],
navMoreFrom: [
{ title: "استودیوی ری یو آی", url: "#", icon: Radio },
{ title: "موسیقی ری یو آی", url: "#", icon: Music2 },
{ title: "ری یو آی کودکان", url: "#", icon: Baby },
],
navFooter: [
{ title: "تنظیمات", url: "#", icon: Settings },
{ title: "تاریخچه گزارش", url: "#", icon: Flag },
{ title: "راهنما", url: "#", icon: HelpCircle },
{ title: "ارسال بازخورد", url: "#", icon: MessageSquare },
],
};
// Primary nav (Home, Shorts, Subscriptions, You)
function NavPrimary({
items,
}: {
items: { title: string; url: string; icon: LucideIcon; isActive?: boolean }[];
}) {
return (
<SidebarGroup>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
isActive={item.isActive}
tooltip={item.title}
>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
// Subscriptions section with channel avatars
function NavSubscriptions({
subscriptions,
}: {
subscriptions: {
name: string;
avatar: string;
isLive?: boolean;
hasNew?: boolean;
}[];
}) {
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<Collapsible defaultOpen>
<SidebarGroupLabel asChild>
<CollapsibleTrigger className="group/collapsible flex w-full items-center text-start">
اشتراک‌ها
<ChevronRight className="ms-auto transition-transform group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarMenu>
{subscriptions.map((channel) => (
<SidebarMenuItem key={channel.name}>
<SidebarMenuButton asChild>
<a href="#">
<Avatar className="size-6 ring-2 ring-muted">
<AvatarImage src={channel.avatar} />
<AvatarFallback className="bg-primary text-xs text-primary-foreground">
{channel.name[0]}
</AvatarFallback>
</Avatar>
<span className="truncate">{channel.name}</span>
{channel.isLive && (
<Radio className="ms-auto size-4 text-red-500" />
)}
{channel.hasNew && !channel.isLive && (
<span className="ms-auto size-2 rounded-full bg-blue-500" />
)}
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton>
<ChevronDown className="size-4" />
<span>نمایش بیشتر</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</CollapsibleContent>
</Collapsible>
</SidebarGroup>
);
}
// "You" section - History, Playlists, etc.
function NavYou({
items,
}: {
items: { title: string; url: string; icon: LucideIcon }[];
}) {
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<Collapsible defaultOpen>
<SidebarGroupLabel asChild>
<CollapsibleTrigger className="group/collapsible flex w-full items-center text-start">
شما
<ChevronRight className="ms-auto transition-transform group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton>
<ChevronDown className="size-4" />
<span>نمایش بیشتر</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</CollapsibleContent>
</Collapsible>
</SidebarGroup>
);
}
// Explore section - Shopping, Music, Movies
function NavExplore({
items,
}: {
items: { title: string; url: string; icon: LucideIcon }[];
}) {
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<Collapsible defaultOpen>
<SidebarGroupLabel asChild>
<CollapsibleTrigger className="group/collapsible flex w-full items-center text-start">
کاوش
<ChevronRight className="ms-auto transition-transform group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton>
<ChevronDown className="size-4" />
<span>نمایش بیشتر</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</CollapsibleContent>
</Collapsible>
</SidebarGroup>
);
}
// More from Zerotube section
function NavMoreFrom({
items,
}: {
items: { title: string; url: string; icon: LucideIcon }[];
}) {
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>بیشتر از ری یو آی</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
// Footer nav - Settings, Help, etc.
function NavFooter({
items,
}: {
items: { title: string; url: string; icon: LucideIcon }[];
}) {
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild size="sm">
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
);
}
// Footer links at the very bottom
function FooterLinks() {
return (
<div className="px-4 py-4 text-start text-xs text-muted-foreground">
<div className="flex flex-wrap gap-x-2 gap-y-1">
<a href="#" className="hover:underline">
درباره
</a>
<a href="#" className="hover:underline">
مطبوعات
</a>
<a href="#" className="hover:underline">
حق نشر
</a>
<a href="#" className="hover:underline">
تماس با ما
</a>
<a href="#" className="hover:underline">
سازندگان
</a>
<a href="#" className="hover:underline">
تبلیغات
</a>
<a href="#" className="hover:underline">
توسعه‌دهندگان
</a>
</div>
<div className="mt-2 flex flex-wrap gap-x-2 gap-y-1">
<a href="#" className="hover:underline">
قوانین
</a>
<a href="#" className="hover:underline">
حریم خصوصی
</a>
<a href="#" className="hover:underline">
سیاست و ایمنی
</a>
</div>
<p className="mt-3">© ۱۴۰۴ ری یو آی</p>
</div>
);
}
function SiteHeader() {
const { toggleSidebar } = useSidebar();
return (
<header className="fixed top-0 z-50 hidden w-full items-center border-b bg-background md:flex">
{/* Left: Menu + Logo */}
<div className="flex h-(--header-height) w-(--sidebar-width-icon) shrink-0 items-center justify-center">
<Button
className="size-9"
variant="ghost"
size="icon"
onClick={toggleSidebar}
aria-label="منو"
>
<Menu className="size-5" />
</Button>
</div>
<div className="flex h-(--header-height) items-center pe-4">
<a href="#" className="flex items-center gap-2">
<div className="flex size-8 items-center justify-center rounded-sm bg-primary">
<img
src="/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo.svg"
alt="شاد‌سی‌ان بلاکس"
className="size-5 invert dark:invert-0"
/>
</div>
<span className="hidden text-lg font-semibold sm:block">
ری یو آی
</span>
</a>
</div>
{/* Center: Search Bar */}
<div className="flex flex-1 justify-center px-4">
<InputGroup className="h-10 max-w-xl rounded-full">
<InputGroupAddon>
<Search className="text-muted-foreground" />
</InputGroupAddon>
<InputGroupInput placeholder="جستجو" />
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
</div>
{/* Right: Avatar */}
<div className="flex h-(--header-height) items-center gap-1 px-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="flex h-auto items-center gap-2 px-2 py-1"
>
<Avatar className="size-8">
<AvatarImage src={data.user.avatar} alt={data.user.name} />
<AvatarFallback>{getInitials(data.user.name)}</AvatarFallback>
</Avatar>
<span className="hidden font-medium sm:inline">
{data.user.name}
</span>
<ChevronDown className="size-3 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start">
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1 text-start">
<p className="text-sm font-medium">{data.user.name}</p>
<p className="text-xs text-muted-foreground">
{data.user.email}
</p>
</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>
</div>
</header>
);
}
function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar
side="right"
collapsible="icon"
className="top-(--header-height) hidden h-[calc(100svh-var(--header-height))]! md:flex"
{...props}
>
<SidebarContent className="overflow-hidden">
<ScrollArea className="min-h-0 flex-1">
<NavPrimary items={data.navPrimary} />
<NavSubscriptions subscriptions={data.subscriptions} />
<NavYou items={data.navYou} />
<NavExplore items={data.navExplore} />
<NavMoreFrom items={data.navMoreFrom} />
<NavFooter items={data.navFooter} />
</ScrollArea>
</SidebarContent>
<SidebarFooter className="group-data-[collapsible=icon]:hidden">
<FooterLinks />
</SidebarFooter>
</Sidebar>
);
}
export const iframeHeight = "800px";
export const description = "سایدبار با هدر و فرم جستجو.";
function MobileHeader() {
return (
<header className="sticky top-0 z-50 flex h-14 items-center justify-between border-b bg-background px-4 md:hidden">
<a href="#" className="flex items-center gap-2">
<div className="flex size-8 items-center justify-center rounded-sm bg-primary">
<img
src="/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo.svg"
alt="شاد‌سی‌ان بلاکس"
className="size-5 invert dark:invert-0"
/>
</div>
<span className="text-lg font-semibold">ری یو آی</span>
</a>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" className="size-9" aria-label="جستجو">
<Search className="size-5" />
<span className="sr-only">جستجو</span>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-9">
<Avatar className="size-8">
<AvatarImage src={data.user.avatar} alt={data.user.name} />
<AvatarFallback>{getInitials(data.user.name)}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start">
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1 text-start">
<p className="text-sm font-medium">{data.user.name}</p>
<p className="text-xs text-muted-foreground">
{data.user.email}
</p>
</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>
</div>
</header>
);
}
function MobileBottomNav() {
return (
<nav className="fixed inset-x-0 bottom-0 z-40 border-t bg-background/95 backdrop-blur md:hidden">
<div className="grid grid-cols-4">
{data.navPrimary.map((item) => {
const Icon = item.icon;
return (
<button
key={item.title}
type="button"
className={cn(
"flex flex-col items-center gap-1 py-2 text-xs transition-colors",
item.isActive
? "text-foreground"
: "text-muted-foreground hover:text-foreground",
)}
>
<Icon className="size-5" />
<span className="sr-only">{item.title}</span>
<span aria-hidden="true">{item.title}</span>
</button>
);
})}
</div>
</nav>
);
}
function ContentGrid() {
return (
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
</div>
);
}
export function ApplicationShell11() {
return (
<div className="w-full [--header-height:calc(--spacing(14))]">
<SidebarProvider
dir="rtl"
lang="fa"
className="flex flex-col"
style={{ "--sidebar-width-icon": "3rem" } as React.CSSProperties}
>
{/* Desktop layout */}
<SiteHeader />
<div className="hidden flex-1 pt-(--header-height) md:flex">
<AppSidebar />
<SidebarInset>
<ContentGrid />
</SidebarInset>
</div>
{/* Mobile layout */}
<div className="flex flex-col md:hidden">
<MobileHeader />
<div className="pb-16">
<ContentGrid />
</div>
<MobileBottomNav />
</div>
</SidebarProvider>
</div>
);
}