Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/shiny-ties-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@heroui/pagination": patch
"@heroui/use-pagination": patch
---

fixed pagination in RTL (#2858)
114 changes: 72 additions & 42 deletions packages/components/pagination/src/pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,68 @@ const Pagination = forwardRef<"nav", PaginationProps>((props, ref) => {

const isRTL = direction === "rtl";

const renderChevronIcon = useCallback(
(key: PaginationItemType) => {
if (
(key === PaginationItemType.PREV && !isRTL) ||
(key === PaginationItemType.NEXT && isRTL)
) {
return <ChevronIcon />;
}

return (
<ChevronIcon
className={slots.chevronNext({
class: classNames?.chevronNext,
})}
/>
);
},
[slots, isRTL],
);

const renderPrevItem = useCallback(
(value: PaginationItemValue) => {
return (
<PaginationItem
key={PaginationItemType.PREV}
className={slots.prev({
class: classNames?.prev,
})}
data-slot="prev"
getAriaLabel={getItemAriaLabel}
isDisabled={!loop && activePage === 1}
value={value}
onPress={onPrevious}
>
{renderChevronIcon(PaginationItemType.PREV)}
</PaginationItem>
);
},
[slots, classNames, loop, activePage, isRTL, total, getItemAriaLabel, onPrevious],
);

const renderNextItem = useCallback(
(value: PaginationItemValue) => {
return (
<PaginationItem
key={PaginationItemType.NEXT}
className={slots.next({
class: clsx(classNames?.next),
})}
data-slot="next"
getAriaLabel={getItemAriaLabel}
isDisabled={!loop && activePage === total}
value={value}
onPress={onNext}
>
{renderChevronIcon(PaginationItemType.NEXT)}
</PaginationItem>
);
},
[slots, classNames, loop, activePage, isRTL, total, getItemAriaLabel, onNext],
);

const renderItem = useCallback(
(value: PaginationItemValue, index: number) => {
const isBefore = index < range.indexOf(activePage);
Expand All @@ -66,14 +128,8 @@ const Pagination = forwardRef<"nav", PaginationProps>((props, ref) => {
}

const itemChildren: Record<PaginationItemType, React.ReactNode> = {
[PaginationItemType.PREV]: <ChevronIcon />,
[PaginationItemType.NEXT]: (
<ChevronIcon
className={slots.chevronNext({
class: classNames?.chevronNext,
})}
/>
),
[PaginationItemType.PREV]: renderChevronIcon(PaginationItemType.PREV),
[PaginationItemType.NEXT]: renderChevronIcon(PaginationItemType.NEXT),
[PaginationItemType.DOTS]: (
<>
<EllipsisIcon className={slots?.ellipsis({class: classNames?.ellipsis})} />
Expand Down Expand Up @@ -111,42 +167,10 @@ const Pagination = forwardRef<"nav", PaginationProps>((props, ref) => {
}

if (value === PaginationItemType.PREV) {
return (
<PaginationItem
key={PaginationItemType.PREV}
className={slots.prev({
class: classNames?.prev,
})}
data-slot="prev"
getAriaLabel={getItemAriaLabel}
isDisabled={!loop && activePage === (isRTL ? total : 1)}
value={value}
onPress={onPrevious}
>
<ChevronIcon />
</PaginationItem>
);
return renderPrevItem(value);
}
if (value === PaginationItemType.NEXT) {
return (
<PaginationItem
key={PaginationItemType.NEXT}
className={slots.next({
class: clsx(classNames?.next),
})}
data-slot="next"
getAriaLabel={getItemAriaLabel}
isDisabled={!loop && activePage === (isRTL ? 1 : total)}
value={value}
onPress={onNext}
>
<ChevronIcon
className={slots.chevronNext({
class: classNames?.chevronNext,
})}
/>
</PaginationItem>
);
return renderNextItem(value);
}

if (value === PaginationItemType.DOTS) {
Expand Down Expand Up @@ -191,6 +215,12 @@ const Pagination = forwardRef<"nav", PaginationProps>((props, ref) => {
slots,
classNames,
total,
getItemAriaLabel,
onNext,
onPrevious,
setPage,
renderPrevItem,
renderNextItem,
],
);

Expand Down
9 changes: 2 additions & 7 deletions packages/components/pagination/src/use-pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {Key, ReactNode, Ref} from "react";
import type {HTMLHeroUIProps, PropGetter} from "@heroui/system";

import {objectToDeps, Timer} from "@heroui/shared-utils";
import {useLocale} from "@react-aria/i18n";
import {
UsePaginationProps as UseBasePaginationProps,
PaginationItemValue,
Expand Down Expand Up @@ -195,10 +194,6 @@ export function usePagination(originalProps: UsePaginationProps) {

const cursorTimer = useRef<Timer>();

const {direction} = useLocale();

const isRTL = direction === "rtl";

const disableAnimation =
originalProps?.disableAnimation ?? globalContext?.disableAnimation ?? false;
const disableCursorAnimation = originalProps?.disableCursorAnimation ?? disableAnimation ?? false;
Expand Down Expand Up @@ -321,15 +316,15 @@ export function usePagination(originalProps: UsePaginationProps) {
const baseStyles = clsx(classNames?.base, className);

const onNext = () => {
if (loop && activePage === (isRTL ? 1 : total)) {
if (loop && activePage === total) {
return first();
}

return next();
};

const onPrevious = () => {
if (loop && activePage === (isRTL ? total : 1)) {
if (loop && activePage === 1) {
return last();
}

Expand Down
17 changes: 13 additions & 4 deletions packages/components/pagination/stories/pagination.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Meta} from "@storybook/react";
import {button, pagination} from "@heroui/theme";
import {cn} from "@heroui/theme";
import {ChevronIcon} from "@heroui/shared-icons";
import {useLocale} from "@react-aria/i18n";

import {Pagination, PaginationItemRenderProps, PaginationItemType, usePagination} from "../src";

Expand Down Expand Up @@ -138,6 +139,10 @@ export const Controlled = () => {
};

export const CustomItems = () => {
const {direction} = useLocale();

const isRTL = direction === "rtl";

const renderItem = ({
ref,
value,
Expand All @@ -150,15 +155,15 @@ export const CustomItems = () => {
if (value === PaginationItemType.NEXT) {
return (
<button className={cn(className, "bg-default-200")} onClick={onNext}>
<ChevronIcon className="rotate-180" />
<ChevronIcon className={cn({"rotate-180": !isRTL})} />
</button>
);
}

if (value === PaginationItemType.PREV) {
return (
<button className={cn(className, "bg-default-200")} onClick={onPrevious}>
<ChevronIcon />
<ChevronIcon className={cn({"rotate-180": isRTL})} />
</button>
);
}
Expand Down Expand Up @@ -217,6 +222,10 @@ export const CustomWithHooks = () => {
boundaries: 10,
});

const {direction} = useLocale();

const isRTL = direction === "rtl";

return (
<div className="flex flex-col gap-2">
<p>Active page: {activePage}</p>
Expand All @@ -226,7 +235,7 @@ export const CustomWithHooks = () => {
return (
<li key={page} aria-label="next page" className="w-4 h-4">
<button className="w-full h-full bg-default-200 rounded-full" onClick={onNext}>
<ChevronIcon className="rotate-180" />
<ChevronIcon className={cn({"rotate-180": !isRTL})} />
</button>
</li>
);
Expand All @@ -236,7 +245,7 @@ export const CustomWithHooks = () => {
return (
<li key={page} aria-label="previous page" className="w-4 h-4">
<button className="w-full h-full bg-default-200 rounded-full" onClick={onPrevious}>
<ChevronIcon />
<ChevronIcon className={cn({"rotate-180": isRTL})} />
</button>
</li>
);
Expand Down
12 changes: 5 additions & 7 deletions packages/hooks/use-pagination/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,15 @@ export function usePagination(props: UsePaginationProps) {
[total, activePage, onChangeActivePage],
);

const next = () => (isRTL ? setPage(activePage - 1) : setPage(activePage + 1));
const previous = () => (isRTL ? setPage(activePage + 1) : setPage(activePage - 1));
const first = () => (isRTL ? setPage(total) : setPage(1));
const last = () => (isRTL ? setPage(1) : setPage(total));
const next = () => setPage(activePage + 1);
const previous = () => setPage(activePage - 1);
const first = () => setPage(1);
const last = () => setPage(total);

const formatRange = useCallback(
(range: PaginationItemValue[]) => {
if (showControls) {
return isRTL
? [PaginationItemType.NEXT, ...range, PaginationItemType.PREV]
: [PaginationItemType.PREV, ...range, PaginationItemType.NEXT];
return [PaginationItemType.PREV, ...range, PaginationItemType.NEXT];
}

return range;
Expand Down