جدول داده (DataTable)

جدول داده ترکیبی با مرتب‌سازی، صفحه‌بندی، انتخاب ردیف، و حالت‌های بارگذاری و خالی

معرفی

DataTable یک کامپوننت ترکیبی است که جدول، مرتب‌سازی، صفحه‌بندی، انتخاب ردیف، و مدیریت حالت‌های خالی و بارگذاری را در یک API ساده ارائه می‌دهد. این کامپوننت رایج‌ترین الگوی نمایش داده در اپلیکیشن‌های SaaS را پوشش می‌دهد.

جدول سطح مستقلِ روشن‌تر، حاشیهٔ ظریف و گوشه‌های بومی دارد؛ همان محفظهٔ اسکرول این قاب را می‌سازد. آن را داخل Card دیگری نگذارید. رنگِ ردیف‌های عادی و سلول‌های ثابت از سطح خود جدول می‌آید؛ انتخاب و اشاره‌گر حالت‌های خود را حفظ می‌کنند.

متن عادی و عددها از تراکم جدول و وزن regular (400) پیروی می‌کنند؛ شناسهٔ اصلی می‌تواند medium (500) باشد. وضعیت، هویت، اعتماد و دیگر مقدارهای تصمیم‌گیری اندازهٔ بدنه را نگه می‌دارند، نه caption؛ وزن semibold، bold یا black برای وضعیت مجاز نیست. اطلاعات فرعی 12px و regular است؛ نشان بومی نقش پیش‌فرض 12px / 500 خود را حفظ می‌کند. فقط جمع‌بندی واقعی در footer می‌تواند medium باشد. سرستون default همان 13px / 500 و compact همان 12px / 500 است. برای ستون وضعیت از StatusCell استفاده کنید: نقطهٔ معنایی، برچسب متنی با وزن 400 و caption.

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

  • لیست داده‌ها با بیش از 5 آیتم که نیاز به مرتب‌سازی یا صفحه‌بندی دارند
  • جداول با قابلیت انتخاب برای عملیات دسته‌ای
  • جدول هر صفحهٔ فهرست در اپلیکیشن (اینفلوئنسرها، کمپین‌ها، منشن‌ها)، به‌عنوان فرزند قالب ListPage

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

  • نمایش ساده 3-5 آیتم بدون تعامل — از Table مستقیم استفاده کنید
  • جداول با ساختار بسیار سفارشی — از کامپوننت‌های Table به صورت ترکیبی استفاده کنید
  • نمایش کارتی — از Card استفاده کنید
رضا کریمی234,0002.8٪
سارا احمدی87,3003.1٪
امیر رضایی45,8003.5٪
علی محمدی12,5004.2٪
مریم حسینی5,2006.7٪
import { DataTable } from '@partodata/ui'

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

در یک صفحهٔ اپلیکیشن، جدولِ فهرست فرزند ListPage است و هیچ‌یک از size، pagination، emptyState و isLoading خودش را ندارد: تراکم را قالب تعیین می‌کند، صفحه‌بندی pagination قالب است و بارگذاری، خطا و حالت خالی state={pageState(…)} قالب‌اند که جای کل جدول می‌نشینند. جست‌وجو، فیلترها و اقدام‌های بالای جدول هم propهای همان قالب‌اند (search، filters، secondaryActions، primaryAction). نمونه‌های صفحه‌بندی و ترکیب کامل پایین این صفحه، جدولی را نشان می‌دهند که صفحه نیست (مثلاً فهرستی داخل DashboardChart یا یک Sheet).


زمین بازی

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

زمین بازی
احساس
پست الفاینستاگرام12,345
78٪
پست بتوییتر8,900
65٪
پست جتلگرام5,400
42٪
پست داینستاگرام4,200
88٪
پست هتوییتر3,100
35٪
تنظیمات
ظاهر
داده
5
چیدمان
حالت
محتوا
کد این نمونه به‌صورت خودکار قابل تولید نیست — برای کد آماده‌ی copy/paste به بخش «استفاده» در بالای صفحه مراجعه کنید.

استفاده پایه

import { DataTable, type DataTableColumn } from '@partodata/ui'

interface Influencer {
  id: number
  name: string
  followers: number
  engagementRate: number
}

