feat(pagination): next, prev and ellipsis buttons added

This commit is contained in:
Junior Garcia 2023-03-11 18:43:28 -03:00
parent 99dffc3c9d
commit b9980139ef
12 changed files with 130 additions and 567 deletions

View File

@ -41,6 +41,7 @@
"@nextui-org/shared-utils": "workspace:*",
"@nextui-org/theme": "workspace:*",
"@nextui-org/dom-utils": "workspace:*",
"@nextui-org/shared-icons": "workspace:*",
"@nextui-org/use-pagination": "workspace:*",
"@react-aria/focus": "^3.11.0",
"@react-aria/interactions": "^3.14.0",

View File

@ -1,77 +0,0 @@
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {useState, MouseEvent} from "react";
import {clsx} from "@nextui-org/shared-utils";
import PaginationItem from "./pagination-item";
import {StyledPaginationEllipsis} from "./pagination.styles";
export interface PaginationEllipsisProps extends HTMLNextUIProps<"svg"> {
value?: string | number;
isBefore?: boolean;
onlyDots?: boolean;
animated?: boolean;
bordered?: boolean;
onClick?: (e: MouseEvent) => void;
}
const PaginationEllipsis = forwardRef<PaginationEllipsisProps, "svg">((props, ref) => {
const [showMore, setShowMore] = useState(false);
const {className, value, animated, bordered, onlyDots, isBefore, onClick, ...otherProps} = props;
const domRef = useDOMRef(ref);
return (
<PaginationItem
animated={animated}
bordered={bordered}
onlyDots={onlyDots}
value={value}
onClick={(e) => onClick?.(e)}
onMouseEnter={() => setShowMore(true)}
onMouseLeave={() => setShowMore(false)}
>
{showMore ? (
<StyledPaginationEllipsis
ref={domRef}
className={clsx("nextui-pagination-ellipsis", className)}
fill="none"
focusable="false"
isBefore={isBefore}
isEllipsis={true}
role="presentation"
shapeRendering="geometricPrecision"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
viewBox="0 0 24 24"
{...otherProps}
>
<path d="M13 17l5-5-5-5" />
<path d="M6 17l5-5-5-5" />
</StyledPaginationEllipsis>
) : (
<StyledPaginationEllipsis
fill="none"
isBefore={isBefore}
isEllipsis={false}
shapeRendering="geometricPrecision"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" fill="currentColor" r="1" />
<circle cx="19" cy="12" fill="currentColor" r="1" />
<circle cx="5" cy="12" fill="currentColor" r="1" />
</StyledPaginationEllipsis>
)}
</PaginationItem>
);
});
PaginationEllipsis.displayName = "NextUI.PaginationEllipsis";
export default PaginationEllipsis;

View File

@ -1,58 +0,0 @@
import {MouseEvent} from "react";
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx} from "@nextui-org/shared-utils";
import PaginationItem from "./pagination-item";
import {StyledPaginationIcon} from "./pagination.styles";
export interface PaginationIconProps extends HTMLNextUIProps<"svg"> {
isPrev?: boolean;
disabled?: boolean;
onlyDots?: boolean;
animated?: boolean;
bordered?: boolean;
onClick?: (e: MouseEvent) => void;
}
const PaginationIcon = forwardRef<PaginationIconProps, "svg">((props, ref) => {
const {className, isPrev, disabled, onlyDots, animated, bordered, onClick, ...otherProps} = props;
const domRef = useDOMRef(ref);
return (
<PaginationItem
preserveContent
animated={animated}
bordered={bordered}
disabled={disabled}
onlyDots={onlyDots}
value={isPrev ? "<" : ">"}
onClick={(e) => onClick?.(e)}
>
<StyledPaginationIcon
ref={domRef}
className={clsx("nextui-pagination-icon", className)}
fill="none"
focusable="false"
isPrev={isPrev}
role="presentation"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...otherProps}
>
<path
d="M15.5 19l-7-7 7-7"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
/>
</StyledPaginationIcon>
</PaginationItem>
);
});
PaginationIcon.displayName = "NextUI.PaginationIcon";
export default PaginationIcon;

View File

@ -1,13 +0,0 @@
import {keyframes} from "@nextui-org/system";
export const scale = keyframes({
"0%": {
transform: "scale(1)",
},
"60%": {
transform: "scale($$paginationScaleTransform)",
},
"100%": {
transform: "scale(1)",
},
});

View File

