diff --git a/src/app/[locale]/_components/DashboardInput.tsx b/src/app/[locale]/_components/DashboardInput.tsx index 1624a9ac..73db5598 100644 --- a/src/app/[locale]/_components/DashboardInput.tsx +++ b/src/app/[locale]/_components/DashboardInput.tsx @@ -28,8 +28,9 @@ export const DashboardInput = () => { } }; + // add main landmark return ( - <> +
@@ -38,6 +39,6 @@ export const DashboardInput = () => { : - +
); }; diff --git a/src/app/[locale]/_components/ManualAasInput.tsx b/src/app/[locale]/_components/ManualAasInput.tsx index 2dc695c0..233749f2 100644 --- a/src/app/[locale]/_components/ManualAasInput.tsx +++ b/src/app/[locale]/_components/ManualAasInput.tsx @@ -76,6 +76,7 @@ export function ManualAasInput(props: { onSubmit: (input: string) => Promise { setInputValue(''); }} + aria-label="Reset input" > @@ -90,6 +91,7 @@ export function ManualAasInput(props: { onSubmit: (input: string) => Promise ); diff --git a/src/app/[locale]/_components/QrScanner.tsx b/src/app/[locale]/_components/QrScanner.tsx index 578f1ba9..b7d9e148 100644 --- a/src/app/[locale]/_components/QrScanner.tsx +++ b/src/app/[locale]/_components/QrScanner.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import ScannerLogo from 'assets/ScannerLogo.svg'; import { Box, CircularProgress, IconButton, useTheme } from '@mui/material'; import { QrStream } from 'app/[locale]/_components/QrStream'; @@ -41,15 +41,22 @@ export function QrScanner(props: { onScan: (scanResult: string) => Promise } }, []); - const expandFromCenter = keyframes` - 0% { - width: 0; - left: 50%; - } - 100% { - width: 100%; - left: 0; + const handleKeyDown = (state: State) => (event: React.KeyboardEvent) => { + if (!(event.key === 'Enter')) { + return; } + setState(state); + }; + + const expandFromCenter = keyframes` + 0% { + width: 0; + left: 50%; + } + 100% { + width: 100%; + left: 0; + } `; interface VideoContainerProps { @@ -109,11 +116,15 @@ export function QrScanner(props: { onScan: (scanResult: string) => Promise {state === State.Stopped && ( setState(State.LoadScanner)} + onKeyDown={handleKeyDown(State.LoadScanner)} padding="50px" position="absolute" height={size} width={size} data-testid="scanner-start" + aria-label="open QR code scanner" + tabIndex={0} + role="button" > @@ -123,11 +134,13 @@ export function QrScanner(props: { onScan: (scanResult: string) => Promise data-testid="scanner-close-button" aria-label="close scanner" onClick={() => setState(State.Stopped)} + onKeyDown={handleKeyDown(State.Stopped)} style={{ position: 'absolute', zIndex: 995, right: 0, }} // Align to the right top corner and render in front of everything + tabIndex={0} > diff --git a/src/app/[locale]/compare/_components/add-aas/AddAasToCompareCard.tsx b/src/app/[locale]/compare/_components/add-aas/AddAasToCompareCard.tsx index 78f62d59..0c447a2f 100644 --- a/src/app/[locale]/compare/_components/add-aas/AddAasToCompareCard.tsx +++ b/src/app/[locale]/compare/_components/add-aas/AddAasToCompareCard.tsx @@ -20,6 +20,10 @@ export function AddAasToCompareCard(props: AddAasToCompareCardProps) { onClick={props.onClick} sx={{ cursor: 'pointer' }} data-testid="add-aas-to-compare-button" + // a custom component acting like a button + role="button" + // make it keyboard accessible + tabIndex={0} > diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index da2bb389..b502015e 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -11,7 +11,7 @@ export type LocalizedIndexLayoutProps = { }; export const metadata: Metadata = { - title: 'Mnestix', + title: 'Dashboard | Mnestix', description: 'AAS made easy', }; diff --git a/src/app/[locale]/list/layout.tsx b/src/app/[locale]/list/layout.tsx new file mode 100644 index 00000000..f524fcc0 --- /dev/null +++ b/src/app/[locale]/list/layout.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from 'next'; +import { ReactNode } from 'react'; + +// add this file to every page for distinctive page titles +export const metadata: Metadata = { + title: 'AAS List | Mnestix', + description: 'A list of AAS', +}; + +interface Props { + children: ReactNode; +} + +export default function ClientLayout({ children }: Props) { + return children; +} diff --git a/src/app/[locale]/settings/layout.tsx b/src/app/[locale]/settings/layout.tsx new file mode 100644 index 00000000..8b7f6691 --- /dev/null +++ b/src/app/[locale]/settings/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from 'next'; +import { ReactNode } from 'react'; + +export const metadata: Metadata = { + title: 'Settings | Mnestix', + description: 'Settings', +}; + +interface Props { + children: ReactNode; +} + +export default function ClientLayout({ children }: Props) { + return children; +} diff --git a/src/app/[locale]/templates/_components/ChooseTemplateItem.tsx b/src/app/[locale]/templates/_components/ChooseTemplateItem.tsx index f6fa6a7f..78819fd6 100644 --- a/src/app/[locale]/templates/_components/ChooseTemplateItem.tsx +++ b/src/app/[locale]/templates/_components/ChooseTemplateItem.tsx @@ -22,7 +22,8 @@ const StyledBox = styled(Box)(({ theme }) => ({ export function ChooseTemplateItem(props: ChooseTemplateItemProps) { return ( - + {/* make the box keyboard accessible */} + diff --git a/src/app/[locale]/templates/_components/CustomTemplateItem.tsx b/src/app/[locale]/templates/_components/CustomTemplateItem.tsx index 72280030..1d1c2d68 100644 --- a/src/app/[locale]/templates/_components/CustomTemplateItem.tsx +++ b/src/app/[locale]/templates/_components/CustomTemplateItem.tsx @@ -82,14 +82,28 @@ export function CustomTemplateItem(props: CustomTemplateItemProps) { } }; + // navigate to template when pressing Enter + const handleOnKeyDown = (event: React.KeyboardEvent) => { + if (!(event.code === 'Enter')) { + return; + } + navigateToTemplate(); + }; + const navigateToTemplate = () => { if (props.item.id) { navigate.push(`/templates/${encodeURIComponent(props.item.id)}`); } }; + return ( <> - + diff --git a/src/app/[locale]/templates/_components/template-edit/TemplateEditTreeItem.tsx b/src/app/[locale]/templates/_components/template-edit/TemplateEditTreeItem.tsx index 4902ff1e..858a2112 100644 --- a/src/app/[locale]/templates/_components/template-edit/TemplateEditTreeItem.tsx +++ b/src/app/[locale]/templates/_components/template-edit/TemplateEditTreeItem.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useState } from 'react'; import { TreeItem, TreeItemContentProps, TreeItemProps, useTreeItemState } from '@mui/x-tree-view'; import clsx from 'clsx'; import Typography from '@mui/material/Typography'; @@ -6,7 +7,6 @@ import { Box, styled } from '@mui/material'; import { TextSnippet } from '@mui/icons-material'; import { MultiplicityEnum } from 'lib/enums/Multiplicity.enum'; import { TemplateEditTreeItemMenu } from './TemplateEditTreeItemMenu'; -import { useState } from 'react'; import { messages } from 'lib/i18n/localization'; import { useIntl } from 'react-intl'; @@ -41,7 +41,7 @@ const StyledTreeItem = styled(TreeItem)(({ theme }) => ({ userSelect: 'none', margin: 0, '&.Mui-focused': { - backgroundColor: 'transparent', + backgroundColor: theme.palette.action.selected, }, '&.Mui-focused:hover': { backgroundColor: theme.palette.action.hover, diff --git a/src/app/[locale]/templates/layout.tsx b/src/app/[locale]/templates/layout.tsx new file mode 100644 index 00000000..ee279ff0 --- /dev/null +++ b/src/app/[locale]/templates/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from 'next'; +import { ReactNode } from 'react'; + +export const metadata: Metadata = { + title: 'Templates | Mnestix', + description: 'A list of templates', +}; + +interface Props { + children: ReactNode; +} + +export default function ClientLayout({ children }: Props) { + return children; +} diff --git a/src/layout/HeaderLogo.tsx b/src/layout/HeaderLogo.tsx index a1df4221..92760c73 100644 --- a/src/layout/HeaderLogo.tsx +++ b/src/layout/HeaderLogo.tsx @@ -1,6 +1,8 @@ import { Box, useTheme } from '@mui/material'; import { useRouter } from 'next/navigation'; import { MnestixLogo } from 'components/basics/MnestixLogo'; +import React from 'react'; + export function HeaderLogo() { const theme = useTheme(); const navigate = useRouter(); @@ -9,8 +11,23 @@ export function HeaderLogo() { navigate.push('/'); }; + const handleKeyDown = (event: React.KeyboardEvent) => { + if (!(event.key === 'Enter')) { + return; + } + goToHome(); + }; + return ( - + {theme?.productLogo?.logo ? ( {'default ) : ( diff --git a/src/layout/menu/MainMenu.tsx b/src/layout/menu/MainMenu.tsx index 3518e540..f2b19b89 100644 --- a/src/layout/menu/MainMenu.tsx +++ b/src/layout/menu/MainMenu.tsx @@ -28,10 +28,11 @@ const StyledDrawer = styled(Drawer)(({ theme }) => ({ '.MuiListItemButton-root': { color: theme.palette.primary.contrastText, '&:hover': { - backgroundColor: alpha(theme.palette.primary.dark, 0.8), + backgroundColor: alpha(theme.palette.primary.dark, 0.6), }, + // do not remove focus indicators '&:focus': { - backgroundColor: 'transparent', + backgroundColor: alpha(theme.palette.primary.dark, 0.8), }, '&.active': { backgroundColor: theme.palette.primary.light, @@ -105,6 +106,7 @@ export default function MainMenu() { label: , icon: , onClick: () => auth.logout(), + onKeyDown: () => auth.logout(), }, ]; @@ -113,6 +115,7 @@ export default function MainMenu() { label: , icon: , onClick: () => auth.login(), + onKeyDown: () => auth.login(), }, { label: , @@ -150,6 +153,7 @@ export default function MainMenu() { sx={{ m: 1, zIndex: 1 }} onClick={handleMenuInteraction(true)} data-testid="header-burgermenu" + aria-label="main menu" > @@ -166,7 +170,7 @@ export default function MainMenu() { )} - + {!useAuthentication || auth.isLoggedIn ? ( <> diff --git a/src/layout/menu/MenuListItem.tsx b/src/layout/menu/MenuListItem.tsx index d88a3c20..56daa091 100644 --- a/src/layout/menu/MenuListItem.tsx +++ b/src/layout/menu/MenuListItem.tsx @@ -8,6 +8,7 @@ export interface MenuListItemProps { label?: React.ReactElement | string; target?: string; onClick?: React.MouseEventHandler; + onKeyDown?: React.KeyboardEventHandler; } export function MenuListItem(props: MenuListItemProps) { @@ -24,10 +25,14 @@ export function MenuListItem(props: MenuListItemProps) { href={props.to} target={props.target} onClick={props.onClick} + // not sure if any of this is actually needed; needs testing + onKeyDown={props.onKeyDown} > {content} ) : ( - {content} + + {content} + ); }