import * as React from "react"; import { cx } from "@emotion/css"; import { Flex, FlexItem } from "../styleUtils/layout"; import { Icon } from "../icon"; import { ResetButton } from "../button"; import { ResetLink } from "../link"; import { SelectInput } from "../selectInput"; import { SystemIcons } from "../icons/dist/system-icons-enum"; import { TextInput } from "../textInput"; import { themeBgDisabled } from "../design-tokens/build/js/designTokens"; import { ExpandedLinkProps } from "../link/types"; import { defaultInputWidth } from "./style"; import { border, display, margin, padding } from "../shared/styles/styleUtils"; export interface PaginationProps { /** Prop used to control which page the user is currently navigated to */ activePage?: number; /** The default page the user starts on before the user navigates using the text input or the prev/next buttons. For use when the component is uncontrolled. */ initialActivePage?: number; /** The label used to describe the items being paginated through */ itemsLabel?: string; /** Prop used to control how many items are shown per page */ pageLength?: number; /** The default number of items shown per page before the user selects a new number from the dropdown. For use when the component is uncontrolled. */ initialPageLength?: number; /** The link used for the "next page" button */ nextUrl?: string; /** A callback for when the user clicks the prev/next buttons or changes the selected option for how many items are shown per page */ onChange?: (newPage: number, newPageLength: number) => void; /** The link used for the "previous page" button */ prevUrl?: string; /** Whether to show the menu to set how many items are shown per page */ showPageLengthMenu?: boolean; /** The total number of items on all pages */ totalItems?: number; /** The total number of pages */ totalPages?: number; } const DEFAULT_PAGE_LENGTHS = [10, 30, 50, 100]; const LARGE_PAGE_LENGTHS = [10, 30, 50, 100, 200]; export const INITIAL_PAGE_LENGTH = DEFAULT_PAGE_LENGTHS[1]; const LARGE_PAGE_LENGTH_THRESHOLD = 500; const DEFAULT_ITEMS_LABEL = "Items"; export const getItemCountString = ( activePage: number, itemsLabel: string, pageLength: number, totalItems?: number ) => { const startItemIndex = (activePage - 1) * pageLength + 1; const endItemIndex = totalItems ? Math.min(activePage * pageLength, totalItems) : activePage * pageLength; const itemRange = `${startItemIndex}–${endItemIndex}`; const totalItemsLabel = totalItems ? `${totalItems} ${itemsLabel}` : ""; if (!totalItemsLabel) { return itemRange; } return `${itemRange} of ${totalItemsLabel}`; }; const getItemCountClassName = (showPageLengthMenu?: boolean) => { if (showPageLengthMenu) { return cx(border("horiz"), padding("horiz"), margin("horiz")); } return cx(border("right"), padding("right"), margin("right")); }; const NavButton = ({ direction, disabled, url, ...other }: Partial> & Partial & { direction: "prev" | "next" }) => { const ariaLabel = direction === "prev" ? "previous page" : "next page"; const Component = url ? ResetLink : ResetButton; return ( ); }; const Pagination = ({ activePage: activePageProp, initialActivePage = 1, itemsLabel = DEFAULT_ITEMS_LABEL, pageLength: pageLengthProp, initialPageLength = INITIAL_PAGE_LENGTH, nextUrl, onChange, prevUrl, showPageLengthMenu, totalItems, totalPages: totalPagesProp }: PaginationProps) => { const pageLengthMenuId = `pageLengthMenu-${React.useId()}`; const [activePageState, setActivePageState] = React.useState(initialActivePage); const [pageInputVal, setPageInputVal] = React.useState(initialActivePage); const [pageLengthState, setPageLengthState] = React.useState(initialPageLength); const activePage = activePageProp || activePageState; const pageLength = pageLengthProp || pageLengthState; const isTotalPagesCalculable = Boolean(totalItems && pageLength); const calculatedTotalPages = isTotalPagesCalculable && totalItems ? Math.ceil(totalItems / pageLength) : undefined; const totalPages = !totalPagesProp && isTotalPagesCalculable ? calculatedTotalPages : totalPagesProp; const pageLengthOptions = ( totalItems && totalItems >= LARGE_PAGE_LENGTH_THRESHOLD ? LARGE_PAGE_LENGTHS : DEFAULT_PAGE_LENGTHS ).map(option => { return { label: option.toString(), value: option.toString() }; }); if (calculatedTotalPages && totalPages !== calculatedTotalPages) { console.warn( `The 'totalPages' prop is set to ${totalPagesProp}, but this doesn't add up with these props: \n totalItems: ${totalItems} \n pageLength: ${pageLength} \n\n the expected 'totalPages' value is ${calculatedTotalPages}` ); } const onPageChange = (newPage: number) => { if (!activePageProp) { setActivePageState(newPage); } if (onChange) { onChange(newPage, pageLength); } }; const handlePageInputChange = (e: React.ChangeEvent) => { setPageInputVal(parseInt(e.currentTarget.value, 10)); }; const handlePageLengthChange = (e: React.ChangeEvent) => { const newPageLength = parseInt(e.currentTarget.value, 10); if (!pageLengthProp) { setPageLengthState(newPageLength); setActivePageState(1); } if (onChange) { onChange(activePage, newPageLength); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onPageChange(pageInputVal); }; const handlePrevClick = () => { onPageChange(activePage - 1); }; const handleNextClick = () => { onPageChange(activePage + 1); }; React.useEffect(() => { setPageInputVal(activePage); }, [activePage, pageLength]); return ( {showPageLengthMenu && ( )}
{getItemCountString(activePage, itemsLabel, pageLength, totalItems)}
{Boolean(totalPages) && ` / ${totalPages}`}
); }; export default Pagination;