@ -1,366 +0,0 @@
import {styled} from "@nextui-org/system";
import {sharedFocus} from "@nextui-org/shared-css";
import {scale} from "./pagination.animations";
export const StyledPaginationEllipsis = styled("svg", {
color: "currentColor",
stroke: "currentColor",
variants: {
isEllipsis: {
true: {
transform: "0deg",
},
},
isBefore: {
true: {},
},
},
compoundVariants: [
{
// isEllipsis && isBefore
isEllipsis: true,
isBefore: true,
css: {
transform: "rotate(180deg)",
},
},
],
});
export const StyledPaginationIcon = styled("svg", {
transform: "rotate(180deg)",
variants: {
isPrev: {
true: {
transform: "rotate(0deg)",
},
},
},
});
export const StyledPaginationItemContent = styled("span", {
position: "relative",
display: "inline-flex",
alignItems: "center",
top: 0,
left: 0,
zIndex: "$2",
});
export const StyledPaginationItem = styled(
"button",
{
border: "none",
position: "relative",
display: "inline-flex",
margin: "0 $$paginationItemMargin",
ai: "center",
jc: "center",
padding: 0,
boxSizing: "border-box",
tt: "capitalize",
us: "none",
whiteSpace: "nowrap",
ta: "center",
verticalAlign: "middle",
bs: "none",
outline: "none",
height: "$$paginationSize",
minWidth: "$$paginationSize",
fs: "inherit",
cursor: "pointer",
br: "$$paginationItemRadius",
color: "$text",
bg: "$accents0",
"@motion": {
transition: "none",
},
"&:hover": {
bg: "$accents1",
},
[`& ${StyledPaginationIcon}`]: {
size: "$$paginationFontSize",
},
[`& ${StyledPaginationEllipsis}`]: {
size: "$$paginationFontSize",
},
variants: {
active: {
true: {
fontWeight: "$bold",
cursor: "default",
boxShadow: "$sm",
[`& ${StyledPaginationItemContent}`]: {
color: "$white",
},
},
},
disabled: {
true: {
color: "$accents5",
cursor: "not-allowed",
},
},
bordered: {
true: {
bg: "transparent",
border: "$$paginationItemBorderWeight solid $accents2",
},
},
onlyDots: {
true: {},
},
preserveContent: {
true: {},
},
animated: {
true: {
transition: "transform 0.25s ease 0s, background 0.25s ease 0s, box-shadow 0.25s ease 0s",
},
false: {
transition: "none",
},
},
},
compoundVariants: [
// onlyDots && !preserveContent
{
onlyDots: true,
preserveContent: false,
css: {
[`& ${StyledPaginationItemContent}`]: {
display: "none",
},
},
},
// animated && !disabled && !active
{
animated: true,
disabled: false,
active: false,
css: {
"&:active": {
transform: "scale($$paginationScaleTransform)",
fs: "calc($$paginationFontSize * 0.9)",
},
},
},
],
},
sharedFocus,
);
export const StyledPaginationHighlight = styled("div", {
position: "absolute",
contain: "strict",
top: "0px",
zIndex: "$1",
bg: "$$paginationColor",
br: "$$paginationItemRadius",
height: "$$paginationSize",
minWidth: "$$paginationSize",
animationName: `${scale}`,
animationDirection: "normal",
"&.nextui-pagination-highlight--moving": {
transform: "scale($$paginationScaleTransform)",
},
"@motion": {
transition: "none",
"&.nextui-pagination-highlight--moving": {
transform: "scale(1)",
},
},
variants: {
animated: {
true: {
animationDuration: "350ms",
animationTimingFunction: "ease",
transition: "left 350ms ease 0s, transform 300ms ease 0s",
},
false: {
animationDuration: "none",
animationTimingFunction: "none",
transition: "none",
"&.nextui-pagination-highlight--moving": {
transform: "scale(1)",
},
},
},
noMargin: {
true: {
br: "$squared",
},
},
rounded: {
true: {},
},
shadow: {
true: {
normalShadowVar: "$$paginationShadowColor",
},
},
},
compoundVariants: [
{
// rounded && noMargin
rounded: true,
noMargin: true,
css: {
br: "$pill",
},
},
],
});
export const StyledPagination = styled("nav", {
m: 0,
p: 0,
d: "inline-flex",
position: "relative",
fontVariant: "tabular-nums",
fontFeatureSettings: "tnum",
variants: {
color: {
default: {
$$paginationColor: "$colors$primary",
$$paginationShadowColor: "$colors$primaryShadow",
},
primary: {
$$paginationColor: "$colors$primary",
$$paginationShadowColor: "$colors$primaryShadow",
},
secondary: {
$$paginationColor: "$colors$secondary",
$$paginationShadowColor: "$colors$secondaryShadow",
},
success: {
$$paginationColor: "$colors$success",
$$paginationShadowColor: "$colors$successShadow",
},
warning: {
$$paginationColor: "$colors$warning",
$$paginationShadowColor: "$colors$warningShadow",
},
error: {
$$paginationColor: "$colors$error",
$$paginationShadowColor: "$colors$errorShadow",
},
gradient: {
$$paginationColor: "$colors$gradient",
$$paginationShadowColor: "$colors$primaryShadow",
},
},
size: {
xs: {
$$paginationWidth: "$space$10",
$$paginationFontSize: "$space$5",
fs: "$$paginationFontSize",
},
sm: {
$$paginationWidth: "$space$12",
$$paginationFontSize: "$space$6",
fs: "$$paginationFontSize",
},
md: {
$$paginationWidth: "$space$13",
$$paginationFontSize: "$space$7",
fs: "$$paginationFontSize",
},
lg: {
$$paginationWidth: "$space$14",
$$paginationFontSize: "$space$8",
fs: "$$paginationFontSize",
},
xl: {
$$paginationWidth: "$space$15",
$$paginationFontSize: "$space$9",
fs: "$$paginationFontSize",
},
},
borderWeight: {
light: {
$$paginationItemBorderWeight: "$borderWeights$light",
},
normal: {
$$paginationItemBorderWeight: "$borderWeights$normal",
},
bold: {
$$paginationItemBorderWeight: "$borderWeights$bold",
},
extrabold: {
$$paginationItemBorderWeight: "$borderWeights$extrabold",
},
black: {
$$paginationItemBorderWeight: "$borderWeights$black",
},
},
bordered: {
true: {},
},
onlyDots: {
true: {
$$paginationSize: "calc($$paginationWidth / 2)",
$$paginationItemRadius: "$radii$pill",
$$paginationScaleTransform: 1.05,
},
false: {
$$paginationSize: "$$paginationWidth",
$$paginationScaleTransform: 1.1,
},
},
rounded: {
true: {
$$paginationItemRadius: "$radii$pill",
},
false: {
$$paginationItemRadius: "$radii$squared",
},
},
noMargin: {
true: {
$$paginationItemRadius: "0px",
$$paginationItemMargin: "0",
[`& ${StyledPaginationItem}:first-of-type`]: {
btlr: "$squared",
bblr: "$squared",
},
[`& ${StyledPaginationItem}:last-of-type`]: {
btrr: "$squared",
bbrr: "$squared",
},
},
false: {
$$paginationItemMargin: "$space$1",
},
},
},
compoundVariants: [
{
// bordered && noMargin
bordered: true,
noMargin: true,
css: {
[`& ${StyledPaginationItem}:not(:last-child)`]: {
borderRight: 0,
},
},
},
{
// noMargin && rounded
noMargin: true,
rounded: true,
css: {
$$paginationItemRadius: "0px",
},
},
{
// !rounded && noMargin
rounded: false,
noMargin: true,
css: {
$$paginationItemRadius: "0px",
},
},
],
});

