diff --git a/frontend/src/components/ui/pagination.ts b/frontend/src/components/ui/pagination.ts
index 91e8625964..b76bcd8719 100644
--- a/frontend/src/components/ui/pagination.ts
+++ b/frontend/src/components/ui/pagination.ts
@@ -41,23 +41,7 @@ export function calculatePages({
}
/**
- * Pagination
- *
- * Persists via a search param in the URL. Defaults to `page`, but can be set with the `name` attribute.
- *
- * Usage example:
- * ```ts
- *
- *
- * ```
- *
- * You can have multiple paginations on one page by setting different names:
- * ```ts
- *
- *
- *
- *
- * ```
+ * Displays navigation links for paginated content.
*
* @fires page-change {PageChangeEvent}
*/
@@ -157,6 +141,7 @@ export class Pagination extends LitElement {
];
searchParams = new SearchParamsController(this, (params) => {
+ if (this.disablePersist) return;
const page = parsePage(params.get(this.name));
if (this._page !== page) {
this.dispatchEvent(
@@ -172,6 +157,9 @@ export class Pagination extends LitElement {
@state()
private _page = 1;
+ /**
+ * Current page
+ */
@property({ type: Number })
set page(page: number) {
if (page !== this._page) {
@@ -184,18 +172,41 @@ export class Pagination extends LitElement {
return this._page;
}
+ /**
+ * Name of search param in URL.
+ * You can have multiple pagination elements in one view by setting different names.
+ */
@property({ type: String })
name = "page";
+ /**
+ * Total number of items in the collection.
+ */
@property({ type: Number })
totalCount = 0;
+ /**
+ * Size of the paginated set
+ *
+ * @TODO Rename to `pageSize` as to not confuse with Shoelace `size`
+ */
@property({ type: Number })
size = 10;
+ /**
+ * Display pagination as minimally functional controls (previous, current, and next)
+ *
+ * @TODO Switch to a more standard attribute
+ */
@property({ type: Boolean })
compact = false;
+ /**
+ * Disable persisting current page in the URL
+ */
+ @property({ type: Boolean })
+ disablePersist = false;
+
@state()
private inputValue = "";
@@ -207,18 +218,22 @@ export class Pagination extends LitElement {
super.connectedCallback();
}
- async willUpdate(changedProperties: PropertyValues) {
+ async willUpdate(
+ changedProperties: PropertyValues & Map,
+ ) {
if (changedProperties.has("totalCount") || changedProperties.has("size")) {
this.calculatePages();
}
- const parsedPage = parseFloat(
- this.searchParams.searchParams.get(this.name) ?? "1",
- );
- if (parsedPage != this._page) {
- const page = parsePage(this.searchParams.searchParams.get(this.name));
- const constrainedPage = Math.max(1, Math.min(this.pages, page));
- this.onPageChange(constrainedPage, { dispatch: false });
+ if (!this.disablePersist) {
+ const parsedPage = parseFloat(
+ this.searchParams.searchParams.get(this.name) ?? "1",
+ );
+ if (parsedPage != this._page) {
+ const page = parsePage(this.searchParams.searchParams.get(this.name));
+ const constrainedPage = Math.max(1, Math.min(this.pages, page));
+ this.onPageChange(constrainedPage, { dispatch: false });
+ }
}
// if page is out of bounds, clamp it & dispatch an event to re-fetch data
@@ -230,8 +245,8 @@ export class Pagination extends LitElement {
this.onPageChange(constrainedPage, { dispatch: true });
}
- if (changedProperties.get("page") && this._page) {
- this.inputValue = `${this._page}`;
+ if (changedProperties.get("_page")) {
+ this.inputValue = `${this.page}`;
}
}
@@ -407,10 +422,14 @@ export class Pagination extends LitElement {
}
private setPage(page: number) {
- if (page === 1) {
- this.searchParams.delete(this.name);
+ if (!this.disablePersist) {
+ if (page === 1) {
+ this.searchParams.delete(this.name);
+ } else {
+ this.searchParams.set(this.name, page.toString());
+ }
} else {
- this.searchParams.set(this.name, page.toString());
+ this._page = page;
}
}
diff --git a/frontend/src/stories/components/Pagination.stories.ts b/frontend/src/stories/components/Pagination.stories.ts
new file mode 100644
index 0000000000..6c19a925a1
--- /dev/null
+++ b/frontend/src/stories/components/Pagination.stories.ts
@@ -0,0 +1,51 @@
+import type { Meta, StoryObj } from "@storybook/web-components";
+import { html } from "lit";
+
+import { renderComponent, type RenderProps } from "./Pagination";
+
+const meta = {
+ title: "Components/Pagination",
+ component: "btrix-pagination",
+ tags: ["autodocs"],
+ decorators: (story) =>
+ html` ${story()}
`,
+ render: renderComponent,
+ argTypes: {
+ searchParams: { table: { disable: true } },
+ },
+ args: {
+ totalCount: 10,
+ page: 1,
+ size: 1,
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ args: {},
+};
+
+/**
+ * By default, the current page persists between page reloads via a search param in the URL.
+ * You can disable pagination persistence by setting `disablePersist`.
+ */
+export const DisablePersistence: Story = {
+ args: {
+ disablePersist: true,
+ },
+};
+
+/**
+ * Pagination can be displayed with a reduced amount of controls to fit a smaller visual space.
+ * Only the controls for the previous, current, and next page will be visible. Users can jump
+ * to a page by entering the page number in the input field for the current page.
+ *
+ * This should be used sparingly, such as for paginating secondary content in a view.
+ */
+export const Compact: Story = {
+ args: {
+ compact: true,
+ },
+};
diff --git a/frontend/src/stories/components/Pagination.ts b/frontend/src/stories/components/Pagination.ts
new file mode 100644
index 0000000000..adb071205f
--- /dev/null
+++ b/frontend/src/stories/components/Pagination.ts
@@ -0,0 +1,30 @@
+import { html } from "lit";
+import { ifDefined } from "lit/directives/if-defined.js";
+
+import type { Pagination } from "@/components/ui/pagination";
+
+import "@/components/ui/pagination";
+
+export type RenderProps = Pagination;
+
+export const renderComponent = ({
+ page,
+ name,
+ totalCount,
+ size,
+ compact,
+ disablePersist,
+}: Partial) => {
+ return html`
+
+
+ `;
+};