Skip to content

Commit c630588

Browse files
committed
Use useId to generate unique ids for accessibility attributes.
1 parent 644b804 commit c630588

12 files changed

+53
-37
lines changed

__tests__/src/components/WindowTopMenuButton.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ describe('WindowTopMenuButton', () => {
4747
render(<Subject />);
4848
await user.click(screen.getByLabelText('Window views & thumbnail display'));
4949
// when 'open' is true, aria-owns is set to the id of the window
50-
expect(screen.getByLabelText('Window views & thumbnail display')).toHaveAttribute('aria-owns', 'window-menu_xyz'); // eslint-disable-line testing-library/no-node-access
50+
expect(screen.getByLabelText('Window views & thumbnail display')).toHaveAttribute('aria-owns'); // eslint-disable-line testing-library/no-node-access
5151
});
5252
});

src/components/ManifestRelatedLinks.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Typography from '@mui/material/Typography';
44
import Link from '@mui/material/Link';
55
import classNames from 'classnames';
66
import { useTranslation } from 'react-i18next';
7+
import { useId } from 'react';
78
import CollapsibleSection from '../containers/CollapsibleSection';
89
import ns from '../config/css-ns';
910
import { PluginHook } from './PluginHook';
@@ -28,18 +29,19 @@ export function ManifestRelatedLinks({
2829
...rest
2930
}) {
3031
const { t } = useTranslation();
32+
const titleId = useId();
33+
3134
const pluginProps = {
3235
homepage, id, manifestUrl, related, renderings, seeAlso, t, ...rest,
3336
};
3437

3538
return (
3639
<CollapsibleSection
37-
id={`${id}-related`}
40+
aria-labelledby={titleId}
3841
label={t('related')}
3942
>
4043
<Typography
41-
aria-labelledby={`${id}-related ${id}-related-heading`}
42-
id={`${id}-related-heading`}
44+
id={titleId}
4345
variant="h4"
4446
component="h5"
4547
>

src/components/SearchHit.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo } from 'react';
1+
import { useEffect, useId, useMemo } from 'react';
22
import { useEffectEvent } from 'use-effect-event';
33
import PropTypes from 'prop-types';
44
import Button from '@mui/material/Button';
@@ -86,11 +86,12 @@ export function SearchHit({
8686
);
8787
});
8888

89+
const canvasLabelHtmlId = useId();
90+
8991
if (focused && !selected) return null;
9092

9193
const renderedHit = focused ? hit : hit && truncatedHit;
9294
const truncated = hit && (renderedHit.before !== hit.before || renderedHit.after !== hit.after);
93-
const canvasLabelHtmlId = `${companionWindowId}-${index}`;
9495
const ownerState = {
9596
adjacent, focused, selected, windowSelected,
9697
};

