پنل فیلتر (FilterPanel)
پنل فیلتر composable برای داشبوردهای تحلیل — بخشهای قابلجمعشدن، شمارندهی فعال، حذف به تفکیک، و ActiveFiltersBar جداگانه
معرفی
FilterPanel اسکلت composable یک پنل فیلتر است — هدر با شمارنده و دکمهی «پاک کردن همه»، بدنهی اسکرولدار با FilterSectionهای قابلجمعشدن، و فوتر چسبان برای دکمههای اعمال/لغو. خودش state را مدیریت نمیکند؛ فرمها و state را مصرفکننده میچیند — همین باعث میشود هم بهصورت سایدبار همیشگی داخل یک <aside> کار کند و هم داخل یک Sheet بهصورت slide-in.
چه زمانی استفاده کنیم:
- داشبوردهای افکارسنجی/سوشیال لیستنینگ که ۴+ بُعد فیلتر دارند (بازه زمانی، پلتفرم، احساس، جریان، کلیدواژه، …)
- صفحاتی که کاربر چندین فیلتر را همزمان میچرخاند و باید یک دید کلی از «چه چیزی فعال است» داشته باشد
- وقتی میخواهید در دسکتاپ پنل همیشه باز باشد و در موبایل به صورت Sheet روباز شود
چه زمانی استفاده نکنیم:
- فقط ۱-۲ فیلتر ساده دارید — از
FilterBar(پایه) یا یک ردیفButton/Selectافقی استفاده کنید - کاربر معمولاً فقط یک فیلتر را تغییر میدهد (مثل جستجوی ساده) —
SearchInputیاAutocompleteتنها کافی است - بیش از ۱۰-۱۲ بخش دارید — پنل طولانیتر از ارتفاع viewport کاربر را گیج میکند؛ فیلترها را در چند صفحه یا tab بشکنید
فیلترها
طبقهبندی ۵گانه گرایش
زمین بازی
با تغییر تنظیمات زیر، پیشنمایش زنده را مشاهده کنید.
فیلترها۲
استفاده
FilterPanel، state-agnostic است. شما state را با useState/useReducer/TanStack Query/… مدیریت میکنید و هر FilterSection یک slot برای ورودی مربوط به خودش فراهم میکند.
'use client'
import * as React from 'react'
import {
FilterPanel,
FilterPanelHeader,
FilterPanelTitle,
FilterPanelClearAll,
FilterPanelBody,
FilterPanelFooter,
FilterSection,
DateRangePicker,
Checkbox,
Button,
} from '@parto-system-design/ui'
const DEFAULT = { dateRange: undefined, platforms: [] as string[] }
export function AnalyticsFilters() {
const [filters, setFilters] = React.useState(DEFAULT)
const count = (filters.dateRange ? 1 : 0) + filters.platforms.length
return (
<aside className="w-80 border-s border-border">
<FilterPanel>
<FilterPanelHeader>
<FilterPanelTitle activeCount={count} />
{count > 0 && <FilterPanelClearAll onClear={() => setFilters(DEFAULT)} />}
</FilterPanelHeader>
<FilterPanelBody>
<FilterSection
title="بازه زمانی"
activeCount={filters.dateRange ? 1 : 0}
onClear={filters.dateRange ? () => setFilters((f) => ({ ...f, dateRange: undefined })) : undefined}
>
<DateRangePicker
value={filters.dateRange}
onChange={(range) => setFilters((f) => ({ ...f, dateRange: range }))}
usePersianCalendar
/>
</FilterSection>
<FilterSection
title="پلتفرمها"
activeCount={filters.platforms.length}
onClear={filters.platforms.length ? () => setFilters((f) => ({ ...f, platforms: [] })) : undefined}
defaultOpen={false}
>
{/* محتوای فیلتر */}
</FilterSection>
</FilterPanelBody>
<FilterPanelFooter>
<Button variant="outline" size="sm" onClick={() => setFilters(DEFAULT)}>
پیشفرض
</Button>
<Button variant="primary" size="sm">
اعمال
</Button>
</FilterPanelFooter>
</FilterPanel>
</aside>
)
}داخل Sheet (حالت Slide-in)
برای موبایل یا پنل قابلباز/بسته، FilterPanel را داخل Sheet قرار دهید. FilterPanelTrigger یک دکمهی آماده با آیکون و badge شمارنده است.
<>
<FilterPanelTrigger activeCount={count} onClick={() => setOpen(true)} />
<Sheet open={open} onOpenChange={setOpen}>
<SheetContent side="right" className="p-0 gap-0 flex flex-col">
<SheetTitle className="sr-only">فیلترها</SheetTitle>
<FilterPanel>{/* همان ساختار هدر/بدنه/فوتر */}</FilterPanel>
</SheetContent>
</Sheet>
</>نکتهی a11y
Sheet روی Radix Dialog سوار است و برای screen reader باید یک SheetTitle داشته باشد. اگر عنوان را درون FilterPanelHeader نشان دادهاید، یک <SheetTitle className="sr-only"> بگذارید تا تکرار بصری نداشته باشید.
ActiveFiltersBar
نوار چیپ فیلترهای فعال که بالای محتوای صفحه مینشیند — جدا از پنل، چون روی صفحه دائماً دیده میشود.
<ActiveFiltersBar>
{activeChips.map((chip) => (
<FilterChip key={chip.id} label={chip.label} onRemove={chip.remove} />
))}
{activeChips.length > 0 && <ActiveFiltersClearAll onClear={clearAll} />}
</ActiveFiltersBar>label={false}برای حذف متن «فیلترهای فعال:» ابتدای نوارhideWhenEmptyبرای پنهان کردن کامل وقتی فیلتر فعالی نیست- چیپها را خودتان از state اصلی تولید میکنید —
FilterChip.onRemoveمستقیم state را برمیگرداند
FilterSection — انواع
این بخش بهطور پیشفرض باز است. روی عنوان کلیک کنید تا جمع شود.
برای فیلترهای تکفیلدی که همیشه قابلمشاهده باشند
- پیشفرض — قابلجمعشدن با chevron،
defaultOpen={true} defaultOpen={false}— در رندر اول بسته است (مناسب بخشهای پرکاربرد کمتر مثل «تنظیمات پیشرفته»)activeCount— یک pill برند کنار عنوان ظاهر میشود؛ همراه باonClearیک دکمهی × برای پاک کردن همان بخشcollapsible={false}— هدر static میشود (برای فیلدهای تکورودی مثل جستجو که همیشه باید دیده شوند)- کنترلشده —
open+onOpenChangeبرای کنترل کامل از بیرون
Props
FilterPanel
FilterPanelTitle
FilterPanelClearAll
FilterSection
FilterPanelTrigger
ActiveFiltersBar
راهنمای استفاده
بکنید
- state را بیرون از FilterPanel (در والد) نگه دارید و از همانجا
activeCountو چیپهایActiveFiltersBarرا مشتق کنید -ActiveFiltersBarرا بالای محتوای صفحه بگذارید وFilterPanelرا کنار — کاربر همیشه باید بداند چه فیلترهایی فعالاند، حتی با پنل بسته - بخشهای پرکاربرد راdefaultOpenبگذارید و بخشهای پیشرفته راdefaultOpen={false}- اگر بخش فقط یک فیلد دارد و همیشه باید دیده شود،collapsible={false}بگذارید تا یک کلیک اضافی نباشد
نکنید
FilterPanelرا به تنهایی برای ۱-۲ فیلتر استفاده نکنید — FilterBar افقی کافی است - بیش از ۸-۱۰ بخش در یک پنل نگذارید — یا tab کنید یا به چند صفحه بشکنید -activeCountرا بدونFilterSection.onClearنگذارید — کاربر انتظار دارد دکمهی پاک کردن ببیند - بعد از اعمال فیلتر، سایر state (URL, query cache) را فراموش نکنید بهروز کنید — FilterPanel فقط UI است
دسترسیپذیری
FilterSectionاز@radix-ui/react-collapsibleاستفاده میکند —aria-expandedخودکار، Space/Enter برای toggleActiveFiltersBarدارایrole="group"باaria-label="فیلترهای فعال"است- دکمههای × (clear-section, remove-chip) دارای
aria-labelاختصاصی مناسب - رنگها همگی از توکنهای DS خوانده میشوند — در هر دو تم light/dark به درستی کنتراست دارند
- انیمیشن expand/collapse با
prefers-reduced-motionسازگار است
کامپوننتهای مرتبط
- FilterBar — نوار فیلتر افقی ساده؛ وقتی پنل کامل نیاز نیست از این استفاده کنید
- FilterChip — چیپ فیلتر با دکمهی ×؛ داخل
ActiveFiltersBarاستفاده میشود - Sheet — برای حالت slide-in موبایل یا toggle
- DateRangePicker — انتخاب بازه زمانی، معمولترین ورودی FilterSection
- MultiSelect — برای لیست پلتفرمها یا برچسبها بهجای checkbox list