Skip to content

DataTable ​

Типизированная таблица с локальной или серверной обработкой данных. Сортировка выполняется из заголовков, а фильтры открываются в отдельном доступном диалоге и не занимают место над таблицей.

Полный пример ​

Сортировка, фильтры, pagination, sticky header и слоты
Команда7 сотрудников
Список сотрудников
Статус
Анна ПетроваDesignАктивен287
Борис ВолковFrontendАктивен3412
Виктор ОрловBackendВ отпуске234
Галина МироваFrontendАктивен319
7 результатов

sort, filters и page — controlled state. Таблица отправляет обновления через v-model, а родитель хранит состояние. При изменении сортировки или фильтров страница автоматически сбрасывается на первую.

Ленивая загрузка ​

lazy отключает обычную pagination и показывает уже загруженные строки. Когда пользователь приближается к нижней границе, компонент отправляет load-more с текущим offset, сортировкой и фильтрами.

Infinite scroll с сохранением загруженных строк
IDКомандаВозраст
1Пользователь 1Design20
2Пользователь 2Frontend21
3Пользователь 3Backend22
4Пользователь 4QA23
5Пользователь 5Design24
6Пользователь 6Frontend25
7Пользователь 7Backend26
8Пользователь 8QA27
vue
<UiDataTable
  :rows="items"
  :columns="columns"
  lazy
  :has-more="response.hasNextPage"
  :loading-more="loadingMore"
  :max-height="480"
  @load-more="({ offset, sort, filters }) =>
    fetchNextPage({ offset, sort, filters })"
/>
  • hasMore=false останавливает автоматические запросы.
  • loadingMore блокирует повторный запрос, но не скрывает текущие строки.
  • lazyThreshold задаёт расстояние до нижней границы в пикселях.
  • Кнопка внизу остаётся доступным fallback для клавиатуры и повторной загрузки.
  • Слот loading-more полностью заменяет состояние догрузки.

Закреплённые колонки ​

Укажите sticky: 'start' или sticky: 'end' в описании колонки. Таблица учитывает LTR/RTL и использует logical CSS properties.

Sticky header, первая и последняя колонки
СотрудникКомандаСтатусВозрастПроектыEmailГород
Анна ПетроваDesignАктивен287анна.петрова@example.comКазань
Борис ВолковFrontendАктивен3412борис.волков@example.comТбилиси
Виктор ОрловBackendВ отпуске234виктор.орлов@example.comМосква
Галина МироваFrontendАктивен319галина.мирова@example.comКазань
Денис ЛевинDesignНеактивен3915денис.левин@example.comТбилиси
Елена СоколоваBackendАктивен266елена.соколова@example.comМосква
Жанна БеловаQAВ отпуске308жанна.белова@example.comКазань

Если закреплено несколько колонок с одной стороны, задайте накопительный stickyOffset:

ts
[
  { key: 'select', label: '', sticky: 'start', width: 48 },
  {
    key: 'name',
    label: 'Название',
    sticky: 'start',
    stickyOffset: 48,
    width: 220,
  },
]

Для предсказуемой широкой таблицы используйте tableMinWidth, фиксированные width колонок и при необходимости tableLayout="fixed". truncate: true добавляет ellipsis длинному содержимому.

Плотность, границы и строки ​

dense, bordered, striped, rowClass
Статус
Анна ПетроваDesignactive287
Борис ВолковFrontendactive3412
Виктор ОрловBackendvacation234
Галина МироваFrontendactive319
Денис ЛевинDesigninactive3915
Елена СоколоваBackendactive266
Жанна БеловаQAvacation308
  • dense уменьшает вертикальные и горизонтальные отступы.
  • bordered добавляет вертикальные разделители.
  • striped чередует фон строк.
  • hoverable управляет hover-подсветкой.
  • rowClass принимает строку или функцию (row, index) => className.

Описание колонок ​

ts
import type { UiDataTableColumn } from '@neytron/compact-ui';

interface User {
  id: number;
  profile: { name: string };
  balance: number;
}

const columns: UiDataTableColumn<User>[] = [
  {
    key: 'name',
    label: 'Пользователь',
    accessor: (row) => row.profile.name,
    sortable: true,
    minWidth: 220,
    filter: {
      type: 'text',
      placeholder: 'Начните вводить имя',
    },
  },
  {
    key: 'balance',
    label: 'Баланс',
    align: 'end',
    width: 140,
    sortable: true,
    format: (value) => `${value} ₽`,
    comparator: (left, right) => left.balance - right.balance,
    filter: {
      type: 'number-range',
      min: 0,
      step: 100,
    },
  },
];

accessor может быть ключом строки или функцией для вложенных и вычисляемых значений. comparator переопределяет стандартное сравнение, format меняет только отображение, а headerClass и cellClass позволяют точечно добавить классы.

Встроенные фильтры ​

