refactor: revamp pagination UI view logic (#10567)

* chore: revamp Page Utility tests

* refactor: simplify component design for PageButton

* chore: beef up isNonInitialPage and add tests

* docs: clean up comments

* chore: quick refactor for buildPagedList

* refactor: clean up math calculations for buildPagedList

* chore: rename PageButtons file

* chore: revamp how nav buttons are defined

* fix: remove test disabled state

* chore: clean up base nav button

* chore: rename props for clarity

* refactor: clean up logic for isNonInitialPage

* chore: add more tests and catch bugs

* docs: fix confusing typo in comments

* chore: add one more test case for pagination buttons

* refactor: update props definition for PaginationNavButton

* fix: remove possible state sync bugs
This commit is contained in:
Michael Smith
2023-11-09 09:10:14 -05:00
committed by GitHub
parent 8a7f0e9eb9
commit ad3abe350f
13 changed files with 552 additions and 197 deletions
@@ -1,45 +0,0 @@
import Button from "@mui/material/Button";
import { css, useTheme } from "@emotion/react";
interface PageButtonProps {
activePage?: number;
page?: number;
placeholder?: string;
numPages?: number;
onPageClick?: (page: number) => void;
disabled?: boolean;
}
export const PageButton = ({
activePage,
page,
placeholder = "...",
numPages,
onPageClick,
disabled = false,
}: PageButtonProps): JSX.Element => {
const theme = useTheme();
return (
<Button
css={[
css`
&:not(:last-of-type) {
margin-right: 4px;
}
`,
activePage === page && {
borderColor: `${theme.palette.info.main}`,
backgroundColor: `${theme.palette.info.dark}`,
},
]}
aria-label={`${page === activePage ? "Current Page" : ""} ${
page === numPages ? "Last Page" : ""
} Page${page}`}
name={page === undefined ? undefined : "Page button"}
onClick={() => onPageClick && page && onPageClick(page)}
disabled={disabled}
>
<div>{page ?? placeholder}</div>
</Button>
);
};
@@ -0,0 +1,108 @@
import { PropsWithChildren } from "react";
import Button from "@mui/material/Button";
import { useTheme } from "@emotion/react";
type NumberedPageButtonProps = {
pageNumber: number;
totalPages: number;
onClick?: () => void;
highlighted?: boolean;
disabled?: boolean;
};
export function NumberedPageButton({
pageNumber,
totalPages,
onClick,
highlighted = false,
disabled = false,
}: NumberedPageButtonProps) {
return (
<BasePageButton
name="Page button"
aria-label={getNumberedButtonLabel(pageNumber, totalPages, highlighted)}
onClick={onClick}
highlighted={highlighted}
disabled={disabled}
>
{pageNumber}
</BasePageButton>
);
}
type PlaceholderPageButtonProps = PropsWithChildren<{
pagesOmitted: number;
}>;
export function PlaceholderPageButton({
pagesOmitted,
children = <>&hellip;</>,
}: PlaceholderPageButtonProps) {
return (
<BasePageButton
disabled
name="Omitted pages"
aria-label={`Omitting ${pagesOmitted} pages`}
>
{children}
</BasePageButton>
);
}
type BasePageButtonProps = PropsWithChildren<{
name: string;
"aria-label": string;
onClick?: () => void;
highlighted?: boolean;
disabled?: boolean;
}>;
function BasePageButton({
children,
onClick,
name,
"aria-label": ariaLabel,
highlighted = false,
disabled = false,
}: BasePageButtonProps) {
const theme = useTheme();
return (
<Button
css={
highlighted && {
borderColor: `${theme.palette.info.main}`,
backgroundColor: `${theme.palette.info.dark}`,
}
}
aria-label={ariaLabel}
name={name}
disabled={disabled}
onClick={onClick}
>
{children}
</Button>
);
}
function getNumberedButtonLabel(
page: number,
totalPages: number,
highlighted: boolean,
): string {
if (highlighted) {
return "Current Page";
}
if (page === 1) {
return "First Page";
}
if (page === totalPages) {
return "Last Page";
}
return `Page ${page}`;
}
@@ -0,0 +1,105 @@
import {
type ButtonHTMLAttributes,
type ReactNode,
useEffect,
useState,
} from "react";
import { useTheme } from "@emotion/react";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
type PaginationNavButtonProps = Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
| "aria-disabled"
// Need to omit color for MUI compatibility
| "color"
> & {
// Required/narrowed versions of default props
children: ReactNode;
disabled: boolean;
onClick: () => void;
"aria-label": string;
// Bespoke props
disabledMessage: ReactNode;
disabledMessageTimeout?: number;
};
function PaginationNavButtonCore({
onClick,
disabled,
disabledMessage,
disabledMessageTimeout = 3000,
...delegatedProps
}: PaginationNavButtonProps) {
const theme = useTheme();
const [showDisabledMessage, setShowDisabledMessage] = useState(false);
// Inline state sync - this is safe/recommended by the React team in this case
if (!disabled && showDisabledMessage) {
setShowDisabledMessage(false);
}
useEffect(() => {
if (!showDisabledMessage) {
return;
}
const timeoutId = setTimeout(
() => setShowDisabledMessage(false),
disabledMessageTimeout,
);
return () => clearTimeout(timeoutId);
}, [showDisabledMessage, disabledMessageTimeout]);
return (
<Tooltip title={disabledMessage} open={showDisabledMessage}>
{/*
* Going more out of the way to avoid attaching the disabled prop directly
* to avoid unwanted side effects of using the prop:
* - Not being focusable/keyboard-navigable
* - Not being able to call functions in response to invalid actions
* (mostly for giving direct UI feedback to those actions)
*/}
<Button
aria-disabled={disabled}
css={
disabled && {
borderColor: theme.palette.divider,
color: theme.palette.text.disabled,
cursor: "default",
"&:hover": {
backgroundColor: theme.palette.background.default,
borderColor: theme.palette.divider,
},
}
}
onClick={() => {
if (disabled) {
setShowDisabledMessage(true);
} else {
onClick();
}
}}
{...delegatedProps}
/>
</Tooltip>
);
}
export function PaginationNavButton({
disabledMessageTimeout = 3000,
...delegatedProps
}: PaginationNavButtonProps) {
return (
// Key prop ensures that if timeout changes, the component just unmounts and
// remounts, avoiding a swath of possible sync issues
<PaginationNavButtonCore
key={disabledMessageTimeout}
disabledMessageTimeout={disabledMessageTimeout}
{...delegatedProps}
/>
);
}
@@ -5,9 +5,9 @@ const meta: Meta<typeof PaginationWidgetBase> = {
title: "components/PaginationWidgetBase",
component: PaginationWidgetBase,
args: {
page: 1,
limit: 12,
count: 200,
currentPage: 1,
pageSize: 12,
totalRecords: 200,
},
};
@@ -17,19 +17,17 @@ type Story = StoryObj<typeof PaginationWidgetBase>;
export const MoreThan8Pages: Story = {};
export const LessThan8Pages: Story = {
args: {
count: 84,
},
args: { totalRecords: 84 },
};
export const MoreThan7PagesWithActivePageCloseToStart: Story = {
args: { page: 2, limit: 12 },
args: { currentPage: 2, pageSize: 12 },
};
export const MoreThan7PagesWithActivePageFarFromBoundaries: Story = {
args: { page: 4, limit: 12 },
args: { currentPage: 4, pageSize: 12 },
};
export const MoreThan7PagesWithActivePageCloseToEnd: Story = {
args: { page: 17, limit: 12 },
args: { currentPage: 17, pageSize: 12 },
};
@@ -0,0 +1,86 @@
import { screen } from "@testing-library/react";
import {
PaginationWidgetBase,
PaginationWidgetBaseProps,
} from "./PaginationWidgetBase";
import { renderWithAuth } from "testHelpers/renderHelpers";
import userEvent from "@testing-library/user-event";
type SampleProps = Omit<PaginationWidgetBaseProps, "onPageChange">;
describe(PaginationWidgetBase.name, () => {
it("Should have its previous button be aria-disabled while on page 1", async () => {
const sampleProps: SampleProps[] = [
{ currentPage: 1, pageSize: 5, totalRecords: 6 },
{ currentPage: 1, pageSize: 50, totalRecords: 200 },
{ currentPage: 1, pageSize: 20, totalRecords: 3000 },
];
for (const props of sampleProps) {
const onPageChange = jest.fn();
const { unmount } = renderWithAuth(
<PaginationWidgetBase {...props} onPageChange={onPageChange} />,
);
const prevButton = await screen.findByLabelText("Previous page");
expect(prevButton).not.toBeDisabled();
expect(prevButton).toHaveAttribute("aria-disabled", "true");
await userEvent.click(prevButton);
expect(onPageChange).not.toHaveBeenCalled();
unmount();
}
});
it("Should have its next button be aria-disabled while on last page", async () => {
const sampleProps: SampleProps[] = [
{ currentPage: 2, pageSize: 5, totalRecords: 6 },
{ currentPage: 4, pageSize: 50, totalRecords: 200 },
{ currentPage: 10, pageSize: 100, totalRecords: 1000 },
];
for (const props of sampleProps) {
const onPageChange = jest.fn();
const { unmount } = renderWithAuth(
<PaginationWidgetBase {...props} onPageChange={onPageChange} />,
);
const button = await screen.findByLabelText("Next page");
expect(button).not.toBeDisabled();
expect(button).toHaveAttribute("aria-disabled", "true");
await userEvent.click(button);
expect(onPageChange).not.toHaveBeenCalled();
unmount();
}
});
it("Should have neither button be disabled for all other pages", async () => {
const sampleProps: SampleProps[] = [
{ currentPage: 11, pageSize: 5, totalRecords: 60 },
{ currentPage: 2, pageSize: 50, totalRecords: 200 },
{ currentPage: 3, pageSize: 20, totalRecords: 100 },
];
for (const props of sampleProps) {
const onPageChange = jest.fn();
const { unmount } = renderWithAuth(
<PaginationWidgetBase {...props} onPageChange={onPageChange} />,
);
const prevButton = await screen.findByLabelText("Previous page");
const nextButton = await screen.findByLabelText("Next page");
expect(prevButton).not.toBeDisabled();
expect(prevButton).toHaveAttribute("aria-disabled", "false");
expect(nextButton).not.toBeDisabled();
expect(nextButton).toHaveAttribute("aria-disabled", "false");
await userEvent.click(prevButton);
await userEvent.click(nextButton);
expect(onPageChange).toHaveBeenCalledTimes(2);
unmount();
}
});
});
@@ -1,34 +1,35 @@
import Button from "@mui/material/Button";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@emotion/react";
import { PlaceholderPageButton, NumberedPageButton } from "./PageButtons";
import { buildPagedList } from "./utils";
import { PaginationNavButton } from "./PaginationNavButton";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import { useTheme } from "@emotion/react";
import { PageButton } from "./PageButton";
import { buildPagedList } from "./utils";
export type PaginationWidgetBaseProps = {
count: number;
page: number;
limit: number;
onChange: (page: number) => void;
currentPage: number;
pageSize: number;
totalRecords: number;
onPageChange: (newPage: number) => void;
};
export const PaginationWidgetBase = ({
count,
page,
limit,
onChange,
}: PaginationWidgetBaseProps): JSX.Element | null => {
currentPage,
pageSize,
totalRecords,
onPageChange,
}: PaginationWidgetBaseProps) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const totalPages = Math.ceil(totalRecords / pageSize);
const numPages = Math.ceil(count / limit);
if (numPages < 2) {
if (totalPages < 2) {
return null;
}
const isFirstPage = page <= 1;
const isLastPage = page >= numPages;
const onFirstPage = currentPage <= 1;
const onLastPage = currentPage >= totalPages;
return (
<div
@@ -38,59 +39,88 @@ export const PaginationWidgetBase = ({
display: "flex",
flexDirection: "row",
padding: "20px",
columnGap: "6px",
}}
>
<Button
css={{
marginRight: 4,
}}
<PaginationNavButton
disabledMessage="You are already on the first page"
disabled={onFirstPage}
aria-label="Previous page"
disabled={isFirstPage}
onClick={() => {
if (!isFirstPage) {
onChange(page - 1);
if (!onFirstPage) {
onPageChange(currentPage - 1);
}
}}
>
<KeyboardArrowLeft />
</Button>
{isMobile ? (
<PageButton activePage={page} page={page} numPages={numPages} />
) : (
buildPagedList(numPages, page).map((pageItem) => {
if (pageItem === "left" || pageItem === "right") {
return (
<PageButton
key={pageItem}
activePage={page}
placeholder="..."
disabled
/>
);
}
</PaginationNavButton>
return (
<PageButton
key={pageItem}
page={pageItem}
activePage={page}
numPages={numPages}
onPageClick={() => onChange(pageItem)}
/>
);
})
{isMobile ? (
<NumberedPageButton
highlighted
pageNumber={currentPage}
totalPages={totalPages}
/>
) : (
<PaginationRow
currentPage={currentPage}
totalPages={totalPages}
onChange={onPageChange}
/>
)}
<Button
<PaginationNavButton
disabledMessage="You're already on the last page"
disabled={onLastPage}
aria-label="Next page"
disabled={isLastPage}
onClick={() => {
if (!isLastPage) {
onChange(page + 1);
if (!onLastPage) {
onPageChange(currentPage + 1);
}
}}
>
<KeyboardArrowRight />
</Button>
</PaginationNavButton>
</div>
);
};
type PaginationRowProps = {
currentPage: number;
totalPages: number;
onChange: (newPage: number) => void;
};
function PaginationRow({
currentPage,
totalPages,
onChange,
}: PaginationRowProps) {
const pageInfo = buildPagedList(totalPages, currentPage);
const pagesOmitted = totalPages - pageInfo.length - 1;
return (
<>
{pageInfo.map((pageEntry) => {
if (pageEntry === "left" || pageEntry === "right") {
return (
<PlaceholderPageButton
key={pageEntry}
pagesOmitted={pagesOmitted}
/>
);
}
return (
<NumberedPageButton
key={pageEntry}
pageNumber={pageEntry}
totalPages={totalPages}
highlighted={pageEntry === currentPage}
onClick={() => onChange(pageEntry)}
/>
);
})}
</>
);
}
@@ -1,40 +1,106 @@
import { buildPagedList, getOffset } from "./utils";
import { buildPagedList, getOffset, isNonInitialPage } from "./utils";
describe("buildPagedList", () => {
it.each<{
numPages: number;
activePage: number;
expected: (string | number)[];
}>([
{ numPages: 7, activePage: 1, expected: [1, 2, 3, 4, 5, 6, 7] },
{ numPages: 17, activePage: 1, expected: [1, 2, 3, 4, 5, "right", 17] },
{
numPages: 17,
activePage: 9,
expected: [1, "left", 8, 9, 10, "right", 17],
},
{
numPages: 17,
activePage: 17,
expected: [1, "left", 13, 14, 15, 16, 17],
},
])(
`buildPagedList($numPages, $activePage)`,
({ numPages, activePage, expected }) => {
expect(buildPagedList(numPages, activePage)).toEqual(expected);
},
);
describe(buildPagedList.name, () => {
it("has no placeholder entries when there are seven or fewer pages", () => {
for (let i = 1; i <= 7; i++) {
const expectedResult: number[] = [];
for (let j = 1; j <= i; j++) {
expectedResult.push(j);
}
expect(buildPagedList(i, i)).toEqual(expectedResult);
}
});
it("has 'right' placeholder for long lists when active page is near beginning", () => {
expect(buildPagedList(17, 1)).toEqual([1, 2, 3, 4, 5, "right", 17]);
});
it("has 'left' placeholder for long lists when active page is near end", () => {
expect(buildPagedList(17, 17)).toEqual([1, "left", 13, 14, 15, 16, 17]);
});
it("has both placeholders for long lists when active page is in the middle", () => {
expect(buildPagedList(17, 9)).toEqual([1, "left", 8, 9, 10, "right", 17]);
});
it("produces an empty array when there are no pages", () => {
expect(buildPagedList(0, 0)).toEqual([]);
});
it("makes sure all values are unique (for React rendering keys)", () => {
type TestEntry = [numPages: number, activePage: number];
const testData: TestEntry[] = [
[0, 0],
[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5],
[6, 6],
[7, 7],
[10, 3],
[7, 1],
[17, 1],
[17, 9],
];
for (const [numPages, activePage] of testData) {
const result = buildPagedList(numPages, activePage);
const uniqueCount = new Set(result).size;
expect(uniqueCount).toEqual(result.length);
}
});
});
describe("getOffset", () => {
describe(getOffset.name, () => {
it("returns 0 on page 1", () => {
const page = 1;
const limit = 10;
expect(getOffset(page, limit)).toEqual(0);
});
it("returns the limit on page 2", () => {
const page = 2;
const limit = 10;
expect(getOffset(page, limit)).toEqual(limit);
it("Returns the results for page 1 when input is invalid", () => {
const inputs = [0, -1, -Infinity, NaN, Infinity, 3.6, 7.4545435];
for (const input of inputs) {
expect(getOffset(input, 10)).toEqual(0);
}
});
it("Returns offset based on the current page for all valid pages after 1", () => {
expect(getOffset(2, 10)).toEqual(10);
expect(getOffset(3, 10)).toEqual(20);
expect(getOffset(4, 45)).toEqual(135);
});
});
describe(isNonInitialPage.name, () => {
it("Should detect your page correctly if input is set and valid", () => {
const params1 = new URLSearchParams({ page: String(1) });
expect(isNonInitialPage(params1)).toBe(false);
const inputs = [2, 50, 500, 3722];
for (const input of inputs) {
const params = new URLSearchParams({ page: String(input) });
expect(isNonInitialPage(params)).toBe(true);
}
});
it("Should act as if you are on page 1 if input is set but invalid", () => {
const inputs = ["", Infinity, -Infinity, NaN, 3.74, -3];
for (const input of inputs) {
const params = new URLSearchParams({ page: String(input) });
expect(isNonInitialPage(params)).toBe(false);
}
});
it("Should act as if you are on page 1 if input does not exist", () => {
const params = new URLSearchParams();
expect(isNonInitialPage(params)).toBe(false);
});
});
+50 -43
View File
@@ -1,67 +1,74 @@
/**
* Generates a ranged array with an option to step over values.
* Shamelessly stolen from:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range}
*/
const range = (start: number, stop: number, step = 1) =>
Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
export const DEFAULT_RECORDS_PER_PAGE = 25;
// Number of pages to the left or right of the current page selection.
// Number of pages to display on either side of the current page selection
const PAGE_NEIGHBORS = 1;
// Number of pages displayed for cases where there are multiple ellipsis showing. This can be
// thought of as the minimum number of page numbers to display when multiple ellipsis are showing.
// Minimum number of pages to display at all times (assuming there are enough
// pages). Needed to handle case where the left and right placeholder/ellipsis
// elements are both showing
const PAGES_TO_DISPLAY = PAGE_NEIGHBORS * 2 + 3;
// Total page blocks(page numbers or ellipsis) displayed, including the maximum number of ellipsis (2).
// This gives us maximum number of 7 page blocks to be displayed when the page neighbors value is 1.
const NUM_PAGE_BLOCKS = PAGES_TO_DISPLAY + 2;
// Total number of pagination elements to display (accounting for visible pages
// and up to two ellipses placeholders). With 1 page neighbor on either side,
// the UI will show up to seven elements total
const TOTAL_PAGE_BLOCKS = PAGES_TO_DISPLAY + 2;
/**
* Builds a list of pages based on how many pages exist and where the user is in their navigation of those pages.
* List result is used to from the buttons that make up the Pagination Widget
* Takes the total number of pages from a pagination result, and truncates it
* into a UI-friendly list.
*/
export const buildPagedList = (
numPages: number,
activePage: number,
): ("left" | "right" | number)[] => {
if (numPages > NUM_PAGE_BLOCKS) {
let pages = [];
const leftBound = activePage - PAGE_NEIGHBORS;
const rightBound = activePage + PAGE_NEIGHBORS;
const beforeLastPage = numPages - 1;
const startPage = leftBound > 2 ? leftBound : 2;
const endPage = rightBound < beforeLastPage ? rightBound : beforeLastPage;
pages = range(startPage, endPage);
const singleSpillOffset = PAGES_TO_DISPLAY - pages.length - 1;
const hasLeftOverflow = startPage > 2;
const hasRightOverflow = endPage < beforeLastPage;
const leftOverflowPage = "left" as const;
const rightOverflowPage = "right" as const;
if (hasLeftOverflow && !hasRightOverflow) {
const extraPages = range(startPage - singleSpillOffset, startPage - 1);
pages = [leftOverflowPage, ...extraPages, ...pages];
} else if (!hasLeftOverflow && hasRightOverflow) {
const extraPages = range(endPage + 1, endPage + singleSpillOffset);
pages = [...pages, ...extraPages, rightOverflowPage];
} else if (hasLeftOverflow && hasRightOverflow) {
pages = [leftOverflowPage, ...pages, rightOverflowPage];
}
return [1, ...pages, numPages];
if (numPages <= TOTAL_PAGE_BLOCKS) {
return range(1, numPages);
}
return range(1, numPages);
const pageBeforeLast = numPages - 1;
const startPage = Math.max(activePage - PAGE_NEIGHBORS, 2);
const endPage = Math.min(activePage + PAGE_NEIGHBORS, pageBeforeLast);
let pages: ReturnType<typeof buildPagedList> = range(startPage, endPage);
const singleSpillOffset = PAGES_TO_DISPLAY - pages.length - 1;
const hasLeftOverflow = startPage > 2;
const hasRightOverflow = endPage < pageBeforeLast;
if (hasLeftOverflow && !hasRightOverflow) {
const extraPages = range(startPage - singleSpillOffset, startPage - 1);
pages = ["left", ...extraPages, ...pages];
} else if (!hasLeftOverflow && hasRightOverflow) {
const extraPages = range(endPage + 1, endPage + singleSpillOffset);
pages = [...pages, ...extraPages, "right"];
} else if (hasLeftOverflow && hasRightOverflow) {
pages = ["left", ...pages, "right"];
}
return [1, ...pages, numPages];
};
// pages count from 1
export const getOffset = (page: number, limit: number): number =>
(page - 1) * limit;
/**
* Calculates the current offset from the start of a paginated dataset
*/
export const getOffset = (page: number, pageSize: number): number => {
const pageIsValid = Number.isInteger(page) && page >= 1;
const pageToUse = pageIsValid ? page : 1;
export const nonInitialPage = (searchParams: URLSearchParams): boolean => {
return (pageToUse - 1) * pageSize;
};
export const isNonInitialPage = (searchParams: URLSearchParams): boolean => {
const page = searchParams.get("page");
const numberPage = page ? Number(page) : 1;
return numberPage > 1;
const conversion = Number(page);
return Number.isInteger(conversion) && conversion > 1;
};
+2 -2
View File
@@ -1,6 +1,6 @@
import {
DEFAULT_RECORDS_PER_PAGE,
nonInitialPage,
isNonInitialPage,
} from "components/PaginationWidget/utils";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { FC } from "react";
@@ -73,7 +73,7 @@ const AuditPage: FC = () => {
page={pagination.page}
limit={pagination.limit}
onPageChange={pagination.goToPage}
isNonInitialPage={nonInitialPage(searchParamsResult[0])}
isNonInitialPage={isNonInitialPage(searchParamsResult[0])}
isAuditLogVisible={isAuditLogVisible}
error={error}
filterProps={{
+4 -4
View File
@@ -133,10 +133,10 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
{count !== undefined && (
<PaginationWidgetBase
count={count}
limit={limit}
onChange={onPageChange}
page={page}
totalRecords={count}
pageSize={limit}
onPageChange={onPageChange}
currentPage={page}
/>
)}
</Cond>
+2 -2
View File
@@ -28,7 +28,7 @@ import { prepareQuery } from "utils/filters";
import { Helmet } from "react-helmet-async";
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
import { nonInitialPage } from "components/PaginationWidget/utils";
import { isNonInitialPage } from "components/PaginationWidget/utils";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { ResetPasswordDialog } from "./ResetPasswordDialog";
import { pageTitle } from "utils/page";
@@ -157,7 +157,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
isLoading={isLoading}
canEditUsers={canEditUsers}
canViewActivity={entitlements.features.audit_log.enabled}
isNonInitialPage={nonInitialPage(searchParams)}
isNonInitialPage={isNonInitialPage(searchParams)}
actorID={me.id}
filterProps={{
filter: useFilterResult,
+4 -4
View File
@@ -102,10 +102,10 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
{count !== undefined && (
<PaginationWidgetBase
count={count}
limit={limit}
onChange={onPageChange}
page={page}
totalRecords={count}
pageSize={limit}
onPageChange={onPageChange}
currentPage={page}
/>
)}
</>
@@ -208,10 +208,10 @@ export const WorkspacesPageView = ({
{count !== undefined && (
<PaginationWidgetBase
count={count}
limit={limit}
onChange={onPageChange}
page={page}
totalRecords={count}
pageSize={limit}
onPageChange={onPageChange}
currentPage={page}
/>
)}
</Margins>