Table

A powerful, responsive table and datagrids built using Tanstack

Examples

Basic

PropDefaultTypeDescription
columns[]arrayTable columns.
data[]arrayTable data.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
TurnerBahringer19404relationship18
MitchelHauck-Grimes2410single70
UrsulaBechtelar4506single73
MakenzieFeest27734single44
AnnetteTowne23107single90

Row Selection

Row selection allows you to select rows in the table. This is useful when you want to select rows in the table.

PropDefaultTypeDescription
rowSelection-objectSelected row state, can be binded with v-model.
enableRowSelectionfalsebooleanEnable row selection.
enableMultiRowSelectiontruebooleanEnable multiple row selection.
rowIdidstringRow id to uniquely identify each row.
enableSubRowSelectionfalsebooleanEnable sub row selection.
@select-event, rowEmitted when a row is selected.
@select-all-event, rowsEmitted when all rows are selected.
@row-event, rowEmitted when a row is clicked.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
JakeMayer8399complicated45
EinoBalistreri3916single90
WandaJacobs7250single78
UrsulaSchamberger18139complicated29
BradenSchulist2492complicated26
ElsieHand35743complicated81
LaviniaFerry13980relationship42
LavonHyatt29380complicated18
FeltonVon0515single1
ArloGleason30469complicated91
of row(s) selected.

Loading

Loading allows you to show a loading progress indicator in the table. This is useful when you want to show a loading progress indicator in the table.

PropDefaultTypeDescription
loadingfalsebooleanLoading state.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
MyrticeToy3257single61
KoryWilkinson1730relationship60
KayaWeissnat23431relationship27
MakaylaCollier32602complicated13
MargaritaMcCullough19677relationship67
VioletteGerhold32728complicated36
ElmiraHeidenreich14209complicated8
FloyMarvin27680single35
MyrtleMueller17978complicated78
DarrenReilly33920single100

Pagination

Pagination allows you to paginate rows in the table. This is useful when you want to paginate rows in the table.

PropDefaultTypeDescription
pagination{pageIndex: 0, pageSize: 10}{pageIndex: Number, pageSize: Number}Pagination state, can be binded with v-model.
manualPaginationfalsebooleanEnable manual pagination, ideal for server-side pagination.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
SierraRunolfsson1999complicated96
EusebioTowne26248relationship60
ZoeMoen16358relationship95
GayleKrajcik1286single84
EllieHackett28386relationship9
Page 1 of

Sorting

Sorting allows you to sort columns in ascending or descending order. This is useful when you want to sort columns in the table.

PropDefaultTypeDescription
sorting-arraySorting state, can be binded with v-model.
enableMultiSort-booleanEnable multi-column sorting
enableSorting-booleanEnable all column sorting
column.enableSorting-booleanEnable specific column sorting
enableSortingRemovaltruebooleanEnables the ability to remove sorting for the table.
Preview
Code
Data
Status
CorrineVolkman34437single4
EmmieVonRueden21206complicated2
VivienneTromp-Lemke18556complicated49
MinnieKling24334relationship33
KennedyHermiston24476relationship29
JulianSchowalter1468complicated76
MekhiKautzer17785complicated12
SusanaWeber32542complicated100
JulienJakubowski2507relationship31
JasperHauck40402complicated79

Visibility

Visibility allows you to show or hide columns in the table. This is useful when you want to show or hide columns in the table.

PropDefaultTypeDescription
columnVisibility-objectColumn visibility state, can be binded with v-model.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
FatimaZiemann29412single27
TracyMitchell3144single51
DanielleBlanda40249relationship4
AlexandrineRunte21500complicated90
LitzyBechtelar29997relationship10

Global Filtering

Global filtering allows you to filter rows based on the value entered in the filter input. This is useful when you want to filter rows in the table.

PropDefaultTypeDescription
globalFilter-stringGlobal filter state, can be binded with v-model.
manualFiltering-booleanEnable manual filtering. Ideal for server-side filtering.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
BuddySwift-Schimmel14401single88
AlysaHeller-Mraz32536complicated79
ChynaCrist7867relationship4
LeeVonRueden-Beahan21388complicated59
AmericoFahey20672complicated61
LuellaKlein13897complicated70
JosephO'Keefe17201single14
PascaleCollier12607relationship55
MargieEmmerich15882complicated48
AdelleWeimann39162single54