const columns: DataTableColumn<Influencer>[] = [
  { id: 'name', header: 'نام', cell: (row) => row.name },
  {
    id: 'followers',
    header: 'دنبال‌کننده‌ها',
    cell: (row) => row.followers.toLocaleString('en-US'),
    align: 'end',
  },
  {
    id: 'engagementRate',
    header: 'نرخ تعامل',
    cell: (row) => `${row.engagementRate}٪`,
    align: 'end',
  },
]

const data: Influencer[] = [
  { id: 1, name: 'محمد رضایی', followers: 125000, engagementRate: 4.2 },
  { id: 2, name: 'سارا احمدی', followers: 89000, engagementRate: 6.8 },
  { id: 3, name: 'علی محمدی', followers: 350000, engagementRate: 2.1 },
]

<DataTable columns={columns} data={data} />

با مرتب‌سازی

'use client'

import { useState } from 'react'
import { DataTable, type DataTableColumn, type SortDirection } from '@partodata/ui'

function SortableExample() {
  const [sortColumn, setSortColumn] = useState<string | null>(null)
  const [sortDirection, setSortDirection] = useState<SortDirection | null>(null)

  return (
    <DataTable
      columns={columns}
      data={data}
      sort={{
        column: sortColumn,
        direction: sortDirection,
        onSort: (col, dir) => {
          setSortColumn(col)
          setSortDirection(dir)
        },
      }}
    />
  )
}

با صفحه‌بندی

<DataTable
  columns={columns}
  data={pageData}
  pagination={{
    currentPage: 2,
    totalPages: 10,
    onPageChange: (page) => setCurrentPage(page),
  }}
/>

نوار صفحه‌بندی کامل

با افزودن pageSize/onPageSizeChange و totalRows، نوار پایین علاوه بر شماره‌ی صفحه‌ها، انتخاب تعداد ردیف در هر صفحه، بازه‌ی نتایج و شمار ردیف‌های انتخاب‌شده را هم نشان می‌دهد. هر سه اختیاری‌اند؛ بدون آن‌ها همان نوار شماره‌ایِ وسط‌چین قبلی رندر می‌شود.

<DataTable
  columns={columns}
  data={pageData}
  selection={{ selectedRows, onSelectionChange: setSelectedRows, getRowKey: (row) => row.id }}
  pagination={{
    currentPage,
    totalPages,
    onPageChange: setCurrentPage,
    pageSize,
    onPageSizeChange: setPageSize,
    pageSizeOptions: [10, 20, 50, 100],
    totalRows: 1240,
  }}
/>

totalRows از سرور می‌آید

این جدول سرور-صفحه‌بندی است و فقط یک صفحه ردیف در دست دارد؛ پس نمی‌تواند تعداد کل را حدس بزند. اگر totalRows را ندهید، بازه‌ی «1–20 از 1,240» رندر نمی‌شود.


با انتخاب ردیف

در صفحهٔ فهرست: selection قالب ListPage

جدول فقط چک‌باکس‌ها را می‌کشد. در صفحهٔ فهرست همان وضعیت انتخاب به selection قالب ListPage هم می‌رود، با bulkActions: قالب شمار، اقدام‌های گروهی و «لغو انتخاب» را تا ردیفی انتخاب شده به‌جای ردیف نوارابزار نشان می‌دهد و با بارگذاری دوبارهٔ فهرست یا نمایش صفحهٔ دیگر انتخاب را پاک می‌کند. selection جدول همیشه getRowKey: (row) => row.id دارد: بی آن کلید هر ردیف جایگاهش در صفحه است و اقدام گروهی جایگاه‌ها را به‌جای شناسه‌ها می‌گیرد. نوار اقدام گروهی را خودتان نسازید و اقدام گروهی را در secondaryActions نگذارید.

'use client'

import { useState } from 'react'

function SelectableExample() {
  const [selected, setSelected] = useState<Set<number>>(new Set())

  return (
    <DataTable
      columns={columns}
      data={data}
      selection={{
        selectedRows: selected,
        onSelectionChange: setSelected,
        getRowKey: (row) => row.id,
      }}
    />
  )
}

انتخاب بازه‌ای با Shift-click

