Paginator
A debounced search box and page-size picker for a list your own code pages. Use it when you are driving the fetch yourself — Paginated Data Table has this built in.
'use client'
import * as React from 'react'
import { Paginator } from '@/components/custom/paginator'
export default function PaginatorDemo() {
const [search, setSearch] = React.useState('')
const [size, setSize] = React.useState(10)
return (
<div className="w-full space-y-3">
<Paginator
search={search}
size={size}
onSearchChange={(value) => setSearch(value ?? '')}
onSizeChange={setSize}
placeholder="Search payouts..."
/>
<p className="text-sm text-muted-foreground">
Would fetch <code className="font-mono">?search={search || '∅'}&size={size}</code> — the search lifts only
after you stop typing.
</p>
</div>
)
}Installation
pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/paginator.jsonnpx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/paginator.jsonyarn dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/paginator.jsonbunx --bun shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/paginator.jsonUsage
'use client'
import { Paginator } from '@/components/custom/paginator'
import { DataTable } from '@/components/custom/data-table'
export function PayoutList({ search, size, setSearch, setSize, data, isFetching }) {
return (
<DataTable
columns={columns}
data={data}
isLoading={isFetching}
toolbar={
<Paginator
search={search}
size={size}
isLoading={isFetching}
onSearchChange={(value) => setSearch(value ?? '')}
onSizeChange={setSize}
placeholder="Search payouts..."
/>
}
/>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
search | string | — | Required. Current committed term. |
size | number | — | Required. Current page size. |
onSearchChange | (value: string | null) => void | — | Fires after the debounce. null when cleared. |
onSizeChange | (value: number) => void | — | Fires immediately. |
isLoading | boolean | false | Swaps the search icon for a spinner. |
placeholder | string | 'Search...' | Search placeholder and accessible name. |
pageSizes | readonly number[] | [5, 10, 100, 500, 1000] | Options in the picker. |
debounceMs | number | 400 | Quiet period before the search fires. |
Behaviour notes
null, not '', when cleared. That lets the caller drop the query parameter entirely rather than send a blank one — plenty of back ends read search= as "match nothing" rather than "no filter".
Typing stays local. The input holds its own draft between keystrokes and lifts the value only after the debounce, so a slow parent refetch never lags the cursor.
External changes re-sync. A back button, a cleared filter or a link arriving with a query already in the URL all update the box, because it re-syncs whenever search changes from outside.