Column Filtering

Column filtering allows you to filter columns based on the value entered in the filter input. This is useful when you want to filter columns in the table.

PropDefaultTypeDescription
columnFilters-arrayColumn filter state, can be binded with v-model.
enableColumnFilter-booleanEnable all column filtering
column.enableColumnFilter-booleanEnable specific column filtering
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
DulcePaucek11349complicated72
HailieHuel40167single45
DasiaBaumbach20852single5
FredyReinger33224single71
KristopherWolf22622complicated59

Column Ordering

Column ordering allows you to reorder columns by dragging and dropping them. This is useful when you want to change the order of columns in the table.

PropDefaultTypeDescription
columnOrder-arrayColumn order state, can be binded with v-model.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
EzraBreitenberg35649complicated62
ZariaConn7292complicated85
BonnieTreutel0510single65
VivianeGibson37358complicated9
RicoO'Kon27113complicated92

Column Pinning

Column pinning allows you to pin columns to the left or right of the table. This is useful when you have a large number of columns and you want to keep some columns in view while scrolling.

PropDefaultTypeDescription
columnPinning-{ left: array, right: array }Column pinning state, can be binded with v-model.
Preview
Code
Data
StatusFirst NameLast NameAgeVisitsProfile Progress
singleZoeySauer3139788
singleMartaSchoen769079
relationshipAnnabelHansen3496995
singleCarolynReynolds751114
relationshipElliotBartell85928

Expanding

Expanding allows you to expand rows to show additional information. This is useful when you want to show additional information about a row.

PropDefaultTypeDescription
expanded-arrayExpanded state, can be binded with v-model.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
EmmettHamill307single47
MadgeBalistreri28521complicated15
AmeliaJacobson10193complicated54
GeovanyAltenwerth8765relationship66
BethConsidine21286complicated3

Grouping

Grouping allows you to group rows based on a column value. This is useful when you want to group rows in the table.

PropDefaultTypeDescription
grouping-arrayGrouping state, can be binded with v-model.
manualGrouping-booleanEnable manual grouping.
Preview
Code
Data
InfoNameInfo
StatusProgressFirst NameLast NameAgeVisits
relationship30DallasKautzer15631
relationship71LuraWalter14505
single20AndreanneHarvey21994
single9DorianCarter33328
complicated29CrawfordPfannerstill11171

Server-side

Allows you to fetch data from the server. This is useful when you want to fetch data from the server.

Preview
Code
Data
NameUrl
bulbasaurhttps://pokeapi.co/api/v2/pokemon/1/
ivysaurhttps://pokeapi.co/api/v2/pokemon/2/
venusaurhttps://pokeapi.co/api/v2/pokemon/3/
charmanderhttps://pokeapi.co/api/v2/pokemon/4/
charmeleonhttps://pokeapi.co/api/v2/pokemon/5/
Page 1 of 261

Customization

Configure the progress using the una prop and utility classes.

PropDefaultTypeDescription
columns.meta.una{}objectColumn Una meta data.
una{}objectGlobal Una attribute.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
JadonOberbrunner20366complicated19
DarylDickens33684complicated28
AnselRempel16824single91
DestineyHoppe7742relationship52
CamillaDaugherty23411relationship87

Slots

NamePropsDescription
{column}-filtercolumnColumn filter slot.
{column}-headercolumnColumn header slot.
{column}-cellcellColumn cell slot.
{column}-footercolumnColumn footer slot.
headertableHeader slot.
bodytableBody slot.
rawrowRow slot.
footertableFooter slot.
expandedrowExpanded slot.
empty-Empty slot.
loading-Loading slot.
Preview
Code
Data
Account
AH
Amya Huel
Alyce_Pouros68@gmail.com
AmyaHuelrelationship
6%
PM
Patrick Miller
Jody_Adams@hotmail.com
PatrickMillersingle
77%
MJ
Monique Johns
Maida.Kuhic@gmail.com
MoniqueJohnsrelationship
83%
YW
Yvette Windler
Brionna_Heaney@hotmail.com
YvetteWindlerrelationship
27%
MH
Molly Hagenes
Kamryn47@gmail.com
MollyHagenessingle
51%
NH
Nakia Hand
Lorenzo_Wunsch13@hotmail.com
NakiaHandcomplicated
65%
HB
Holden Beatty
Oceane_Runte@gmail.com
HoldenBeattysingle
34%
CK
Cheyenne Kub
Abagail.Zulauf@gmail.com
CheyenneKubrelationship
26%
MW
Myrna Will
Deborah.Romaguera43@yahoo.com
MyrnaWillsingle
57%
KO
Kellen Orn
Braulio1@yahoo.com
KellenOrncomplicated
83%
Page 1 of

