Skip to content

Commit

Permalink
pagination component
Browse files Browse the repository at this point in the history
  • Loading branch information
KlonD90 committed Jul 26, 2024
1 parent a57f775 commit 2c5e8b1
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export * from "./toggle";
export * from "./button-icon";
export * from "./copy-button";
export * from "./menu";
export * from "./pagination";
56 changes: 56 additions & 0 deletions src/components/pagination/Pagination.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Canvas, Meta, Story, ArgsTable, Source } from "@storybook/blocks";
import { Pagination } from "./Pagination";
import { useState } from "react";
import { BUTTON_SIZE } from "../button/types";

<Meta title="Navigation/Pagination" component={Pagination} />

export const Template = ({ ...args }) => {
const [page, setPage] = useState(args.currentPage);
return (
<Pagination
{...args}
currentPage={page}
pageHandler={(p) => {
setPage(p);
}}
/>
);
};

# Pagination

<Canvas isColumn>
<Story name="Default" args={{ currentPage: 1, totalPages: 12 }}>
{Template.bind({})}
</Story>
<Story name="Mini" args={{ currentPage: 1, totalPages: 12, buttonSize: BUTTON_SIZE.mini }}>
{Template.bind({})}
</Story>
<Story name="Compact" args={{ currentPage: 1, totalPages: 12, buttonSize: BUTTON_SIZE.compact }}>
{Template.bind({})}
</Story>
<Story
name="Link"
args={{ currentPage: 1, totalPages: 12, buttonSize: BUTTON_SIZE.compact, linkMapper: (p) => `/page/${p}` }}
>
{Template.bind({})}
</Story>
</Canvas>

<ArgsTable of={Pagination} />

### Usage:

To use, import the component `Pagination` from `@nilfoundation/ui-kit`.

<Source
language="tsx"
dark
format={true}
code={`
import {Pagination} from "@nilfoundation/ui-kit";
...
<Pagination currentPage={1} totalPages={5} />
`}
/>
142 changes: 142 additions & 0 deletions src/components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useMemo } from "react";
import { BUTTON_KIND, BUTTON_SIZE, Button, ButtonProps } from "../button";
import { ChevronLeftIcon, ChevronRightIcon } from "../icons";
import { PageElementProps, PaginationProps } from "./types";
import { useStyletron } from "baseui";

export const PageElement = ({
page,
linkMapper,
pageHandler,
label,
disabled,
active,
buttonSize,
}: PageElementProps) => {
const buttonProps: ButtonProps = {
disabled,
kind: active ? BUTTON_KIND.primary : BUTTON_KIND.secondary,
size: buttonSize,
};
if (pageHandler) {
buttonProps.onClick = (e) => {
e.preventDefault();
pageHandler(page);
};
}
if (linkMapper && !disabled) {
return (
<li>
<a
href={linkMapper(page)}
onClick={(e) => {
e.preventDefault();
if (pageHandler) pageHandler(page);
}}
aria-label={`${active ? "Current Page, " : ""}Page ${page}`}
aria-current={active}
>
<Button tab-index={-1} {...buttonProps}>
{label ?? page}
</Button>
</a>
</li>
);
}
return (
<Button {...buttonProps} aria-label={`${active ? "Current Page, " : ""}Page ${page}`} aria-current={active}>
{label ?? page}
</Button>
);
};

export const Pagination = ({
currentPage,
totalPages,
visiblePages = 3,
pageHandler,
linkMapper,
buttonSize = BUTTON_SIZE.default,
}: PaginationProps) => {
const [css] = useStyletron();

const pages = useMemo(() => {
const pages = [1];
for (
let i = Math.max(currentPage - visiblePages, 2);
i <= Math.min(totalPages - 1, currentPage + visiblePages);
i++
) {
pages.push(i);
}
pages.push(totalPages);
return pages;
}, [currentPage, totalPages, visiblePages]);

const elements = useMemo(() => {
const elements = [];
let prevPage = 1;
for (const page of pages) {
if (page - prevPage > 1) {
elements.push(
<Button kind={BUTTON_KIND.tertiary} size={buttonSize} disabled>
...
</Button>
);
}
elements.push(
<li key={page} className="page-item">
<PageElement
page={page}
linkMapper={linkMapper}
pageHandler={pageHandler}
active={page === currentPage}
buttonSize={buttonSize}
/>
</li>
);
prevPage = page;
}
return elements;
}, [pages, pageHandler, linkMapper, currentPage, buttonSize]);

return (
<nav
role="navigation"
aria-label="Pagination navigation"
className={css({
display: "flex",
flexDirection: "row",
})}
>
<ul
className={css({
display: "flex",
flexDirection: "row",
listStyle: "none",
gap: "8px",
})}
>
<PageElement
key="prev"
disabled={currentPage === 1}
page={currentPage - 1}
label={<ChevronLeftIcon />}
linkMapper={linkMapper}
pageHandler={pageHandler}
buttonSize={buttonSize}
/>
{elements}
<PageElement
key="next"
disabled={currentPage === totalPages}
page={currentPage + 1}
label={<ChevronRightIcon />}
linkMapper={linkMapper}
pageHandler={pageHandler}
buttonSize={buttonSize}
/>
</ul>
</nav>
);
};
2 changes: 2 additions & 0 deletions src/components/pagination/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./Pagination";
export * from "./types";
21 changes: 21 additions & 0 deletions src/components/pagination/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ReactNode } from "react";
import { BUTTON_SIZE } from "../button";

export type PaginationProps = {
currentPage: number;
totalPages: number;
visiblePages?: number;
linkMapper?: (page: number) => string;
pageHandler?: (page: number) => void;
buttonSize?: BUTTON_SIZE;
};

export type PageElementProps = {
page: number;
label?: ReactNode;
linkMapper?: (page: number) => string;
pageHandler?: (page: number) => void;
disabled?: boolean;
active?: boolean;
buttonSize: BUTTON_SIZE;
};

0 comments on commit 2c5e8b1

Please sign in to comment.