From 4d7e6b4a5872be94e472565ca64efac1ea3117ed Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 12 Dec 2025 17:18:21 -0500 Subject: [PATCH] =?UTF-8?q?=E2=8C=A8=EF=B8=8F=20refactor:=20Favorite=20Ite?= =?UTF-8?q?m=20Selection=20&=20Keyboard=20Navigation/Focus=20Improvements?= =?UTF-8?q?=20(#10952)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Reuse conversation switching logic from useSelectMention hook for Favorite Items - Added onSelectEndpoint prop to FavoriteItem for improved endpoint selection handling. - Refactored conversation initiation logic to utilize the new prop instead of direct navigation. - Updated FavoritesList to pass onSelectEndpoint to FavoriteItem, streamlining the interaction flow. - Replaced EndpointIcon with MinimalIcon for a cleaner UI representation of favorite models. * refactor: Enhance FavoriteItem and FavoritesList for improved accessibility and interaction - Added onRemoveFocus prop to FavoriteItem for better focus management after item removal. - Refactored event handling in FavoriteItem to support keyboard interactions for accessibility. - Updated FavoritesList to utilize the new onRemoveFocus prop, ensuring focus shifts appropriately after removing favorites. - Enhanced aria-labels and roles for better screen reader support and user experience. * refactor: Enhance EndpointModelItem for improved accessibility and interaction - Added useRef and useState hooks to manage active state and focus behavior. - Implemented MutationObserver to track changes in the data-active-item attribute for better accessibility. - Refactored favorite button handling to improve interaction and accessibility. - Updated button tabIndex based on active state to enhance keyboard navigation. * chore: Update Radix UI dependencies in package-lock and package.json files - Upgraded @radix-ui/react-alert-dialog and @radix-ui/react-dialog to version 1.1.15 across client and packages/client. - Updated related dependencies for improved compatibility and performance. - Removed outdated debug module references from package-lock.json. * refactor: Improve accessibility and interaction in conversation options - Added event handling to prevent unintended actions when renaming conversations. - Updated ConvoOptions to use Ariakit components for better accessibility and interaction. - Refactored button handlers for sharing and deleting conversations for clarity and consistency. - Enhanced dialog components with proper aria attributes and improved structure for better screen reader support. * refactor: Improve nested dialog accessibility for deleting shared link - Eliminated the setShareDialogOpen prop from both ShareButton and SharedLinkButton components to streamline the code. - Updated the delete mutation success handler in SharedLinkButton to improve focus management for accessibility. - Enhanced the OGDialog component in SharedLinkButton with a triggerRef for better interaction. --- client/package.json | 4 +- .../components/EndpointModelItem.tsx | 39 +- client/src/components/Conversations/Convo.tsx | 3 + .../ConvoOptions/ConvoOptions.tsx | 42 +- .../ConvoOptions/DeleteButton.tsx | 13 +- .../ConvoOptions/ShareButton.tsx | 1 - .../ConvoOptions/SharedLinkButton.tsx | 97 ++-- .../components/Nav/Favorites/FavoriteItem.tsx | 113 ++-- .../Nav/Favorites/FavoritesList.tsx | 61 ++- package-lock.json | 503 +++++++++++++++--- packages/client/package.json | 4 +- packages/client/src/components/Button.tsx | 9 +- 12 files changed, 670 insertions(+), 219 deletions(-) diff --git a/client/package.json b/client/package.json index aef5f4697a..27224493ce 100644 --- a/client/package.json +++ b/client/package.json @@ -39,10 +39,10 @@ "@marsidev/react-turnstile": "^1.1.0", "@mcp-ui/client": "^5.7.0", "@radix-ui/react-accordion": "^1.1.2", - "@radix-ui/react-alert-dialog": "^1.0.2", + "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3", - "@radix-ui/react-dialog": "^1.0.2", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx index 32a480e726..aab5b5889f 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { EarthIcon, Pin, PinOff } from 'lucide-react'; import { isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider'; import { useModelSelectorContext } from '../ModelSelectorContext'; @@ -18,6 +18,26 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod const { handleSelectModel } = useModelSelectorContext(); const { isFavoriteModel, toggleFavoriteModel, isFavoriteAgent, toggleFavoriteAgent } = useFavorites(); + + const itemRef = useRef(null); + const [isActive, setIsActive] = useState(false); + + useEffect(() => { + const element = itemRef.current; + if (!element) { + return; + } + + const observer = new MutationObserver(() => { + setIsActive(element.hasAttribute('data-active-item')); + }); + + observer.observe(element, { attributes: true, attributeFilter: ['data-active-item'] }); + setIsActive(element.hasAttribute('data-active-item')); + + return () => observer.disconnect(); + }, []); + let isGlobal = false; let modelName = modelId; const avatarUrl = endpoint?.modelIcons?.[modelId ?? ''] || null; @@ -42,8 +62,7 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod ? isFavoriteAgent(modelId ?? '') : isFavoriteModel(modelId ?? '', endpoint.value); - const handleFavoriteClick = (e: React.MouseEvent) => { - e.stopPropagation(); + const handleFavoriteToggle = () => { if (!modelId) { return; } @@ -55,6 +74,11 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod } }; + const handleFavoriteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + handleFavoriteToggle(); + }; + const renderAvatar = () => { const isAgentOrAssistant = isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value); @@ -84,6 +108,7 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod return ( handleSelectModel(endpoint, modelId ?? '')} className="group flex w-full cursor-pointer items-center justify-between rounded-lg px-2 text-sm" @@ -94,20 +119,18 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod {isGlobal && } {isSelected && ( diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index 382eace7a8..c10aad33e3 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -155,6 +155,9 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co if (renaming) { return; } + if (e.target !== e.currentTarget) { + return; + } if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleNavigation(false); diff --git a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx index aa82b3eb1d..b090027645 100644 --- a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx +++ b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx @@ -1,5 +1,5 @@ import { useState, useId, useRef, memo, useCallback, useMemo } from 'react'; -import * as Menu from '@ariakit/react/menu'; +import * as Ariakit from '@ariakit/react'; import { useParams, useNavigate } from 'react-router-dom'; import { QueryKeys } from 'librechat-data-provider'; import { useQueryClient } from '@tanstack/react-query'; @@ -49,6 +49,7 @@ function ConvoOptions({ const { conversationId: currentConvoId } = useParams(); const { newConversation } = useNewConvo(); + const menuId = useId(); const shareButtonRef = useRef(null); const deleteButtonRef = useRef(null); const [showShareDialog, setShowShareDialog] = useState(false); @@ -106,11 +107,11 @@ function ConvoOptions({ const isArchiveLoading = archiveConvoMutation.isLoading; const isDeleteLoading = deleteMutation.isLoading; - const handleShareClick = useCallback(() => { + const shareHandler = useCallback(() => { setShowShareDialog(true); }, []); - const handleDeleteClick = useCallback(() => { + const deleteHandler = useCallback(() => { setShowDeleteDialog(true); }, []); @@ -189,13 +190,15 @@ function ConvoOptions({ () => [ { label: localize('com_ui_share'), - onClick: handleShareClick, + onClick: shareHandler, icon: