feat: A11y fixes (#2148)

This commit is contained in:
Philip Okugbe
2026-05-04 21:21:37 +01:00
committed by GitHub
parent fe18f22dc6
commit dbe6c2d6ba
62 changed files with 587 additions and 163 deletions
@@ -19,6 +19,7 @@ import { buildPageUrl } from "@/features/page/page.utils.ts";
import { usePageQuery } from "@/features/page/queries/page-query.ts";
import { extractPageSlugId } from "@/lib";
import { useMediaQuery } from "@mantine/hooks";
import { useTranslation } from "react-i18next";
function getTitle(name: string, icon: string) {
if (icon) {
@@ -28,6 +29,7 @@ function getTitle(name: string, icon: string) {
}
export default function Breadcrumb() {
const { t } = useTranslation();
const treeData = useAtomValue(treeDataAtom);
const [breadcrumbNodes, setBreadcrumbNodes] = useState<
SpaceTreeNode[] | null
@@ -80,7 +82,7 @@ export default function Breadcrumb() {
));
const renderAnchor = useCallback(
(node: SpaceTreeNode) => (
(node: SpaceTreeNode, isCurrent = false) => (
<Tooltip label={node.name} key={node.id}>
<Anchor
component={Link}
@@ -89,6 +91,7 @@ export default function Breadcrumb() {
fz="sm"
key={node.id}
className={classes.truncatedText}
aria-current={isCurrent ? "page" : undefined}
>
{getTitle(node.name, node.icon)}
</Anchor>
@@ -115,7 +118,11 @@ export default function Breadcrumb() {
key="hidden-nodes"
>
<Popover.Target>
<ActionIcon color="gray" variant="transparent">
<ActionIcon
color="gray"
variant="transparent"
aria-label={t("Show hidden breadcrumbs")}
>
<IconDots size={20} stroke={2} />
</ActionIcon>
</Popover.Target>
@@ -124,11 +131,13 @@ export default function Breadcrumb() {
</Popover.Dropdown>
</Popover>,
//renderAnchor(secondLastNode),
renderAnchor(lastNode),
renderAnchor(lastNode, true),
];
}
return breadcrumbNodes.map(renderAnchor);
return breadcrumbNodes.map((node, i) =>
renderAnchor(node, i === breadcrumbNodes.length - 1),
);
};
const getMobileBreadcrumbItems = () => {
@@ -144,8 +153,12 @@ export default function Breadcrumb() {
key="mobile-hidden-nodes"
>
<Popover.Target>
<Tooltip label="Breadcrumbs">
<ActionIcon color="gray" variant="transparent">
<Tooltip label={t("Breadcrumbs")}>
<ActionIcon
color="gray"
variant="transparent"
aria-label={t("Breadcrumbs")}
>
<IconCornerDownRightDouble size={20} stroke={2} />
</ActionIcon>
</Tooltip>
@@ -157,16 +170,18 @@ export default function Breadcrumb() {
];
}
return breadcrumbNodes.map(renderAnchor);
return breadcrumbNodes.map((node, i) =>
renderAnchor(node, i === breadcrumbNodes.length - 1),
);
};
return (
<div className={classes.breadcrumbDiv}>
<nav aria-label={t("Breadcrumb")} className={classes.breadcrumbDiv}>
{breadcrumbNodes && (
<Breadcrumbs className={classes.breadcrumbs}>
{isMobile ? getMobileBreadcrumbItems() : getBreadcrumbItems()}
</Breadcrumbs>
)}
</div>
</nav>
);
}
@@ -1,4 +1,4 @@
import { ActionIcon, Group, Menu, Text, Tooltip } from "@mantine/core";
import { ActionIcon, Group, Menu, Text, ThemeIcon, Tooltip } from "@mantine/core";
import {
IconArrowRight,
IconArrowsHorizontal,
@@ -99,6 +99,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
<ActionIcon
variant="subtle"
color="dark"
aria-label={t("Comments")}
onClick={() => toggleAside("comments")}
>
<IconMessage size={20} stroke={2} />
@@ -109,6 +110,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
<ActionIcon
variant="subtle"
color="dark"
aria-label={t("Table of contents")}
onClick={() => toggleAside("toc")}
>
<IconList size={20} stroke={2} />
@@ -205,7 +207,11 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
arrowPosition="center"
>
<Menu.Target>
<ActionIcon variant="subtle" color="dark">
<ActionIcon
variant="subtle"
color="dark"
aria-label={t("Page actions")}
>
<IconDots size={20} />
</ActionIcon>
</Menu.Target>
@@ -416,9 +422,15 @@ function ConnectionWarning() {
openDelay={250}
withArrow
>
<ActionIcon variant="default" c="red" style={{ border: "none" }}>
<ThemeIcon
variant="default"
c="red"
role="status"
aria-label={t("Real-time editor connection lost. Retrying...")}
style={{ border: "none" }}
>
<IconWifiOff size={20} stroke={2} />
</ActionIcon>
</ThemeIcon>
</Tooltip>
);
}
@@ -67,7 +67,7 @@ export default function PageImportModal({
<Modal.Content style={{ overflow: "hidden" }}>
<Modal.Header py={0}>
<Modal.Title fw={500}>{t("Import pages")}</Modal.Title>
<Modal.CloseButton />
<Modal.CloseButton aria-label={t("Close")} />
</Modal.Header>
<Modal.Body>
<ImportFormatSelection spaceId={spaceId} onClose={onClose} />
@@ -332,7 +332,15 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
return (
<>
<SimpleGrid cols={2}>
<FileButton onChange={handleFileUpload} accept=".md" multiple resetRef={markdownFileRef}>
<FileButton
onChange={handleFileUpload}
accept=".md"
multiple
resetRef={markdownFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "Markdown" }),
}}
>
{(props) => (
<Button
justify="start"
@@ -345,7 +353,15 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
)}
</FileButton>
<FileButton onChange={handleFileUpload} accept="text/html" multiple resetRef={htmlFileRef}>
<FileButton
onChange={handleFileUpload}
accept="text/html"
multiple
resetRef={htmlFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "HTML" }),
}}
>
{(props) => (
<Button
justify="start"
@@ -363,6 +379,9 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
accept=".docx"
multiple
resetRef={docxFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "Word (DOCX)" }),
}}
>
{(props) => (
<Tooltip
@@ -387,6 +406,9 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
accept=".pdf"
multiple
resetRef={pdfFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "PDF" }),
}}
>
{(props) => (
<Tooltip
@@ -410,6 +432,9 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
onChange={(file) => handleZipUpload(file, "notion")}
accept="application/zip"
resetRef={notionFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "Notion" }),
}}
>
{(props) => (
<Button
@@ -426,6 +451,9 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
onChange={(file) => handleZipUpload(file, "confluence")}
accept="application/zip"
resetRef={confluenceFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "Confluence" }),
}}
>
{(props) => (
<Tooltip
@@ -463,6 +491,9 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
onChange={(file) => handleZipUpload(file, "generic")}
accept="application/zip"
resetRef={zipFileRef}
inputProps={{
"aria-label": t("Choose {{format}} file", { format: "ZIP" }),
}}
>
{(props) => (
<Group justify="center">
@@ -19,7 +19,7 @@ export default function TrashPageContentModal({
const title = pageTitle || t("Untitled");
return (
<Modal.Root size={1200} opened={opened} onClose={onClose}>
<Modal.Root size={1200} opened={opened} onClose={onClose} aria-label={t("Preview")}>
<Modal.Overlay />
<Modal.Content style={{ overflow: "hidden" }}>
<Modal.Header>
@@ -129,7 +129,7 @@ export default function Trash() {
<Table.Th style={{ whiteSpace: "nowrap" }}>
{t("Deleted at")}
</Table.Th>
<Table.Th></Table.Th>
<Table.Th aria-label={t("Action")} />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
@@ -458,6 +458,8 @@ interface CreateNodeProps {
}
function CreateNode({ node, treeApi, onExpandTree }: CreateNodeProps) {
const { t } = useTranslation();
function handleCreate() {
if (node.data.hasChildren && node.children.length === 0) {
node.toggle();
@@ -475,6 +477,7 @@ function CreateNode({ node, treeApi, onExpandTree }: CreateNodeProps) {
<ActionIcon
variant="transparent"
c="gray"
aria-label={t("Create page")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -591,6 +594,7 @@ function NodeMenu({ node, treeApi, spaceId }: NodeMenuProps) {
<ActionIcon
variant="transparent"
c="gray"
aria-label={t("Page menu")}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -725,6 +729,8 @@ interface PageArrowProps {
}
function PageArrow({ node, onExpandTree }: PageArrowProps) {
const { t } = useTranslation();
useEffect(() => {
if (node.isOpen) {
onExpandTree();
@@ -736,6 +742,8 @@ function PageArrow({ node, onExpandTree }: PageArrowProps) {
size={20}
variant="subtle"
c="gray"
aria-label={node.isOpen ? t("Collapse") : t("Expand")}
aria-expanded={node.isInternal ? node.isOpen : undefined}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();