نوارِ شناور با فضای کار
پیشنمایش تمامصفحهنوارِ کارتیِ شناور با منوی تعویضِ فضای کار (حرفِ اول و پلن، با فضای تازه) و علاقهمندیها.
۱. نصب کامپوننتها
این بلاک از ۷ آیتم دیگ استفاده میکند: breadcrumbdropdown-menuiconspersianprogressseparatorsidebar
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/separator.json https://docs.digdesign.ir/r/sidebar.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/separator.json https://docs.digdesign.ir/r/sidebar.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/separator.json https://docs.digdesign.ir/r/sidebar.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/separator.json https://docs.digdesign.ir/r/sidebar.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
app-shell-floating.tsx
"use client"
import * as React from "react"
import { BarChartIcon, CalendarIcon, CheckIcon, ChevronsUpDownIcon, DashboardIcon, GridIcon, PlusIcon, UsersIcon } from "@/components/icons"
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb"
import { Progress } from "@/components/ui/progress"
import { Separator } from "@/components/ui/separator"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
import { toFaDigits } from "@/lib/persian"
const WORKSPACES = [
{ id: "avand", name: "آوند", plan: "پلنِ تیمی", initial: "آ" },
{ id: "noor", name: "استودیو نور", plan: "پلنِ رایگان", initial: "ن" },
{ id: "sekke", name: "سکه", plan: "پلنِ سازمانی", initial: "س" },
]
const NAV = [
{ title: "پیشخوان", icon: <DashboardIcon /> },
{ title: "پروژهها", icon: <GridIcon /> },
{ title: "تقویم", icon: <CalendarIcon /> },
{ title: "تیم", icon: <UsersIcon /> },
{ title: "گزارشها", icon: <BarChartIcon /> },
]
const FAVORITES = ["بازطراحیِ اپِ بانکی", "کمپینِ پاییزه", "سیستمِ نوبتدهی"]
/**
* پوستهٔ شناور با تعویضِ فضای کار: نوار کارتی با فاصله از لبه است و بالایش منوی فضاهای کاری (هر فضا با حرفِ
* اولِ رنگی و پلن)، با «فضای تازه». زیرِ منو، علاقهمندیها برای پرش به پروژههای پرکاربرد.
*/
export default function AppShellFloatingBlock() {
const [workspace, setWorkspace] = React.useState(WORKSPACES[0])
const [active, setActive] = React.useState("پروژهها")
return (
<SidebarProvider>
<Sidebar variant="floating">
<SidebarHeader className="p-2">
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton size="lg" aria-label={`فضای کار: ${workspace.name}؛ تعویض`}>
<span aria-hidden className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary font-bold text-primary-foreground">
{workspace.initial}
</span>
<span className="flex min-w-0 flex-col">
<span className="truncate font-semibold">{workspace.name}</span>
<span className="truncate text-xs text-muted-foreground">{workspace.plan}</span>
</span>
<ChevronsUpDownIcon className="ms-auto size-4!" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-64">
<DropdownMenuLabel>فضاهای کار</DropdownMenuLabel>
{WORKSPACES.map((item) => (
<DropdownMenuItem key={item.id} onSelect={() => setWorkspace(item)}>
<span aria-hidden className="flex size-6 items-center justify-center rounded-md border border-border text-xs font-bold">
{item.initial}
</span>
{item.name}
{item.id === workspace.id && <CheckIcon className="ms-auto" />}
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem>
<PlusIcon />
فضای تازه
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{NAV.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton isActive={active === item.title} onClick={() => setActive(item.title)}>
{item.icon}
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>علاقهمندیها · {toFaDigits(FAVORITES.length)}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{FAVORITES.map((name) => (
<SidebarMenuItem key={name}>
<SidebarMenuButton isActive={active === name} onClick={() => setActive(name)}>
<span aria-hidden className="size-2 shrink-0 rounded-full bg-primary" />
<span>{name}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
<ShellBody title={active} />
</SidebarInset>
</SidebarProvider>
)
}
/** بدنهٔ مشترکِ پوسته: سرصفحه با دکمهٔ نوار و مسیر، سه آمار و فهرستِ پروژهها؛ جای صفحهٔ واقعیِ محصول. */
function ShellBody({ title = "پروژهها", children }: { title?: string; children?: React.ReactNode }) {
return (
<>
<header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b border-border bg-background px-4">
<SidebarTrigger className="-ms-1" />
<Separator orientation="vertical" className="mx-1 h-5" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="max-sm:hidden">
<BreadcrumbLink href="#">فضای کار</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="max-sm:hidden" />
<BreadcrumbItem>
<BreadcrumbPage>{title}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</header>
<div className="flex flex-1 flex-col gap-6 p-4 sm:p-6">
{children}
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
{STATS.map((stat) => (
<div key={stat.label} className="flex flex-col gap-1 rounded-xl border border-border bg-card p-4">
<span className="text-sm text-muted-foreground">{stat.label}</span>
<span className="text-2xl font-bold tabular-nums">{stat.value}</span>
</div>
))}
</div>
<section aria-labelledby="shell-projects" className="flex flex-col gap-3">
<h2 id="shell-projects" className="text-lg leading-snug font-bold">
پروژههای فعال
</h2>
<ul className="flex flex-col divide-y divide-border rounded-xl border border-border bg-card">
{PROJECTS.map((project) => (
<li key={project.name} className="flex flex-col gap-2 p-4 sm:flex-row sm:items-center sm:gap-6">
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate font-semibold">{project.name}</span>
<span className="text-sm text-muted-foreground">{project.owner}</span>
</span>
<span className="flex items-center gap-3 sm:w-56">
<Progress value={project.progress} aria-label={`پیشرفتِ ${project.name}`} className="flex-1" />
<span className="w-10 text-end text-sm tabular-nums">٪{toFaDigits(project.progress)}</span>
</span>
</li>
))}
</ul>
</section>
</div>
</>
)
}
const STATS = [
{ label: "کارهای باز", value: "۲۴" },
{ label: "سررسیدِ این هفته", value: "۷" },
{ label: "انجامشده در مهر", value: "۱۱۸" },
]
const PROJECTS = [
{ name: "بازطراحیِ اپِ بانکی", owner: "نگار احمدی", progress: 72 },
{ name: "داشبوردِ فروشِ فصل", owner: "امیر رضایی", progress: 45 },
{ name: "سیستمِ نوبتدهیِ کلینیک", owner: "سارا محمدی", progress: 88 },
{ name: "کمپینِ پاییزه", owner: "کیان مرادی", progress: 20 },
]