# useTablePagination

## Usage

Call useTablePagination with a config object containing page state and callback. Pass the returned plugin to Table via the plugins prop.

```ts
import { paginateData, useTablePagination } from '@astryx-vue/core/Table'
```

The hook draws the controls only. You slice the rows yourself: `paginateData(rows, page, pageSize)` returns the window for the current page, and the same `pageSize` has to reach both the slice and the config.

`totalItems` is what the controls count with. When you already know the page count, give `totalPages` instead, and for cursor-based data where the total is unknown, set `hasMore: true` while another page exists. The controls stay out of the way when there is only one page and nothing more to load.

```ts
const page = ref(1)
const pageSize = 5
const plugin = useTablePagination(() => ({
  page: page.value,
  onPageChange(next) {
    page.value = next
  },
  totalItems: users.length,
  pageSize,
}))
const rows = computed(() => paginateData(users, page.value, pageSize))
```

`variant` picks what sits between the previous and next buttons: page numbers, a count, a compact "page X of Y", dots, or nothing. `position` decides whether the controls render above the table, below it, on both sides, or not at all; with `both` the two navs get distinct names so screen readers can tell them apart.

The config is read again on every access, so pass a getter when the page lives in a `ref`. A plain object works too, but the controls keep the page they saw first.

## Examples

### Table — Paginated Data

Paginated data table navigating through a larger dataset page by page.

```vue
<script setup lang="ts">
import type { TableColumn } from '@astryx-vue/core/Table'
import { paginateData, proportional, Table, useTablePagination } from '@astryx-vue/core/Table'
import { computed, ref } from 'vue'

interface User extends Record<string, unknown> {
  id: string
  name: string
  email: string
  role: string
}

const names = [
  'Alice Johnson',
  'Bob Smith',
  'Charlie Brown',
  'Diana Prince',
  'Eve Davis',
  'Frank Miller',
  'Grace Lee',
  'Hank Wilson',
  'Ivy Chen',
  'Jack Turner',
  'Karen White',
  'Leo Garcia',
  'Mia Thompson',
  'Noah Martinez',
  'Olivia Clark',
  'Paul Harris',
  'Quinn Walker',
  'Rachel Adams',
  'Sam Robinson',
  'Tina Scott',
]
const roles = ['Engineer', 'Designer', 'Manager', 'Admin', 'Analyst']
const users: User[] = names.map((name, index) => ({
  id: String(index + 1),
  name,
  email: `${name.split(' ')[0]!.toLowerCase()}@example.com`,
  role: roles[index % roles.length]!,
}))

const columns: TableColumn<User>[] = [
  { key: 'name', header: 'Name', width: proportional(1) },
  { key: 'email', header: 'Email', width: proportional(2) },
  { key: 'role', header: 'Role', width: proportional(1) },
]

const page = ref(1)
const pageSize = 5
const plugin = useTablePagination<User>(() => ({
  page: page.value,
  onPageChange(next) {
    page.value = next
  },
  totalItems: users.length,
  pageSize,
}))
const rows = computed(() => paginateData(users, page.value, pageSize))
</script>

<template>
  <Table
    :data="rows"
    :columns="columns"
    id-key="id"
    :plugins="{ pagination: plugin }"
  />
</template>
```

## Parameters

`useTablePagination()` takes one config object. Wrap it in a getter or a `ref` so the controls read the current page: `useTablePagination(() => ({ ... }))`.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `page` | `number` | — | Required. Current page number, 1-based. |
| `onPageChange` | `(page: number) => void` | — | Required. Called when the page changes. |
| `totalItems` | `number` | — | Total number of items across all pages, used to calculate the page count. Without it, and without `totalPages`, the controls only know about `hasMore`. |
| `totalPages` | `number` | — | Total number of pages. Use it when you know the page count but not the item count. It wins over the count derived from `totalItems`. |
| `hasMore` | `boolean` | — | Whether more pages exist after the current one. Use it for cursor-based data where the total is unknown; the next button stays enabled while it is true. |
| `pageSize` | `number` | `10` | Number of items per page. Slice the rows with the same number. |
| `onPageSizeChange` | `(pageSize: number) => void` | — | Called when the user changes the page size. A page size selector appears only when this and `pageSizeOptions` are both set. |
| `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | Available page size options. The selector still needs `onPageSizeChange` to be shown. |
| `variant` | `'pages' \| 'count' \| 'compact' \| 'dots' \| 'none'` | `'pages'` | What the controls show between the previous and next buttons: page number buttons, a "X-Y of Z" count, a compact "page X of Y", dot indicators, or nothing. |
| `position` | `'below' \| 'above' \| 'both' \| 'none'` | `'below'` | Where the controls render relative to the table. With `'both'` there are two sets, one on each side. |
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Horizontal alignment of the controls. |
| `size` | `'sm' \| 'md'` | `'md'` | Height of the controls. |
| `label` | `string` | localized "Table pagination" | Accessible name for the pagination nav landmark. With `position: 'both'` the two navs get distinct names, the label suffixed with "(top)" and "(bottom)", so same-type landmarks stay unique. |

## Returns

`useTablePagination()` returns one `TablePlugin&lt;T&gt;`. Pass it to `Table` in `plugins`. The plugin fills two fields of the plugin contract:

| Name | Type | Description |
| --- | --- | --- |
| `above` | `() => VNodeChild` | Renders the controls above the table for `position: 'above'` and `'both'`. |
| `below` | `() => VNodeChild` | Renders the controls under the table for `position: 'below'` and `'both'`. |

Both return <code>null</code> when <code>position</code> is <code>'none'</code>, or when the table holds a single page and <code>hasMore</code> is not <code>true</code>.
