refactor(pagination): rtl (#4843)

* refactor(pagination): rtl

* chore(changeset): add changeset
This commit is contained in:
աӄա 2025-02-17 22:31:43 +08:00 committed by GitHub
parent 09241faa4b
commit 4693fb7b4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 98 additions and 60 deletions

View File

@ -0,0 +1,6 @@
---
"@heroui/pagination": patch
"@heroui/use-pagination": patch
---
fixed pagination in RTL (#2858)

View File

@ -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);
@ -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})} />
@ -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) {
@ -191,6 +215,12 @@ const Pagination = forwardRef<"nav", PaginationProps>((props, ref) => {
slots,
classNames,
total,
getItemAriaLabel,
onNext,
onPrevious,
setPage,
renderPrevItem,
renderNextItem,
],
);

View File

@ -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,
@ -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;
@ -321,7 +316,7 @@ 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();
}
@ -329,7 +324,7 @@ export function usePagination(originalProps: UsePaginationProps) {
};
const onPrevious = () => {
if (loop && activePage === (isRTL ? total : 1)) {
if (loop && activePage === 1) {
return last();
}

View File

@ -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";
@ -138,6 +139,10 @@ export const Controlled = () => {
};
export const CustomItems = () => {
const {direction} = useLocale();
const isRTL = direction === "rtl";
const renderItem = ({
ref,
value,
@ -150,7 +155,7 @@ 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>
);
}
@ -158,7 +163,7 @@ export const CustomItems = () => {
if (value === PaginationItemType.PREV) {
return (
<button className={cn(className, "bg-default-200")} onClick={onPrevious}>
<ChevronIcon />
<ChevronIcon className={cn({"rotate-180": isRTL})} />
</button>
);
}
@ -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>
@ -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>
);
@ -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>
);

View File

@ -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;