بلاک‌ها · پنل مدیریت

نوارِ شناور با فضای کار

پیش‌نمایش تمام‌صفحه

نوارِ کارتیِ شناور با منوی تعویضِ فضای کار (حرفِ اول و پلن، با فضای تازه) و علاقه‌مندی‌ها.

۱. نصب کامپوننت‌ها

این بلاک از ۷ آیتم دیگ استفاده می‌کند: 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.json
pnpm 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.json
yarn 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.json
bunx --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 },
]