Presets

shortcuts/table.ts
type TablePrefix = 'table'

export const staticTable: Record<`${TablePrefix}-${string}` | TablePrefix, string> = {
  // config
  'table-default-variant': 'table-solid-gray',

  // table-root
  'table-root': 'relative w-full overflow-x-auto overflow-y-hidden border border-base rounded-md',
  'table': 'w-full caption-bottom text-sm',
  'table-body': '[&_tr:last-child]:border-0',
  'table-caption': 'mt-4 text-sm text-muted',

  // table-head
  'table-head': 'h-12 px-4 text-left align-middle font-medium text-muted [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-0.5',
  'table-head-pinned': 'sticky bg-base',
  'table-head-pinned-left': 'left-0',
  'table-head-pinned-right': 'right-0',

  // table-header
  'table-header': '[&_tr]:border-b [&_tr]:border-base',

  // table-row
  'table-row': 'border-b border-base transition-colors hover:bg-muted data-[filter=true]:hover:bg-base data-[state=selected]:bg-muted',

  // table-cell
  'table-cell': 'p-4 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-0.5',
  'table-cell-pinned': 'sticky bg-base',
  'table-cell-pinned-left': 'left-0',
  'table-cell-pinned-right': 'right-0',

  // table-empty
  'table-empty-row': '',
  'table-empty-cell': 'p-4 whitespace-nowrap align-middle text-sm text-muted bg-base',
  'table-empty': 'flex items-center justify-center py-10',

  // table-loading
  'table-loading-icon': 'animate-spin text-lg', // TODO: to add
  'table-loading-icon-name': 'i-lucide-refresh-ccw', // TODO: to add
  'table-loading-row': 'data-[loading=true]:border-0 absolute inset-x-0 -mt-1.5px',
  'table-loading-cell': '',
  'table-loading': 'absolute inset-x-0 overflow-hidden p-0',

  // table-footer
  'table-footer': 'border-t border-base bg-muted font-medium [&>tr]:last:border-b-0',
}

export const dynamicTable: [RegExp, (params: RegExpExecArray) => string][] = [
]

export const table = [
  ...dynamicTable,
  staticTable,
]

Props

types/table.ts
import type {
  ColumnDef,
  CoreOptions,
  GroupColumnDef,
} from '@tanstack/vue-table'
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { NProgressProps } from './progress'
import type { NScrollAreaProps, NScrollAreaUnaProps } from './scroll-area'

export interface NTableProps<TData, TValue> extends Omit<CoreOptions<TData>, 'data' | 'columns' | 'getCoreRowModel' | 'state' | 'onStateChange' | 'renderFallbackValue'> {
  class?: HTMLAttributes['class']
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#state
   */
  state?: CoreOptions<TData>['state']
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#onstatechange
   */
  onStateChange?: CoreOptions<TData>['onStateChange']
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#renderfallbackvalue
   */
  renderFallbackValue?: CoreOptions<TData>['renderFallbackValue']
  /**
   * @see https://tanstack.com/table/latest/docs/guide/data
   */
  data: TData[]
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/column
   */
  columns: ColumnDef<TData, TValue>[] | GroupColumnDef<TData, TValue>[]
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#getrowid
   */
  rowId?: string
  /**
   * @see https://tanstack.com/table/latest/docs/api/core/table#autoresetall
   */
  autoResetAll?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/row-selection#enablerowselection
   */
  enableRowSelection?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/row-selection#enablemultirowselection
   */
  enableMultiRowSelection?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/row-selection#enablesubrowselection
   */
  enableSubRowSelection?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/column-filtering#enablecolumnfilters
   */
  enableColumnFilters?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/column-filtering#manualfiltering
   */
  manualFiltering?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablesorting
   */
  enableSorting?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablemultisort
   */
  enableMultiSort?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablemultiremove
   */
  enableMultiRemove?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#enablesortingremoval
   */
  enableSortingRemoval?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#manualsorting
   */
  manualSorting?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#maxmultisortcolcount
   */

