# Pagination

## Introduction

Pagination allows users to navigate through large datasets or content collections by dividing them into discrete pages. It provides intuitive controls for moving between pages, jumping to specific pages, and understanding the current position within the total dataset. Pagination comes in two variants: standard (page numbers) and compact (dropdown selector), making it suitable for different space constraints and use cases.

```tsx
<Stack spacing={4}>
  <Stack spacing={1}>
    <div>Standard</div>
    <Pagination {...args} variant="standard" />
  </Stack>
  <Stack spacing={1}>
    <div>Compact</div>
    <Pagination {...args} variant="compact" />
  </Stack>
</Stack>
```

| Field                        | Description | Default |
| ---------------------------- | ----------- | ------- |
| Controls resolved at runtime | —           | —       |

## Usage

```tsx
import { Pagination } from '@ceed/cds';

function DataList() {
  const [page, setPage] = useState(1);
  const pageSize = 10;
  const totalItems = 250;

  return (
    <>
      <ItemList items={items} page={page} pageSize={pageSize} />
      <Pagination
        rowCount={totalItems}
        paginationModel={{ page, pageSize }}
        onPageChange={setPage}
      />
    </>
  );
}
```

## Examples

### Playground

Both standard and compact variants displayed together.

```tsx
<Stack spacing={4}>
  <Stack spacing={1}>
    <div>Standard</div>
    <Pagination {...args} variant="standard" />
  </Stack>
  <Stack spacing={1}>
    <div>Compact</div>
    <Pagination {...args} variant="compact" />
  </Stack>
</Stack>
```

### Sizes

Pagination supports three sizes for different layouts.

```tsx
<Stack spacing={4}>
  <Stack spacing={2}>
    <div>Standard</div>
    <Pagination {...args} variant="standard" size="sm" />
    <Pagination {...args} variant="standard" size="md" />
    <Pagination {...args} variant="standard" size="lg" />
  </Stack>
  <Stack spacing={2}>
    <div>Compact</div>
    <Pagination {...args} variant="compact" size="sm" />
    <Pagination {...args} variant="compact" size="md" />
    <Pagination {...args} variant="compact" size="lg" />
  </Stack>
</Stack>
```

### Pages

Navigation through different page positions.

```tsx
<Stack spacing={4}>
  <Stack spacing={2}>
    <div>Standard</div>
    <Pagination {...args} variant="standard" defaultPaginationModel={{
      page: 1,
      pageSize: 1
    }} />
    <Pagination {...args} variant="standard" defaultPaginationModel={{
      page: 10,
      pageSize: 1
    }} />
    <Pagination {...args} variant="standard" defaultPaginationModel={{
      page: 20,
      pageSize: 1
    }} />
  </Stack>
  <Stack spacing={2}>
    <div>Compact</div>
    <Pagination {...args} variant="compact" defaultPaginationModel={{
      page: 1,
      pageSize: 1
    }} />
    <Pagination {...args} variant="compact" defaultPaginationModel={{
      page: 10,
      pageSize: 1
    }} />
    <Pagination {...args} variant="compact" defaultPaginationModel={{
      page: 20,
      pageSize: 1
    }} />
  </Stack>
</Stack>
```

### Uncontrolled

Component manages its own page state.

```tsx
<Pagination {...args} />
```

### Controlled

Parent component controls the page state.

```tsx
<Pagination {...args} paginationModel={paginationModel} onPageChange={handlePageChange} />
```

### Change Row Count

Pagination automatically adjusts when total items change.

```tsx
<Stack gap={2}>
  <Pagination {...args} rowCount={rowCount} />
  <Stack direction="row" gap={2}>
    <Button onClick={() => setRowCount(rowCount - 1)}>Decrease Row count</Button>
    <Button onClick={() => setRowCount(rowCount + 1)}>Increase Row count</Button>
  </Stack>
</Stack>
```

## When to Use

### ✅ Good Use Cases

- **Data tables**: Navigate through paginated table data
- **Search results**: Browse through multiple pages of results
- **Content listings**: Articles, products, or any list content
- **Admin panels**: Managing records across multiple pages
- **API data**: When server returns paginated responses

### ❌ When Not to Use

- **Infinite scroll**: When content loads continuously on scroll
- **Small datasets**: Less than 20 items don't need pagination
- **Single content**: Individual articles or detail views
- **Sequential flows**: Use Stepper for multi-step processes
- **Real-time data**: Frequently updating data may shift pages unpredictably

## Common Use Cases

### Data Table Pagination