View File

@ -2,6 +2,8 @@ import {forwardRef} from "@nextui-org/system";
import {PaginationItemValue} from "@nextui-org/use-pagination";
import {useCallback} from "react";
import {PaginationItemType} from "@nextui-org/use-pagination";
import {ChevronIcon, EllipsisIcon, ForwardIcon} from "@nextui-org/shared-icons";
import {clsx} from "@nextui-org/shared-utils";
import {UsePaginationProps, usePagination} from "./use-pagination";
import PaginationItem from "./pagination-item";
@ -12,20 +14,19 @@ export interface PaginationProps extends Omit<UsePaginationProps, "ref"> {}
const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
const {
Component,
// showControls,
dotsJump,
// loop,
slots,
styles,
total,
range,
loop,
activePage,
// setPage,
// onPrevious,
// onNext,
disableCursor,
disableAnimation,
renderItem: renderItemProp,
onNext,
onPrevious,
setPage,
getBaseProps,
getItemProps,
getCursorProps,
@ -33,7 +34,7 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
const renderItem = useCallback(
(value: PaginationItemValue, index: number) => {
// const isBefore = index < range.indexOf(activePage);
const isBefore = index < range.indexOf(activePage);
if (renderItemProp && typeof renderItemProp === "function") {
return renderItemProp({
@ -48,29 +49,56 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
});
}
if (value === PaginationItemType.PREV) {
return <PaginationItem className={slots.prev({class: styles?.prev})}>{"<"}</PaginationItem>;
return (
<PaginationItem
className={slots.prev({
class: clsx(
styles?.prev,
!loop && activePage === 1 && "opacity-50 pointer-events-none",
),
})}
onPress={onPrevious}
>
<ChevronIcon />
</PaginationItem>
);
}
if (value === PaginationItemType.NEXT) {
return <PaginationItem className={slots.next({class: styles?.next})}>{">"}</PaginationItem>;
return (
<PaginationItem
className={slots.next({
class: clsx(
styles?.next,
!loop && activePage === total && "opacity-50 pointer-events-none",
),
})}
onPress={onNext}
>
<ChevronIcon className="rotate-180" />
</PaginationItem>
);
}
if (value === PaginationItemType.DOTS) {
return <PaginationItem className={slots.item({class: styles?.item})}>...</PaginationItem>;
// return (
// <PaginationEllipsis
// key={`nextui-pagination-item-${value}-${index}`}
// animated={animated}
// bordered={bordered}
// isBefore={isBefore}
// onlyDots={onlyDots}
// value={value}
// onClick={() =>
// isBefore
// ? setPage(active - dotsJump >= 1 ? active - dotsJump : 1)
// : setPage(active + dotsJump <= total ? active + dotsJump : total)
// }
// />
// );
return (
<PaginationItem
className={slots.item({
class: clsx(styles?.item, "group"),
})}
onPress={() =>
isBefore
? setPage(activePage - dotsJump >= 1 ? activePage - dotsJump : 1)
: setPage(activePage + dotsJump <= total ? activePage + dotsJump : total)
}
>
<EllipsisIcon className="group-hover:hidden" />
{isBefore ? (
<ForwardIcon className="hidden group-hover:block rotate-180" />
) : (
<ForwardIcon className="hidden group-hover:block" />
)}
</PaginationItem>
);
}
return (
@ -79,41 +107,13 @@ const Pagination = forwardRef<PaginationProps, "ul">((props, ref) => {
</PaginationItem>
);
},
[activePage, dotsJump, getItemProps, range, renderItemProp, slots, styles, total],
[activePage, dotsJump, getItemProps, loop, range, renderItemProp, slots, styles, total],
);
return (
<Component {...getBaseProps()}>
{!disableCursor && !disableAnimation && <PaginationCursor {...getCursorProps()} />}
{range.map(renderItem)}
{/* {controls && (
<PaginationIcon
isPrev
animated={animated}
bordered={bordered}
disabled={!loop && active === 1}
onlyDots={onlyDots}
onClick={onPrevious}
/>
)}
<PaginationHighlight
active={controls ? range.indexOf(active) + 1 : range.indexOf(active)}
animated={animated}
noMargin={noMargin}
rounded={rounded}
shadow={shadow}
/>
{range.map(renderItem)}
{controls && (
<PaginationIcon
animated={animated}
bordered={bordered}
disabled={!loop && active === total}
onlyDots={onlyDots}
onClick={onNext}
/>
)} */}
</Component>
);
});

View File

@ -83,6 +83,13 @@ WithControls.args = {
showControls: true,
};
export const PaginationLoop = Template.bind({});
PaginationLoop.args = {
...defaultProps,
showControls: true,
loop: true,
};
export const InitialPage = Template.bind({});
InitialPage.args = {
...defaultProps,

View File

@ -0,0 +1,22 @@
import {IconSvgProps} from "./types";
export const ChevronIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="1em"
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<path
d="M15.5 19l-7-7 7-7"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
);

View File

@ -0,0 +1,20 @@
import {IconSvgProps} from "./types";
export const EllipsisIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
height="1em"
shapeRendering="geometricPrecision"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<circle cx="12" cy="12" fill="currentColor" r="1" />
<circle cx="19" cy="12" fill="currentColor" r="1" />
<circle cx="5" cy="12" fill="currentColor" r="1" />
</svg>
);

View File

@ -0,0 +1,22 @@
import {IconSvgProps} from "./types";
export const ForwardIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="1em"
role="presentation"
shapeRendering="geometricPrecision"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<path d="M13 17l5-5-5-5" />
<path d="M6 17l5-5-5-5" />
</svg>
);

View File

@ -4,3 +4,6 @@ export * from "./check";
export * from "./avatar";
export * from "./close";
export * from "./close-filled";
export * from "./chevron";
export * from "./ellipsis";
export * from "./forward";

2
pnpm-lock.yaml generated
View File

@ -607,6 +607,7 @@ importers:
packages/components/pagination:
specifiers:
'@nextui-org/dom-utils': workspace:*
'@nextui-org/shared-icons': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/system': workspace:*
'@nextui-org/theme': workspace:*
@ -618,6 +619,7 @@ importers:
react: ^18.2.0
dependencies:
'@nextui-org/dom-utils': link:../../utilities/dom-utils
'@nextui-org/shared-icons': link:../../utilities/shared-icons
'@nextui-org/shared-utils': link:../../utilities/shared-utils
'@nextui-org/system': link:../../core/system
'@nextui-org/theme': link:../../core/theme