بدون هیچ پیکربندی اضافه: یک ردیف را کلیک کنید (لنگر می‌شود)، بعد با Shift ردیف دیگری را کلیک کنید — همه‌ی ردیف‌های بینشان همان حالتِ جدیدِ ردیفِ کلیک‌شده را می‌گیرند. لنگر تا کلیکِ ساده‌ی بعدی ثابت می‌ماند، پس چند Shift-click پشتِ سرِ هم همه از یک نقطه بازه می‌سازند. ردیف‌های غیرقابل‌انتخاب (isRowSelectable که false برگرداند) از بازه کنار گذاشته می‌شوند — دقیقاً مثل رفتارشان در «انتخاب همه».

بازه در محدوده‌ی همین صفحه است

جدول فقط رکوردهای data همین رندر را می‌شناسد؛ برای جدولی که سرور-صفحه‌بندی است، یک بازه‌ی Shift-click از صفحه‌ای عبور نمی‌کند — دقیقاً چون خودِ لنگر و ردیفِ کلیک‌شده هم باید همین حالا روی صفحه دیده شده باشند تا اصلاً بشود رویشان کلیک کرد.


تراکم

مثل Table: default (سرِ جدول 40، ردیف حداقل 44، متن 14) برای همهٔ جدول‌های فهرست، و size="compact" (36 / 36 / 13) برای جدول‌های پرتراکم منشن. دلیل عددها در صفحهٔ Table آمده است. درون یک ListPage، size نمی‌دهید: قالب تراکم همهٔ صفحه‌های فهرست را یکی می‌کند.

  • محتوای ستون‌ها را زیر سقف تراکم نگه دارید تا همهٔ ردیف‌ها یک ارتفاع داشته باشند. در default کنترل‌های sm (30 پیکسل، پیش‌فرض Button) و آواتار تا 32 پیکسل (Avatar size="md") جا می‌شوند، و در compact کنترل‌های xs (26) و نشان‌ها و آواتار تا 24 پیکسل (Avatar size="sm").
  • تراکم مال خود جدول است. DataTable فشرده داخل PageContainer فشرده می‌ماند، و جدولی که در ردیف بازشدهٔ (expansion) آن قرار می‌گیرد تراکم خودش را دارد.
  • با virtualize، مقدار rowHeight را برابر ارتفاع ردیف بدهید: 44 در default و 36 در compact (ردیف‌های 3٫x حدود 31 پیکسل بودند). فاصله‌گذاری و اسکرول از همین عدد حساب می‌شود.
  • اسکلت بارگذاری بیرون از جدول (TableSkeleton) هم size="compact" می‌گیرد، تا جایگزین شدنش با جدول پرش نداشته باشد.

حالت‌های بارگذاری، خطا و خالی

جدول حالت‌هایش را خودش نمی‌کشد: قالب صفحه آن‌ها را جای کل جدول می‌گذارد. در صفحهٔ فهرست با state قالب ListPage، و برای جدولی که جدا از صفحه بارگذاری می‌شود (بخشی از صفحهٔ جزئیات) با state همان DetailSection یا یک PageState:

import { DataTable, useAsync } from '@partodata/ui'
import { ListPage, pageState } from '@partodata/ui/templates'

const { data, isLoading, error, run } = useAsync<Paged<Influencer>>()

;<ListPage
  title="اینفلوئنسرها"
  state={pageState({
    data: data?.items,
    isLoading,
    error,
    onRetry: load,
    emptyCopy: { title: 'هنوز اینفلوئنسری ثبت نشده است' },
  })}
>
  <DataTable columns={columns} data={data?.items ?? []} />
</ListPage>
  • بارگذاری اول: اسکلتی به شکل جدول جای جدول؛ هنگام بارگذاری صفحهٔ بعد، جست‌وجو یا فیلتر ردیف‌ها کم‌رنگ سر جایشان می‌مانند.
  • خطا: ErrorState با «تلاش مجدد» (که onRetry را صدا می‌زند) جای جدول.
  • خالی: وقتی جست‌وجو یا فیلتری فعال است «نتیجه‌ای یافت نشد» با «پاک کردن فیلترها»؛ وگرنه عنوان emptyCopy که نام آنچه نیست را می‌برد. سرستون‌های جدول نمی‌مانند.

