Data Table
Filters
Faceted multi-select filters above the table, with static or async options from your API.
Preview
Usage
Each filter is a multi-select dropdown next to the search input. It owns one
query parameter and stores picks comma-separated, like ?roleId=1,3. Changing a
filter returns to page one.
<ContentDataTable
id="users-table"
filters={[
{
id: 'status',
label: 'Status',
options: [
{ value: 'active', label: 'Active' },
{ value: 'banned', label: 'Banned' },
],
},
]}
columns={columns}
edges={data.edges}
pageInfo={data.pageInfo}
order={{ defaultOrder: { column: 'name', order: 'asc' } }}
/>Static options are searched on the client.
Async options
For long lists, pass onSearch instead. It runs when the dropdown opens and
400ms after typing stops. Return results already filtered and capped by the
server.
import type { FilterOption } from '@vitnode/core/components/table/filters'
import { RoleFormatContent } from '@vitnode/core/components/role-format-content'
import { fetcher } from '@vitnode/core/tanstack/fetcher'
export const searchRoles = async (search: string): Promise<FilterOption[]> => {
const res = await fetcher({
plugin: '@vitnode/core',
method: 'get',
module: 'admin/roles',
path: '/list',
args: { query: { search, first: '20' } },
withPagination: true,
})
if (res.status !== 200) return []
const data = await res.json()
return data.edges.map((role) => ({
value: String(role.id),
label: <RoleFormatContent role={role} />,
keywords: role.name.map((item) => item.name),
}))
}filters={[{ id: 'roleId', label: 'Group', onSearch: searchRoles }]}Read it in the API
A filter only writes to the URL. The API route reads the parameter and adds the
where clause:
const roleIds = (query.roleId?.split(',') ?? [])
.filter(Boolean)
.map(Number)
.filter(id => !Number.isNaN(id))
where: roleIds.length ? inArray(core_users.roleId, roleIds) : undefined,API
Prop
Type