  maxMultiSortColCount?: number
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#manualpagination
   */
  manualPagination?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#pagecount
   */
  pageCount?: number
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#rowcount
   */
  rowCount?: number
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/pagination#autoresetpageindex
   */
  autoResetPageIndex?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#sortingfns
   */
  sortingFns?: Record<string, (a: any, b: any) => number>
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#sortdescfirst-1
   */
  sortDescFirst?: boolean
  /**
   * @see https://tanstack.com/table/latest/docs/api/features/sorting#ismultisortevent
   */
  isMultiSortEvent?: (e: unknown) => boolean

  // sub-components props
  _tableHead?: NTableHeadProps
  _tableHeader?: NTableHeaderProps
  _tableFooter?: NTableFooterProps
  _tableBody?: NTableBodyProps
  _tableCaption?: NTableCaptionProps
  _tableRow?: NTableRowProps | ((row?: TData) => NTableRowProps)
  _tableCell?: NTableCellProps
  _tableEmpty?: NTableEmptyProps
  _tableLoading?: NTableLoadingProps
  _scrollArea?: NScrollAreaProps

  loading?: boolean

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/table.ts
   */
  una?: NTableUnaProps & NScrollAreaUnaProps
}

export interface NTableBodyProps extends PrimitiveProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableBody'>
}

export interface NTableHeadProps extends PrimitiveProps {
  class?: HTMLAttributes['class']

  dataPinned?: 'left' | 'right' | false

  una?: Pick<NTableUnaProps, 'tableHead'>
}

export interface NTableHeaderProps extends PrimitiveProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableHeader'>
}

export interface NTableFooterProps extends PrimitiveProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableFooter'>
}

export interface NTableRowProps extends PrimitiveProps {
  class?: HTMLAttributes['class']
  una?: Pick<NTableUnaProps, 'tableRow'>
}

export interface NTableCellProps extends PrimitiveProps {
  class?: HTMLAttributes['class']

  dataPinned?: 'left' | 'right' | false

  una?: Pick<NTableUnaProps, 'tableCell'>
}

export interface NTableEmptyProps {
  class?: HTMLAttributes['class']
  colspan?: number

  _tableCell?: NTableCellProps
  _tableRow?: NTableRowProps

  una?: Pick<NTableUnaProps, 'tableEmpty' | 'tableRow' | 'tableCell'>
}

export interface NTableLoadingProps {
  size?: HTMLAttributes['class']
  enabled?: boolean
  class?: HTMLAttributes['class']
  colspan?: number

  _tableCell?: NTableCellProps
  _tableRow?: NTableRowProps
  _tableProgress?: NProgressProps

  una?: Pick<NTableUnaProps, 'tableLoading' | 'tableLoadingCell' | 'tableLoadingRow'>
}

export interface NTableCaptionProps extends PrimitiveProps {
  class?: HTMLAttributes['class']

  una?: Pick<NTableUnaProps, 'tableCaption'>
}

interface NTableUnaProps {
  table?: HTMLAttributes['class']
  tableRoot?: HTMLAttributes['class']
  tableBody?: HTMLAttributes['class']
  tableHead?: HTMLAttributes['class']
  tableHeader?: HTMLAttributes['class']
  tableFooter?: HTMLAttributes['class']
  tableRow?: HTMLAttributes['class']
  tableCell?: HTMLAttributes['class']
  tableCaption?: HTMLAttributes['class']
  tableEmpty?: HTMLAttributes['class']
  tableLoading?: HTMLAttributes['class']
  tableLoadingRow?: HTMLAttributes['class']
  tableLoadingCell?: HTMLAttributes['class']
}

Components

Table.vue
TableRoot.vue
TableHeader.vue
TableHead.vue
TableBody.vue
TableFooter.vue
TableCell.vue
TableRow.vue
TableEmpty.vue
TableLoading.vue
TableCaption.vue
<script setup lang="ts" generic="TData, TValue">
import type {
  ColumnFiltersState,
  ColumnOrderState,
  ColumnPinningState,
  ExpandedState,
  GroupingState,
  Header,
  PaginationState,
  Row,
  RowSelectionState,
  SortingState,
  Table,
  VisibilityState,
} from '@tanstack/vue-table'
import type { NTableProps } from '../../../types'

