Skip to content

Commit aef28e6

Browse files
committed
chore: revamp how nav buttons are defined
1 parent edc0de9 commit aef28e6

File tree

2 files changed

+129
-30
lines changed

2 files changed

+129
-30
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import {
2+
type PropsWithChildren,
3+
type ReactNode,
4+
useEffect,
5+
useState,
6+
} from "react";
7+
8+
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
9+
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
10+
import Button from "@mui/material/Button";
11+
import Tooltip from "@mui/material/Tooltip";
12+
import { useTheme } from "@emotion/react";
13+
14+
type NavProps = {
15+
currentPage: number;
16+
onChange: (newPage: number) => void;
17+
};
18+
19+
export function LeftNavButton({ currentPage, onChange }: NavProps) {
20+
const isFirstPage = currentPage <= 1;
21+
22+
return (
23+
<BaseNavButton
24+
disabledMessage="You are already on the first page"
25+
disabled={isFirstPage}
26+
aria-label="Previous page"
27+
onClick={() => {
28+
if (!isFirstPage) {
29+
onChange(currentPage - 1);
30+
}
31+
}}
32+
>
33+
<KeyboardArrowLeft />
34+
</BaseNavButton>
35+
);
36+
}
37+
38+
export function RightNavButton({ currentPage, onChange }: NavProps) {
39+
const isLastPage = currentPage <= 1;
40+
41+
return (
42+
<BaseNavButton
43+
disabledMessage="You're already on the last page"
44+
disabled={isLastPage}
45+
aria-label="Previous page"
46+
onClick={() => {
47+
if (!isLastPage) {
48+
onChange(currentPage + 1);
49+
}
50+
}}
51+
>
52+
<KeyboardArrowRight />
53+
</BaseNavButton>
54+
);
55+
}
56+
57+
type BaseButtonProps = PropsWithChildren<{
58+
disabled: boolean;
59+
disabledMessage: ReactNode;
60+
onClick: () => void;
61+
"aria-label": string;
62+
63+
disabledMessageTimeout?: number;
64+
}>;
65+
66+
function BaseNavButton({
67+
children,
68+
onClick,
69+
disabled,
70+
disabledMessage,
71+
"aria-label": ariaLabel,
72+
disabledMessageTimeout = 3000,
73+
}: BaseButtonProps) {
74+
const theme = useTheme();
75+
const [showDisabledMessage, setShowDisabledMessage] = useState(false);
76+
77+
useEffect(() => {
78+
if (!showDisabledMessage) {
79+
return;
80+
}
81+
82+
const timeoutId = window.setTimeout(
83+
() => setShowDisabledMessage(false),
84+
disabledMessageTimeout,
85+
);
86+
87+
return () => window.clearTimeout(timeoutId);
88+
}, [showDisabledMessage, disabledMessageTimeout]);
89+
90+
// Using inline state sync to help MUI render accurately more quickly; same
91+
// idea as the useEffect approach, but state changes flush faster
92+
if (!disabled && showDisabledMessage) {
93+
setShowDisabledMessage(false);
94+
}
95+
96+
return (
97+
<Tooltip title={disabledMessage} open={showDisabledMessage}>
98+
<Button
99+
disabled={false}
100+
aria-label={ariaLabel}
101+
css={
102+
disabled && {
103+
borderColor: theme.palette.divider,
104+
color: theme.palette.text.disabled,
105+
cursor: "default",
106+
"&:hover": {
107+
backgroundColor: theme.palette.background.default,
108+
borderColor: theme.palette.divider,
109+
},
110+
}
111+
}
112+
onClick={() => {
113+
if (disabled) {
114+
setShowDisabledMessage(true);
115+
} else {
116+
onClick();
117+
}
118+
}}
119+
>
120+
{children}
121+
</Button>
122+
</Tooltip>
123+
);
124+
}

site/src/components/PaginationWidget/PaginationWidgetBase.tsx

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { type ReactElement } from "react";
2-
import Button from "@mui/material/Button";
31
import useMediaQuery from "@mui/material/useMediaQuery";
4-
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
5-
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
62
import { useTheme } from "@emotion/react";
3+
74
import { PlaceholderPageButton, NumberedPageButton } from "./PageButtons";
85
import { buildPagedList } from "./utils";
6+
import { LeftNavButton, RightNavButton } from "./PaginationNavButtons";
97

108
export type PaginationWidgetBaseProps = {
119
count: number;
@@ -19,7 +17,7 @@ export const PaginationWidgetBase = ({
1917
limit,
2018
onChange,
2119
page: currentPage,
22-
}: PaginationWidgetBaseProps): ReactElement | null => {
20+
}: PaginationWidgetBaseProps) => {
2321
const theme = useTheme();
2422
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
2523
const totalPages = Math.ceil(count / limit);
@@ -28,9 +26,6 @@ export const PaginationWidgetBase = ({
2826
return null;
2927
}
3028

31-
const isFirstPage = currentPage <= 1;
32-
const isLastPage = currentPage >= totalPages;
33-
3429
return (
3530
<div
3631
css={{
@@ -42,17 +37,7 @@ export const PaginationWidgetBase = ({
4237
columnGap: "6px",
4338
}}
4439
>
45-
<Button
46-
aria-label="Previous page"
47-
disabled={isFirstPage}
48-
onClick={() => {
49-
if (!isFirstPage) {
50-
onChange(currentPage - 1);
51-
}
52-
}}
53-
>
54-
<KeyboardArrowLeft />
55-
</Button>
40+
<LeftNavButton currentPage={currentPage} onChange={onChange} />
5641

5742
{isMobile ? (
5843
<NumberedPageButton
@@ -68,17 +53,7 @@ export const PaginationWidgetBase = ({
6853
/>
6954
)}
7055

71-
<Button
72-
aria-label="Next page"
73-
disabled={isLastPage}
74-
onClick={() => {
75-
if (!isLastPage) {
76-
onChange(currentPage + 1);
77-
}
78-
}}
79-
>
80-
<KeyboardArrowRight />
81-
</Button>
56+
<RightNavButton currentPage={currentPage} onChange={onChange} />
8257
</div>
8358
);
8459
};

0 commit comments

Comments
 (0)