propهای isLoading، loadingRows و emptyState خود جدول فقط برای سازگاری با کدهای پیشین مانده‌اند؛ در صفحه آن‌ها را به کار نبرید.


ترکیب کامل

'use client'

import { useState } from 'react'
import { DataTable, type DataTableColumn, type SortDirection, Avatar, Badge, formatNumber } from '@partodata/ui'
import { PlatformMark } from '@partodata/ui/social'

function FullExample() {
  const [page, setPage] = useState(1)
  const [sortCol, setSortCol] = useState<string | null>(null)
  const [sortDir, setSortDir] = useState<SortDirection | null>(null)
  const [selected, setSelected] = useState<Set<number>>(new Set())

  const columns: DataTableColumn<Influencer>[] = [
    {
      id: 'name',
      header: 'نام',
      sortable: true,
      cell: (row) => (
        <div className="flex items-center gap-2">
          <Avatar src={row.avatar} fallback={row.name[0]} size="sm" />
          <span className="font-medium">{row.name}</span>
        </div>
      ),
    },
    {
      id: 'platform',
      header: 'پلتفرم',
      cell: (row) => <PlatformMark source={row.platform} variant="badge" showLabel />,
    },
    {
      id: 'followers',
      header: 'دنبال‌کننده‌ها',
      sortable: true,
      align: 'end',
      cell: (row) => formatNumber(row.followers),
    },
    {
      id: 'status',
      header: 'وضعیت',
      cell: (row) => <Badge variant={row.active ? 'success' : 'default'}>{row.active ? 'فعال' : 'غیرفعال'}</Badge>,
    },
  ]

  return (
    <DataTable
      columns={columns}
      data={influencers}
      size="compact"
      striped
      sort={{
        column: sortCol,
        direction: sortDir,
        onSort: (col, dir) => {
          setSortCol(col)
          setSortDir(dir)
        },
      }}
      selection={{
        selectedRows: selected,
        onSelectionChange: setSelected,
        getRowKey: (row) => row.id,
      }}
      pagination={{
        currentPage: page,
        totalPages: 12,
        onPageChange: setPage,
      }}
      resultCount="142 اینفلوئنسر"
    />
  )
}

کلیک روی ردیف

با onRowClick، ردیف‌های بدنه تعاملی می‌شوند: فوکوس‌پذیر با کیبورد (Tab) و قابل فعال‌سازی با Enter/Space، و کلیک/فعال‌سازی این callback را با (row, rowIndex) صدا می‌زند. برای «کلیک ردیف → صفحهٔ جزئیات» مناسب است. کنترل‌های تعاملی داخل سلول (دکمه، منو) رفتار خودشان را حفظ می‌کنند.

<DataTable columns={columns} data={campaigns} onRowClick={(row) => router.push(`/campaigns/${row.id}`)} />

فیلتر facetedِ ستون‌محور

DataTableFacetedFilter یک چندانتخابیِ Popover + Command است برای enumهای محدودِ دامنه — پلتفرم، احساس، موضع، وضعیت، شدت، رده‌ی تعامل. کنارِ هر گزینه، شمارِ نتایج نمایش داده می‌شود.

5 نتیجه
نامپلتفرمپست‌ها
علی محمدیاینستاگرام128
سارا احمدیتلگرام64
رضا کریمیاینستاگرام91
مریم حسینیایکس37
امیر رضاییتلگرام12
'use client'
import { DataTable, DataTableFacetedFilter } from '@partodata/ui'

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

const [platforms, setPlatforms] = useState<Platform[]>([])

<DataTableFacetedFilter
  title="پلتفرم"
  options={PLATFORMS}
  selected={platforms}
  onSelectedChange={setPlatforms}
  facets={{ instagram: 1240, telegram: 87, x: 12 }}
/>

فیلتر با نوع مقدار گزینه‌هایش تایپ می‌شود: وقتی گزینه‌ها و وضعیت با union برنامه تایپ شوند (useState<Platform[]>)، selected و onSelectedChange هم Platform[] هستند و انتخاب بی‌تبدیل نوع (بدون as Platform[]) به APIای می‌رسد که با همان union تایپ شده است. گزینه‌ای بیرون از union خطای نوع است. با مقدارهای string مثل قبل کار می‌کند. در صفحهٔ فهرست، هر تغییر فیلتر صفحه را هم به 1 برمی‌گرداند (filterBy در ListPage).

