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

لایه‌ی ناوبری تودرتو با گروه‌های قابل‌جمع‌شدن، کاسکید حالت فعال، و indent خودکار — برای درخت‌های عمیق درون یک ابزار؛ منوی اصلی برنامه ProductFrame است

منوی اصلی برنامه نیست

منوی اصلی هر محصول را ProductFrame از روی داده می‌سازد (گروه‌ها و یک سطح زیرمجموعه)، یک بار در layout ریشه. NavTree برای درختی عمیق درون یک ابزار یا صفحه است (مثلاً درخت منابع یک ابزار رصد)؛ قاب برنامه را با NavTree نسازید.

معرفی

NavTree درخت ناوبری درون یک ابزار است. سه ویژگی کلیدی:

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

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

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

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

  • منوی اصلی برنامه → ProductFrame
  • ناوبری تخت با کمتر از 8 آیتم — Tabs یا SecondaryNav
  • ناوبری اصلی برنامه — از ProductFrame (نوار ناوبری و ناوبری دوم) استفاده کنید
  • ناوبری bottom-tab موبایل — از الگوی اختصاصی استفاده کنید (NavTree عمودی است)

تلویزیون › خبری

منشن‌ها و پوشش این منبع در هفتهٔ گذشته.

زمین بازی

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

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

استفاده

'use client'
import { usePathname } from 'next/navigation'
import { NavTree, NavTreeProvider, NavGroup, NavItem } from '@partodata/ui'
import { Newspaper, Radio, Tv } from 'lucide-react'

// Inside the sources page of a monitoring tool: its own deep tree, next to the page content. The product's main
// menu is ProductFrame's. The tree stays in the page's flow (not fixed to the viewport over the frame's menu).
// `aria-label`: the tree is named after what it navigates.
export function SourcesTree() {
  const pathname = usePathname()

  return (
    <div className="rounded-lg border p-2">
          <NavTreeProvider activePath={pathname}>
            <NavTree aria-label="منابع">
              <NavGroup heading="منابع رسانه‌ای">
                <NavItem href="/sources/press" icon={Newspaper}>
                  مطبوعات
                </NavItem>
                <NavItem icon={Tv}>
                  تلویزیون
                  <NavItem href="/sources/tv/news">خبری</NavItem>
                  <NavItem href="/sources/tv/sports">ورزشی</NavItem>
                </NavItem>
              </NavGroup>

              <NavGroup heading="صوتی" defaultOpen={false}>
                <NavItem href="/sources/radio" icon={Radio}>
                  رادیو
                </NavItem>
              </NavGroup>
            </NavTree>
          </NavTreeProvider>
    </div>
  )
}

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

وقتی 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 می‌کند
  • defaultOpen={false} برای بسته بودن در رندر اول
  • open + onOpenChange برای کنترل controlled

Props

Prop

Type

Prop

Type

Prop

Type

خط جداکننده‌ی افقی (<hr>) برای تفکیک بصری بخش‌های ناوبری درون درخت. فقط ویژگی‌های استاندارد <hr> را می‌پذیرد.

Prop

Type

درون یک صفحه

NavTree ستونی از خود صفحه است که روی موبایل بالای محتوا و از md کنار آن می‌نشیند؛ <main> دوم و نوار چسبیده به viewport ندارد (زیر منوی قاب پنهان می‌شود). عنوان و پانویس درخت، عناصر خود صفحه‌اند.

<div className="flex flex-col rounded-lg border md:flex-row">
  <div className="w-full border-b p-2 md:w-64 md:border-e md:border-b-0">
    <NavTreeProvider activePath={pathname}>
      <NavTree aria-label="منابع">...</NavTree>
    </NavTreeProvider>
  </div>
  <div className="min-w-0 flex-1 p-4">{/* محتوای منبع انتخاب‌شده */}</div>
</div>

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

بکنید

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

نکنید

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

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

  • NavTree یک لندمارک <nav> است؛ با aria-label (یا aria-labelledby) آن را به نام چیزی که پیمایش می‌کند بنامید، مثل aria-label="منابع". بدون نام، درون صفحهٔ ProductFrame «ناوبری صفحه» نام می‌گیرد (نام «ناوبری اصلی» مال منوی قاب است و دو لندمارک هم‌نام نمی‌سازد) و بیرون از آن «ناوبری اصلی»
  • parent NavItem بدون href/onClick (صرفاً اطلاع‌رسانی): کل ردیف یک دکمه است با aria-expanded؛ کلیک، Space و Enter آن را toggle می‌کنند
  • parent NavItem با href یا onClick (یعنی خودش هم مقصد ناوبری/عمل است): ردیف به دو المان مجزا تقسیم می‌شود — برچسب اصلی (<a>/<button> واقعی که ناوبری می‌کند) و یک دکمهٔ کوچک جداگانه با aria-expanded برای باز/بسته‌کردن زیرمنو؛ چون دو المان تعاملی نمی‌توانند تودرتو باشند
  • active leaf دارای data-active="true" — بصری و برای automated testing
  • chevron بصری در RTL به شکل صحیح می‌چرخد (left → down در باز، rotate-90 در بسته)

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

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

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

  • قاب محصول (ProductFrame) — منوی اصلی برنامه (nav، با children برای پیوندهای هر بخش)
  • ProductFrame — قاب برنامه با ناوبری اصلی
  • PageHeader — مسیر فعلی صفحه (breadcrumb) در breadcrumbs سربرگ صفحه