src/components/WindowListButton.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useId, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import BookmarksIcon from '@mui/icons-material/BookmarksSharp';
44
import { useTranslation } from 'react-i18next';
@@ -11,6 +11,7 @@ import MiradorMenuButton from '../containers/MiradorMenuButton';
1111
export function WindowListButton({ disabled = false, windowCount }) {
1212
const { t } = useTranslation();
1313
const [windowListAnchor, setWindowListAnchor] = useState(null);
14+
const id = useId();
1415

1516
/** */
1617
const handleClose = () => { setWindowListAnchor(null); };
@@ -22,7 +23,7 @@ export function WindowListButton({ disabled = false, windowCount }) {
2223
<MiradorMenuButton
2324
aria-haspopup="true"
2425
aria-label={t('listAllOpenWindows')}
25-
aria-owns={windowListAnchor ? 'window-list' : null}
26+
aria-owns={windowListAnchor ? id : null}
2627
selected={Boolean(windowListAnchor)}
2728
disabled={disabled}
2829
badge
@@ -42,7 +43,7 @@ export function WindowListButton({ disabled = false, windowCount }) {
4243
{Boolean(windowListAnchor) && (
4344
<WindowList
4445
anchorEl={windowListAnchor}
45-
id="window-list"
46+
id={id}
4647
open={Boolean(windowListAnchor)}
4748
handleClose={handleClose}
4849
/>

src/components/WindowSideBarCanvasPanel.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useRef } from 'react';
1+
import { useId, useRef } from 'react';
22
import PropTypes from 'prop-types';
33
import { styled } from '@mui/material/styles';
44
import Tabs from '@mui/material/Tabs';
@@ -49,6 +49,7 @@ export function WindowSideBarCanvasPanel({
4949
}) {
5050
const { t } = useTranslation();
5151
const containerRef = useRef();
52+
const tabPanelId = useId();
5253

5354
/** */
5455
const handleSequenceChange = (event) => {
@@ -126,15 +127,15 @@ export function WindowSideBarCanvasPanel({
126127
textColor="primary"
127128
>
128129
{showToc && (
129-
<Tooltip title={t('tableOfContentsList')} value="tableOfContents"><Tab sx={{ minWidth: 'auto' }} value="tableOfContents" aria-label={t('tableOfContentsList')} aria-controls={`tab-panel-${id}`} icon={<TocIcon style={{ transform: 'scale(-1, 1)' }} />} /></Tooltip>
130+
<Tooltip title={t('tableOfContentsList')} value="tableOfContents"><Tab sx={{ minWidth: 'auto' }} value="tableOfContents" aria-label={t('tableOfContentsList')} aria-controls={tabPanelId} icon={<TocIcon style={{ transform: 'scale(-1, 1)' }} />} /></Tooltip>
130131
)}
131-
<Tooltip title={t('itemList')} value="item"><Tab sx={{ minWidth: 'auto' }} value="item" aria-label={t('itemList')} aria-controls={`tab-panel-${id}`} icon={<ItemListIcon />} /></Tooltip>
132-
<Tooltip title={t('thumbnailList')} value="thumbnail"><Tab sx={{ minWidth: 'auto' }} value="thumbnail" aria-label={t('thumbnailList')} aria-controls={`tab-panel-${id}`} icon={<ThumbnailListIcon />} /></Tooltip>
132+
<Tooltip title={t('itemList')} value="item"><Tab sx={{ minWidth: 'auto' }} value="item" aria-label={t('itemList')} aria-controls={tabPanelId} icon={<ItemListIcon />} /></Tooltip>
133+
<Tooltip title={t('thumbnailList')} value="thumbnail"><Tab sx={{ minWidth: 'auto' }} value="thumbnail" aria-label={t('thumbnailList')} aria-controls={tabPanelId} icon={<ThumbnailListIcon />} /></Tooltip>
133134
</Tabs>
134135
</>
135136
)}
136137
>
137-
<div id={`tab-panel-${id}`}>
138+
<div id={tabPanelId}>
138139
{ collection && (
139140
<Button
140141
fullWidth

src/components/WindowTopBarPluginMenu.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext, useState } from 'react';
1+
import { useContext, useId, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import MoreVertIcon from '@mui/icons-material/MoreVertSharp';
44
import Menu from '@mui/material/Menu';
@@ -18,6 +18,7 @@ export function WindowTopBarPluginMenu({
1818
const pluginProps = arguments[0]; // eslint-disable-line prefer-rest-params
1919
const [anchorEl, setAnchorEl] = useState(null);
2020
const [open, setOpen] = useState(false);
21+
const windowPluginMenuId = useId();
2122

2223
/** */
2324
const handleMenuClick = (event) => {
@@ -31,7 +32,6 @@ export function WindowTopBarPluginMenu({
3132
setOpen(false);
3233
};
3334

34-
const windowPluginMenuId = `window-plugin-menu_${windowId}`;
3535
if (!PluginComponents || PluginComponents.length === 0) return null;
3636

3737
return (

src/components/WindowTopMenuButton.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useId, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import { useTranslation } from 'react-i18next';
44
import WindowTopMenu from '../containers/WindowTopMenu';
@@ -11,6 +11,7 @@ export function WindowTopMenuButton({ classes = {}, windowId }) {
1111
const { t } = useTranslation();
1212
const [anchorEl, setAnchorEl] = useState(null);
1313
const [open, setOpen] = useState(false);
14+
const menuId = useId();
1415

1516
/** */
1617
const handleMenuClick = (event) => {
@@ -24,7 +25,6 @@ export function WindowTopMenuButton({ classes = {}, windowId }) {
2425
setOpen(false);
2526
};
2627

27-
const menuId = `window-menu_${windowId}`;
2828
return (
2929
<>
3030
<MiradorMenuButton

src/components/WorkspaceExport.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useId, useState } from 'react';
22
import Button from '@mui/material/Button';
33
import DialogActions from '@mui/material/DialogActions';
44
import DialogTitle from '@mui/material/DialogTitle';
@@ -19,10 +19,11 @@ import { WorkspaceDialog } from './WorkspaceDialog';
1919
/**
2020
*/
2121
export function WorkspaceExport({
22-
children = null, container = null, open = false, handleClose, exportableState,
22+
children = null, container = null, id = undefined, open = false, handleClose, exportableState,
2323
}) {
2424
const { t } = useTranslation();
2525
const [copied, setCopied] = useState(false);
26+
const titleId = useId();
2627
const exportedState = JSON.stringify(exportableState, null, 2);
2728

2829
if (copied) {
@@ -47,15 +48,16 @@ export function WorkspaceExport({
4748

4849
return (
4950
<WorkspaceDialog
50-
id="workspace-export"
51+
aria-labelledby={titleId}
52+
id={id}
5153
container={container}
5254
open={open}
5355
onClose={handleClose}
5456
scroll="paper"
5557
fullWidth
5658
maxWidth="sm"
5759
>
58-
<DialogTitle id="form-dialog-title">
60+
<DialogTitle id={titleId}>
5961
{t('downloadExport')}
6062
</DialogTitle>
6163

@@ -93,5 +95,6 @@ WorkspaceExport.propTypes = {
9395
container: PropTypes.object, // eslint-disable-line react/forbid-prop-types
9496
exportableState: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
9597
handleClose: PropTypes.func.isRequired,
98+
id: PropTypes.string,
9699
open: PropTypes.bool,
97100
};

src/components/WorkspaceImport.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useId, useState } from 'react';
22
import DialogTitle from '@mui/material/DialogTitle';
33
import PropTypes from 'prop-types';
44
import {
@@ -13,10 +13,11 @@ import ScrollIndicatedDialogContent from '../containers/ScrollIndicatedDialogCon
1313
/**
1414
*/
1515
export function WorkspaceImport({
16-
addError, importConfig, classes = {}, handleClose, open = false,
16+
addError, id = undefined, importConfig, classes = {}, handleClose, open = false,
1717
}) {
1818
const { t } = useTranslation();
1919
const [configImportValue, setConfigImportValue] = useState('');
20+
const titleId = useId();
2021

2122
/** */
2223
const handleChange = (event) => {
@@ -37,14 +38,14 @@ export function WorkspaceImport({
3738

3839
return (
3940
<WorkspaceDialog
40-
aria-labelledby="workspace-import-title"
41-
id="workspace-import"
41+
aria-labelledby={titleId}
42+
id={id}
4243
onClose={handleClose}
4344
open={open}
4445
fullWidth
4546
maxWidth="sm"
4647
>
47-
<DialogTitle id="workspace-import-title">
48+
<DialogTitle id={titleId}>
4849
{t('importWorkspace')}
4950
</DialogTitle>
5051
<ScrollIndicatedDialogContent>
@@ -79,6 +80,7 @@ WorkspaceImport.propTypes = {
7980
addError: PropTypes.func.isRequired,
8081
classes: PropTypes.objectOf(PropTypes.string),
8182
handleClose: PropTypes.func.isRequired,
83+
id: PropTypes.string,
8284
importConfig: PropTypes.func.isRequired,
8385
open: PropTypes.bool,
8486
};

src/components/WorkspaceMenuButton.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useId, useState } from 'react';
22
import SettingsIcon from '@mui/icons-material/SettingsSharp';
33
import { useTranslation } from 'react-i18next';
44
import WorkspaceMenu from '../containers/WorkspaceMenu';
@@ -10,6 +10,7 @@ export function WorkspaceMenuButton() {
1010
const { t } = useTranslation();
1111
const [anchorEl, setAnchorEl] = useState(null);
1212
const [open, setOpen] = useState(false);
13+
const id = useId();
1314

1415
/** */
1516
const handleMenuClick = (event) => {
@@ -28,7 +29,7 @@ export function WorkspaceMenuButton() {
2829
<MiradorMenuButton
2930
aria-haspopup="true"
3031
aria-label={t('workspaceMenu')}
31-
aria-owns={open ? 'workspace-menu' : undefined}
32+
aria-owns={open ? id : undefined}
3233
selected={open}
3334
id="menuBtn"
3435
onClick={handleMenuClick}
@@ -37,7 +38,7 @@ export function WorkspaceMenuButton() {
3738
</MiradorMenuButton>
3839
<WorkspaceMenu
3940
anchorEl={anchorEl}
40-
id="workspace-menu"
41+
id={id}
4142
handleClose={handleMenuClose}
4243
open={open}
4344
/>

0 commit comments

Comments
 (0)