From a423eb8c7b3f27834c60d2a3149bce98a548b2b6 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sat, 28 Dec 2024 12:58:12 -0500 Subject: [PATCH] =?UTF-8?q?=E2=99=BF=20fix:=20Improve=20Accessibility=20in?= =?UTF-8?q?=20Endpoints=20Menu/Navigation=20(#5123)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: prevent mobile nav toggle from being focusable when not in mobile view, add types to * fix: appropriate endpoint menu item role, add up/down focus mgmt, ensure set api key is focusable and accessible * fix: localize link titles and update text color for improved accessibility in Nav component --- .../Chat/Menus/Endpoints/MenuItem.tsx | 25 +++++++++--- .../components/Chat/Menus/EndpointsMenu.tsx | 38 ++++++++++++++++++- .../Chat/Menus/Models/MenuButton.tsx | 1 - .../Chat/Menus/Models/ModelSpec.tsx | 23 +++++++---- .../Chat/Menus/Models/ModelSpecsMenu.tsx | 38 ++++++++++++++++++- .../components/Chat/Menus/UI/TitleButton.tsx | 1 - client/src/components/Nav/Nav.tsx | 27 +++++++------ client/src/components/Nav/NavToggle.tsx | 8 ++++ client/src/components/SidePanel/Nav.tsx | 8 ++-- 9 files changed, 137 insertions(+), 32 deletions(-) diff --git a/client/src/components/Chat/Menus/Endpoints/MenuItem.tsx b/client/src/components/Chat/Menus/Endpoints/MenuItem.tsx index 25bb0e4ab2..66a5d048bf 100644 --- a/client/src/components/Chat/Menus/Endpoints/MenuItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/MenuItem.tsx @@ -38,9 +38,9 @@ const MenuItem: FC = ({ const { getExpiry } = useUserKey(endpoint); const localize = useLocalize(); - const expiryTime = getExpiry(); + const expiryTime = getExpiry() ?? ''; - const onSelectEndpoint = (newEndpoint: EModelEndpoint) => { + const onSelectEndpoint = (newEndpoint?: EModelEndpoint) => { if (!newEndpoint) { return; } @@ -95,7 +95,8 @@ const MenuItem: FC = ({ return ( <>
= ({ {userProvidesKey ? (
) : null} diff --git a/client/src/components/Chat/Menus/Models/ModelSpecsMenu.tsx b/client/src/components/Chat/Menus/Models/ModelSpecsMenu.tsx index b209c8e415..4387f06aaf 100644 --- a/client/src/components/Chat/Menus/Models/ModelSpecsMenu.tsx +++ b/client/src/components/Chat/Menus/Models/ModelSpecsMenu.tsx @@ -1,9 +1,10 @@ -import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; +import { useMemo, useCallback, useRef } from 'react'; import { Content, Portal, Root } from '@radix-ui/react-popover'; import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider'; import type { TModelSpec, TConversation, TEndpointsConfig } from 'librechat-data-provider'; +import type { KeyboardEvent } from 'react'; import { useChatContext, useAssistantsMapContext } from '~/Providers'; import { useDefaultConvo, useNewConvo, useLocalize } from '~/hooks'; import { getConvoSwitchLogic, getModelSpecIconURL } from '~/utils'; @@ -88,6 +89,39 @@ export default function ModelSpecsMenu({ modelSpecs }: { modelSpecs?: TModelSpec return spec; }, [modelSpecs, conversation?.spec]); + const menuRef = useRef(null); + + const handleKeyDown = useCallback((event: KeyboardEvent) => { + const menuItems = menuRef.current?.querySelectorAll('[role="option"]'); + if (!menuItems) { + return; + } + if (!menuItems.length) { + return; + } + + const currentIndex = Array.from(menuItems).findIndex((item) => item === document.activeElement); + + switch (event.key) { + case 'ArrowDown': + event.preventDefault(); + if (currentIndex < menuItems.length - 1) { + (menuItems[currentIndex + 1] as HTMLElement).focus(); + } else { + (menuItems[0] as HTMLElement).focus(); + } + break; + case 'ArrowUp': + event.preventDefault(); + if (currentIndex > 0) { + (menuItems[currentIndex - 1] as HTMLElement).focus(); + } else { + (menuItems[menuItems.length - 1] as HTMLElement).focus(); + } + break; + } + }, []); + return ( diff --git a/client/src/components/Chat/Menus/UI/TitleButton.tsx b/client/src/components/Chat/Menus/UI/TitleButton.tsx index 2dfa0809dc..c23fdeb3ab 100644 --- a/client/src/components/Chat/Menus/UI/TitleButton.tsx +++ b/client/src/components/Chat/Menus/UI/TitleButton.tsx @@ -16,7 +16,6 @@ export default function TitleButton({ primaryText = '', secondaryText = '' }) { role="combobox" aria-haspopup="listbox" aria-controls="llm-endpoint-menu" - aria-activedescendant={isExpanded ? 'selected-endpoint' : undefined} onClick={() => setIsExpanded(!isExpanded)} >
diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index a2eba54276..ec8b1b475f 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -213,18 +213,21 @@ const Nav = ({ navVisible={navVisible} className="fixed left-0 top-1/2 z-40 hidden md:flex" /> -
{ - if (e.key === 'Enter' || e.key === ' ') { - toggleNavVisible(); - } - }} - aria-label="Toggle navigation" - /> + {isSmallScreen && ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + toggleNavVisible(); + } + }} + aria-label="Toggle navigation" + /> + )} ); }; diff --git a/client/src/components/Nav/NavToggle.tsx b/client/src/components/Nav/NavToggle.tsx index c082c70f25..ec8dad939d 100644 --- a/client/src/components/Nav/NavToggle.tsx +++ b/client/src/components/Nav/NavToggle.tsx @@ -10,6 +10,14 @@ export default function NavToggle({ side = 'left', className = '', translateX = true, +}: { + onToggle: () => void; + navVisible: boolean; + isHovering: boolean; + setIsHovering: (isHovering: boolean) => void; + side?: 'left' | 'right'; + className?: string; + translateX?: boolean; }) { const localize = useLocalize(); const transition = { diff --git a/client/src/components/SidePanel/Nav.tsx b/client/src/components/SidePanel/Nav.tsx index 56964c9a34..44ef52b39d 100644 --- a/client/src/components/SidePanel/Nav.tsx +++ b/client/src/components/SidePanel/Nav.tsx @@ -48,10 +48,10 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr }} > - {link.title} + {localize(link.title)} } - > + /> ) : ( {link.label} @@ -90,7 +90,7 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr - + {link.Component && }