From b1a2b9627642dfe1b6e6525a766f20ff4ea0cf8d Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 2 Jan 2026 11:43:03 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=9C=20fix:=20Layering=20Conflicts=20an?= =?UTF-8?q?d=20UX=20Polish=20(#11177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 refactor: Update z-index values for popover components - Reduced z-index from 50 to 40 across various popover components including Artifacts, ArtifactsSubMenu, MCPSubMenu, CustomMenu, and others to ensure consistent layering and improve UI behavior. - Adjusted related CSS styles in Dropdown.css and DropdownMenu.tsx to align with the new z-index values, enhancing overall component visibility and interaction. * chore: remove string template for className concatenation in CustomMenu component - Improved the readability of the className prop in the CustomMenu component by restructuring the concatenation of class names. This change enhances maintainability and clarity in the styling logic. * refactor: Simplify button visibility logic in SiblingHeader component - Updated the button rendering logic in the SiblingHeader component to improve clarity and maintainability. The button is now always rendered, with its visibility controlled by the disabled state based on messageId, agentId, and submission status, enhancing user experience during interactions. * refactor: Update shift key handling in Conversation and ConvoOptions components - Modified the handling of the `isShiftHeld` state in both the Conversation and ConvoOptions components to improve clarity and functionality. The logic now ensures that the shift key state is accurately reflected based on the active conversation status, enhancing user interaction during conversations. - Cleaned up imports in ConvoOptions by removing the unused `useShiftKey` hook, streamlining the component's dependencies. * refactor: Improve Escape key handling in OriginalDialog component - Updated the Escape key handling logic to prevent closing the dialog when a tooltip or dropdown menu has focus. This change enhances accessibility by ensuring compliance with WCAG standards for dismissable tooltips. - Simplified the focus checking mechanism by directly assessing the active element within dropdown menus and tooltips, improving code clarity and maintainability. * chore: imports * refactor: Enhance Escape key handling in OriginalDialog component - Updated the Escape key handling logic to prevent closing the dialog when a trigger with an open popover is focused. This change improves accessibility and user experience by ensuring that the dialog remains open during interactions with popovers, dropdowns, and listboxes. - Simplified the focus checking mechanism to include additional roles, enhancing the clarity and maintainability of the code. * refactor: Add dropdownClassName prop to FilterPrompts component - Enhanced the FilterPrompts component by introducing a new dropdownClassName prop, allowing for customizable styling of the dropdown element. - Updated the PromptsView component to utilize the new prop, improving the flexibility of the FilterPrompts integration within the UI. * refactor: Clean up imports and remove unused code in DashBreadcrumb component - Streamlined the DashBreadcrumb component by removing commented-out imports and unused code, enhancing clarity and maintainability. - Adjusted the import order for better organization and readability. * refactor: Update z-index handling in Dropdown component - Removed the z-index property from Dropdown.css to streamline styling. - Adjusted the className in Dropdown.tsx to include a new z-40 class for consistent z-index management, enhancing UI layering and interaction. * refactor: Enhance file type acceptance in AttachFileMenu and useDragHelpers - Updated the AttachFileMenu component to accept additional image formats (.heif, .heic) alongside existing types, improving file upload flexibility. - Modified the useDragHelpers hook to utilize inferMimeType for better file type detection, ensuring accurate handling of dragged files. * refactor: Enhance FloatingThinkingBar with copy functionality - Added a copy button to the FloatingThinkingBar component, allowing users to copy thoughts to the clipboard. - Updated the tooltip descriptions for the expand/collapse and copy actions to improve user experience. - Cleaned up imports and adjusted prop types for better clarity and maintainability. * refactor: Enhance RunCode component with icon-only mode - Updated the RunCode component to accept an `iconOnly` prop, allowing for a simplified button display that shows only the icon when desired. - Adjusted the button rendering logic to improve user experience and maintainability. - Cleaned up imports and ensured consistent styling in the FloatingCodeBar component. --- .../src/components/Chat/Input/Artifacts.tsx | 2 +- .../Chat/Input/ArtifactsSubMenu.tsx | 2 +- .../Chat/Input/Files/AttachFileMenu.tsx | 6 +- .../src/components/Chat/Input/MCPSubMenu.tsx | 2 +- .../Chat/Menus/Endpoints/CustomMenu.tsx | 3 +- .../Chat/Messages/Content/Parts/Reasoning.tsx | 1 + .../Chat/Messages/Content/Parts/Thinking.tsx | 57 +++++- .../Chat/Messages/Content/SiblingHeader.tsx | 33 ++- client/src/components/Conversations/Convo.tsx | 3 +- .../ConvoOptions/ConvoOptions.tsx | 10 +- .../components/Messages/Content/CodeBlock.tsx | 2 +- .../components/Messages/Content/RunCode.tsx | 192 ++++++++++-------- .../Nav/ExportConversation/ExportModal.tsx | 6 +- .../Prompts/Groups/FilterPrompts.tsx | 10 +- client/src/components/Prompts/PromptsView.tsx | 2 +- client/src/hooks/Files/useDragHelpers.ts | 5 +- client/src/routes/Layouts/DashBreadcrumb.tsx | 9 +- packages/client/src/components/Combobox.tsx | 2 +- .../client/src/components/ControlCombobox.tsx | 2 +- packages/client/src/components/Dropdown.css | 1 - packages/client/src/components/Dropdown.tsx | 2 +- .../client/src/components/DropdownMenu.tsx | 4 +- .../client/src/components/DropdownPopup.tsx | 2 +- .../client/src/components/MultiSelect.tsx | 2 +- .../client/src/components/OriginalDialog.tsx | 35 ++-- packages/client/src/components/Select.tsx | 2 +- 26 files changed, 233 insertions(+), 164 deletions(-) diff --git a/client/src/components/Chat/Input/Artifacts.tsx b/client/src/components/Chat/Input/Artifacts.tsx index 8bc92744b4..46528c8af4 100644 --- a/client/src/components/Chat/Input/Artifacts.tsx +++ b/client/src/components/Chat/Input/Artifacts.tsx @@ -107,7 +107,7 @@ function Artifacts() { portal={true} unmountOnHide={true} className={cn( - 'animate-popover-left z-50 ml-3 mt-6 flex min-w-[250px] flex-col rounded-xl', + 'animate-popover-left z-40 ml-3 mt-6 flex min-w-[250px] flex-col rounded-xl', 'border border-border-light bg-surface-secondary shadow-lg', )} > diff --git a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx index 8c63953d5f..218328b086 100644 --- a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx @@ -94,13 +94,13 @@ const AttachFileMenu = ({ } inputRef.current.value = ''; if (fileType === 'image') { - inputRef.current.accept = 'image/*'; + inputRef.current.accept = 'image/*,.heif,.heic'; } else if (fileType === 'document') { inputRef.current.accept = '.pdf,application/pdf'; } else if (fileType === 'image_document') { - inputRef.current.accept = 'image/*,.pdf,application/pdf'; + inputRef.current.accept = 'image/*,.heif,.heic,.pdf,application/pdf'; } else if (fileType === 'image_document_video_audio') { - inputRef.current.accept = 'image/*,.pdf,application/pdf,video/*,audio/*'; + inputRef.current.accept = 'image/*,.heif,.heic,.pdf,application/pdf,video/*,audio/*'; } else { inputRef.current.accept = ''; } diff --git a/client/src/components/Chat/Input/MCPSubMenu.tsx b/client/src/components/Chat/Input/MCPSubMenu.tsx index 66b816e934..ca547ca1f7 100644 --- a/client/src/components/Chat/Input/MCPSubMenu.tsx +++ b/client/src/components/Chat/Input/MCPSubMenu.tsx @@ -87,7 +87,7 @@ const MCPSubMenu = React.forwardRef( unmountOnHide={true} aria-label={localize('com_ui_mcp_servers')} className={cn( - 'animate-popover-left z-50 ml-3 flex min-w-[260px] max-w-[320px] flex-col rounded-xl', + 'animate-popover-left z-40 ml-3 flex min-w-[260px] max-w-[320px] flex-col rounded-xl', 'border border-border-light bg-presentation p-1.5 shadow-lg', )} > diff --git a/client/src/components/Chat/Menus/Endpoints/CustomMenu.tsx b/client/src/components/Chat/Menus/Endpoints/CustomMenu.tsx index 2ce578b8e4..14ce5bb209 100644 --- a/client/src/components/Chat/Menus/Endpoints/CustomMenu.tsx +++ b/client/src/components/Chat/Menus/Endpoints/CustomMenu.tsx @@ -64,7 +64,8 @@ export const CustomMenu = React.forwardRef(func unmountOnHide gutter={parent ? -4 : 4} className={cn( - `${parent ? 'animate-popover-left ml-3' : 'animate-popover'} outline-none! z-50 flex max-h-[min(450px,var(--popover-available-height))] w-full`, + parent ? 'animate-popover-left ml-3' : 'animate-popover', + 'outline-none! z-40 flex max-h-[min(450px,var(--popover-available-height))] w-full', 'w-[var(--menu-width,auto)] min-w-[300px] flex-col overflow-auto rounded-xl border border-border-light', 'bg-presentation px-3 py-2 text-sm text-text-primary shadow-lg', 'max-w-[calc(100vw-4rem)] sm:max-h-[calc(65vh)] sm:max-w-[400px]', diff --git a/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx b/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx index 208812600b..85d1b00b4b 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Reasoning.tsx @@ -121,6 +121,7 @@ const Reasoning = memo(({ reasoning, isLast }: ReasoningProps) => { isVisible={isBarVisible && isExpanded} isExpanded={isExpanded} onClick={handleClick} + content={reasoningText} /> diff --git a/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx b/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx index b81f5a48a9..0c5992f4ab 100644 --- a/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/Thinking.tsx @@ -1,8 +1,8 @@ -import { useState, useMemo, memo, useCallback, useRef } from 'react'; +import { useState, useMemo, memo, useCallback, useRef, type MouseEvent } from 'react'; import { useAtomValue } from 'jotai'; import { Clipboard, CheckMark, TooltipAnchor } from '@librechat/client'; import { Lightbulb, ChevronDown, ChevronUp } from 'lucide-react'; -import type { MouseEvent, FocusEvent, FC } from 'react'; +import type { FocusEvent, FC } from 'react'; import { showThinkingAtom } from '~/store/showThinking'; import { fontSizeAtom } from '~/store/fontSize'; import { useLocalize } from '~/hooks'; @@ -122,7 +122,7 @@ export const ThinkingButton = memo( ); /** - * FloatingThinkingBar - Floating bar with expand/collapse button + * FloatingThinkingBar - Floating bar with expand/collapse and copy buttons * Shows on hover/focus, positioned at bottom right of thinking content * Inspired by CodeBlock's FloatingCodeBar pattern */ @@ -131,16 +131,36 @@ export const FloatingThinkingBar = memo( isVisible, isExpanded, onClick, + content, }: { isVisible: boolean; isExpanded: boolean; onClick: (e: MouseEvent) => void; + content?: string; }) => { const localize = useLocalize(); - const tooltipText = isExpanded + const [isCopied, setIsCopied] = useState(false); + + const handleCopy = useCallback( + (e: MouseEvent) => { + e.stopPropagation(); + if (content) { + navigator.clipboard.writeText(content); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } + }, + [content], + ); + + const collapseTooltip = isExpanded ? localize('com_ui_collapse_thoughts') : localize('com_ui_expand_thoughts'); + const copyTooltip = isCopied + ? localize('com_ui_copied_to_clipboard') + : localize('com_ui_copy_thoughts_to_clipboard'); + return (
} /> + {content && ( + + {isCopied ? ( +
); }, @@ -265,6 +309,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN isVisible={isBarVisible && isExpanded} isExpanded={isExpanded} onClick={handleClick} + content={textContent} /> diff --git a/client/src/components/Chat/Messages/Content/SiblingHeader.tsx b/client/src/components/Chat/Messages/Content/SiblingHeader.tsx index ec76aa046e..080974ed2b 100644 --- a/client/src/components/Chat/Messages/Content/SiblingHeader.tsx +++ b/client/src/components/Chat/Messages/Content/SiblingHeader.tsx @@ -118,23 +118,22 @@ export default function SiblingHeader({ {displayName} - {messageId && agentId && !isSubmitting && ( - - )} + ); } diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index c1d2fa58fd..efdf2c48b2 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -142,6 +142,7 @@ export default function Conversation({ conversationId, isPopoverActive, setIsPopoverActive, + isShiftHeld: isActiveConvo ? isShiftHeld : false, }; return ( @@ -236,7 +237,7 @@ export default function Conversation({ isPopoverActive || isActiveConvo ? 'pointer-events-auto scale-x-100 opacity-100' : 'pointer-events-none max-w-0 scale-x-0 opacity-0 group-focus-within:pointer-events-auto group-focus-within:max-w-[60px] group-focus-within:scale-x-100 group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:max-w-[60px] group-hover:scale-x-100 group-hover:opacity-100', - (isPopoverActive || isActiveConvo) && (isShiftHeld ? 'max-w-[60px]' : 'max-w-[28px]'), + !isPopoverActive && isActiveConvo && isShiftHeld ? 'max-w-[60px]' : 'max-w-[28px]', )} // Removing aria-hidden to fix accessibility issue: ARIA hidden element must not be focusable or contain focusable elements // but not sure what its original purpose was, so leaving the property commented out until it can be cleared safe to delete. diff --git a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx index 9ab4ff7114..14c6b424b4 100644 --- a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx +++ b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx @@ -13,7 +13,7 @@ import { useGetStartupConfig, useArchiveConvoMutation, } from '~/data-provider'; -import { useLocalize, useNavigateToConvo, useNewConvo, useShiftKey } from '~/hooks'; +import { useLocalize, useNavigateToConvo, useNewConvo } from '~/hooks'; import { NotificationSeverity } from '~/common'; import { useChatContext } from '~/Providers'; import DeleteButton from './DeleteButton'; @@ -28,6 +28,7 @@ function ConvoOptions({ isPopoverActive, setIsPopoverActive, isActiveConvo, + isShiftHeld = false, }: { conversationId: string | null; title: string | null; @@ -36,10 +37,10 @@ function ConvoOptions({ isPopoverActive: boolean; setIsPopoverActive: React.Dispatch>; isActiveConvo: boolean; + isShiftHeld?: boolean; }) { const localize = useLocalize(); const queryClient = useQueryClient(); - const isShiftHeld = useShiftKey(); const { index } = useChatContext(); const { data: startupConfig } = useGetStartupConfig(); const { navigateToConvo } = useNavigateToConvo(index); @@ -253,7 +254,7 @@ function ConvoOptions({ : 'opacity-0 focus:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100 data-[open]:opacity-100', ); - if (isShiftHeld) { + if (isShiftHeld && isActiveConvo && !isPopoverActive && !showShareDialog && !showDeleteDialog) { return (
- - - ); -}); + ); + + return ( + <> + {iconOnly ? ( + + ) : ( + button + )} + + + ); + }, +); export default RunCode; diff --git a/client/src/components/Nav/ExportConversation/ExportModal.tsx b/client/src/components/Nav/ExportConversation/ExportModal.tsx index 2083ddec1a..a140251568 100644 --- a/client/src/components/Nav/ExportConversation/ExportModal.tsx +++ b/client/src/components/Nav/ExportConversation/ExportModal.tsx @@ -1,13 +1,13 @@ import filenamify from 'filenamify'; import { useEffect, useState, useMemo, useCallback } from 'react'; import { - OGDialogTemplate, - OGDialog, - Button, Input, Label, + Button, + OGDialog, Checkbox, Dropdown, + OGDialogTemplate, } from '@librechat/client'; import type { TConversation } from 'librechat-data-provider'; import { useLocalize, useExportConversation } from '~/hooks'; diff --git a/client/src/components/Prompts/Groups/FilterPrompts.tsx b/client/src/components/Prompts/Groups/FilterPrompts.tsx index ad4972c4b7..1bd25baf9b 100644 --- a/client/src/components/Prompts/Groups/FilterPrompts.tsx +++ b/client/src/components/Prompts/Groups/FilterPrompts.tsx @@ -9,7 +9,13 @@ import { usePromptGroupsContext } from '~/Providers'; import { cn } from '~/utils'; import store from '~/store'; -export default function FilterPrompts({ className = '' }: { className?: string }) { +export default function FilterPrompts({ + className = '', + dropdownClassName = '', +}: { + className?: string; + dropdownClassName?: string; +}) { const localize = useLocalize(); const { name, setName, hasAccess, promptGroups } = usePromptGroupsContext(); const { categories } = useCategories({ className: 'h-4 w-4', hasAccess }); @@ -94,7 +100,7 @@ export default function FilterPrompts({ className = '' }: { className?: string } value={categoryFilter || SystemCategories.ALL} onChange={onSelect} options={filterOptions} - className="rounded-lg bg-transparent" + className={cn('rounded-lg bg-transparent', dropdownClassName)} icon={} label="Filter: " ariaLabel={localize('com_ui_filter_prompts')} diff --git a/client/src/components/Prompts/PromptsView.tsx b/client/src/components/Prompts/PromptsView.tsx index d605038281..f390ffddd3 100644 --- a/client/src/components/Prompts/PromptsView.tsx +++ b/client/src/components/Prompts/PromptsView.tsx @@ -93,7 +93,7 @@ export default function PromptsView() { onClose={isSmallerScreen && isDetailView ? togglePanel : undefined} >
- +
diff --git a/client/src/hooks/Files/useDragHelpers.ts b/client/src/hooks/Files/useDragHelpers.ts index cd3efdb0b0..f931da408c 100644 --- a/client/src/hooks/Files/useDragHelpers.ts +++ b/client/src/hooks/Files/useDragHelpers.ts @@ -8,6 +8,7 @@ import { Tools, QueryKeys, Constants, + inferMimeType, EToolResources, EModelEndpoint, mergeFileConfig, @@ -118,7 +119,9 @@ export default function useDragHelpers() { } /** Determine if dragged files are all images (enables the base image option) */ - const allImages = item.files.every((f) => f.type?.startsWith('image/')); + const allImages = item.files.every((f) => + inferMimeType(f.name, f.type)?.startsWith('image/'), + ); const shouldShowModal = allImages || diff --git a/client/src/routes/Layouts/DashBreadcrumb.tsx b/client/src/routes/Layouts/DashBreadcrumb.tsx index f1178ebc7f..527fe058ad 100644 --- a/client/src/routes/Layouts/DashBreadcrumb.tsx +++ b/client/src/routes/Layouts/DashBreadcrumb.tsx @@ -1,27 +1,20 @@ import { useMemo, useCallback } from 'react'; import { useSetRecoilState } from 'recoil'; +import { Sidebar } from '@librechat/client'; import { useLocation } from 'react-router-dom'; import { SystemRoles } from 'librechat-data-provider'; import { ArrowLeft, MessageSquareQuote } from 'lucide-react'; -import { Sidebar } from '@librechat/client'; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, - // BreadcrumbEllipsis, - // DropdownMenu, - // DropdownMenuItem, - // DropdownMenuContent, - // DropdownMenuTrigger, } from '@librechat/client'; import { useLocalize, useCustomLink, useAuthContext } from '~/hooks'; import AdvancedSwitch from '~/components/Prompts/AdvancedSwitch'; -// import { RightPanel } from '../../components/Prompts/RightPanel'; import AdminSettings from '~/components/Prompts/AdminSettings'; import { useDashboardContext } from '~/Providers'; -// import { PromptsEditorMode } from '~/common'; import store from '~/store'; const promptsPathPattern = /prompts\/(?!new(?:\/|$)).*$/; diff --git a/packages/client/src/components/Combobox.tsx b/packages/client/src/components/Combobox.tsx index 7a513ecae1..147d3c1939 100644 --- a/packages/client/src/components/Combobox.tsx +++ b/packages/client/src/components/Combobox.tsx @@ -104,7 +104,7 @@ export default function ComboboxComponent({ aria-label={ariaLabel + 's'} position="popper" className={cn( - 'bg-popover text-popover-foreground relative z-50 max-h-[52vh] min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600', + 'bg-popover text-popover-foreground relative z-40 max-h-[52vh] min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600', 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', 'bg-white dark:bg-gray-700', )} diff --git a/packages/client/src/components/ControlCombobox.tsx b/packages/client/src/components/ControlCombobox.tsx index e512b11a54..950708d0c0 100644 --- a/packages/client/src/components/ControlCombobox.tsx +++ b/packages/client/src/components/ControlCombobox.tsx @@ -134,7 +134,7 @@ function ControlCombobox({ gutter={4} portal className={cn( - 'animate-popover z-50 overflow-hidden rounded-xl border border-border-light bg-surface-secondary shadow-lg', + 'animate-popover z-40 overflow-hidden rounded-xl border border-border-light bg-surface-secondary shadow-lg', )} style={{ width: isCollapsed ? '300px' : (buttonWidth ?? '300px') }} > diff --git a/packages/client/src/components/Dropdown.css b/packages/client/src/components/Dropdown.css index 6bb50aebb2..e529af953f 100644 --- a/packages/client/src/components/Dropdown.css +++ b/packages/client/src/components/Dropdown.css @@ -23,7 +23,6 @@ translate: 0 -0.5rem; margin-top: 4px; margin-right: -2px; - z-index: 100; } .popover-animate { diff --git a/packages/client/src/components/Dropdown.tsx b/packages/client/src/components/Dropdown.tsx index 536bbc5829..63aed0ac76 100644 --- a/packages/client/src/components/Dropdown.tsx +++ b/packages/client/src/components/Dropdown.tsx @@ -102,7 +102,7 @@ const Dropdown: React.FC = ({ portal={portal} store={selectProps} className={cn( - 'popover-ui', + 'popover-ui z-40', sizeClasses, className, 'max-h-[80vh] overflow-y-auto', diff --git a/packages/client/src/components/DropdownMenu.tsx b/packages/client/src/components/DropdownMenu.tsx index 4c050a2713..488ab18f6e 100644 --- a/packages/client/src/components/DropdownMenu.tsx +++ b/packages/client/src/components/DropdownMenu.tsx @@ -30,7 +30,7 @@ function DropdownMenuContent({ data-slot="dropdown-menu-content" sideOffset={sideOffset} className={cn( - 'text-popover-foreground max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border-light bg-surface-secondary p-1 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + 'text-popover-foreground max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) z-40 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border-light bg-surface-secondary p-1 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className, )} {...props} @@ -198,7 +198,7 @@ function DropdownMenuSubContent({ = ({ finalFocus={finalFocus} unmountOnHide={unmountOnHide} preserveTabOrder={preserveTabOrder} - className={cn('popover-ui z-50', className)} + className={cn('popover-ui z-40', className)} {...props} > {items diff --git a/packages/client/src/components/MultiSelect.tsx b/packages/client/src/components/MultiSelect.tsx index fa84b6031d..bb48841468 100644 --- a/packages/client/src/components/MultiSelect.tsx +++ b/packages/client/src/components/MultiSelect.tsx @@ -137,7 +137,7 @@ export default function MultiSelect({ unmountOnHide finalFocus={selectRef} className={cn( - 'animate-popover z-50 flex max-h-[300px]', + 'animate-popover z-40 flex max-h-[300px]', 'flex-col overflow-auto overscroll-contain rounded-xl', 'bg-surface-secondary px-1.5 py-1 text-text-primary shadow-lg', 'border border-border-light', diff --git a/packages/client/src/components/OriginalDialog.tsx b/packages/client/src/components/OriginalDialog.tsx index b9bdcc2f66..451c87c227 100644 --- a/packages/client/src/components/OriginalDialog.tsx +++ b/packages/client/src/components/OriginalDialog.tsx @@ -96,33 +96,34 @@ const DialogContent = React.forwardRef< const depth = React.useContext(DialogDepthContext); const contentZIndex = 100 + (depth - 1) * 60; - /* Handle Escape key to prevent closing dialog if a tooltip or dropdown is open + /* Handle Escape key to prevent closing dialog if a tooltip or dropdown has focus (this is a workaround in order to achieve WCAG compliance which requires that our tooltips be dismissable with Escape key) */ const handleEscapeKeyDown = React.useCallback( (event: KeyboardEvent) => { - const tooltips = document.querySelectorAll('.tooltip'); - const dropdownMenus = document.querySelectorAll('[role="menu"]'); + const activeElement = document.activeElement; - for (const tooltip of tooltips) { - const computedStyle = window.getComputedStyle(tooltip); - if ( - computedStyle.display !== 'none' && - computedStyle.visibility !== 'hidden' && - parseFloat(computedStyle.opacity) > 0 - ) { + // Check if active element is a trigger with an open popover (aria-expanded="true") + if (activeElement?.getAttribute('aria-expanded') === 'true') { + event.preventDefault(); + return; + } + + // Check if a dropdown menu, listbox, or combobox has focus (focus is within it) + const popoverElements = document.querySelectorAll( + '[role="menu"], [role="listbox"], [role="combobox"]', + ); + for (const popover of popoverElements) { + if (popover.contains(activeElement)) { event.preventDefault(); return; } } - for (const dropdownMenu of dropdownMenus) { - const computedStyle = window.getComputedStyle(dropdownMenu); - if ( - computedStyle.display !== 'none' && - computedStyle.visibility !== 'hidden' && - parseFloat(computedStyle.opacity) > 0 - ) { + // Check if a tooltip has focus (focus is within it) + const tooltips = document.querySelectorAll('.tooltip'); + for (const tooltip of tooltips) { + if (tooltip.contains(activeElement)) { event.preventDefault(); return; } diff --git a/packages/client/src/components/Select.tsx b/packages/client/src/components/Select.tsx index 39f32f4f3a..51671c0782 100644 --- a/packages/client/src/components/Select.tsx +++ b/packages/client/src/components/Select.tsx @@ -75,7 +75,7 @@ const SelectContent = React.forwardRef<