ТипЗначениеНазначение
textstringПоиск подстроки, без учёта регистра по умолчанию
selectprimitive или массивОдин или несколько вариантов, опциональный поиск
number-range{ min?, max? }Числовой диапазон
customлюбоеПользовательский UI и predicate

Фильтры одной таблицы объединяются через AND. Для любой колонки можно задать свой predicate:

ts
{
  key: 'tags',
  label: 'Теги',
  filter: {
    type: 'custom',
    predicate: (cellValue, filterValue) =>
      cellValue.some((tag) => filterValue.includes(tag)),
  },
}

Для custom используйте общий слот filter и выберите UI по ключу колонки:

vue
<UiDataTable
  v-model:filters="customFilters"
  :rows="rows"
  :columns="columns"
>
  <template #filter="{ column, value, setValue }">
    <UiInput
      v-if="column.key === 'name'"
      :model-value="value ?? ''"
      label="Кастомный поиск"
      @update:model-value="setValue"
    />
  </template>
</UiDataTable>

При наличии слота filter он отвечает за отображение всех фильтров. Если нужна полностью отдельная кнопка или собственный toolbar, можно напрямую использовать UiDataTableFilters.

Server-side режим ​

Каждый этап можно отключить независимо:

vue
<UiDataTable
  v-model:sort="query.sort"
  v-model:filters="query.filters"
  v-model:page="query.page"
  :rows="response.items"
  :columns="columns"
  :total="response.total"
  :page-size="25"
  :loading="pending"
  manual-sorting
  manual-filtering
  manual-pagination
  @sort-change="load"
  @filters-change="load"
  @page-change="load"
/>
  • manualSorting отключает локальную сортировку.
  • manualFiltering отключает локальные predicates.
  • manualPagination считает, что rows уже содержат нужную страницу.
  • total задаёт полное число строк для pagination.
  • Для cursor/offset API вместо manualPagination можно использовать lazy.

Флаги можно комбинировать: например, оставить локальную сортировку, но загружать фильтры и страницы с API.

Слоты ​

СлотДанные
toolbarrows, total, filters, sort
header-{key}column, sort, toggleSort
cell-{key}row, column, value, rowIndex
filtercolumn, value, setValue, filters
loading—
loading-moreloading, loadMore
emptyfilters
footerrows, total, page

События ​

  • update:sort, sort-change
  • update:filters, filters-change
  • update:page, page-change
  • row-click
  • row-dblclick

update:* предназначены для v-model, а *-change удобны для загрузки данных с сервера.

Props ​

PropТипDefaultОбязательный
rowsreadonly TRow[]—Да
columnsreadonly UiDataTableColumn<TRow>[]—Да
rowKeyUiDataTableRowKey<TRow>—Нет
sortUiDataTableSort | nullnullНет
filtersUiDataTableFilterValues{}Нет
pagenumber1Нет
pageSizenumber10Нет
totalnumber—Нет
paginationbooleantrueНет
manualSortingbooleanfalseНет
manualFilteringbooleanfalseНет
manualPaginationbooleanfalseНет
loadingbooleanfalseНет
stripedbooleanfalseНет
hoverablebooleantrueНет
stickyHeaderbooleanfalseНет
densebooleanfalseНет
borderedbooleanfalseНет
maxHeightstring | number—Нет
tableMinWidthstring | number—Нет
tableLayoutUiDataTableLayout'auto'Нет
rowClassUiDataTableRowClass<TRow>—Нет
captionstring—Нет
ariaLabelstring—Нет
filterablebooleantrueНет
filterDialogTeleportbooleantrueНет
lazybooleanfalseНет
hasMorebooleantrueНет
loadingMorebooleanfalseНет
lazyThresholdnumber96Нет
labelsPartial<UiDataTableLabels>—Нет

Настройка внешнего вида ​

css
.users-table {
  --cui-data-table-radius: 16px;
  --cui-data-table-background: #ffffff;
  --cui-data-table-header-background: #f8fafc;
  --cui-data-table-row-hover-background: #f1f5f9;
  --cui-data-table-row-striped-background: #fafafa;
  --cui-data-table-border-color: #e2e8f0;
  --cui-data-table-sort-color: #7c3aed;
  --cui-data-table-sticky-background: #ffffff;
  --cui-data-table-sticky-shadow-color: rgb(15 23 42 / 22%);
  --cui-data-table-cell-padding-block: 0.75rem;
  --cui-data-table-cell-padding-inline: 1rem;
  --cui-data-table-row-min-height: 3rem;
}

Токены можно задать глобально, отдельно внутри light/dark темы или только на конкретной таблице.

Доступность ​

Компонент использует нативные table, thead, th, tbody и caption. Сортируемые заголовки являются кнопками и обновляют aria-sort. Диалог фильтров удерживает фокус, закрывается по Escape и возвращает фокус на кнопку. Состояние загрузки передаётся через aria-busy, а горизонтальная прокрутка остаётся доступной с клавиатуры и touch-устройств.