Components
Pagination
Where a list is, and the controls that move through it. Flux hands its pager a Laravel paginator; Rask hands it a UiPaginator, and the page decides what each page shows.
Drawn and behaving as Flux UI's pagination does, measured against its live docs.
Pagination
A paginator that knows its total: the summary, and every page numbered once the pager has 640px to itself.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class PaginationExample : Component
{
private int _page = 1;
protected override Component? Render() =>
Ui.Pagination
.Paginator(new UiPaginator { Page = _page, PerPage = 5, Total = 24 })
.OnPage(page => _page = page);
}Simple paginator
No total, for a list too large to count: only Previous and Next, and HasMore says whether Next leads anywhere.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class PaginationSimpleExample : Component
{
private int _page = 1;
protected override Component? Render() =>
Ui.Pagination
.Paginator(new UiPaginator { Page = _page, HasMore = _page < 5 })
.OnPage(page => _page = page);
}Large result set
A thousand results at the default fifteen a page: the first two pages, the last two, and a window round the current one.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class PaginationLargeExample : Component
{
private int _page = 1;
protected override Component? Render() =>
Ui.Pagination
.Paginator(new UiPaginator { Page = _page, Total = 1000 })
.OnPage(page => _page = page);
}Scroll to top
ScrollTo names what to bring back into view when a page is chosen: the list being paged here.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class PaginationScrollExample : Component
{
private const int PerPage = 5;
private int _page = 1;
private int First => ((_page - 1) * PerPage) + 1;
protected override Component? Render() =>
Div[
Ol.Id("ui-pagination-rows").Class("mb-3 list-decimal space-y-1 ps-6 text-sm").Start(First)[
Enumerable.Range(First, PerPage).Select(row => Li.Key(row)[$"Order {row}"])
],
Ui.Pagination
.Paginator(new UiPaginator { Page = _page, PerPage = PerPage, Total = 40 })
.ScrollTo("#ui-pagination-rows")
.OnPage(page => _page = page)
];
}- Order 1
- Order 2
- Order 3
- Order 4
- Order 5
Pages with an address
Given Href, every page is a link to where it lives: the page goes in the query string.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class PaginationLinksExample : Component
{
protected override Component? Render() =>
Ui.Pagination
.Paginator(new UiPaginator { Page = 1, PerPage = 5, Total = 20 })
.Href(page => PageMeta.LinkTo(Routes.UiPaginationPage() with { QueryString = $"?page={page}" }));
}Rows per page
The page size is the paginator's PerPage, and the choice is a select of the page's own above the pager. A new size goes back to the first page.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class PaginationPageSizeExample : Component
{
private int _page = 1;
private int _perPage = 10;
protected override Component? Render() =>
Div.Class("space-y-3")[
Div.Class("flex items-center gap-3")[
Ui.Text["Rows per page"],
Ui.Select.Value(_perPage).OnChange(size => { _perPage = size; _page = 1; }).Class("w-24")[
Ui.SelectOption.Key(10).Value(10)["10"],
Ui.SelectOption.Key(25).Value(25)["25"],
Ui.SelectOption.Key(50).Value(50)["50"]
]
],
Ui.Pagination
.Paginator(new UiPaginator { Page = _page, PerPage = _perPage, Total = 240 })
.OnPage(page => _page = page)
];
}Rows per page
Behaviour
What a paginator knows
A UiPaginator is what a Laravel paginator tells Flux's pager: the page being shown, counted from one; how many results a page holds, PerPage, fifteen when unset, as Laravel's is; and how many there are in all, Total. With a total it is Order::paginate(). Without one it is Order::simplePaginate(), for a list too long to count, and HasMore says whether Next leads anywhere. A page past the last is held to the last.
What it numbers
A counted pager says "Showing 16 to 30 of 240 results" and draws Previous, Next and the pages between them. Below fourteen pages every page is numbered. From fourteen it numbers the first two, the last two and the current page with three either side, and writes ... where pages are left out; within seven pages of either end the window holds ten pages from that end instead, so the pager does not shrink as you approach it.
The numbers need room: the pager measures its own width (a container query, not the viewport), and under 640px draws Previous and Next alone, as 32px targets on a phone. The simple paginator has no summary and no numbers at any width.
Buttons, or links in the query string
With OnPage each page is a <button> that reports the page chosen, and the page decides what to show. With Href each is a link to where that page lives: shareable, bookmarkable, working before the runtime boots, and OnPage is not called. Given both, the link wins: the client cancels the default action of a click it dispatches, so a link that also ran a handler would stop navigating.
That is how the page goes in the query string: [QueryParam] public int Page { get; set; } = 1; on the page, and .Href(page => Routes.LogsPage(Page: page)) on the pager. A generated route, or one with { QueryString = $"?page={page}" }, is an in-app link under the app's path base, so it is right wherever the app is mounted. A string, .Href(page => $"/logs?page={page}"), is an ordinary <a> written exactly as given, with no path base added: the rule every linking component of the kit follows.
Paging on the server, and under a table
The pager pages nothing itself: ask the store for one page and for the count, and hand it the three numbers. var total = await Orders.Count(); and await Orders.OrderBy(o => o.Date).Skip((page - 1) * PerPage).Take(PerPage).ToList(), then Ui.Pagination.Paginator(new UiPaginator { Page = page, PerPage = PerPage, Total = total }).OnPage(Load).
Ui.Table.Paginate(…) takes the pager and draws it where Flux does: under the rows, outside the scroll area, full width with its rule above, and it keeps its height when the box scrolls: Ui.Table.Paginate(Ui.Pagination.Paginator(orders).ScrollTo("#orders").OnPage(Load)).Id("orders")[…].
ScrollTo brings something back into view when a page is chosen, for a pager at the foot of a long table: .ScrollTo("body") for the top of the document, .ScrollTo("#orders") for the table. It is a CSS selector, written as data-rask-scroll-to for the runtime, which scrolls on a press of any button or link inside the pager.
While the next page loads
The pager's root carries data-rask-loading="within", so the runtime marks it with data-loading once a press inside it has waited 200 ms, and takes the mark off when the page that answers it is whole. The page number that was pressed is replaced by that page's render, which is why the mark lives on the root. A table dims its rows by it; a list of your own can too, with the pager right after it: Div.Class("has-[+[data-ui-pagination][data-loading]]:opacity-50")[rows].
Rows per page
Flux's pagination has no page-size prop, so the kit has none: the size is the paginator's PerPage, and the choice is a Ui.Select of your own beside the pager, as in the example above. Go back to the first page when it changes: the page you were on may not exist any more.
What a screen reader hears
Either way the current page is not a control: it says aria-current="page". On the first page Previous is not one either, and on the last page Next: each keeps its place and says aria-disabled="true".
A counted pager labels its arrows « Previous and Next », Laravel's translation, with the character a reader hears rather than the name of its entity. The simple paginator's arrows carry no label, and its spent arrow says nothing at all; a test finds them by position.
Reference
Ui.Pagination
Where a list is, and the buttons or links that move through it.
Step | Takes | What it does |
|---|---|---|
.Paginator(…) | a UiPaginator: new() { Page = 2, Total = 240 } | Where the list is and how far it runs: Page, PerPage (15 when unset), and Total or HasMore. |
.ScrollTo(…) | string | A CSS selector brought back into view when a page is chosen: "body", "#orders". |
.OnPage(…) | a handler given the page: page => … | Runs with the page chosen, counted from one. Each page is a button. |
.Href(…) | page => a route, or a URL string | Each page's address, which makes every page a link. Wins over OnPage. |