import { View, SxProp, useSx } from 'dripsy' import { useState, useMemo, Dispatch, SetStateAction, useEffect } from 'react' import { Pressable, ScrollView, StyleSheet } from 'react-native' import { useReactTable, ColumnDef, flexRender, Row, SortingState, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, FilterMeta, filterFns, } from '@tanstack/react-table' import { RowSelector, Pagination } from './components' interface TableProps { data: TData[] columns: ColumnDef[] globalFilterState?: [string, Dispatch>] rowSelectionState?: [any, Dispatch>] containerSx?: SxProp rowSx?: SxProp headerSx?: SxProp showHeader?: boolean pageSize?: number paddingWidth?: number filterFn?: ( row: Row, columnId: string, filterValue: any, addMeta: (meta: FilterMeta) => void ) => boolean onRowPress?: (row: Row) => void checkboxContainerSx?: SxProp checkmarkSize?: number } export function Table({ data, columns, globalFilterState, rowSelectionState, containerSx, rowSx, headerSx, showHeader, pageSize, paddingWidth, filterFn, onRowPress, checkboxContainerSx, checkmarkSize, }: TableProps) { // UI state const tableData = useMemo(() => data, [data]) const tableColumns = useMemo(() => columns, [data]) const [sorting, setSorting] = useState([]) const [descSort, setDescSort] = useState(true) const [rowSelection, setRowSelection] = useState({}) const [bodyRowStyle, setBodyRowStyle] = useState(() => { const rowStyle = data.map((_) => { const style = { ...styles.bodyRow, ...rowSx } return style }) return rowStyle }) const containerStyles = { ...styles.container, ...containerSx } const [globalFilter, setGlobalFilter] = getGlobalFilterState() const displayHeader = showHeader ?? true const tablePageSize = pageSize ?? 10 const tablePaddingWidth = paddingWidth ?? 20 const filterFunction = filterFn ?? filterFns.includesString const rowPressDisabled = !onRowPress const sx = useSx() const tableOptions = { data: tableData, columns: tableColumns, initialState: { pagination: { pageSize: tablePageSize, }, }, state: { rowSelection, sorting, globalFilter, }, globalFilterFn: filterFunction, enableRowSelection: true, autoResetPageIndex: false, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onGlobalFilterChange: setGlobalFilter, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getColumnCanGlobalFilter: () => true, } const { getHeaderGroups, getRowModel, getIsAllRowsSelected, getPrePaginationRowModel, toggleAllRowsSelected, getSelectedRowModel, getAllColumns, getState, setPageIndex, setPageSize, getPageCount, } = useReactTable(tableOptions) useEffect(() => { updateSelectionState() }, [rowSelection]) // Updates row selection state function updateSelectionState() { if (!rowSelection || !rowSelectionState) return const setSelection = rowSelectionState[1] const dataRows = getSelectedRowModel().flatRows.map((row) => { return row.original }) setSelection(dataRows) } function getGlobalFilterState(): [ string, Dispatch> ] { const filterState = globalFilterState ? globalFilterState : useState('') return filterState } function onRowPressed(row: Row) { if (!onRowPress) return onRowPress(row) } function onHoverIn(rowIndex: number) { if (rowPressDisabled) return const newBodyRowStyle = bodyRowStyle.map((_: SxProp, index: number) => { if (index === rowIndex) { const style = { ...styles.bodyRowHover, ...rowSx } return style } const style = { ...styles.bodyRow, ...rowSx } return style }) setBodyRowStyle(newBodyRowStyle) } function onHoverOut() { if (rowPressDisabled) return const newBodyRowStyle = bodyRowStyle.map((sx: SxProp) => { const style = { ...styles.bodyRow, ...rowSx } return style }) setBodyRowStyle(newBodyRowStyle) } // Adds left padding to the first item of each row function getPaddingLeft(index: number) { if (index === 0) { return tablePaddingWidth } return 0 } // Adds right padding to the last item of each row function getPaddingRight(index: number) { const columns = getAllColumns() if (index === columns.length - 1) { return tablePaddingWidth } return 0 } return ( {displayHeader && ( {getHeaderGroups().map((headerGroup) => ( {rowSelectionState && ( )} {headerGroup.headers.map((header, index) => ( { header.column.toggleSorting( descSort ) setDescSort(!descSort) }} > {flexRender( header.column.columnDef.header, header.getContext() )} ))} ))} )} {rowPressDisabled ? ( {getRowModel().rows.map((row, index) => ( {rowSelectionState && ( )} {row .getVisibleCells() .map((cell, columnIndex) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} ))} ) : ( {getRowModel().rows.map((row, index) => ( onRowPressed(row)} key={row.id} onHoverIn={() => onHoverIn(row.index)} onHoverOut={() => onHoverOut()} > {rowSelectionState && ( )} {row .getVisibleCells() .map((cell, columnIndex) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} ))} )} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: 'transparent', borderRadius: 3, }, scrollView: { height: '65vh', minWidth: '100%', }, horizontalScrollContainer: { flex: 1, flexDirection: 'column', alignItems: 'flex-start', }, header: { marginBottom: '2px', minWidth: '100%', }, headerRow: { flexDirection: 'row', borderRadius: 3, backgroundColor: '$secondary.5', }, bodyRowContainer: { marginBottom: '2px', }, bodyRow: { flexDirection: 'row', borderRadius: 3, backgroundColor: '$secondary.5', }, bodyRowHover: { flexDirection: 'row', borderRadius: 3, backgroundColor: '$secondary.7', }, footer: { maxWidth: '100%', height: 50, marginTop: '4px', }, })