نوارابزار صفحه (PageToolbar)

جزء سطح پایین ردیف فیلتر و اقدامی که ListPage می‌سازد — فیلترها در ابتدای خط، اقدام‌ها در انتهای خط، یک اندازه و عرض‌های ذاتی

معرفی

PageToolbar ردیف فیلتر و اقدامی است که قالب ListPage (و DashboardPage) از propهای خودش می‌سازد: جست‌وجو و فیلترها در ابتدای خط (راست)، اقدام‌های صفحه در انتهای خط (چپ)، ردیف دوم برای فیلترهای فعال، و جدول یا فهرستی که فیلتر می‌شود زیر آن. همهٔ قاعده‌های این ردیف را خودش اجرا می‌کند، پس صفحه هیچ‌کدام را نمی‌نویسد:

  • یک اندازه برای همهٔ کنترل‌ها (sm، 30 پیکسل؛ فیلدها هم که پیش‌فرضشان md است)؛
  • کنترل‌هایی که به‌اندازهٔ محتوایشان‌اند و در ردیف کش نمی‌آیند؛
  • جست‌وجو با عرض نام‌دار؛
  • فاصلهٔ 8 پیکسل بین کنترل‌ها و 16 پیکسل تا جدول (توکن‌های هندسهٔ صفحه)؛
  • شکستن مرتب روی صفحهٔ باریک.

PageToolbar روی FilterBar ساخته شده است. در صفحهٔ فهرست آن را خودتان رندر نکنید: ListPage می‌سازدش؛ مستقیم فقط درون یک CustomPage. FilterBar جزء سطح پایین‌تری است برای ردیف فیلتری که نوارابزار صفحه نیست.

در صفحهٔ فهرست: ListPage

صفحهٔ فهرست ListPage است، که این نوارابزار را از search، filters، primaryAction و secondaryActions خودش می‌سازد. PageToolbar را مستقیم فقط درون CustomPage به کار ببرید.

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

  • درون محتوای یک CustomPage (صفحه‌ای که در هیچ قالبی نمی‌گنجد و DS-GAP دارد): جست‌وجو، فیلترها و اقدام‌های بالای محتوایی که فیلتر می‌شود (یک نقشه، یک خط زمانی)، با آن محتوا به‌عنوان فرزند نوارابزار.
  • صفحهٔ فهرستی با جست‌وجو یا فیلتر (منشن‌ها، منابع، هشدارها، کاربران) نه: آن ListPage است.

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

  • صفحهٔ فهرست، با یا بدون جست‌وجو و فیلتر: ListPage (قالب اقدام اصلی را خودش در نوارابزار یا سرِ صفحه می‌گذارد).
  • ردیف فیلتر داخل یک کارت: FilterBar با ControlSizeProvider. فیلترهای زیاد صفحه: سایدبار فیلتر (CustomPage filterPanel).
  • فرم: فیلدهای فرم اندازه و چیدمان خودشان را دارند.
هشتگ: #تخفیف_فصلی
نویسندهمتنپلتفرمتعامل
نگاربسته‌بندی تازه خیلی بهتر شدهاینستاگرام1,240
محمدارسال سفارش دیر رسیداینستاگرام95

استفاده

PageToolbar فقط از مسیر جدای خودش وارد می‌شود و فقط درون محتوای یک CustomPage به کار می‌رود — صفحه‌ای که در هیچ قالبی جا نمی‌شود، این‌جا نقشهٔ گفت‌وگوها به تفکیک استان. صفحهٔ فهرست این را نمی‌نویسد: ListPage نوارابزارش را از search، filters و اقدام‌هایش می‌سازد.

'use client'
import * as React from 'react'
import { Button, DataTableFacetedFilter, PageSection, SearchInput } from '@partodata/ui'
import { Icons } from '@partodata/ui/icons'
import { PageToolbar } from '@partodata/ui/page-toolbar'
import { CustomPage } from '@partodata/ui/templates'

type Platform = 'instagram' | 'telegram'

const PLATFORMS: { value: Platform; label: string }[] = [
  { value: 'instagram', label: 'اینستاگرام' },
  { value: 'telegram', label: 'تلگرام' },
]

// The map the toolbar filters (the product's own component).
declare function ConversationMap(props: { q: string; platforms: Platform[] }): React.ReactElement
declare function createAlert(): void

export function ConversationMapScreen() {
  const [q, setQ] = React.useState('')
  const [platforms, setPlatforms] = React.useState<Platform[]>([])
  const filtered = q !== '' || platforms.length > 0
  const clear = () => {
    setQ('')
    setPlatforms([])
  }
  return (
    <CustomPage dsGap="DS-GAP-21: نقشهٔ گفت‌وگوها به تفکیک استان" title="نقشهٔ گفت‌وگوها">
      <PageSection>
        <PageToolbar
          search={
            <SearchInput
              placeholder="جست‌وجو در گفت‌وگوها"
              aria-label="جست‌وجو در گفت‌وگوها"
              value={q}
              onChange={(e) => setQ(e.target.value)}
              onClear={() => setQ('')}
            />
          }
          filters={
            <DataTableFacetedFilter
              title="پلتفرم"
              options={PLATFORMS}
              selected={platforms}
              onSelectedChange={setPlatforms}
            />
          }
          // PageToolbar's own API: the clear button shows while a filter is active (ListPage takes `filtered` instead).
          onClearFilters={filtered ? clear : undefined}
          primaryAction={
            <Button onClick={createAlert} iconStart={<Icons.plus />}>
              ساخت هشدار
            </Button>
          }
        >
          <ConversationMap q={q} platforms={platforms} />
        </PageToolbar>
      </PageSection>
    </CustomPage>
  )
}

