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
LacyUllrich2295complicated25
SibylSchamberger23237relationship89
HiramRuecker25503single95
VaughnFisher19566complicated95
PatRowe12642relationship81

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
MabelFay8843relationship86
UriahKessler38704relationship88
OrphaShanahan9468complicated78
AdrianHarris6569single43
BennieRowe38989relationship70
MaurineNader6700single67
ChristineKulas0847single50
AvaFisher33202relationship87
FaustoGorczany13236complicated39
CindyLockman11873single10
of row(s) selected.

Empty

Empty allows you to show a message when the table is empty. This is useful when you want to show a message when the table is empty.

PropDefaultTypeDescription
empty-textNo results.stringEmpty text.
empty-iconi-tabler-database-xstringEmpty icon.
Preview
Code
Data
First NameLast NameAgeVisitsStatusProfile Progress
No data.

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
IsmaelSpencer12523relationship2
BryanaPouros37760single15
BurniceBlick0741single54
ErlingHowell28873relationship98
EladioHegmann12490single63
RavenMoore5955relationship94
JasminBotsford2922complicated97
HaroldKassulke26989relationship65
KarlSchimmel14785complicated53
DulceConnelly25163single26

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
MichaelaStiedemann29837single86
JadynSchroeder16794relationship25
EltaKling31805complicated96
ShanelBergnaum38834single28
JovannyDaugherty2601single74
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
SantosMurray20160relationship98
VilmaGerlach-Carroll24735single24
JolieSawayn13296single42
GeovanyGerhold27918complicated76
BlakeWelch4563complicated62
RachelGrant32241relationship12
KacieBeatty17301single81
DillonMoen2170complicated54
HuldaMcGlynn6828relationship83
LelahBernier35882relationship21

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
TabithaFlatley9418relationship13
BraulioDaniel16101complicated20
GloriaLindgren17193complicated13
LylaStrosin21667relationship85
VelmaOkuneva33391relationship77

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
EleonoreSatterfield17924relationship49
TatumCummerata7772complicated84
IdellSchaden39973single41
UnaMoen29898single24
VladimirLeuschke24186relationship11
UbaldoMacGyver17219relationship22
SimoneGutmann28593relationship7
LeifLittle14311relationship92
YasmineBoehm27249single81
GayleLedner2098single38

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
MckaylaBalistreri36595complicated94
LindsayDicki37686single5
MyrtleChamplin25558complicated10
OliverGreen34950complicated14
EstevanNader38533relationship53

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
LudieOlson4794relationship86
FeltonBayer10850complicated2
FreidaWisozk2932complicated49
DorcasBernier30125relationship58
SiennaZieme18673complicated72

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
singleTristinTurner35888100
complicatedGladysBradtke4027636
singleMelisaMurray2611245
relationshipCheyenneCole133024
relationshipLondonWalter32100039

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
KaileyMitchell24844relationship65
OrphaBruen36736single4
ElinorEmmerich25396relationship72
MoseSenger845single32
JudahBashirian36379complicated88

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
complicated57WileyEffertz39992
complicated86MontyMorar18927
complicated47RaymondHayes15336
complicated59KendallPredovic33759
single38CelestineDoyle-Rempel21992

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
JadaSchuster21481single40
ArnulfoKlocko20863single88
JudgeCollier-West39969relationship90
MadelynThiel21793single44
DorianMorar10230relationship99

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
RB
Reva Beer
Ines_Franey@hotmail.com
RevaBeerrelationship
11%
CL
Catalina Lehner
Katherine.Ebert@hotmail.com
CatalinaLehnerrelationship
80%
ZK
Zoie Koss
Henri.Homenick57@gmail.com
ZoieKosscomplicated
8%
CT
Cleora Treutel-Bogan
Maynard_Haley-Marks@gmail.com
CleoraTreutel-Boganrelationship
65%
DH
Dylan Hammes
Greyson.Hodkiewicz@yahoo.com
DylanHammescomplicated
100%
GT
Gerardo Turcotte
Myron_Bahringer24@hotmail.com
GerardoTurcottesingle
68%
VV
Verner Vandervort
Virginia_Rutherford32@hotmail.com
VernerVandervortrelationship
93%
DK
Delta Kutch
Sunny36@hotmail.com
DeltaKutchsingle
26%
DL
Deanna Lockman
Lavon78@hotmail.com
DeannaLockmanrelationship
43%
CM
Clarabelle Muller
Meagan.Hintz4@hotmail.com
ClarabelleMullerrelationship
52%
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 flex-col justify-center py-10 gap-4',
  'table-empty-text': 'text-center text-wrap',
  'table-empty-icon-name': 'i-tabler-database-x size-2xl',

  // 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'>, Pick<NTableEmptyProps, 'emptyText' | 'emptyIcon'> {
  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

  /**
   * Whether the table is loading.
   */
  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 {
  [key: string]: any
  class?: HTMLAttributes['class']
  una?: Pick<NTableUnaProps, 'tableBody'>
}

export interface NTableHeadProps extends PrimitiveProps {
  [key: string]: any
  class?: HTMLAttributes['class']
  dataPinned?: 'left' | 'right' | false
  una?: Pick<NTableUnaProps, 'tableHead'>
}

export interface NTableHeaderProps extends PrimitiveProps {
  [key: string]: any
  class?: HTMLAttributes['class']
  una?: Pick<NTableUnaProps, 'tableHeader'>
}

export interface NTableFooterProps extends PrimitiveProps {
  [key: string]: any
  class?: HTMLAttributes['class']
  una?: Pick<NTableUnaProps, 'tableFooter'>
}

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

export interface NTableCellProps extends PrimitiveProps {
  [key: string]: any
  class?: HTMLAttributes['class']
  dataPinned?: 'left' | 'right' | false
  una?: Pick<NTableUnaProps, 'tableCell'>
}

export interface NTableEmptyProps {
  [key: string]: any
  class?: HTMLAttributes['class']
  colspan?: number
  /**
   * The text to display when the table is empty.
   */
  emptyText?: string
  /**
   * The icon to display when the table is empty.
   */
  emptyIcon?: string
  _tableCell?: NTableCellProps
  _tableRow?: NTableRowProps

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

export interface NTableLoadingProps {
  [key: string]: any
  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 {
  [key: string]: any
  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']
  tableLoading?: HTMLAttributes['class']
  tableLoadingRow?: HTMLAttributes['class']
  tableLoadingCell?: HTMLAttributes['class']
  tableEmpty?: HTMLAttributes['class']
  tableEmptyText?: HTMLAttributes['class']
  tableEmptyIcon?: 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,
  getSubRows: (row: any) => row.subRows,
  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
              :empty-text="props.emptyText"
              :empty-icon="props.emptyIcon"
              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>