import {
  FlexRender,
  getCoreRowModel,
  getExpandedRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useVueTable,
} from '@tanstack/vue-table'

import { computed, h } from 'vue'

import { cn, valueUpdater } from '../../../utils'
import Button from '../../elements/Button.vue'
import Checkbox from '../../forms/Checkbox.vue'
import Input from '../../forms/Input.vue'
import ScrollArea from '../../scroll-area/ScrollArea.vue'
import TableBody from './TableBody.vue'
import TableCell from './TableCell.vue'
import TableEmpty from './TableEmpty.vue'
import TableFooter from './TableFooter.vue'
import TableHead from './TableHead.vue'
import TableHeader from './TableHeader.vue'
import TableLoading from './TableLoading.vue'
import TableRow from './TableRow.vue'

const props = withDefaults(defineProps <NTableProps<TData, TValue>>(), {
  enableMultiRowSelection: true,
  enableSortingRemoval: true,
})

const emit = defineEmits<{
  select: [row: TData]
  selectAll: [rows: TData[]]
  expand: [row: TData]
  row: [event: Event, row: TData]
}>()

const slots = defineSlots()

const rowSelection = defineModel<RowSelectionState>('rowSelection')
const sorting = defineModel<SortingState>('sorting')
const columnVisibility = defineModel<VisibilityState>('columnVisibility')
const columnFilters = defineModel<ColumnFiltersState>('columnFilters')
const globalFilter = defineModel<string>('globalFilter')
const columnOrder = defineModel<ColumnOrderState>('columnOrder')
const columnPinning = defineModel<ColumnPinningState>('columnPinning')
const expanded = defineModel<ExpandedState>('expanded')
const grouping = defineModel<GroupingState>('grouping')
const pagination = defineModel<PaginationState>('pagination', {
  default: () => ({
    pageIndex: 0,
    pageSize: 10,
  }),
})

const columnsWithMisc = computed(() => {
  let data = []

  // add selection column
  data = props.enableRowSelection
    ? [
        {
          accessorKey: 'selection',
          header: props.enableMultiRowSelection
            ? ({ table }: { table: Table<TData> }) => h(Checkbox, {
                'modelValue': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
                'onUpdate:modelValue': (value: boolean | 'indeterminate' | null) => {
                  table.toggleAllPageRowsSelected(!!value)
                  emit('selectAll', table.getRowModel().rows.map(row => row.original))
                },
                'areaLabel': 'Select all rows',
                'onClick': (event: Event) => {
                  event.stopPropagation()
                },
              })
            : '',
          cell: ({ row }: { row: Row<TData> }) => h(Checkbox, {
            'modelValue': row.getIsSelected() ?? false,
            'onUpdate:modelValue': (value: boolean | 'indeterminate' | null) => {
              row.toggleSelected(!!value)
              emit('select', row.original)
            },
            'areaLabel': 'Select row',
            'onClick': (event: Event) => {
              event.stopPropagation()
            },
          }),
          enableSorting: false,
        },
        ...props.columns,
      ]
    : props.columns

  // add expanded column
  data = slots.expanded
    ? [
        {
          accessorKey: 'expanded',
          header: '',
          cell: ({ row }: any) => h(Button, {
            size: 'xs',
            icon: true,
            square: true,
            btn: 'ghost-gray',
            label: 'i-radix-icons-chevron-down',
            onClick: () => {
              row.toggleExpanded()
              emit('expand', row)
            },
            una: {
              btnIconLabel: cn(
                'transform transition-transform duration-200',
                row.getIsExpanded() ? '-rotate-180' : 'rotate-0',
              ),
            },
          }),
          enableSorting: false,
          enableHiding: false,
        },
        ...data,
      ]
    : data

  return data
})