کنترل‌های داخل search، filters و actions را بدون size و بدون کلاس عرض بنویسید: اندازه و عرض را نوارابزار تعیین می‌کند. کنترل‌ها می‌توانند از @partodata/ui یا از مسیرهای جدا بیایند؛ نوارابزار به هر دو می‌رسد.

حالت‌ها و انواع

جای اقدام اصلی

صفحهاقدام اصلی کجاست
صفحه‌ای با PageToolbar (جست‌وجو یا فیلتر)primaryAction در PageToolbar
صفحه‌ای بدون PageToolbar (بی جست‌وجو و فیلتر)primaryAction در PageHeader

در هر صفحه فقط یک اقدام اصلی هست و یک جا برای آن: primaryAction، یک Button بدون variant که نوارابزار همیشه آخرِ خط (چپ در RTL) رندر می‌کند. اقدام‌های دیگر در actions می‌آیند و ثانوی‌اند: دکمهٔ بدون variant در actions، مثل همه‌جا، همان default خنثی است، پس نوارابزار دو دکمهٔ اصلی پیدا نمی‌کند و اقدام اصلی هیچ‌وقت اول نمی‌نشیند. برای ظاهر دیگر variant را صریح بنویسید (outline، ghost)؛ variant="primary" هیچ‌وقت در actions نمی‌آید. در محیط توسعه، دکمهٔ اصلی دوم (مثلاً variant="primary" در actions) یا اقدام اصلی در PageHeader یا ردیف breadcrumb همان صفحه (درون PageContainer صفحه یا درست بالای آن) هشدار می‌دهد، و قانون ESLint parto/page-primary-action همین‌ها را در کد نشان می‌دهد. اقدام‌های بیشتر از سه تا را در یک DropdownMenu با دکمهٔ فقط‌آیکون جمع کنید.

اقدامی که کاربر اجازه‌اش را ندارد پنهان نمی‌شود: همان‌جا غیرفعال با دلیلش می‌ماند، با GatedAction دور Button (<GatedAction allowed={canCreate} reason="…">)؛ نه canCreate && <Button/>. false و null هیچ جایی در خط نمی‌گیرند؛ دکمهٔ اصلی را به actions نبرید، آن‌جا ثانوی می‌شود.

دکمهٔ نمایش ستون‌ها (DataTableColumnVisibilityToggle) و دکمهٔ خروجی (DataTableExportButton) اقدام ثانوی‌اند: همیشه در actions، نه در filters.

<PageToolbar
  search={
    <SearchInput
      placeholder="جست‌وجو در گفت‌وگوها"
      aria-label="جست‌وجو در گفت‌وگوها"
      value={q}
      onChange={(e) => setQ(e.target.value)}
      onClear={() => setQ('')}
    />
  }
  actions={
    <Button variant="default" onClick={shareMap}>
      اشتراک‌گذاری
    </Button>
  }
  primaryAction={
    <Button onClick={createAlert} iconStart={<Icons.plus />}>
      ساخت هشدار
    </Button>
  }
>
  <ConversationMap q={q} platforms={platforms} />
</PageToolbar>

عرض جست‌وجو

searchWidth یکی از سه عرض نام‌دار را انتخاب می‌کند: narrow (192)، default (256) یا wide (384 پیکسل). عدد دلخواه نمی‌پذیرد. عرض جست‌وجو فقط از searchWidth می‌آید: کنترلی که در search می‌گذارید (SearchInput، یا Autocomplete برای جست‌وجو با پیشنهاد) جای جست‌وجو را پر می‌کند، و کلاس عرض روی آن نمی‌تواند پهن‌ترش کند.

فیلترهای فعال و پاک‌کردن

  • activeFilters: چیپ‌های فیلتر فعال (FilterChip) در ردیف دوم، فقط برای فیلتری که ماشهٔ خودش را در همین نوار ندارد (کلیک روی نمودار). وقتی خالی باشد ردیف رندر نمی‌شود. چیپ‌های سایدبار فیلتر هرگز این‌جا نمی‌آیند.
  • onClearFilters: دکمهٔ «پاک کردن فیلترها» را در انتهای فیلترها نشان می‌دهد؛ وقتی chip فعال هست، در انتهای ردیف chipها (تا به خطی جدا بین کنترل‌ها و chipها نشکند). در PageToolbar آن را فقط وقتی فیلتری فعال است بدهید: onClearFilters={filtered ? clear : undefined}. (ListPage این را خودش تصمیم می‌گیرد: آن‌جا filtered و همیشه onClearFilters={clear}؛ شکل بالا در ListPage خطای نوع است.)
  • search، filters، actions و activeFilters مقدار false را مثل خالی می‌گیرند، پس الگوی {cond && <X />} جای خالی نمی‌گذارد.

