import { Checkbox } from '@/components/ui/checkbox' import { valueUpdater } from '@/lib/utils' import type { ColumnDef, ColumnFiltersState, PaginationState, RowData, RowSelectionState, SortingState, TableOptions, } from '@tanstack/vue-table' import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useVueTable, } from '@tanstack/vue-table' import type { MaybeRefOrGetter } from 'vue' import { computed, h, reactive, ref, toValue, type Ref } from 'vue' export interface UseDataTableOptions { data: MaybeRefOrGetter columns: MaybeRefOrGetter[]> pageSize?: number manualPagination?: boolean manualSorting?: boolean getRowId?: TableOptions['getRowId'] /** * Whether to enable row selection (will automatically add selection column) * @default false */ enableRowSelection?: boolean } export interface UseDataTableReturn { /** TanStack Table instance providing all table operations */ table: ReturnType> /** Array of selected row data (reactive) */ selectedRows: Ref /** Array of selected row IDs (reactive, requires getRowId) */ selectedRowIds: Ref } /** * Create selection column (implemented according to shadcn-vue official docs) * Reference: https://www.shadcn-vue.com/docs/components/data-table.html#row-selection * * Key: reka-ui Checkbox uses modelValue/onUpdate:modelValue instead of checked/onUpdate:checked */ function createSelectColumn(): ColumnDef { return { id: 'select', enableSorting: false, enableHiding: false, header: ({ table }) => { return h(Checkbox, { modelValue: table.getIsAllPageRowsSelected(), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value), ariaLabel: 'Select all', class: 'translate-y-0.5', }) }, cell: ({ row }) => { return h(Checkbox, { modelValue: row.getIsSelected(), 'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value), ariaLabel: 'Select row', class: 'translate-y-0.5', }) }, size: 48, } } export function useDataTable(options: UseDataTableOptions): UseDataTableReturn { const sorting = ref([]) const columnFilters = ref([]) const rowSelection = ref({}) const pagination = ref({ pageIndex: 0, pageSize: options.pageSize ?? 10, }) const dataRef = computed(() => toValue(options.data)) // If row selection is enabled, automatically prepend selection column to column definitions const columnsRef = computed(() => { const baseColumns = toValue(options.columns) if (options.enableRowSelection) { return [createSelectColumn(), ...baseColumns] } return baseColumns }) const table = useVueTable({ data: dataRef, get columns() { return columnsRef.value }, state: reactive({ get sorting() { return sorting.value }, get columnFilters() { return columnFilters.value }, get rowSelection() { return rowSelection.value }, get pagination() { return pagination.value }, }), enableSorting: !options.manualSorting, enableRowSelection: options.enableRowSelection ?? false, manualSorting: options.manualSorting ?? false, manualPagination: options.manualPagination ?? false, getRowId: options.getRowId, onSortingChange: updater => valueUpdater(updater, sorting), onColumnFiltersChange: updater => valueUpdater(updater, columnFilters), onRowSelectionChange: updater => valueUpdater(updater, rowSelection), onPaginationChange: updater => valueUpdater(updater, pagination), getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: options.manualPagination ? undefined : getPaginationRowModel(), }) // Compute selected row data (reactive) const selectedRows = computed(() => { return table.getSelectedRowModel().rows.map(row => row.original) }) // Compute selected row IDs (reactive) const selectedRowIds = computed(() => { return table.getSelectedRowModel().rows.map(row => row.id) }) return { table, selectedRows, selectedRowIds, } }