const table = useVueTable({
  get data() {
    return props.data ?? []
  },
  get columns() {
    return columnsWithMisc.value ?? []
  },
  state: {
    get sorting() { return sorting.value },
    get columnFilters() { return columnFilters.value },
    get globalFilter() { return globalFilter.value },
    get rowSelection() { return rowSelection.value },
    get columnVisibility() { return columnVisibility.value },
    get pagination() { return pagination.value },
    get columnOrder() { return columnOrder.value },
    get columnPinning() { return columnPinning.value },
    get expanded() { return expanded.value },
    get grouping() { return grouping.value },
  },

  enableMultiRowSelection: props.enableMultiRowSelection,
  enableSubRowSelection: props.enableSubRowSelection,
  autoResetAll: props.autoResetAll,
  enableRowSelection: props.enableRowSelection,
  enableColumnFilters: props.enableColumnFilters,
  manualPagination: props.manualPagination,
  manualSorting: props.manualSorting,
  manualFiltering: props.manualFiltering,
  pageCount: props.pageCount,
  rowCount: props.rowCount,
  autoResetPageIndex: props.autoResetPageIndex,
  enableSorting: props.enableSorting,
  enableSortingRemoval: props.enableSortingRemoval,
  enableMultiSort: props.enableMultiSort,
  enableMultiRemove: props.enableMultiRemove,
  maxMultiSortColCount: props.maxMultiSortColCount,
  sortingFns: props.sortingFns,
  isMultiSortEvent: props.isMultiSortEvent,

  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getRowId: (row: any) => props.rowId ? row[props.rowId] : row.id,
  getExpandedRowModel: getExpandedRowModel(),

  onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
  onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
  onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
  onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
  onGlobalFilterChange: updaterOrValue => valueUpdater(updaterOrValue, globalFilter),
  onPaginationChange: updaterOrValue => valueUpdater(updaterOrValue, pagination),
  onColumnOrderChange: updaterOrValue => valueUpdater(updaterOrValue, columnOrder),
  onColumnPinningChange: updaterOrValue => valueUpdater(updaterOrValue, columnPinning),
  onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
  onGroupingChange: updaterOrValue => valueUpdater(updaterOrValue, grouping),
})

function getHeaderColumnFiltersCount(headers: Header<unknown, unknown>[]): number {
  let count = 0
  headers.forEach((header) => {
    if (header.column.columnDef.enableColumnFilter)
      count++
  })

  return count
}

function getRowAttrs(data?: TData) {
  if (typeof props._tableRow === 'function') {
    return props._tableRow(data)
  }
  return props._tableRow
}

defineExpose({
  ...table,
})
</script>

