Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 8 additions & 66 deletions site/src/components/PaginationWidget/PaginationNavButton.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,28 @@
import Tooltip from "@mui/material/Tooltip";
import { Button } from "components/Button/Button";
import {
type ButtonHTMLAttributes,
type ReactNode,
useEffect,
useState,
} from "react";
import type { ButtonHTMLAttributes, ReactNode } from "react";

type PaginationNavButtonProps = Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
| "aria-disabled"
// Need to omit color for MUI compatibility
| "color"
"aria-disabled"
> & {
// Required/narrowed versions of default props
children: ReactNode;
disabled: boolean;
onClick: () => void;
"aria-label": string;

// Bespoke props
disabledMessage: ReactNode;
disabledMessageTimeout?: number;
};

function PaginationNavButtonCore({
export function PaginationNavButton({
onClick,
disabled,
disabledMessage,
disabledMessageTimeout = 3000,
...delegatedProps
}: PaginationNavButtonProps) {
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
variant="outline"
size="icon"
disabled={disabled}
onClick={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}
<Button
variant="outline"
size="icon"
disabled={disabled}
onClick={onClick}
{...delegatedProps}
/>
);
Expand Down
2 changes: 0 additions & 2 deletions site/src/components/PaginationWidget/PaginationWidgetBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const PaginationWidgetBase: FC<PaginationWidgetBaseProps> = ({
return (
<div className="flex flex-row items-center justify-center px-5 gap-x-1.5">
<PaginationNavButton
disabledMessage="You are already on the first page"
disabled={isPrevDisabled}
aria-label="Previous page"
onClick={() => {
Expand All @@ -68,7 +67,6 @@ export const PaginationWidgetBase: FC<PaginationWidgetBaseProps> = ({
)}

<PaginationNavButton
disabledMessage="You are already on the last page"
disabled={isNextDisabled}
aria-label="Next page"
onClick={() => {
Expand Down
Loading