محتوای زیر نوارابزار

محتوایی که نوارابزار فیلترش می‌کند فرزند PageToolbar است و 16 پیکسل زیر کنترل‌ها قرار می‌گیرد (--layout-toolbar-gap). اندازهٔ نوارابزار به آن نمی‌رسد.

صفحهٔ باریک

فیلترها در فضای خودشان به چند خط می‌شکنند و اقدام‌ها در خط اول و در انتهای خط می‌مانند؛ پس فعال شدن یک فیلتر و آمدن دکمهٔ پاک‌کردن، اقدام اصلی را جابه‌جا نمی‌کند. فقط وقتی حتی جست‌وجو کنار اقدام‌ها جا نشود (صفحهٔ موبایل)، اقدام‌ها به خط بعد و انتهای آن می‌روند. جست‌وجو هرگز از عرض صفحه پهن‌تر نمی‌شود.

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

بکنید

  • صفحهٔ فهرست همیشه ListPage است و نوارابزارش را از search و filters خودش می‌سازد؛ PageToolbar را فقط درون محتوای یک CustomPage بگذارید، به‌عنوان اولین بلوک بخش و با محتوایی که فیلتر می‌کند به‌عنوان فرزندش.
  • جست‌وجو را در search، فیلترها را به ترتیب اهمیت در filters، اقدام اصلی را در primaryAction و بقیهٔ اقدام‌ها را با variant="default" در actions بنویسید.
  • برای فیلترهای شمارشی DataTableFacetedFilter و برای بازهٔ زمانی DateRangePicker را به کار ببرید.

نکنید

  • به کنترل‌های داخل نوارابزار size، w-* یا autoWidth ندهید؛ نوارابزار آن‌ها را هم‌قد و هم‌شکل می‌کند.
  • اقدام اصلی صفحه را هم در PageHeader و هم در PageToolbar نگذارید، و آن را به‌جای primaryAction در actions ننویسید (آن‌جا ثانوی رندر می‌شود).
  • نوارابزار را داخل Card نگذارید و خودتان با div و flex یا با FilterBar نوارابزار نسازید.
  • کنترل‌ها را فرزند PageToolbar نکنید؛ فرزند فقط محتوایی است که فیلتر می‌شود.
  • اقدامی را که کاربر اجازه‌اش را ندارد پنهان نکنید: GatedAction.

Props

PageToolbar

Prop

Type

className و style نمی‌پذیرد: چیدمان مال خود نوارابزار است، و اگر از کد بدون نوع (JavaScript ساده یا گسترش یک شیء any) هم برسند کنار گذاشته می‌شوند. ویژگی‌های دیگر div (id، aria-*، data-*) به عنصر ریشه می‌رسند.

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

  • ترتیب DOM همان ترتیب خواندن است: جست‌وجو، فیلترها، پاک‌کردن، اقدام‌های ثانوی، اقدام اصلی، ردیف فیلترهای فعال و در آخر جدول؛ ترتیب Tab هم همین است.
  • وقتی دکمهٔ پاک‌کردن یا دکمهٔ حذف یک چیپ با صفحه‌کلید (یا فناوری کمکی) فعال می‌شود و از صفحه می‌رود، تمرکز گم نمی‌شود: به چیپ بعدی، اگر نبود به چیپ قبلی، و اگر ردیف چیپ‌ها رفت به جست‌وجو (یا اولین فیلتر) می‌رود. با کلیک ماوس یا لمس تمرکز جابه‌جا نمی‌شود تا روی موبایل صفحه‌کلید باز نشود.
  • ردیف فیلترهای فعال role="group" و برچسب «فیلترهای فعال» دارد.
  • دکمهٔ فقط‌آیکون در actions مربعی به ارتفاع ردیف است و به aria-label نیاز دارد.
  • SearchInput نقش search و برچسب قابل خواندن دارد؛ aria-label یا placeholder معنادار بدهید.

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

  • FilterBar — جزء سطح پایینی که PageToolbar روی آن ساخته شده است؛ برای ردیف فیلتر داخل پنل یا کارت، نه برای نوارابزار صفحه.
  • ControlSizeProvider — اگر ردیف کنترلی غیر از نوارابزار صفحه دارید و می‌خواهید یک اندازه داشته باشد.
  • DataTable — جدول صفحهٔ فهرست، فرزند ListPage (نه PageToolbar)؛ DataTableFacetedFilter فیلتر شمارشی همین نوارابزار است.
  • PageHeader — سری که قالب‌ها می‌سازند؛ صفحه‌ای بی جست‌وجو و فیلتر اقدام اصلی‌اش را به primaryAction قالبش می‌دهد.
  • بلاک جست‌وجوی پست و صفحهٔ «منشن‌ها» در قالب شروع — صفحه‌های کامل با همین نوارابزار؛ صفحهٔ «منشن‌ها» یک ListPage است که نوارابزار را خودش می‌سازد.