Pagination
In reviewLive · web
Numbered: ellipsis truncation, current page in cyan (tab / click / arrow keys)
Compact: “Page X of Y” with prev/next; the tone prop sets the label emphasis
neutralaccent
Prev / next only: a labelled pair, disabled at the bounds
Load more: the mobile / infinite-scroll idiom (Android + Voice lists)
Loaded 25 of 487 · 462 more
Numbered page 6 of 20 · 25 rows/page · compact page 3. The island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;// this is the canonical API the published @dizyx/nockerl-react package exposes.)// NockerlPagination owns ONLY the control row (prev + windowed cells + next). The rows-per-page// selector and the "showing X to Y of Z" summary stay with the consumer. They speak the// limit/offset/total vocabulary the apps' list endpoints already return.import { NockerlPagination } from '@dizyx/nockerl-react';
export function TaskListFooter({ total }: { total: number }) { const [page, setPage] = useState(1); const pageSize = 25; const pageCount = Math.max(1, Math.ceil(total / pageSize)); const from = (page - 1) * pageSize + 1; const to = Math.min(page * pageSize, total);
return ( <div className="task-list-footer"> <span>Showing {from} to {to} of {total}</span> <NockerlPagination page={page} pageCount={pageCount} onChange={setPage} siblings={1} /> </div> );}// Android: Jetpack Compose (canonical). The native lists DON'T use numbered// pages: NockerlApiTasks.fetchTasks pages with limit/offset and returns a total// (TaskListResponse), and the UI appends pages as you scroll. This is the real// "load more" idiom. Numbered pagination is a web/dashboard pattern (see drift).suspend fun NockerlApiClient.fetchTasks( workspaceSlug: String? = null, projectSlug: String? = null, status: String? = null, limit: Int = 500, // page size (server cap) offset: Int = 0, // (page - 1) * limit sortBy: String = "updatedAt", sortOrder: String = "desc",): ApiResult<TaskListResponse> // -> data: List<…>, total: Int
// Append-on-demand ("load more") in the list ViewModel:fun loadMore() { val next = uiState.value.items.size // current offset if (next >= uiState.value.total) return // reached the end viewModelScope.launch { val res = taskRepository.fetchTasks(offset = next, limit = PAGE) if (res is ApiResult.Success) _uiState.update { it.copy(items = it.items + res.data, total = res.total) } }}// macOS: SwiftUI (Nockerl Voice, canonical). History is an in-memory SwiftData// @Query rendered in a LazyVStack: rows materialize on scroll, so there are no// page numbers, no prev/next, and no "of N" cursor. This is the honest paging// the app ships; numbered pagination is a web/dashboard pattern (see drift).struct HistorySection: View { @Query(sort: \TranscriptionRecord.createdAt, order: .reverse) private var records: [TranscriptionRecord]
var body: some View { ScrollView { LazyVStack(spacing: 0) { // lazily paged by the scroller ForEach(records) { row($0) } } } }}Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
page * | number | Current page (1-based). | |
pageCount * | number | Total number of pages. | |
onChange * | (page: number) => void | Called with the next page (already clamped to 1..pageCount). | |
variant | NockerlPaginationVariant | 'numbered' | numbered (default) · compact · prev-next. |
siblings | number | 1 | How many page cells to show either side of the current page (numbered). |
tone | NockerlPaginationTone | 'neutral' | Color of the compact "Page X of Y" label. 'neutral' (default) keeps the WHOLE phrase in the card text color (accent-restraint law); 'accent' tints the ENTIRE phrase cyan. Never a partial tint: the whole phrase shares one color, never a lone cyan digit. No effect on the numbered / prev-next variants. |
label | string | 'NockerlPagination' | Accessible name for the <nav> landmark. |
Compose ships offset paging + load-more, not a numbered control. These are the
real fetchTasks paging parameters; a numbered bar would be new work (see drift).
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | Int | 500 | Page size. The number of rows fetched per request (server cap is 500). |
offset | Int | 0 | Rows to skip: (page - 1) * limit for an equivalent page cursor. |
status | String? | null | Server-side filter (single or comma-separated) so paging never drops active rows behind the cap. |
sortBy | String | "updatedAt" | Sort key applied before paging, so page boundaries are stable. |
sortOrder | String | "desc" | Sort direction (asc / desc). |
TaskListResponse.data | List<…> | The page of rows. Appended to the list on "load more". | |
TaskListResponse.total | Int | Total matching rows. Compared against the loaded count to know when the end is reached. |
SwiftUI (Voice) has no pagination control: a @Query + LazyVStack pages
lazily on scroll. These are the knobs that exist, listed honestly (see drift).
| Style | Type | Default | Description |
|---|---|---|---|
@Query(sort:order:) | property wrapper | Backs the list with a sorted SwiftData fetch. The whole result set is bound; no page cursor. | |
LazyVStack | View | Materializes rows only as they scroll into view: the lazy "paging" the app actually does. | |
filtered | [TranscriptionRecord] | Optional client-side search predicate applied over the query results before rendering. |