feat(pagination): tests added

This commit is contained in:
Junior Garcia 2023-03-11 23:42:08 -03:00
parent b9980139ef
commit bfd603c3ba
9 changed files with 199 additions and 191 deletions

View File

@ -11,14 +11,14 @@ describe("Pagination", () => {
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
const ref = React.createRef<HTMLUListElement>();
render(<Pagination ref={ref} total={10} />);
expect(ref.current).not.toBeNull();
});
it("should render correctly with controls", () => {
const wrapper = render(<Pagination controls={true} total={10} />);
const wrapper = render(<Pagination showControls total={10} />);
const nextButton = wrapper.getByLabelText("next page button");
const prevButton = wrapper.getByLabelText("previous page button");
@ -27,8 +27,8 @@ describe("Pagination", () => {
expect(prevButton).not.toBeNull();
});
it("should render correctly with no controls", () => {
const wrapper = render(<Pagination controls={false} total={10} />);
it("should render correctly without controls", () => {
const wrapper = render(<Pagination total={10} />);
const nextButton = wrapper.queryByLabelText("next page button");
const prevButton = wrapper.queryByLabelText("previous page button");
@ -53,11 +53,11 @@ describe("Pagination", () => {
expect(dots).toHaveLength(2);
});
it("the pagination highlight should have a aria-hidden attribute", () => {
it("the pagination cursor should have a aria-hidden attribute", () => {
const {container} = render(<Pagination total={10} />);
const highlight = container.querySelector(".nextui-pagination-highlight");
const cursor = container.querySelector("span[aria-hidden]");
expect(highlight).toHaveAttribute("aria-hidden", "true");
expect(cursor).toHaveAttribute("aria-hidden", "true");
});
});

View File

@ -27,6 +27,7 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
onNext,
onPrevious,
setPage,
getItemRef,
getBaseProps,
getItemProps,
getCursorProps,
@ -35,28 +36,36 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
const renderItem = useCallback(
(value: PaginationItemValue, index: number) => {
const isBefore = index < range.indexOf(activePage);
const key = `${value}-${index}`;
if (renderItemProp && typeof renderItemProp === "function") {
return renderItemProp({
value,
index,
activePage,
isActive: value === activePage,
isPrevious: value === activePage - 1,
isNext: value === activePage + 1,
isFirst: value === 1,
isLast: value === total,
onNext,
onPrevious,
setPage,
ref: typeof value === "number" ? (node) => getItemRef(node, value) : undefined,
className: slots.item({class: styles?.item}),
});
}
if (value === PaginationItemType.PREV) {
return (
<PaginationItem
key={key}
className={slots.prev({
class: clsx(
styles?.prev,
!loop && activePage === 1 && "opacity-50 pointer-events-none",
),
})}
value={value}
onPress={onPrevious}
>
<ChevronIcon />
@ -66,12 +75,14 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
if (value === PaginationItemType.NEXT) {
return (
<PaginationItem
key={key}
className={slots.next({
class: clsx(
styles?.next,
!loop && activePage === total && "opacity-50 pointer-events-none",
),
})}
value={value}
onPress={onNext}
>
<ChevronIcon className="rotate-180" />
@ -82,9 +93,11 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
if (value === PaginationItemType.DOTS) {
return (
<PaginationItem
key={key}
className={slots.item({
class: clsx(styles?.item, "group"),
})}
value={value}
onPress={() =>
isBefore
? setPage(activePage - dotsJump >= 1 ? activePage - dotsJump : 1)

View File

@ -3,7 +3,7 @@ import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
import type {PressEvent} from "@react-types/shared";
import {useMemo} from "react";
import {PaginationItemType} from "@nextui-org/use-pagination";
import {PaginationItemType, PaginationItemValue} from "@nextui-org/use-pagination";
import {ringClasses} from "@nextui-org/theme";
import {clsx, dataAttr, warn} from "@nextui-org/shared-utils";
import {mergeProps} from "@react-aria/utils";
@ -19,7 +19,7 @@ export interface UsePaginationItemProps extends Omit<HTMLNextUIProps<"li">, "onC
/**
* Value of the item.
*/
value?: string | number;
value?: PaginationItemType;
/**
* Whether the item is active.
* @default false
@ -44,10 +44,10 @@ export interface UsePaginationItemProps extends Omit<HTMLNextUIProps<"li">, "onC
/**
* Function to get the aria-label of the item.
* @param page string | number
* @param page PaginationItemValue
* @returns string
*/
getAriaLabel?: (page?: string | number) => string;
getAriaLabel?: (page?: PaginationItemValue) => string;
}
const getItemAriaLabel = (page?: string | number) => {

View File

@ -14,15 +14,20 @@ import {pagination} from "@nextui-org/theme";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
export type PaginationItemRenderProps = {
export type PaginationItemRenderProps<T extends HTMLElement = HTMLElement> = {
ref?: Ref<T>;
value: PaginationItemValue;
index: number;
activePage: number;
isActive: boolean;
isFirst: boolean;
isLast: boolean;
isNext: boolean;
isPrevious: boolean;
className: string;
onNext: () => void;
onPrevious: () => void;
setPage: (page: number) => void;
};
interface Props extends Omit<HTMLNextUIProps<"ul">, "onChange"> {
@ -50,7 +55,7 @@ interface Props extends Omit<HTMLNextUIProps<"ul">, "onChange"> {
* @param props Pagination item props
* @returns ReactNode
*/
renderItem?: (props: PaginationItemRenderProps) => ReactNode;
renderItem?: <T extends HTMLElement>(props: PaginationItemRenderProps<T>) => ReactNode;
/**
* Classname or List of classes to change the styles of the element.
* if `className` is passed, it will be added to the base slot.
@ -112,7 +117,7 @@ export function usePagination(originalProps: UsePaginationProps) {
return itemsRef.current;
}
function getItemRef(node: HTMLElement, value: number) {
function getItemRef(node: HTMLElement | null, value: number) {
const map = getItemsRefMap();
if (node) {
@ -168,7 +173,7 @@ export function usePagination(originalProps: UsePaginationProps) {
}, [
activePage,
originalProps.disableAnimation,
originalProps.isEven,
originalProps.isCompact,
originalProps.disableCursor,
]);
@ -247,6 +252,7 @@ export function usePagination(originalProps: UsePaginationProps) {
total,
range,
activePage,
getItemRef,
disableCursor: originalProps.disableCursor,
disableAnimation: originalProps.disableAnimation,
setPage,

View File

@ -1,8 +1,15 @@
import React from "react";
import {ComponentStory, ComponentMeta} from "@storybook/react";
import {button, pagination} from "@nextui-org/theme";
import {button, pagination, cn} from "@nextui-org/theme";
import {ChevronIcon} from "@nextui-org/shared-icons";
import {Pagination, PaginationProps} from "../src";
import {
Pagination,
PaginationItemRenderProps,
PaginationItemType,
PaginationProps,
usePagination,
} from "../src";
export default {
title: "Components/Pagination",
@ -96,10 +103,11 @@ InitialPage.args = {
initialPage: 3,
};
export const IsEven = Template.bind({});
IsEven.args = {
export const IsCompact = Template.bind({});
IsCompact.args = {
...defaultProps,
isEven: true,
showControls: true,
isCompact: true,
};
export const Controlled = () => {
@ -133,162 +141,126 @@ export const Controlled = () => {
);
};
// import {Grid} from "@nextui-org/grid";
export const CustomItems = () => {
const renderItem = ({
ref,
value,
isActive,
onNext,
onPrevious,
setPage,
className,
}: PaginationItemRenderProps<HTMLButtonElement>) => {
if (value === PaginationItemType.NEXT) {
return (
<button className={cn(className, "bg-neutral-200")} onClick={onNext}>
<ChevronIcon className="rotate-180" />
</button>
);
}
// import {Pagination} from "../src";
if (value === PaginationItemType.PREV) {
return (
<button className={cn(className, "bg-neutral-200")} onClick={onPrevious}>
<ChevronIcon />
</button>
);
}
// export default {
// title: "Navigation/Pagination",
// component: Pagination,
// } as Meta;
if (value === PaginationItemType.DOTS) {
return <button className={className}>...</button>;
}
// export const Default = () => <Pagination initialPage={1} total={20} />;
return (
<button
ref={ref}
className={cn(
className,
isActive &&
"bg-gradient-to-b shadow-lg from-neutral-500 to-neutral-800 dark:from-neutral-300 dark:to-neutral-100 text-white font-bold",
)}
onClick={() => setPage(value)}
>
{value}
</button>
);
};
// export const Colors = () => (
// <>
// <Grid xs={12}>
// <Pagination color="primary" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination color="secondary" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination color="success" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination color="warning" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination color="error" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination color="gradient" total={10} />
// </Grid>
// </>
// );
return (
<Pagination
{...defaultProps}
disableCursor
showControls
className="gap-2"
radius="full"
renderItem={renderItem}
variant="light"
/>
);
};
// export const Sizes = () => (
// <>
// <Grid xs={12}>
// <Pagination size="xs" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination size="sm" total={5} />
// </Grid>
// <Grid xs={12}>
// <Pagination initialPage={6} size="md" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination initialPage={6} size="lg" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination initialPage={6} size="xl" total={30} />
// </Grid>
// </>
// );
export const CustomWithStyles = Template.bind({});
CustomWithStyles.args = {
...defaultProps,
showShadow: true,
styles: {
base: "gap-0 rounded border-1 border-neutral",
item: "rounded-none bg-transparent",
cursor: "rounded-sm",
},
};
// export const Rounded = () => (
// <>
// <Grid xs={12}>
// <Pagination rounded size="xs" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded size="sm" total={5} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded initialPage={6} size="md" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded initialPage={6} size="lg" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded initialPage={6} size="xl" total={30} />
// </Grid>
// </>
// );
export const CustomWithHooks = () => {
const {activePage, range, setPage, onNext, onPrevious} = usePagination({
...defaultProps,
total: 6,
showControls: true,
siblings: 10,
boundaries: 10,
});
// export const Bordered = () => (
// <>
// <Grid xs={12}>
// <Pagination bordered initialPage={1} total={20} />
// </Grid>
// <Grid xs={12}>
// <Pagination bordered rounded initialPage={1} total={20} />
// </Grid>
// </>
// );
return (
<div className="flex flex-col gap-2">
<p>Active page: {activePage}</p>
<ul className="flex gap-2">
{range.map((page) => {
if (page === PaginationItemType.NEXT) {
return (
<li key={page} aria-label="next page">
<button className="w-5 h-5 bg-neutral-200 rounded-full" onClick={onNext}>
<ChevronIcon className="rotate-180" />
</button>
</li>
);
}
// export const Shadow = () => (
// <>
// <Grid xs={12}>
// <Pagination shadow color="primary" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded shadow color="secondary" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination shadow color="success" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded shadow color="warning" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination shadow color="error" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination rounded shadow color="gradient" total={10} />
// </Grid>
// </>
// );
if (page === PaginationItemType.PREV) {
return (
<li key={page} aria-label="previous page">
<button className="w-5 h-5 bg-neutral-200 rounded-full" onClick={onPrevious}>
<ChevronIcon />
</button>
</li>
);
}
// export const OnlyDots = () => (
// <>
// <Grid xs={12}>
// <Pagination onlyDots color="primary" size="xs" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination onlyDots shadow color="secondary" size="sm" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination onlyDots color="success" size="md" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination onlyDots shadow color="warning" size="lg" total={10} />
// </Grid>
// <Grid xs={12}>
// <Pagination onlyDots color="error" size="xl" total={10} />
// </Grid>
// </>
// );
if (page === PaginationItemType.DOTS) {
return <li key={page}>...</li>;
}
// export const Loop = () => (
// <>
// <Grid xs={12}>
// <Pagination loop initialPage={1} total={6} />
// </Grid>
// </>
// );
// export const NoMargin = () => (
// <>
// <Grid xs={12}>
// <Pagination noMargin shadow color="secondary" initialPage={1} total={6} />
// </Grid>
// </>
// );
// export const NoControls = () => (
// <>
// <Grid xs={12}>
// <Pagination shadow color="success" controls={false} initialPage={1} total={20} />
// </Grid>
// </>
// );
// export const NoAnimated = () => (
// <>
// <Grid xs={12}>
// <Pagination animated={false} initialPage={1} total={6} />
// </Grid>
// </>
// );
return (
<li key={page} aria-label={`page ${page}`}>
<button
className={cn(
"w-4 h-4 bg-neutral-400 rounded-full",
activePage === page && "bg-secondary",
)}
onClick={() => setPage(page)}
/>
</li>
);
})}
</ul>
</div>
);
};

View File

@ -53,7 +53,7 @@
"lodash.foreach": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.isempty": "^4.4.0",
"tailwind-variants": "^0.1.0",
"tailwind-variants": "^0.1.1",
"tailwindcss": "^3.2.7"
},
"peerDependencies": {

View File

@ -83,7 +83,7 @@ const pagination = tv({
xl: {},
full: {},
},
isEven: {
isCompact: {
true: {
base: "gap-0",
item: [
@ -91,6 +91,8 @@ const pagination = tv({
"last-of-type:rounded-l-none",
"[&:not(:first-of-type):not(:last-of-type)]:rounded-none",
],
prev: "!rounded-r-none",
next: "!rounded-l-none",
},
},
isDisabled: {
@ -124,7 +126,7 @@ const pagination = tv({
color: "primary",
size: "md",
radius: "xl",
isEven: false,
isCompact: false,
isDisabled: false,
showShadow: false,
disableAnimation: false,
@ -174,9 +176,9 @@ const pagination = tv({
cursor: colorVariants.shadow.danger,
},
},
// isEven / bordered
// isCompact / bordered
{
isEven: true,
isCompact: true,
variant: "bordered",
class: {
item: "[&:not(:first-of-type)]:border-l-0",
@ -317,12 +319,24 @@ const pagination = tv({
"outline-none",
"items-center",
"justify-center",
"bg-neutral-100",
"hover:bg-neutral-200",
"active:bg-neutral-300",
"text-neutral-contrastText",
],
},
{
slots: ["item", "prev", "next"],
variant: "flat",
class: ["bg-neutral-100", "hover:bg-neutral-200", "active:bg-neutral-300"],
},
{
slots: ["item", "prev", "next"],
variant: "faded",
class: ["bg-neutral-100", "hover:bg-neutral-200", "active:bg-neutral-300"],
},
{
slots: ["item", "prev", "next"],
variant: "light",
class: ["hover:bg-neutral-100", "active:bg-neutral-200"],
},
// size
{
slots: ["item", "cursor", "prev", "next"],

View File

@ -2,5 +2,8 @@ export * from "./components";
export * from "./utils";
export * from "./colors";
export {tv, cx as tvCx, cxBase as cx} from "tailwind-variants";
import {cn as cnBase} from "tailwind-variants";
export {tv} from "tailwind-variants";
export type {VariantProps} from "tailwind-variants";
export const cn = (...classes: any) => cnBase(classes)();

26
pnpm-lock.yaml generated
View File

@ -835,7 +835,7 @@ importers:
lodash.foreach: ^4.5.0
lodash.get: ^4.4.2
lodash.isempty: ^4.4.0
tailwind-variants: ^0.1.0
tailwind-variants: ^0.1.1
tailwindcss: ^3.2.7
dependencies:
color: 4.2.3
@ -845,7 +845,7 @@ importers:
lodash.foreach: 4.5.0
lodash.get: 4.4.2
lodash.isempty: 4.4.0
tailwind-variants: 0.1.0_tailwindcss@3.2.7
tailwind-variants: 0.1.1_tailwindcss@3.2.7
tailwindcss: 3.2.7
devDependencies:
'@types/color': 3.0.3
@ -13310,8 +13310,8 @@ packages:
strip-final-newline: 2.0.0
dev: true
/execa/7.0.0:
resolution: {integrity: sha512-tQbH0pH/8LHTnwTrsKWideqi6rFB/QNUawEwrn+WHyz7PX1Tuz2u7wfTvbaNBdP5JD5LVWxNo8/A8CHNZ3bV6g==}
/execa/7.1.0:
resolution: {integrity: sha512-T6nIJO3LHxUZ6ahVRaxXz9WLEruXLqdcluA+UuTptXmLM7nDAn9lx9IfkxPyzEL21583qSt4RmL44pO71EHaJQ==}
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
dependencies:
cross-spawn: 7.0.3
@ -16635,9 +16635,9 @@ packages:
cli-truncate: 3.1.0
commander: 10.0.0
debug: 4.3.4
execa: 7.0.0
execa: 7.1.0
lilconfig: 2.1.0
listr2: 5.0.7
listr2: 5.0.8
micromatch: 4.0.5
normalize-path: 3.0.0
object-inspect: 1.12.3
@ -16659,8 +16659,8 @@ packages:
repeat-string: 1.6.1
dev: true
/listr2/5.0.7:
resolution: {integrity: sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==}
/listr2/5.0.8:
resolution: {integrity: sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==}
engines: {node: ^14.13.1 || >=16.0.0}
peerDependencies:
enquirer: '>= 2.3.0 < 3'
@ -17483,8 +17483,8 @@ packages:
yallist: 4.0.0
dev: true
/minipass/4.2.4:
resolution: {integrity: sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==}
/minipass/4.2.5:
resolution: {integrity: sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==}
engines: {node: '>=8'}
dev: true
@ -21426,8 +21426,8 @@ packages:
resolution: {integrity: sha512-WFnDXSS4kFTZwjKg5/oZSGzBRU/l+qcbv5NVTzLUQvJ9yovDAP05h0F2+ZFW0Lw9EcgRoc2AfURUdZvnEFrXKg==}
dev: false
/tailwind-variants/0.1.0_tailwindcss@3.2.7:
resolution: {integrity: sha512-c023Ia/c2HN/Q+cnY81q/XWgSd1r9sxsOA0LW+Ch4lLqgcnhOcAyGpCqt5MDtZDiiSb1SGHvaCAwwMLbPflBDg==}
/tailwind-variants/0.1.1_tailwindcss@3.2.7:
resolution: {integrity: sha512-kW9kXmVv9ankGydv36pg9M7fwgQfN/DZ3JlqFfmuIDPRa1/psm3KbTcB4vgEBiku7VtGQBm6oy3EsTJUYJKsLg==}
engines: {node: '>=16.x', pnpm: '>=7.x'}
peerDependencies:
tailwindcss: '*'
@ -21483,7 +21483,7 @@ packages:
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 4.2.4
minipass: 4.2.5
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0