```tsx
function DataTable({ columns, fetchData }) {
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(25);
  const { data, totalCount, isLoading } = useFetchData(page, pageSize);

  return (
    <Box>
      <Table columns={columns} data={data} loading={isLoading} />
      <Stack direction="row" justifyContent="space-between" mt={2}>
        <Typography level="body-sm">
          Showing {(page - 1) * pageSize + 1} - {Math.min(page * pageSize, totalCount)} of {totalCount}
        </Typography>
        <Pagination
          rowCount={totalCount}
          paginationModel={{ page, pageSize }}
          onPageChange={setPage}
        />
      </Stack>
    </Box>
  );
}
```

### Search Results

```tsx
function SearchResults({ query }) {
  const [page, setPage] = useState(1);
  const { results, totalCount } = useSearch(query, page);

  // Reset to page 1 when query changes
  useEffect(() => {
    setPage(1);
  }, [query]);

  return (
    <Stack spacing={2}>
      <Typography level="body-sm">{totalCount} results for "{query}"</Typography>
      <ResultList results={results} />
      <Pagination
        rowCount={totalCount}
        defaultPaginationModel={{ page: 1, pageSize: 10 }}
        onPageChange={setPage}
        variant="compact"
      />
    </Stack>
  );
}
```

### Gallery with Pagination

```tsx
function Gallery({ images }) {
  const pageSize = 12;
  const [currentPage, setCurrentPage] = useState(1);

  const paginatedImages = useMemo(() => {
    const start = (currentPage - 1) * pageSize;
    return images.slice(start, start + pageSize);
  }, [images, currentPage, pageSize]);

  return (
    <Box>
      <Grid container spacing={2}>
        {paginatedImages.map((image) => (
          <Grid key={image.id} xs={4}>
            <ImageCard image={image} />
          </Grid>
        ))}
      </Grid>
      <Box display="flex" justifyContent="center" mt={3}>
        <Pagination
          rowCount={images.length}
          paginationModel={{ page: currentPage, pageSize }}
          onPageChange={setCurrentPage}
        />
      </Box>
    </Box>
  );
}
```

### API Pagination

```tsx
function APIList() {
  const [paginationModel, setPaginationModel] = useState({
    page: 1,
    pageSize: 20,
  });

  const { data } = useQuery({
    queryKey: ['items', paginationModel],
    queryFn: () => fetchItems({
      page: paginationModel.page,
      limit: paginationModel.pageSize,
    }),
  });

  return (
    <Box>
      <ItemList items={data?.items || []} />
      <Pagination
        rowCount={data?.totalCount || 0}
        paginationModel={paginationModel}
        onPageChange={(newPage) =>
          setPaginationModel((prev) => ({ ...prev, page: newPage }))
        }
      />
    </Box>
  );
}
```

### Compact Pagination for Mobile

```tsx
function ResponsivePagination({ rowCount, page, onPageChange }) {
  const isMobile = useMediaQuery('(max-width: 600px)');

  return (
    <Pagination
      rowCount={rowCount}
      paginationModel={{ page, pageSize: 10 }}
      onPageChange={onPageChange}
      variant={isMobile ? 'compact' : 'standard'}
      size={isMobile ? 'sm' : 'md'}
    />
  );
}
```

## Props and Customization

### Key Props

| Prop                     | Type                                 | Default                     | Description                  |
| ------------------------ | ------------------------------------ | --------------------------- | ---------------------------- |
| `rowCount`               | `number`                             | -                           | Total number of items        |
| `paginationModel`        | `{ page: number; pageSize: number }` | -                           | Controlled pagination state  |
| `defaultPaginationModel` | `{ page: number; pageSize: number }` | `{ page: 1, pageSize: 25 }` | Default state (uncontrolled) |
| `onPageChange`           | `(newPage: number) => void`          | -                           | Callback when page changes   |
| `variant`                | `'standard' \| 'compact'`            | `'standard'`                | Pagination style             |
| `size`                   | `'sm' \| 'md' \| 'lg'`               | `'md'`                      | Component size               |

### Variant Comparison

| Feature              | Standard     | Compact             |
| -------------------- | ------------ | ------------------- |
| Page numbers visible | Yes          | No                  |
| Quick page jump      | Click number | Dropdown select     |
| Space required       | More         | Less                |
| Best for             | Desktop      | Mobile/tight spaces |

### Controlled vs Uncontrolled

