پرتوپرتو

درخت ناوبری (NavTree)

لایه‌ی ناوبری تودرتو با گروه‌های قابل‌جمع‌شدن، کاسکید حالت فعال، و indent خودکار — لایه‌ی v2 روی Sidebar primitive

معرفی

NavTree لایه‌ی composition ناوبری است که روی primitive های Sidebar می‌نشیند. سه ویژگی کلیدی:

  1. تودرتویی چندسطحیNavItem که NavItemهای دیگر را به‌عنوان children دارد، خودکار به parent قابل‌جمع‌شدن تبدیل می‌شود؛ depth بدون محدودیت.
  2. کاسکید حالت فعال — وقتی activePath با یک leaf تودرتو match می‌کند، همه‌ی parentها به‌طور خودکار expand می‌شوند و data-active-within می‌گیرند (highlight ملایم‌تر از leaf فعال).
  3. گروه‌های قابل‌جمع‌شدنNavGroup با heading قابل‌کلیک؛ کل گروه را باز/بسته می‌کند (بر خلاف سایدبار primitive که گروه‌هایش static بودند).

چه زمانی استفاده کنیم:

  • داشبوردهای enterprise با ناوبری ۳+ سطح (رصد → منابع → تلویزیون)
  • اپ‌هایی که کاربر باید یک نقطه‌ی عمیق در درخت ناوبری را bookmark کند و هنگام بازگشت به‌طور خودکار ببیند
  • سایدبارهای طولانی که نیاز به group collapse دارند

چه زمانی استفاده نکنیم:

  • ناوبری تخت با کمتر از ۸ آیتم — از SidebarMenu primitive استفاده کنید، سبک‌تر است
  • ناوبری افقی top-nav — از NavigationMenu استفاده کنید
  • ناوبری bottom-tab موبایل — از الگوی اختصاصی استفاده کنید (NavTree عمودی است)

زمین بازی

با تغییر تنظیمات زیر، پیش‌نمایش زنده را مشاهده کنید.

زمین بازی
تنظیمات
حالت
ظاهر
محتوا
کد این نمونه به‌صورت خودکار قابل تولید نیست — برای کد آماده‌ی copy/paste به بخش «استفاده» در بالای صفحه مراجعه کنید.

استفاده

'use client'
import { usePathname } from 'next/navigation'
import {
  Sidebar,
  SidebarContent,
  SidebarProvider,
  NavTree,
  NavTreeProvider,
  NavGroup,
  NavItem,
} from '@parto-system-design/ui'
import { LayoutDashboard, BarChart3, Search, Settings } from 'lucide-react'

export function AppSidebar() {
  const pathname = usePathname()

  return (
    <SidebarProvider>
      <Sidebar>
        <SidebarContent className="p-2">
          <NavTreeProvider activePath={pathname}>
            <NavTree>
              <NavGroup heading="رصد و پایش">
                <NavItem href="/" icon={LayoutDashboard}>
                  داشبورد
                </NavItem>
                <NavItem href="/clusters" icon={BarChart3}>
                  خوشه‌ها
                </NavItem>
                <NavItem icon={Search}>
                  جستجو
                  <NavItem href="/search/posts">پست‌ها</NavItem>
                  <NavItem href="/search/pages">صفحات</NavItem>
                </NavItem>
              </NavGroup>

              <NavGroup heading="تنظیمات" defaultOpen={false}>
                <NavItem href="/settings" icon={Settings}>
                  پیکربندی
                </NavItem>
              </NavGroup>
            </NavTree>
          </NavTreeProvider>
        </SidebarContent>
      </Sidebar>
    </SidebarProvider>
  )
}

کاسکید حالت فعال (خودکار)

وقتی activePath مطابق یک leaf تودرتو باشد، همه‌ی parent های بالای آن به‌طور خودکار data-active-within می‌گیرند و expand می‌شوند. دکمه‌های بالا را در دموی زیر تغییر دهید تا رفتار را ببینید:

activePath فعلی را تغییر دهید:
توجه کنید وقتی `/analysis/comments/labels` انتخاب است، همه‌ی والدین به‌طور خودکار باز و data-active-within می‌شوند.

جزئیات الگوریتم:

  • matchStrategy="prefix" (پیش‌فرض): /clusters هم برای /clusters و هم برای /clusters/42 فعال می‌شود. فقط مرز segment رعایت می‌شود (یعنی /cl با /clusters match نمی‌شود).
  • matchStrategy="exact": فقط تطابق دقیق.

گروه‌های قابل‌جمع‌شدن

  • heading نمایش یک ردیف سرگروه و به‌طور پیش‌فرض قابل‌کلیک (collapse/expand)
  • collapsible={false} heading را static می‌کند (مثل primitive SidebarGroupLabel)
  • defaultOpen={false} برای بسته بودن در رندر اول
  • open + onOpenChange برای کنترل controlled

Props

Prop

Type

Prop

Type

Prop

Type

ادغام با Sidebar primitive

NavTree داخل SidebarContent (از primitive Sidebar) قرار می‌گیرد. SidebarProvider رفتار mobile/keyboard-shortcut را مدیریت می‌کند و NavTree ساختار ناوبری را.

<SidebarProvider>
  <Sidebar variant="inset">
    <SidebarHeader>...</SidebarHeader>
    <SidebarContent className="p-2">
      <NavTreeProvider activePath={pathname}>
        <NavTree>...</NavTree>
      </NavTreeProvider>
    </SidebarContent>
    <SidebarFooter>...</SidebarFooter>
  </Sidebar>
</SidebarProvider>

راهنمای استفاده

بکنید

  • برای هر سطح nesting، یک آیکون بصری متمایز در NavItem بگذارید تا سلسله‌مراتب بصری هم واضح باشد - badge را برای شمارنده‌ی notification یا new indicator استفاده کنید — از component Badge یا یک span سفارشی - در بخش تنظیمات (که معمولاً به ندرت باز می‌شود) از NavGroup defaultOpen={false} استفاده کنید - matchStrategy="prefix" را حفظ کنید مگر اینکه routeهای overlap داشته باشید (مثلاً /cluster و /clusters)

نکنید

  • بیش از ۴ سطح nesting نکنید — کاربر جنگل می‌بیند
  • badge را برای متن طولانی (بیش از ۳ کاراکتر) استفاده نکنید — sidebar باریک می‌شود
  • از <NavItem>های داخل یک <div> ساده استفاده نکنید — split-children logic بر اساس React.Children مستقیم کار می‌کند
  • حالت active را روی چند آیتم هم‌زمان تنظیم نکنید — ممکن است highlight چندگانه گیج‌کننده شود

دسترسی‌پذیری

  • <nav aria-label="ناوبری اصلی"> landmark برای screen reader
  • parent NavItem دارای aria-expanded + button semantics
  • کلیک، Space، و Enter همگی parent را toggle می‌کنند
  • active leaf دارای data-active="true" — بصری و برای automated testing
  • chevron بصری در RTL به شکل صحیح می‌چرخد (left → down در باز، rotate-90 در بسته)

محدودیت‌های فعلی

  • جستجو در درخت هنوز ارائه نشده. برنامه‌ی بعدی: NavSearch ورودی که لیست را filter می‌کند و parent های مرتبط را باز نگه می‌دارد. (فاز ۱.۱ روادمپ — مرحله‌ی بعد)
  • Drag-reorder آیتم‌ها در نظر نیست — این یک UI ناوبری است نه یک tree builder.

کامپوننت‌های مرتبط

  • Sidebar — container primitive که NavTree درون SidebarContent قرار می‌گیرد
  • NavigationMenu — برای ناوبری افقی top-nav
  • Breadcrumb — برای نمایش مسیر فعلی درون SiteHeader
  • SiteHeader — هدر اپ که با NavTree جفت می‌شود