شمارش‌ها را سرور می‌دهد

facets را مصرف‌کننده پاس می‌دهد، چون DataTable دقیقاً همان dataای را رندر می‌کند که گرفته (سرور-صفحه‌بندی). شمارشی که از صفحه‌ی جاری حساب شود دروغ است. اگر facets ندهید، گزینه‌ها بدون عدد رندر می‌شوند.

جدول در صفحهٔ فهرست (جست‌وجو + فیلترها + خروجی)

در صفحهٔ فهرست، جدول فرزند قالب ListPage است و جست‌وجو، فیلترها و اقدام‌های بالای آن propهای همان قالب‌اند: قالب نوارابزار را می‌سازد، همهٔ کنترل‌ها را هم‌قد می‌کند، به هر کدام عرض محتوایش را می‌دهد، جست‌وجو را با عرض نام‌دار می‌گذارد و فاصلهٔ 16 پیکسلی تا جدول را خودش تعیین می‌کند. پس این ردیف را با PageToolbar، FilterBar یا div و flex نسازید و به کنترل‌های داخلش size یا کلاس عرض ندهید. جدول خودش size، pagination، emptyState و isLoading ندارد.

Prop در ListPageچه چیزی در آن می‌آید
searchیک SearchInput بدون عرض؛ درخواست مقدارش را با useDebounce(q, 300) می‌گیرد
filtersیک DataTableFacetedFilter برای هر ستون شمارشی، و DateRangePicker با placeholder برای بازهٔ زمانی
filteredآیا جست‌وجو یا فیلتری فعال است؛ هر تغییر جست‌وجو یا فیلتر صفحه را به 1 برمی‌گرداند
onClearFiltersهمه را پاک می‌کند و به صفحهٔ 1 برمی‌گردد (دکمهٔ «پاک کردن فیلترها» را قالب می‌گذارد)
secondaryActionsDataTableExportButton بدون variant (آن‌جا default رندر می‌شود)
toolbarEndDataTableColumnVisibilityToggle، فقط وقتی محصول انتخاب ستون‌ها را می‌خواهد
primaryActionاقدام اصلی صفحه اگر دارد: یک Button بدون variant که کاری می‌کند (یا پیوندی با asChild)
state، paginationحالت‌ها با pageState(…) و هر پنج فیلد صفحه‌بندی
فرزندخود DataTable

نمونهٔ کامل و کامپایل‌شده در ListPage آمده است.

چرا کامپوننتی به‌نام DataTableToolbar نداریم

شادسی‌ان یک DataTableToolbar دارد که فقط چیدمان ردیف بالای جدول است. در پرتو این ردیف را قالب ListPage از propهایش می‌سازد (روی جزء سطح پایین PageToolbar، که مستقیم فقط درون یک CustomPage به کار می‌رود). FilterBar جزء سطح پایین‌تری است برای ردیف فیلتری که نوارابزار صفحه نیست، مثل ردیف فیلتر داخل یک پنل کناری.

جدول ویژگی‌ها

DataTable

Prop

Type

DataTableColumn

ویژگینوعتوضیح
idstringکلید یکتای ستون
headerReactNodeعنوان ستون
cell(row, index) => ReactNodeتابع رندر سلول
sortablebooleanقابلیت مرتب‌سازی
align"start" | "center" | "end"تراز ستون
pinned"start" | "end"سنجاق‌کردن ستون (چسبان هنگام اسکرول افقی)
defaultVisiblebooleanبا false تا فعال‌شدن مخفی است
hideablebooleanبا false ستونِ ساختاری است و در منوی نمایانیِ ستون نمی‌آید (پیش‌فرض true)
classNamestringکلاس اضافی

DataTablePagination

Prop

Type

DataTableFacetedFilter

Prop

Type

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