```tsx
// Uncontrolled - component manages state
<Pagination
  rowCount={100}
  defaultPaginationModel={{ page: 1, pageSize: 10 }}
  onPageChange={(page) => console.log('Page:', page)}
/>

// Controlled - you manage state
const [page, setPage] = useState(1);
<Pagination
  rowCount={100}
  paginationModel={{ page, pageSize: 10 }}
  onPageChange={setPage}
/>
```

### Page Calculation

The component automatically calculates total pages:

```tsx
// With 95 items and pageSize of 10:
// totalPages = Math.ceil(95 / 10) = 10 pages

<Pagination
  rowCount={95}
  paginationModel={{ page: 1, pageSize: 10 }}
  onPageChange={handleChange}
/>
```

## Accessibility

Pagination includes built-in accessibility features:

### ARIA Attributes

- Current page marked with `aria-current="page"`
- Navigation buttons have descriptive `aria-label` attributes
- Disabled buttons properly marked with `disabled`

### Keyboard Navigation

- **Tab**: Move through pagination controls
- **Enter/Space**: Activate focused button
- **Arrow Keys**: Navigate within the compact dropdown

### Screen Reader Support

```tsx
// Buttons announce their purpose
<button aria-label="Previous page">←</button>
<button aria-label="Next page">→</button>
<button aria-label="More previous pages">...</button>
<button aria-current="page">5</button>  // "Page 5, current"
```

### Status Announcements

Consider adding live region for page changes:

```tsx
function PaginatedList() {
  const [page, setPage] = useState(1);

  return (
    <>
      <div aria-live="polite" className="sr-only">
        Page {page} of {totalPages}
      </div>
      <Pagination onPageChange={setPage} />
    </>
  );
}
```

## Best Practices

### ✅ Do

1. **Show context**: Display total items and current range

```tsx
// ✅ Good: Show pagination context
<Stack direction="row" justifyContent="space-between">
  <Typography>Showing 1-25 of 250 items</Typography>
  <Pagination rowCount={250} />
</Stack>
```

2. **Start from page 1**: Users expect 1-based pagination

```tsx
// ✅ Good: 1-based pagination
defaultPaginationModel={{ page: 1, pageSize: 25 }}
```

3. **Handle empty states**: Show appropriate UI when there's no data

```tsx
{totalCount > 0 ? (
  <Pagination rowCount={totalCount} />
) : (
  <Typography>No results found</Typography>
)}
```

4. **Reset on filter changes**: Return to page 1 when filters change

```tsx
useEffect(() => {
  setPage(1);
}, [filters]);
```

### ❌ Don't

1. **Don't use for very small datasets**: Pagination adds complexity

```tsx
// ❌ Bad: Pagination for 5 items
<Pagination rowCount={5} />

// ✅ Good: Just show all items
<ItemList items={items} />
```

2. **Don't hide the page count**: Users need to know how many pages exist

3. **Don't use inconsistent page sizes**: Keep pageSize consistent in session

4. **Don't forget loading states**: Show loading while fetching page data

```tsx
// ✅ Good: Handle loading
<Pagination disabled={isLoading} />
```

## Performance Considerations

### Server-Side Pagination

For large datasets, paginate on the server:

```tsx
function ServerPaginatedList() {
  const [page, setPage] = useState(1);
  const pageSize = 25;

  const { data, isLoading } = useQuery({
    queryKey: ['items', page, pageSize],
    queryFn: () => api.getItems({ page, pageSize }),
    keepPreviousData: true, // Keep old data while loading
  });

  return (
    <>
      <ItemList items={data?.items} loading={isLoading} />
      <Pagination
        rowCount={data?.totalCount || 0}
        paginationModel={{ page, pageSize }}
        onPageChange={setPage}
      />
    </>
  );
}
```

### Memoize Handlers

When used in complex components, memoize the change handler:

```tsx
const handlePageChange = useCallback((newPage) => {
  setPage(newPage);
}, []);
```

### Virtualization Alternative

For very large datasets, consider virtualization instead of pagination:

```tsx
// Instead of pagination for 10,000+ items:
<VirtualizedList items={items} itemHeight={50} />
```

### URL Sync

For shareable pagination state, sync with URL:

```tsx
function URLPaginatedList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const page = parseInt(searchParams.get('page') || '1', 10);

  const handlePageChange = (newPage) => {
    setSearchParams({ page: String(newPage) });
  };

  return (
    <Pagination
      rowCount={totalCount}
      paginationModel={{ page, pageSize: 25 }}
      onPageChange={handlePageChange}
    />
  );
}
```

Pagination is essential for managing large datasets in admin interfaces. Choose between standard and compact variants based on available space and user needs, ensuring a smooth navigation experience through your data.
