Skip to content

Commit

Permalink
refactor(pagination): rtl (#4843)
Browse files Browse the repository at this point in the history
* refactor(pagination): rtl

* chore(changeset): add changeset
  • Loading branch information
wingkwong authored Feb 17, 2025
1 parent 09241fa commit 4693fb7
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 60 deletions.
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

0 comments on commit 4693fb7

Please sign in to comment.