diff --git a/src/components/mui/MuiBaseCustomTheme.js b/src/components/mui/MuiBaseCustomTheme.js index 240b8ade..68c82cb3 100644 --- a/src/components/mui/MuiBaseCustomTheme.js +++ b/src/components/mui/MuiBaseCustomTheme.js @@ -13,6 +13,19 @@ import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; +// pagination controls: grey on hover, darker grey on keyboard focus +const paginationButtonStyles = (theme) => ({ + transition: theme.transitions.create("background-color", { + duration: theme.transitions.duration.shortest + }), + "&:hover": { backgroundColor: theme.palette.action.hover }, + "@media (hover: none)": { "&:hover": { backgroundColor: "transparent" } }, + "&:focus-visible, &:has(:focus-visible)": { + backgroundColor: theme.palette.action.focus, + outline: "none" + } +}); + export const MuiBaseCustomTheme = { // must match the apps' html font-size so MUI's rem values render at their designed px typography: { @@ -21,7 +34,7 @@ export const MuiBaseCustomTheme = { components: { MuiOutlinedInput: { styleOverrides: { - root: { + root: ({ theme }) => ({ "--TextField-brandBorderColor": "#000", "--TextField-brandBorderHoverColor": "#000", "--TextField-brandBorderFocusedColor": "#000", @@ -33,8 +46,26 @@ export const MuiBaseCustomTheme = { }, "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: "var(--TextField-brandBorderFocusedColor)" + }, + // per-page select: keep the border grey on rest, hover and focus + "&.pagination-button": { + ...paginationButtonStyles(theme), + "--TextField-brandBorderColor": theme.palette.divider, + "--TextField-brandBorderHoverColor": theme.palette.divider, + "--TextField-brandBorderFocusedColor": theme.palette.divider, + "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderWidth: 1 } } - } + }) + } + }, + MuiIconButton: { + styleOverrides: { + root: ({ theme }) => ({ "&.pagination-button": paginationButtonStyles(theme) }) + } + }, + MuiTypography: { + styleOverrides: { + root: ({ theme }) => ({ "&.pagination-button": paginationButtonStyles(theme) }) } }, MuiSelect: { diff --git a/src/components/mui/__tests__/mui-table-custom-pagination.test.js b/src/components/mui/__tests__/mui-table-custom-pagination.test.js index 59dccf72..ff75ed3a 100644 --- a/src/components/mui/__tests__/mui-table-custom-pagination.test.js +++ b/src/components/mui/__tests__/mui-table-custom-pagination.test.js @@ -17,7 +17,7 @@ jest.mock("i18n-react/dist/i18n-react", () => ({ })); import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import "@testing-library/jest-dom"; import CustomTablePagination from "../tables/components/CustomTablePagination"; @@ -84,4 +84,16 @@ describe("CustomTablePagination", () => { expect(onPageChange).toHaveBeenCalledWith(4); }); + + test("returns keyboard focus to the page button when the slider collapses", async () => { + setup({ currentPage: 3, totalRows: 100, perPage: 10 }); + const pageButton = screen.getByRole("button", { name: "mui_table.page_of" }); + await userEvent.click(pageButton); + + await userEvent.tab(); + expect(screen.getByRole("slider")).toHaveFocus(); + await userEvent.keyboard("{ArrowRight}"); + + await waitFor(() => expect(pageButton).toHaveFocus()); + }); }); diff --git a/src/components/mui/tables/components/CustomTablePagination.js b/src/components/mui/tables/components/CustomTablePagination.js index 0ea1ce2c..244f3d23 100644 --- a/src/components/mui/tables/components/CustomTablePagination.js +++ b/src/components/mui/tables/components/CustomTablePagination.js @@ -86,12 +86,12 @@ const CustomTablePagination = ({ size="small" value={perPage} onChange={handleRowsPerPageChange} + className="pagination-button" sx={{ height: 40, borderRadius: "20px", minWidth: 80, bgcolor: "background.paper", - "& .MuiOutlinedInput-notchedOutline": { borderColor: "divider" }, "& .MuiSelect-select": { color: "text.secondary" } }} inputProps={{ "aria-label": T.translate("mui_table.rows_per_page") }} diff --git a/src/components/mui/tables/components/SliderPagination.js b/src/components/mui/tables/components/SliderPagination.js index fc80ab61..f00c4418 100644 --- a/src/components/mui/tables/components/SliderPagination.js +++ b/src/components/mui/tables/components/SliderPagination.js @@ -12,7 +12,7 @@ * */ import * as React from "react"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import T from "i18n-react/dist/i18n-react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; @@ -28,6 +28,11 @@ const SliderPagination = ({ currentPage, totalRows, perPage, onPageChange, initi const [expanded, setExpanded] = useState(initialExpanded); const [dragValue, setDragValue] = useState(currentPage); const timeoutRef = useRef(null); + const pageButtonRef = useRef(null); + const sliderBoxRef = useRef(null); + + // cancel a pending collapse if the component unmounts first + useEffect(() => () => clearTimeout(timeoutRef.current), []); const togglePill = () => { if (expanded) { @@ -43,7 +48,11 @@ const SliderPagination = ({ currentPage, totalRows, perPage, onPageChange, initi const onSliderCommit = useCallback( (_ev, value) => { onPageChange(value); - timeoutRef.current = setTimeout(() => setExpanded(false), 150); + timeoutRef.current = setTimeout(() => { + // don't leave keyboard focus on the slider once it's hidden + if (sliderBoxRef.current?.contains(document.activeElement)) pageButtonRef.current?.focus(); + setExpanded(false); + }, 150); }, [onPageChange] ); @@ -79,21 +88,26 @@ const SliderPagination = ({ currentPage, totalRows, perPage, onPageChange, initi onClick={prev} disabled={currentPage <= 1} aria-label={T.translate("mui_table.previous_page")} + disableFocusRipple + className="pagination-button" > {/* explicit px so a host page's root font-size reset can't shrink this */} @@ -130,6 +148,8 @@ const SliderPagination = ({ currentPage, totalRows, perPage, onPageChange, initi onClick={next} disabled={currentPage >= totalPages} aria-label={T.translate("mui_table.next_page")} + disableFocusRipple + className="pagination-button" >