<template>
  <div
    :class="cn('table-root', props.una?.tableRoot)"
  >
    <ScrollArea
      orientation="horizontal"
      v-bind="props._scrollArea"
      :una
    >
      <table
        v-bind="$attrs"
        :class="cn(
          'table',
          props.una?.table,
          props.class,
        )"
      >
        <!-- header -->
        <TableHeader
          :una
          v-bind="props._tableHeader"
        >
          <slot name="header" :table="table">
            <TableRow
              v-for="headerGroup in table.getHeaderGroups()"
              :key="headerGroup.id"
              :una
              v-bind="getRowAttrs()"
            >
              <!-- headers -->
              <TableHead
                v-for="header in headerGroup.headers"
                :key="header.id"
                :colspan="header.colSpan"
                :data-pinned="header.column.getIsPinned()"
                :una
                v-bind="{ ...props._tableHead, ...header.column.columnDef.meta }"
              >
                <Button
                  v-if="header.column.columnDef.enableSorting || (header.column.columnDef.enableSorting !== false && enableSorting)"
                  btn="ghost-gray"
                  size="sm"
                  class="font-normal -ml-1em"
                  :una="{
                    btnTrailing: 'text-sm',
                  }"
                  :trailing="header.column.getIsSorted() === 'asc'
                    ? 'i-lucide-arrow-up-wide-narrow' : header.column.getIsSorted() === 'desc'
                      ? 'i-lucide-arrow-down-narrow-wide' : 'i-lucide-arrow-up-down'"
                  @click="header.column.getToggleSortingHandler()?.($event)"
                >
                  <slot
                    :name="`${header.id}-header`"
                    :column="header.column"
                  >
                    <FlexRender
                      v-if="!header.isPlaceholder"
                      :render="header.column.columnDef.header"
                      :props="header.getContext()"
                    />
                  </slot>
                </Button>

                <slot
                  v-else
                  :name="`${header.id}-header`"
                  :column="header.column"
                >
                  <FlexRender
                    v-if="!header.isPlaceholder"
                    :render="header.column.columnDef.header"
                    :props="header.getContext()"
                  />
                </slot>
              </TableHead>
            </TableRow>

            <!-- column filters -->
            <template
              v-for="headerGroup in table.getHeaderGroups()"
              :key="headerGroup.id"
            >
              <TableRow
                v-if="getHeaderColumnFiltersCount(headerGroup.headers) > 0 || enableColumnFilters"
                data-filter="true"
                :una
                v-bind="getRowAttrs()"
              >
                <TableHead
                  v-for="header in headerGroup.headers"
                  :key="header.id"
                  :una
                  :colspan="header.colSpan"
                  class="font-normal"
                  :data-pinned="header.column.getIsPinned()"
                  v-bind="{ ...props._tableHead, ...header.column.columnDef.meta }"
                >
                  <slot
                    v-if="header.id !== 'selection' && ((header.column.columnDef.enableColumnFilter !== false && enableColumnFilters) || header.column.columnDef.enableColumnFilter)"
                    :name="`${header.id}-filter`"
                    :column="header.column"
                  >
                    <Input
                      class="w-auto"
                      :model-value="header.column.getFilterValue() as string"
                      :placeholder="header.column.columnDef.header"
                      @update:model-value="header.column.setFilterValue($event)"
                    />
                  </slot>
                </TableHead>
              </TableRow>
            </template>
          </slot>

          <TableLoading
            :enabled="props.loading"
            :una
            v-bind="props._tableLoading"
          >
            <slot name="loading" />
          </TableLoading>
        </TableHeader>

        <!-- body -->
        <TableBody
          :una
          v-bind="props._tableBody"
        >
          <slot name="body" :table="table">
            <template v-if="table.getRowModel().rows?.length">
              <template
                v-for="row in table.getRowModel().rows"
                :key="row.id"
              >
                <TableRow
                  :data-state="row.getIsSelected() && 'selected'"
                  :una
                  v-bind="getRowAttrs(row.original)"
                  @click="emit('row', $event, row.original)"
                >
                  <slot
                    name="row"
                    :row="row"
                  >
                    <!-- rows -->
                    <TableCell
                      v-for="cell in row.getVisibleCells()"
                      :key="cell.id"
                      :data-pinned="cell.column.getIsPinned()"
                      :una
                      v-bind="{ ...props._tableCell, ...cell.column.columnDef.meta }"
                    >
                      <slot
                        :name="`${cell.column.id}-cell`"
                        :cell="cell"
                      >
                        <FlexRender
                          :render="cell.column.columnDef.cell"
                          :props="cell.getContext()"
                        />
                      </slot>
                    </TableCell>
                  </slot>
                </TableRow>

                <!-- expanded -->
                <TableRow
                  v-if="row.getIsExpanded() && $slots.expanded"
                  :una
                  v-bind="getRowAttrs(row.original)"
                >
                  <TableCell
                    :colspan="row.getAllCells().length"
                    :una
                    v-bind="props._tableCell"
                  >
                    <slot name="expanded" :row="row" />
                  </TableCell>
                </TableRow>
              </template>
            </template>

            <TableEmpty
              v-else
              :colspan="table.getAllLeafColumns().length"
              :una
              v-bind="props._tableEmpty"
            >
              <slot name="empty" />
            </TableEmpty>
          </slot>
        </TableBody>

        <!-- footer -->
        <TableFooter
          v-if="table.getFooterGroups().length > 0"
          :una
          v-bind="props._tableFooter"
        >
          <slot name="footer" :table="table">
            <template
              v-for="footerGroup in table.getFooterGroups()"
              :key="footerGroup.id"
            >
              <TableRow
                v-if="footerGroup.headers.length > 0"
                :una
                v-bind="getRowAttrs()"
              >
                <template
                  v-for="header in footerGroup.headers"
                  :key="header.id"
                >
                  <TableHead
                    v-if="header.column.columnDef.footer"
                    :colspan="header.colSpan"
                    :una
                    v-bind="{ ...props._tableHead, ...header.column.columnDef.meta }"
                  >
                    <slot :name="`${header.id}-footer`" :column="header.column">
                      <FlexRender
                        v-if="!header.isPlaceholder"
                        :render="header.column.columnDef.footer"
                        :props="header.getContext()"
                      />
                    </slot>
                  </TableHead>
                </template>
              </TableRow>
            </template>
          </slot>
        </TableFooter>
      </table>
    </ScrollArea>
  </div>
</template>