بکنید

  • از DataTable برای لیست داده‌ها با بیش از 5 آیتم که نیاز به مرتب‌سازی یا صفحه‌بندی دارند استفاده کنید - در صفحهٔ فهرست جدول را فرزند ListPage کنید و حالت خالی را با emptyCopy: {title} در pageState قالب بدهید، نه در خود جدول - عملیات هر ردیف را آخرین ستون کنید: یک DropdownMenu پشت Button variant="ghost" با Icons.moreHorizontal و aria-label به نام ردیف - برای جداول با انتخاب ردیف، getRowKey یکتا تعریف کنید

نکنید

  • برای نمایش ساده 3-5 آیتم بدون تعامل از DataTable استفاده نکنید — از Table استفاده کنید - ستون‌های غیرضروری اضافه نکنید — اطلاعات باید قابل اسکن باشند - برای نمایش کارتی از جدول استفاده نکنید — از Card در grid layout استفاده کنید

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

  • ستون‌های قابل مرتب‌سازی از aria-sort استفاده می‌کنند
  • همه نام‌های قابل‌دسترس از prop به نام locale پیروی می‌کنند (fa پیش‌فرض، ar، en) — چک‌باکس انتخاب، دکمه باز/بستن ردیف و دستگیره تغییر اندازه ستون
  • نشان اولویت مرتب‌سازی چندستونه معنای خود را با یک متن sr-only اعلام می‌کند (روی یک <span> بدون role، aria-label نام نمی‌سازد)
  • حالت بارگذاری از اسکلتون بصری استفاده می‌کند
  • ساختار جدول از تگ‌های معنایی HTML (table, thead, tbody, th, td) پیروی می‌کند

تعامل با کیبورد

  • Tab: حرکت بین عناصر تعاملی (چک‌باکس‌ها، دکمه‌های مرتب‌سازی، صفحه‌بندی) - Space: انتخاب ردیف (چک‌باکس) - Enter: فعال‌سازی مرتب‌سازی ستون

v2 features (نمایان‌سازی ستون، expansion، pinning، export)

'use client'
import {
  DataTable,
  DataTableColumnVisibilityToggle,
  DataTableExportButton,
} from '@partodata/ui'

const [visibility, setVisibility] = useState({})
const [expandedRows, setExpandedRows] = useState(new Set<number>())

<div className="flex items-center justify-end gap-2">
  <DataTableColumnVisibilityToggle
    columns={columns}
    visibility={{ visible: visibility, onVisibilityChange: setVisibility }}
  />
  <DataTableExportButton columns={columns} data={rows} filename="pages.csv" />
</div>

<DataTable
  columns={[
    { id: 'name', header: 'نام', cell: (r) => r.name, pinned: 'start', width: 200 },
    { id: 'engagement', header: 'تعامل', cell: (r) => r.engagement },
    { id: 'notes', header: 'یادداشت', cell: (r) => r.notes, defaultVisible: false },
  ]}
  data={rows}
  columnVisibility={{ visible: visibility, onVisibilityChange: setVisibility }}
  expansion={{
    expandedRows,
    onExpandedRowsChange: setExpandedRows,
    renderExpandedRow: (r) => <div className="p-4 text-sm">{r.detailsHtml}</div>,
  }}
/>
  • columnVisibility (4.2) — show/hide columns via a checkbox dropdown. defaultVisible: false on a column hides it until toggled on.
  • expansion (4.5) — adds a chevron column; clicking expands a full-width detail row below.
  • pinned: 'start' | 'end' (4.6) — sticky columns که در زمان horizontal scroll قابل دیدن می‌مانند. RTL-aware via CSS logical inset.
  • width: number — عرض ثابت پیکسل برای layout header (همراه با pinning مفید است).
  • <DataTableExportButton> (4.9) — dropdown با CSV download + TSV-clipboard. exportValue در ستون برای cellهای React-node serialize را override می‌کند.

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

  • ListPage — قالب صفحهٔ فهرست: جست‌وجو، فیلترها، اقدام‌ها، حالت‌ها و صفحه‌بندی جدول؛ جدول فرزند آن است
  • Table — اگر داده‌های شما ساده هستند و نیاز به مرتب‌سازی یا صفحه‌بندی ندارند، از Table استفاده کنید
  • Card — اگر نمایش کارتی مناسب‌تر از جدول است، از Card در grid layout استفاده کنید
  • DataTableCells — renderer های مخصوص cell جدول (sparkline، trend، status، …)