From dbe6c2d6baf60d7d3abfcb191da31b29157f568c Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 4 May 2026 21:21:37 +0100 Subject: [PATCH] feat: A11y fixes (#2148) --- .../public/locales/en-US/translation.json | 28 ++++++- .../src/components/common/avatar-uploader.tsx | 10 +++ apps/client/src/components/common/copy.tsx | 1 + .../src/components/common/recent-changes.tsx | 6 +- .../src/components/common/search-input.tsx | 3 + .../components/icons/icon-people-circle.tsx | 6 +- .../layouts/global/app-shell.module.css | 18 ++++ .../layouts/global/global-app-shell.tsx | 37 ++++++++- .../layouts/global/global-sidebar.module.css | 2 +- .../layouts/global/global-sidebar.tsx | 12 +-- .../src/components/settings/app-version.tsx | 2 +- .../components/settings/settings-sidebar.tsx | 61 +++++++------- .../src/components/ui/custom-avatar.tsx | 34 +++++++- .../ui/destination-picker/page-children.tsx | 13 ++- .../client/src/components/ui/emoji-picker.tsx | 9 +- .../components/ai-chat-sidebar-item.tsx | 1 + .../ee/api-key/components/api-key-table.tsx | 8 +- .../components/page-verification-modal.tsx | 22 ++++- .../ee/scim/components/scim-token-table.tsx | 2 +- .../security/components/sso-provider-list.tsx | 9 +- .../ee/template/components/template-card.tsx | 1 + .../components/template-preview-modal.tsx | 2 +- .../comment/components/comment-dialog.tsx | 1 + .../comment/components/comment-list-item.tsx | 9 ++ .../comment/components/comment-menu.tsx | 6 +- .../editor/components/audio/audio-view.tsx | 4 +- .../components/bubble-menu/color-selector.tsx | 83 +++++++++++++------ .../components/bubble-menu/node-selector.tsx | 3 + .../bubble-menu/text-alignment-selector.tsx | 3 + .../editor/components/drawio/drawio-view.tsx | 8 +- .../components/emoji-menu/emoji-list.tsx | 16 +++- .../components/link/link-editor-panel.tsx | 17 ++++ .../components/mention/mention-list.tsx | 27 +++++- .../editor/components/pdf/pdf-view.tsx | 15 +++- .../search-and-replace-dialog.tsx | 28 ++++++- .../components/slash-menu/command-list.tsx | 40 ++++++--- .../editor/components/status/status-view.tsx | 19 +++++ .../editor/components/video/video-view.tsx | 4 +- .../favorite/components/star-button.tsx | 12 +-- .../group/components/group-action-menu.tsx | 2 +- .../group/components/group-members.tsx | 2 +- .../home/components/created-by-me.tsx | 6 +- .../home/components/favorites-pages.tsx | 6 +- .../components/notification-popover.tsx | 3 + .../page-history/components/history-modal.tsx | 2 + .../components/breadcrumbs/breadcrumb.tsx | 33 ++++++-- .../components/header/page-header-menu.tsx | 20 ++++- .../page/components/page-import-modal.tsx | 37 ++++++++- .../components/trash-page-content-modal.tsx | 2 +- .../features/page/trash/components/trash.tsx | 2 +- .../page/tree/components/space-tree.tsx | 8 ++ .../search/components/search-control.tsx | 1 + .../session/components/session-list.tsx | 4 +- .../share/components/share-action-menu.tsx | 2 +- .../features/share/components/share-shell.tsx | 2 + .../space/components/space-members.tsx | 2 +- .../spaces-page/all-spaces-list.tsx | 18 ++-- .../components/members-action-menu.tsx | 6 +- .../components/workspace-invites-table.tsx | 1 + .../components/workspace-members-table.tsx | 1 + .../src/pages/favorites/favorites-page.tsx | 6 +- apps/client/src/theme.ts | 2 +- 62 files changed, 587 insertions(+), 163 deletions(-) diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 56709bbe..38fd5f2d 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -416,6 +416,7 @@ "{{latestVersion}} is available": "{{latestVersion}} is available", "Default page edit mode": "Default page edit mode", "Choose your preferred page edit mode. Avoid accidental edits.": "Choose your preferred page edit mode. Avoid accidental edits.", + "Choose {{format}} file": "Choose {{format}} file", "Reading": "Reading", "Delete member": "Delete member", "Member deleted successfully": "Member deleted successfully", @@ -900,5 +901,30 @@ "SCIM tokens": "SCIM tokens", "This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.", "Toggle SCIM provisioning": "Toggle SCIM provisioning", - "Token": "Token" + "Token": "Token", + "Page menu": "Page menu", + "Expand": "Expand", + "Collapse": "Collapse", + "Comment menu": "Comment menu", + "Group menu": "Group menu", + "Show hidden breadcrumbs": "Show hidden breadcrumbs", + "Breadcrumbs": "Breadcrumbs", + "Page actions": "Page actions", + "Pick emoji": "Pick emoji", + "Template menu": "Template menu", + "Chat menu": "Chat menu", + "API key menu": "API key menu", + "Jump to comment selection": "Jump to comment selection", + "Slash commands": "Slash commands", + "Mention suggestions": "Mention suggestions", + "Link suggestions": "Link suggestions", + "Diagram editor": "Diagram editor", + "Add comment": "Add comment", + "Find and replace": "Find and replace", + "Main navigation": "Main navigation", + "Space navigation": "Space navigation", + "Settings navigation": "Settings navigation", + "AI navigation": "AI navigation", + "Breadcrumb": "Breadcrumb", + "Skip to main content": "Skip to main content" } diff --git a/apps/client/src/components/common/avatar-uploader.tsx b/apps/client/src/components/common/avatar-uploader.tsx index 8d9552f6..750e4ba6 100644 --- a/apps/client/src/components/common/avatar-uploader.tsx +++ b/apps/client/src/components/common/avatar-uploader.tsx @@ -80,6 +80,12 @@ export default function AvatarUploader({ } }; + const ariaLabel = { + [AvatarIconType.AVATAR]: t("Change avatar"), + [AvatarIconType.SPACE_ICON]: t("Change space icon"), + [AvatarIconType.WORKSPACE_ICON]: t("Change workspace icon"), + }[type]; + const handleRemove = async () => { if (disabled) return; @@ -104,6 +110,8 @@ export default function AvatarUploader({ ref={fileInputRef} onChange={handleFileInputChange} accept="image/png,image/jpeg,image/jpg" + aria-label={ariaLabel} + tabIndex={-1} style={{ display: "none" }} /> @@ -115,6 +123,8 @@ export default function AvatarUploader({ size={size} avatarUrl={currentImageUrl} name={fallbackName} + aria-label={ariaLabel} + aria-haspopup="menu" style={{ cursor: disabled || isLoading ? "default" : "pointer", opacity: isLoading ? 0.6 : 1, diff --git a/apps/client/src/components/common/copy.tsx b/apps/client/src/components/common/copy.tsx index 745fc4ba..2144417b 100644 --- a/apps/client/src/components/common/copy.tsx +++ b/apps/client/src/components/common/copy.tsx @@ -25,6 +25,7 @@ export default function CopyTextButton({ text, size }: CopyProps) { variant="subtle" onClick={copy} size={size} + aria-label={copied ? t("Copied") : t("Copy")} > {copied ? : } diff --git a/apps/client/src/components/common/recent-changes.tsx b/apps/client/src/components/common/recent-changes.tsx index 277ceb81..8e0e56f2 100644 --- a/apps/client/src/components/common/recent-changes.tsx +++ b/apps/client/src/components/common/recent-changes.tsx @@ -4,7 +4,7 @@ import { UnstyledButton, Badge, Table, - ActionIcon, + ThemeIcon, Button, } from "@mantine/core"; import { Link } from "react-router-dom"; @@ -49,9 +49,9 @@ export default function RecentChanges({ spaceId }: Props) { > {page.icon || ( - + - + )} diff --git a/apps/client/src/components/common/search-input.tsx b/apps/client/src/components/common/search-input.tsx index 08cbbee0..27e50fd4 100644 --- a/apps/client/src/components/common/search-input.tsx +++ b/apps/client/src/components/common/search-input.tsx @@ -6,12 +6,14 @@ import { useTranslation } from "react-i18next"; export interface SearchInputProps { placeholder?: string; + ariaLabel?: string; debounceDelay?: number; onSearch: (value: string) => void; } export function SearchInput({ placeholder, + ariaLabel, debounceDelay = 500, onSearch, }: SearchInputProps) { @@ -28,6 +30,7 @@ export function SearchInput({ } value={value} onChange={(e) => setValue(e.currentTarget.value)} diff --git a/apps/client/src/components/icons/icon-people-circle.tsx b/apps/client/src/components/icons/icon-people-circle.tsx index 99695896..1a2daf73 100644 --- a/apps/client/src/components/icons/icon-people-circle.tsx +++ b/apps/client/src/components/icons/icon-people-circle.tsx @@ -1,11 +1,11 @@ -import { ActionIcon, rem } from "@mantine/core"; +import { ThemeIcon } from "@mantine/core"; import React from "react"; import { IconUsersGroup } from "@tabler/icons-react"; export function IconGroupCircle() { return ( - + - + ); } diff --git a/apps/client/src/components/layouts/global/app-shell.module.css b/apps/client/src/components/layouts/global/app-shell.module.css index ed369612..7ed93772 100644 --- a/apps/client/src/components/layouts/global/app-shell.module.css +++ b/apps/client/src/components/layouts/global/app-shell.module.css @@ -28,4 +28,22 @@ } } +.skipLink { + position: fixed; + left: 8px; + top: 8px; + padding: 8px 12px; + background: var(--mantine-color-blue-6); + color: #fff; + border-radius: 4px; + text-decoration: none; + z-index: 1000; + transform: translateY(-150%); + + &:focus { + transform: translateY(0); + outline: 2px solid var(--mantine-color-blue-3); + } +} + diff --git a/apps/client/src/components/layouts/global/global-app-shell.tsx b/apps/client/src/components/layouts/global/global-app-shell.tsx index 6e842a05..99373814 100644 --- a/apps/client/src/components/layouts/global/global-app-shell.tsx +++ b/apps/client/src/components/layouts/global/global-app-shell.tsx @@ -1,6 +1,7 @@ import { AppShell, Container } from "@mantine/core"; import React, { useEffect, useRef, useState } from "react"; import { useLocation } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import SettingsSidebar from "@/components/settings/settings-sidebar.tsx"; import { useAtom } from "jotai"; import { @@ -23,11 +24,12 @@ export default function GlobalAppShell({ }: { children: React.ReactNode; }) { + const { t } = useTranslation(); useTrialEndAction(); const [mobileOpened] = useAtom(mobileSidebarAtom); const toggleMobile = useToggleSidebar(mobileSidebarAtom); const [desktopOpened] = useAtom(desktopSidebarAtom); - const [{ isAsideOpen }] = useAtom(asideStateAtom); + const [{ isAsideOpen, tab: asideTab }] = useAtom(asideStateAtom); const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom); const [isResizing, setIsResizing] = useState(false); const sidebarRef = useRef(null); @@ -79,7 +81,11 @@ export default function GlobalAppShell({ const showGlobalSidebar = !isSpaceRoute && !isSettingsRoute && !isAiRoute; return ( - + + {t("Skip to main content")} + + {isSpaceRoute && (
@@ -114,7 +129,7 @@ export default function GlobalAppShell({ {isAiRoute && } {showGlobalSidebar && } - + {isSettingsRoute ? ( {children} @@ -125,10 +140,24 @@ export default function GlobalAppShell({ {isPageRoute && ( - +