Skip to content

Commit

Permalink
feat(a11y): Added skip link, and aria-live regions for announcing dic…
Browse files Browse the repository at this point in the history
…e rolls
  • Loading branch information
scottbenton committed Oct 28, 2023
1 parent d9984db commit 6c81693
Show file tree
Hide file tree
Showing 30 changed files with 431 additions and 50 deletions.
3 changes: 3 additions & 0 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useListenToCampaigns } from "stores/campaign/useListenToCampaigns";
import { useListenToAuth } from "stores/auth/useListenToAuth";
import { useListenToWorlds } from "stores/world/useListenToWorlds";
import { useListenToOracleSettings } from "stores/settings/useListenToOracleSettings";
import { useListenToAccessibilitySettings } from "stores/accessibilitySettings/useListenToAccessibilitySettings";

const router = createBrowserRouter(
createRoutesFromElements(
Expand Down Expand Up @@ -108,6 +109,8 @@ const router = createBrowserRouter(
export function Router() {
useListenToAuth();

useListenToAccessibilitySettings();

useListenToCharacters();
useListenToCampaigns();
useListenToWorlds();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
doc,
DocumentReference,
} from "firebase/firestore";
import { OracleSettings } from "types/UserSettings.type";
import { OracleSettings } from "types/UserOracleSettings.type";

export function constructUserSettingsCollectionPath(userId: string) {
return `/users/${userId}/settings`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { onSnapshot, setDoc } from "firebase/firestore";
import { decodeDataswornId } from "functions/dataswornIdEncoder";
import { OracleSettings } from "types/UserSettings.type";
import { OracleSettings } from "types/UserOracleSettings.type";
import { getUserOracleSettingsDoc } from "./_getRef";

export function listenToOracleSettings(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { arrayRemove, arrayUnion, setDoc, updateDoc } from "firebase/firestore";
import { setDoc, updateDoc } from "firebase/firestore";
import { getCampaignSettingsDoc, getCharacterSettingsDoc } from "./_getRef";
import { createApiFunction } from "api-calls/createApiFunction";
import { SettingsDoc } from "types/Settings.type";
Expand Down
30 changes: 30 additions & 0 deletions src/api-calls/user/settings/_getRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { firestore } from "config/firebase.config";
import {
CollectionReference,
DocumentReference,
collection,
doc,
} from "firebase/firestore";
import { IAccessibilitySettings } from "types/UserAccessibilitySettings.type";

export function constructUserSettingsCollectionPath(userId: string) {
return `/users/${userId}/settings`;
}

export function constructUserAccessibilitySettingsDocPath(userId: string) {
return `/users/${userId}/settings/accessibility`;
}

export function getUserSettingsCollectionRef(userId: string) {
return collection(
firestore,
constructUserSettingsCollectionPath(userId)
) as CollectionReference<any>;
}

export function getUserAccessibilitySettingsDoc(userId: string) {
return doc(
firestore,
constructUserAccessibilitySettingsDocPath(userId)
) as DocumentReference<IAccessibilitySettings>;
}
12 changes: 12 additions & 0 deletions src/api-calls/user/settings/listenToAccessibilitySettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { onSnapshot } from "firebase/firestore";
import { IAccessibilitySettings } from "types/UserAccessibilitySettings.type";
import { getUserAccessibilitySettingsDoc } from "./_getRef";

export const listenToAccessibilitySettings = (
uid: string,
onSettings: (settings: IAccessibilitySettings) => void
) => {
return onSnapshot(getUserAccessibilitySettingsDoc(uid), (snapshot) => {
onSettings(snapshot.data() ?? {});
});
};
14 changes: 14 additions & 0 deletions src/api-calls/user/settings/updateAccessibilitySettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiFunction } from "api-calls/createApiFunction";
import { setDoc, updateDoc } from "firebase/firestore";
import { IAccessibilitySettings } from "types/UserAccessibilitySettings.type";
import { getUserAccessibilitySettingsDoc } from "./_getRef";

export const updateAccessibilitySettings: ApiFunction<
{ uid: string; settings: Partial<IAccessibilitySettings> },
void
> = (params) => {
const { uid, settings } = params;
return setDoc(getUserAccessibilitySettingsDoc(uid), settings, {
merge: true,
});
};
8 changes: 4 additions & 4 deletions src/components/features/characters/StatComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function StatComponent(props: StatComponentProps) {
["background-color", "border-color", "outline-width"],
{ duration: theme.transitions.duration.shorter }
),
"&>p[id$='-label']": {
"&>[id$='-label']": {
transition: theme.transitions.create(
["background-color", "color"],
{ duration: theme.transitions.duration.shorter }
Expand All @@ -82,7 +82,7 @@ export function StatComponent(props: StatComponentProps) {
updateTrack || disableRoll
? {}
: {
"&>p[id$='-label']": {
"&>[id$='-label']": {
backgroundColor: theme.palette.background.paperInlayDarker,
color: theme.palette.text.primary,
},
Expand Down Expand Up @@ -114,7 +114,7 @@ export function StatComponent(props: StatComponentProps) {
display={"block"}
textAlign={"center"}
variant={"subtitle1"}
component={"p"}
component={"span"}
lineHeight={1}
id={`${label}-label`}
>
Expand All @@ -135,7 +135,7 @@ export function StatComponent(props: StatComponentProps) {
updateTrack ? { lineHeight: "1.5rem" } : {},
]}
variant={"h6"}
component={"p"}
component={"span"}
textAlign={"center"}
>
<Typography component={"span"} variant={"body1"} mr={0.2}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
FormControlLabel,
} from "@mui/material";
import { DialogTitleWithCloseButton } from "../DialogTitleWithCloseButton";
import { useStore } from "stores/store";

export interface AccessibilitySettingsDialogProps {
open?: boolean;
onClose: () => void;
}

export function AccessibilitySettingsDialog(
props: AccessibilitySettingsDialogProps
) {
const { open, onClose } = props;

const accessibilitySettings = useStore(
(store) => store.accessibilitySettings.settings
);

const updateSettings = useStore(
(store) => store.accessibilitySettings.updateSettings
);

return (
<Dialog open={open ?? false} onClose={onClose}>
<DialogTitleWithCloseButton onClose={onClose}>
Accessibility Settings
</DialogTitleWithCloseButton>
<DialogContent>
<Box sx={{ pt: 1 }}>
<FormControlLabel
control={
<Checkbox
checked={accessibilitySettings.verboseRollResults ?? false}
onChange={(evt, value) =>
updateSettings({ verboseRollResults: value }).catch(() => {})
}
/>
}
label={"Announce rolls and modifiers when rolling?"}
sx={{ textTransform: "capitalize", marginRight: 3 }}
/>
</Box>
</DialogContent>
<DialogActions>
<Button variant={"contained"} onClick={onClose}>
Done
</Button>
</DialogActions>
</Dialog>
);
}
69 changes: 53 additions & 16 deletions src/components/shared/DialogTitleWithCloseButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Box, DialogTitle, IconButton, Typography } from "@mui/material";
import { PropsWithChildren, ReactNode } from "react";
import {
PropsWithChildren,
ReactNode,
useEffect,
useRef,
useState,
} from "react";
import CloseIcon from "@mui/icons-material/Close";
import { useScreenReaderAnnouncement } from "providers/ScreenReaderAnnouncementProvider";

export interface DialogTitleWithCloseButtonProps extends PropsWithChildren {
onClose: () => void;
Expand All @@ -11,22 +18,52 @@ export function DialogTitleWithCloseButton(
) {
const { children, actions, onClose } = props;

const { announcement } = useScreenReaderAnnouncement();

const [changedAnnouncement, setChangedAnnouncement] = useState<
string | undefined
>();
const isFirstLoadRef = useRef(true);
useEffect(() => {
if (!isFirstLoadRef.current) {
setChangedAnnouncement(announcement);
}
isFirstLoadRef.current = false;
}, [announcement]);

return (
<DialogTitle
display={"flex"}
alignItems={"center"}
justifyContent={"space-between"}
component={"div"}
>
<Typography variant={"h6"} component={"h2"}>
{children}
</Typography>
<Box display={"flex"} alignItems={"center"} flexShrink={0} ml={1}>
{actions}
<IconButton aria-label={"Close Dialog"} onClick={() => onClose()}>
<CloseIcon />
</IconButton>
<>
<Box
position={"absolute"}
width={1}
height={1}
padding={0}
m={-1}
overflow={"hidden"}
whiteSpace={"nowrap"}
border={0}
sx={{ clip: "rect(0, 0, 0, 0)" }}
aria-live={"polite"}
id={"live-dialog-announcement"}
>
{changedAnnouncement}
</Box>
</DialogTitle>
<DialogTitle
display={"flex"}
alignItems={"center"}
justifyContent={"space-between"}
component={"div"}
>
<Typography variant={"h6"} component={"h2"}>
{children}
</Typography>
<Box display={"flex"} alignItems={"center"} flexShrink={0} ml={1}>
{actions}
<IconButton aria-label={"Close Dialog"} onClick={() => onClose()}>
<CloseIcon />
</IconButton>
</Box>
</DialogTitle>
</>
);
}
20 changes: 20 additions & 0 deletions src/components/shared/Layout/HeaderMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
CHARACTER_ROUTES,
constructCharacterPath,
} from "pages/Character/routes";
import AccessibilityIcon from "@mui/icons-material/AccessibilityNew";
import { AccessibilitySettingsDialog } from "../AccessibilitySettingsDialog/AccessibilitySettingsDialog";

export function HeaderMenu() {
const userId = useStore((store) => store.auth.uid);
Expand All @@ -34,6 +36,9 @@ export function HeaderMenu() {
const { gameSystem, chooseGameSystem } = useGameSystem();
const isLocal = getIsLocalEnvironment();

const [accessibilitySettingsOpen, setAccessibilitySettingsOpen] =
useState(false);

return (
<>
<ButtonBase
Expand All @@ -50,6 +55,17 @@ export function HeaderMenu() {
onClose={() => setMenuOpen(false)}
anchorEl={anchorRef.current}
>
<MenuItem
onClick={() => {
setMenuOpen(false);
setAccessibilitySettingsOpen(true);
}}
>
<ListItemIcon>
<AccessibilityIcon />
</ListItemIcon>
<ListItemText>Accessibility Settings</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
setMenuOpen(false);
Expand Down Expand Up @@ -99,6 +115,10 @@ export function HeaderMenu() {
</MenuItem>
)}
</Menu>
<AccessibilitySettingsDialog
open={accessibilitySettingsOpen}
onClose={() => setAccessibilitySettingsOpen(false)}
/>
</>
);
}
4 changes: 3 additions & 1 deletion src/components/shared/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { sendPageViewEvent } from "lib/analytics.lib";
import { UserNameDialog } from "components/shared/UserNameDialog";
import { useStore } from "stores/store";
import { AUTH_STATE } from "stores/auth/auth.slice.type";
import { SkipToContentButton } from "./SkipToContentButton";

export interface LayoutProps {}

Expand Down Expand Up @@ -68,8 +69,9 @@ export function Layout(props: LayoutProps) {
backgroundColor: theme.palette.background.default,
})}
>
<SkipToContentButton />
<Header />
<main>
<main id={"main-content"}>
<Outlet />
</main>
<Footer />
Expand Down
24 changes: 24 additions & 0 deletions src/components/shared/Layout/SkipToContentButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Box, Button } from "@mui/material";

export function SkipToContentButton() {
return (
<Button
LinkComponent={"a"}
variant={"contained"}
color={"primary"}
href={"#main-content"}
sx={{
position: "absolute",
top: 0,
left: 0,
transform: "translate(-100%)",
zIndex: 1000,
"&:focus": {
transform: "translate(0%)",
},
}}
>
Skip to Content
</Button>
);
}
Loading

0 comments on commit 6c81693

Please sign in to comment.