Skip to content

SkyDataTable ​

Грід на TanStack Table v9 — headless-ядро тримає стан і обчислення, а розмітка й стилі наші. Візуально це та сама таблиця, що й у POS; відрізняється тільки те, як вона збирається.

Демо ​

Приклад ​

Колонки описуються один раз і повністю визначають таблицю — як у shadcn-ui:

vue
<script setup lang="ts">
import { h } from 'vue'
import { SkyDataTable, SkyBadge, createSkyColumnHelper } from '@skyservice-developers/vue-dev-kit'

interface Product {
  id: number
  name: string
  price: number
  status: 'Активний' | 'Прихований'
}

const column = createSkyColumnHelper<Product>()

const columns = [
  column.accessor('name', { header: 'Назва', meta: { widthFr: 2 } }),
  column.accessor('price', {
    header: 'Ціна',
    size: 110,
    meta: { align: 'right' },
    cell: ({ getValue }) => `${getValue()} ₴`,
  }),
  column.accessor('status', {
    header: 'Статус',
    size: 150,
    enableSorting: false,
    cell: ({ getValue }) =>
      h(SkyBadge, { tone: getValue() === 'Активний' ? 'success' : 'default', label: getValue() }),
  }),
]
</script>

<template>
  <SkyDataTable :columns="columns" :data="products" :page-size="25" row-id="id" searchable />
</template>

Набір фіч ​

Фічі TanStack v9 — це плагіни, які вмикаються явно. Кіт експортує стабільний набір skyTableFeatures, зібраний статично поза компонентом:

ts
export const skyTableFeatures = tableFeatures({
  columnFilteringFeature,
  columnSizingFeature,
  columnVisibilityFeature,
  globalFilteringFeature,
  rowPaginationFeature,
  rowSelectionFeature,
  rowSortingFeature,

  coreRowModel: createCoreRowModel(),
  filteredRowModel: createFilteredRowModel(),
  sortedRowModel: createSortedRowModel(),
  paginatedRowModel: createPaginatedRowModel(),

  sortFns,
  filterFns,
})

Разом із плагінами тут живуть слоти row-моделей і реєстри функцій: ключі sortFns / filterFns стають валідними значеннями sortFn і filterFn у колонках — з підказками в IDE.

Потрібно більше (групування, пінінг, розгортання рядків) — зберіть власний набір і передайте пропом:

ts
import { tableFeatures, rowExpandingFeature, createExpandedRowModel } from '@tanstack/vue-table'

const features = tableFeatures({
  ...skyTableFeatures,
  rowExpandingFeature,
  expandedRowModel: createExpandedRowModel(),
})
vue
<SkyDataTable :features="features" :columns="columns" :data="data" />

Те, чого немає в наборі, не потрапляє ні в типи, ні в бандл — саме тому набір визначається явно, а не «все і одразу».

Props ​

PropТипЗа замовчуваннямОпис
columnsColumnDef[]—Опис колонок (TanStack)
dataTData[]—Дані
featuresTableFeaturesskyTableFeaturesСвій набір фіч
rowIdString | (row) => string—Ключ рядка
pageSizeNumber0Розмір сторінки; 0 — без пагінації. Підтримує v-model:page-size
pageSizeOptionsNumber[]як у SkyPaginationВаріанти селектора кількості
pageAllLabel(total) => String—Підпис пункту «показати все» в селекторі; без нього пункту немає
searchableBooleanfalseПоле глобального пошуку в тулбарі
searchPlaceholderString'Пошук…'Placeholder пошуку
initialSortingSortingState—Початкове сортування
emptyTextString'Даних немає'Текст порожнього стану
interactiveRowsBooleanfalseКурсор і hover на рядках
virtualBoolean | SkyDataTableVirtualOptionsfalseВіртуалізація тіла — у DOM лише видиме вікно рядків
fitRowsBoolean | { rowHeight?, min? }falseРахувати, скільки рядків вміщається в тіло (подія fit-rows); з пагінацією — ще й розмір сторінки

rowId важливіший, ніж здається

Без нього TanStack ідентифікує рядки за індексом — після сортування або фільтрації вибір «переїде» на інші рядки. Завжди передавайте row-id.

