import * as React from 'react'
import ReactDOM from 'react-dom/client'
import {
Box,
Button,
ChakraProvider,
Container,
HStack,
Input,
NativeSelect,
Stack,
Table,
Text,
defaultSystem,
} from '@chakra-ui/react'
import {
IconArrowDown,
IconArrowUp,
IconSearch,
IconSelector,
} from '@tabler/icons-react'
import {
columnFilteringFeature,
createColumnHelper,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
filterFns,
globalFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
import type { Person } from './makeData'
import './index.css'
const features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
columnFilteringFeature,
globalFilteringFeature,
sortedRowModel: createSortedRowModel(),
paginatedRowModel: createPaginatedRowModel(),
filteredRowModel: createFilteredRowModel(),
sortFns,
filterFns,
})
const columnHelper = createColumnHelper<typeof features, Person>()
function getAriaSort(sortDirection: false | 'asc' | 'desc') {
if (sortDirection === 'asc') return 'ascending'
if (sortDirection === 'desc') return 'descending'
return 'none'
}
const columns = columnHelper.columns([
columnHelper.accessor('firstName', {
header: 'First Name',
cell: (info) => info.getValue(),
}),
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
header: 'Last Name',
cell: (info) => <Box as="i">{info.getValue<string>()}</Box>,
}),
columnHelper.accessor((row) => Number(row.age), {
id: 'age',
header: 'Age',
cell: (info) => info.renderValue(),
}),
columnHelper.accessor('visits', {
header: 'Visits',
}),
columnHelper.accessor('status', {
header: 'Status',
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
}),
])
function App() {
const [data, setData] = React.useState(() => makeData(200))
const refreshData = () => setData(makeData(200))
const stressTest = () => setData(makeData(10_000))
const table = useTable(
{
debugTable: true,
features,
columns,
data,
globalFilterFn: 'includesString',
},
(state) => state,
)
return (
<Container maxW="6xl" py="8">
<Stack gap="6">
<Stack
direction={{ base: 'column', md: 'row' }}
gap="3"
align={{ base: 'stretch', md: 'center' }}
justify="space-between"
>
<Box position="relative" maxW={{ base: '100%', md: '360px' }}>
<Box
position="absolute"
left="3"
top="50%"
translateY="-50%"
color="fg.muted"
>
<IconSearch size={16} />
</Box>
<Input
value={table.state.globalFilter ?? ''}
onChange={(event) =>
table.setGlobalFilter(event.currentTarget.value)
}
placeholder="Search all columns..."
ps="9"
/>
</Box>
<HStack gap="2">
<Button variant="outline" onClick={refreshData}>
Regenerate Data
</Button>
<Button variant="outline" onClick={stressTest}>
Stress Test (10k rows)
</Button>
</HStack>
</Stack>
<Box borderWidth="1px" rounded="md" overflow="hidden">
<Table.ScrollArea minW="760px">
<Table.Root
interactive
showColumnBorder
size="sm"
variant="outline"
>
<Table.Header>
{table.getHeaderGroups().map((headerGroup) => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const sortDirection = header.column.getIsSorted()
const sortIcon =
sortDirection === 'asc' ? (
<IconArrowUp size={16} />
) : sortDirection === 'desc' ? (
<IconArrowDown size={16} />
) : (
<IconSelector size={16} opacity={0.45} />
)
return (
<Table.ColumnHeader
key={header.id}
colSpan={header.colSpan}
aria-sort={getAriaSort(sortDirection || false)}
data-sort={sortDirection || undefined}
>
{header.isPlaceholder ? null : header.column.getCanSort() ? (
<Button
variant="plain"
size="xs"
justifyContent="flex-start"
px="0"
w="100%"
onClick={header.column.getToggleSortingHandler()}
>
<HStack gap="2">
<Text fontWeight="semibold">
<table.FlexRender header={header} />
</Text>
{sortIcon}
</HStack>
</Button>
) : (
<Text fontWeight="semibold">
<table.FlexRender header={header} />
</Text>
)}
</Table.ColumnHeader>
)
})}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows.length === 0 ? (
<Table.Row>
<Table.Cell colSpan={columns.length} textAlign="center">
No results.
</Table.Cell>
</Table.Row>
) : (
table.getRowModel().rows.map((row) => (
<Table.Row key={row.id}>
{row.getAllCells().map((cell) => (
<Table.Cell key={cell.id}>
<table.FlexRender cell={cell} />
</Table.Cell>
))}
</Table.Row>
))
)}
</Table.Body>
</Table.Root>
</Table.ScrollArea>
<HStack justify="space-between" p="3">
<Text fontSize="sm" color="fg.muted">
{table.getPrePaginatedRowModel().rows.length.toLocaleString()}{' '}
rows
</Text>
<HStack gap="2">
<NativeSelect.Root size="sm" width="90px">
<NativeSelect.Field
aria-label="Rows per page"
value={String(table.state.pagination.pageSize)}
onChange={(event) => {
table.setPageSize(Number(event.currentTarget.value))
table.setPageIndex(0)
}}
>
{['10', '20', '30', '40', '50'].map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</NativeSelect.Field>
<NativeSelect.Indicator />
</NativeSelect.Root>
<Button
size="sm"
variant="ghost"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Text fontSize="sm">
{table.state.pagination.pageIndex + 1} / {table.getPageCount()}
</Text>
<Button
size="sm"
variant="ghost"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</HStack>
</HStack>
</Box>
</Stack>
</Container>
)
}
const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<ChakraProvider value={defaultSystem}>
<App />
</ChakraProvider>
</React.StrictMode>,
)