پوسته برنامه (AppShell)
الگوی ترکیب Sidebar + Header + Content برای ساخت داشبوردها و پنلهای مدیریت
معرفی
AppShell الگویی برای ساختار کلی برنامههای داشبورد و پنل مدیریت است. این الگو از کامپوننتهای DS ترکیب میشود — خود یک کامپوننت مجزا نیست.
نمونه بصری
ساختار پایه
┌──────────────────────────────────────────┐
│ Header (sticky) │
├────────────┬─────────────────────────────┤
│ │ │
│ Sidebar │ Main Content │
│ (ثابت) │ (scrollable) │
│ │ │
│ │ │
└────────────┴─────────────────────────────┘پیادهسازی با DS
import {
AppBar,
Avatar,
AvatarFallback,
Button,
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarProvider,
SidebarInset,
SidebarTrigger,
PageHeader,
} from '@parto-system-design/ui'
import { Bell, LayoutDashboard, Users, BarChart3, Settings } from 'lucide-react'
function AppShell({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<span className="font-bold text-foreground px-2">پارتو</span>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>اصلی</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/">
<LayoutDashboard className="size-4" />
<span>داشبورد</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/influencers">
<Users className="size-4" />
<span>اینفلوئنسرها</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/analytics">
<BarChart3 className="size-4" />
<span>تحلیلها</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/settings">
<Settings className="size-4" />
<span>تنظیمات</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
{/* AppBar — نوار بالای صفحه */}
<AppBar
logo={<SidebarTrigger />}
actions={
<Button variant="ghost" size="icon">
<Bell className="size-4" />
</Button>
}
user={
<Avatar>
<AvatarFallback>م</AvatarFallback>
</Avatar>
}
/>
{/* Page Content */}
<div className="p-6">{children}</div>
</SidebarInset>
</SidebarProvider>
)
}نکته
SidebarTrigger را بهعنوان logo در AppBar قرار دهید. این دکمه سایدبار را باز و بسته میکند و با Ctrl+B هم کار
میکند.
الگوی داشبورد
KPI + جدول
import {
MetricCard,
MetricCardHeader,
MetricCardLabel,
MetricCardContent,
MetricCardValue,
MetricCardDifferential,
DataTable,
PageHeader,
PageHeaderTitle,
PageHeaderAside,
Button,
} from '@parto-system-design/ui'
function DashboardPage() {
return (
<>
<PageHeader>
<PageHeaderTitle>داشبورد</PageHeaderTitle>
<PageHeaderAside>
<Button size="sm">گزارش جدید</Button>
</PageHeaderAside>
</PageHeader>
{/* KPI Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4 mb-6">
<MetricCard>
<MetricCardHeader>
<MetricCardLabel>مجموع تحلیلها</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue>۱,۲۳۴</MetricCardValue>
<MetricCardDifferential value={12.5} />
</MetricCardContent>
</MetricCard>
{/* سایر KPIها */}
</div>
{/* Data Table */}
<DataTable columns={columns} data={data} />
</>
)
}فیلتر + گرید کارت
import {
FilterBar,
ViewToggle,
Card,
CardHeader,
CardTitle,
CardContent,
Empty,
EmptyIcon,
EmptyTitle,
EmptyDescription,
} from '@parto-system-design/ui'
function ListPage() {
const [view, setView] = useState<'grid' | 'list'>('grid')
return (
<>
<div className="flex items-center justify-between gap-4 mb-4">
<FilterBar filters={filters} onFilterChange={setFilters} />
<ViewToggle view={view} onViewChange={setView} />
</div>
{data.length === 0 ? (
<Empty>
<EmptyIcon>
<Icons.inbox />
</EmptyIcon>
<EmptyTitle>موردی یافت نشد</EmptyTitle>
<EmptyDescription>فیلترها را تغییر دهید یا مورد جدیدی اضافه کنید</EmptyDescription>
</Empty>
) : (
<div
className={cn('gap-4', view === 'grid' ? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3' : 'flex flex-col')}
>
{data.map((item) => (
<Card key={item.id} variant="interactive">
<CardHeader>
<CardTitle>{item.title}</CardTitle>
</CardHeader>
<CardContent>{/* ... */}</CardContent>
</Card>
))}
</div>
)}
</>
)
}الگوی موبایل
Drawer بهعنوان Bottom Sheet
برای فیلترها و اقدامات موبایل، از Drawer با direction="bottom" استفاده کنید:
import {
Drawer,
DrawerTrigger,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerFooter,
Button,
} from '@parto-system-design/ui'
function MobileFilters() {
return (
<Drawer direction="bottom">
<DrawerTrigger asChild>
<Button variant="outline" size="sm" className="lg:hidden">
فیلترها
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>فیلترها</DrawerTitle>
</DrawerHeader>
<div className="p-4">{/* فرم فیلترها */}</div>
<DrawerFooter>
<Button>اعمال</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}الگوی صفحات طولانی
SectionNavigator
برای صفحات تحلیلی با بخشهای متعدد:
import { SectionNavigator } from '@parto-system-design/ui'
function AnalysisPage() {
const sections = [
{ id: 'overview', label: 'خلاصه' },
{ id: 'engagement', label: 'تعامل' },
{ id: 'content', label: 'محتوا' },
{ id: 'audience', label: 'مخاطبان' },
]
return (
<div className="grid grid-cols-1 lg:grid-cols-[240px_1fr] gap-6">
<aside className="hidden lg:block sticky top-20 h-fit">
<SectionNavigator sections={sections} offset={80} />
</aside>
<div>
<section id="overview">{/* ... */}</section>
<section id="engagement">{/* ... */}</section>
<section id="content">{/* ... */}</section>
<section id="audience">{/* ... */}</section>
</div>
</div>
)
}الگوی خطا و بارگذاری
Error Boundary + Loading
import { ErrorBoundary, PageLoader } from '@parto-system-design/ui'
function App() {
return (
<ErrorBoundary
fallback={({ error, reset }) => (
<div className="p-8 text-center">
<p className="text-destructive mb-4">{error.message}</p>
<Button onClick={reset}>تلاش مجدد</Button>
</div>
)}
>
<Suspense fallback={<PageLoader message="در حال بارگذاری..." />}>
<MyPage />
</Suspense>
</ErrorBoundary>
)
}قوانین Grid
| صفحه | Grid پیشنهادی |
|---|---|
| KPI Cards | grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 |
| Card Grid | grid-cols-1 md:grid-cols-2 lg:grid-cols-3 |
| داشبورد ۲ ستونی | grid-cols-1 lg:grid-cols-[2fr_1fr] |
| Analysis + Sidebar | grid-cols-1 lg:grid-cols-[240px_1fr] |
| فرم تنظیمات | max-w-2xl mx-auto |