Ширина колонок ​

Геометрію задають самі колонки, а рахує її наш SkyTableRoot:

Що задатиЯкРезультат
Фіксована ширинаsize: 150150px
Гнучка часткаmeta: { widthFr: 2 }2fr
Вирівнюванняmeta: { align: 'right' }праворуч
Ховати на вузьких екранахmeta: { minWidthScreen: 900 }колонка зникає під 900px

Віртуалізація ​

На кілька тисяч рядків браузер провисає не на даних, а на DOM. Проп virtual лишає в розмітці тільки видиме вікно:

vue
<SkyDataTable :columns="columns" :data="rows" row-id="id" virtual />

<!-- або з налаштуваннями -->
<SkyDataTable
  :columns="columns"
  :data="rows"
  row-id="id"
  :virtual="{ estimateSize: 44, overscan: 8 }"
  @reach-end="loadMore"
/>
ОпціяТипЗа замовчуваннямОпис
estimateSizeNumber40Очікувана висота рядка в px
overscanNumber6Скільки рядків тримати понад видиме вікно
dynamicBooleanfalseРядки різної висоти — вмикає вимірювання через ResizeObserver

Змінюється рівно одне: хто вирішує, які рядки зараз у DOM. Сортування, фільтрація, вибір і getRowCount() працюють по всьому набору даних, а не по видимому вікну.

Потрібна обмежена висота

Скрол-контейнером є тіло таблиці. Якщо таблиця росте разом з контентом і скролиться сторінка, вікно ніколи не зсувається — у DOM залишаться перші кілька рядків і все. Дайте таблиці height або flex: 1; min-height: 0.

Не поєднуйте з pageSize

На сторінці в кілька десятків рядків віртуалізувати нічого. Компонент попередить у консолі, якщо задати обидва. Природна пара до virtual — не пагінація, а довантаження за подією reach-end.

dynamic вмикайте лише за потреби: з фіксованою висотою рядка геометрія рахується арифметикою, без читання з DOM.

Slots ​

SlotPropsОпис
toolbar{ table }Замінює весь тулбар
actions{ table }Кнопки тулбару, притиснуті до правого краю (напр. SkyDataTableColumnToggle)
footer{ table }Замінює футер із пагінацією (SkyPagination + «Знайдено N»)
empty—Вміст порожнього стану

Events ​

EventPayloadОпис
row-clickTDataКлік по рядку
reach-end—Долистали тіло до кінця. Тільки у віртуальному режимі
update:pageNumberПерейшли на іншу сторінку (з одиниці)
update:pageSizeNumberОбрали іншу кількість на сторінці; таблиця сама повертається на першу сторінку
fit-rowsNumberСкільки рядків вміщається в тіло. Лише з fitRows; емітиться, коли число змінилось

Пагінація ​

З pageSize > 0 у футері стоїть SkyPagination: селектор кількості на сторінці й номери сторінок, праворуч — «Знайдено N». Сторінки рахуються на клієнті з усіх відфільтрованих рядків.

vue
<SkyDataTable
  v-model:page-size="pageSize"
  :columns="columns"
  :data="products"
  :page-size-options="[10, 25, 50, 100]"
  :page-all-label="(n) => `Усі ${n}`"
  row-id="id"
/>
  • Нова кількість на сторінці завжди повертає на першу сторінку — інакше поточна могла б опинитись за межами нового числа сторінок.
  • v-model:page-size потрібен, лише якщо вибір треба пам'ятати (наприклад, у налаштуваннях користувача). Без нього таблиця тримає розмір сама.
  • Пагінатор ховається сам, коли вибирати нічого: одна сторінка й один варіант розміру. Без рядків футера немає зовсім.

Скільки рядків вміщається (fitRows) ​

Таблиця міряє вільну висоту тіла, ділить на висоту рядка й віддає результат подією fit-rows. Під неї зручно вішати запит на бек рівно з тією кількістю, що влізе, без скролу й без зайвих рядків.

vue
<div style="height: 600px">
  <SkyDataTable
    :columns="columns"
    :data="rows"
    :page-size="25"
    fit-rows
    row-id="id"
    @fit-rows="(n) => { limit = n; load() }"
    @update:page="(p) => { page = p; load() }"
  />
</div>
  • Опційно: без fitRows таблиця нічого не міряє.
  • Висота таблиці має бути обмежена — батько з height або flex + min-height: 0. Інакше тіло росте разом з рядками, і міряти нема чого.
  • Перераховується на зміну розміру (ResizeObserver) і після нових даних. Подія летить лише коли число змінилось.
  • З пагінацією (pageSize > 0) таблиця сама ставить такий розмір сторінки й ховає селектор кількості. На ресайзі лишається біля того самого першого рядка, а не стрибає на першу сторінку.
  • Висота рядка вимірюється з першого рядка; без рядків — з --sky-table-row-height (+1px рамки). Для рядків нестандартної висоти задайте fitRows: { rowHeight: 56 }; min — не менше стількох рядків, навіть якщо місця менше.
  • З virtual не поєднується: віртуальне тіло й так тримає в DOM лише видиме (компонент попередить у консолі).

Серверна пагінація

fitRows лише рахує кількість — сторінки й сортування таблиця поки що рахує на клієнті з переданих data. Для повноцінної серверної пагінації (таблиця показує одну сторінку з бека, а total приходить окремо) потрібен окремий серверний режим.

Налаштування колонок ​

SkyDataTableColumnToggle — кнопка з іконкою-сіткою і панель зі списком колонок, як вибір колонок у таблицях адмінки. Ставиться в слот actions:

vue
<SkyDataTable :columns="columns" :data="products" row-id="id" searchable>
  <template #actions="{ table }">
    <SkyDataTableColumnToggle :table="table" />
  </template>
</SkyDataTable>
  • У списку — колонки з рядковим header або з meta.label. Колонка без жодного (наприклад, шапка-чекбокс у колонці вибору) у список не потрапляє.
  • Колонка з enableHiding: false — обовʼязкова: вона в списку, але чекбокс вимкнений.
  • «За замовчуванням» повертає видимість колонок до початкової (table.resetColumnVisibility()).
PropТипЗа замовчуваннямОпис
tableінстанс TanStack—Таблиця зі слоту actions
titleString'Колонки'Підказка й доступна назва кнопки
doneLabelString'Готово'
resetLabelString'За замовчуванням'

Доступ до інстансу таблиці ​

Компонент віддає інстанс через defineExpose — звідти доступний увесь API TanStack:

vue
<script setup>
const tableRef = ref()
const selected = computed(() => tableRef.value?.table.getSelectedRowModel().rows ?? [])
</script>

<template>
  <SkyDataTable ref="tableRef" … />
</template>

Стан читається через атоми TanStack Store: table.atoms.sorting.get(), table.atoms.pagination.get(), table.atoms.rowSelection.get(). Vue-адаптер робить ці читання реактивними всередині computed, тож синхронізувати стан з URL чи сервером можна звичайним watch:

ts
watch(
  () => tableRef.value?.table.atoms.sorting.get(),
  (sorting) => router.replace({ query: { sort: sorting?.[0]?.id } }),
)

Колонка вибору ​

Робиться display-колонкою — рівно як у shadcn-ui:

ts
column.display({
  id: 'select',
  size: 44,
  meta: { align: 'center' },
  header: ({ table }) => h(SkyCheckbox, {
    modelValue: table.getIsAllRowsSelected(),
    'onUpdate:modelValue': () => table.toggleAllRowsSelected(),
  }),
  cell: ({ row }) => h(SkyCheckbox, {
    modelValue: row.getIsSelected(),
    'onUpdate:modelValue': () => row.toggleSelected(),
  }),
})

Що обрати ​

SkyDataTableПримітивиSkyTable
Стан (сортування, вибір, пагінація)TanStackваш, через композабливсередині компонента
Опис таблиціколонкирозміткаобʼєкт params
Залежність@tanstack/vue-table (+ vue-virtual з virtual)немає@tanstack/vue-virtual
Колитиповий CRUD-екран зі спискомнестандартна розміткастарі POS-екрани

Опубліковано під ліцензією MIT.