From f6eccee9c78e4b43b5848fe8616ebdeefe31c884 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:06:42 +0200 Subject: [PATCH] fix: scope tooltip elevation to dialogs and restore dialog close button size Tooltips go back to z-150 globally; inside a dialog they now borrow the depth-aware popover z-index so they still clear nested dialogs (the Tool Library item dialog) without outranking freshly opened modals everywhere else. The default dialog close icon returns to its original size, and the lc-field pointer-focus suppression ships with the package next to Input and Textarea so external consumers get the whole mechanism from @librechat/client. --- client/src/style.css | 12 +----------- packages/client/src/components/Field.css | 11 +++++++++++ packages/client/src/components/Input.tsx | 1 + packages/client/src/components/OriginalDialog.tsx | 2 +- packages/client/src/components/Textarea.tsx | 1 + packages/client/src/components/Tooltip.css | 2 +- packages/client/src/components/Tooltip.tsx | 7 +++++++ 7 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 packages/client/src/components/Field.css diff --git a/client/src/style.css b/client/src/style.css index 4839939bf8..91b4a47561 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -2663,7 +2663,7 @@ html { } .tooltip { - z-index: 900; + z-index: 150; cursor: pointer; border-radius: 0.275rem; background-color: var(--surface-primary); @@ -2697,16 +2697,6 @@ html { outline-offset: 2px; } -/* Shared Input/Textarea (.lc-field): show the focus ring for keyboard users - only. Text inputs match :focus-visible on pointer focus too (a UA heuristic - CSS cannot distinguish), so the ring is gated on the tracked input modality. */ -html[data-input-modality='pointer'] .lc-field:focus, -html[data-input-modality='pointer'] .lc-field:focus-visible { - border-color: var(--border-light); - box-shadow: none; - outline: none; -} - .popover-ui { display: flex; max-height: min(var(--popover-available-height, 1700px), 1700px); diff --git a/packages/client/src/components/Field.css b/packages/client/src/components/Field.css new file mode 100644 index 0000000000..049a0591ac --- /dev/null +++ b/packages/client/src/components/Field.css @@ -0,0 +1,11 @@ +/* Shared Input/Textarea (.lc-field): show the focus ring for keyboard users + only. Text inputs match :focus-visible on pointer focus too (a UA heuristic + CSS cannot distinguish), so the ring is gated on the input modality tracked + by useInputModality (mount it once near the app root). Without the hook the + attribute is absent and the ring always shows — the safe fallback. */ +html[data-input-modality='pointer'] .lc-field:focus, +html[data-input-modality='pointer'] .lc-field:focus-visible { + border-color: var(--border-light); + box-shadow: none; + outline: none; +} diff --git a/packages/client/src/components/Input.tsx b/packages/client/src/components/Input.tsx index f488b19144..a55a5c0486 100644 --- a/packages/client/src/components/Input.tsx +++ b/packages/client/src/components/Input.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { cn } from '~/utils'; +import './Field.css'; export type InputProps = React.InputHTMLAttributes; diff --git a/packages/client/src/components/OriginalDialog.tsx b/packages/client/src/components/OriginalDialog.tsx index 7980f3c654..9d17779bed 100644 --- a/packages/client/src/components/OriginalDialog.tsx +++ b/packages/client/src/components/OriginalDialog.tsx @@ -178,7 +178,7 @@ const DialogContent: React.ForwardRefExoticComponent< {children} {showCloseButton && ( - )} diff --git a/packages/client/src/components/Textarea.tsx b/packages/client/src/components/Textarea.tsx index e7b408e5fc..57a2483578 100644 --- a/packages/client/src/components/Textarea.tsx +++ b/packages/client/src/components/Textarea.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { cn } from '~/utils'; +import './Field.css'; export interface TextareaProps extends React.TextareaHTMLAttributes {} diff --git a/packages/client/src/components/Tooltip.css b/packages/client/src/components/Tooltip.css index d885641641..7d885bc90c 100644 --- a/packages/client/src/components/Tooltip.css +++ b/packages/client/src/components/Tooltip.css @@ -1,5 +1,5 @@ .tooltip { - z-index: 900; + z-index: 150; cursor: pointer; pointer-events: auto; border-radius: 0.275rem; diff --git a/packages/client/src/components/Tooltip.tsx b/packages/client/src/components/Tooltip.tsx index 21457c9435..4d5fcc4bbd 100644 --- a/packages/client/src/components/Tooltip.tsx +++ b/packages/client/src/components/Tooltip.tsx @@ -9,6 +9,7 @@ import { import DOMPurify from 'dompurify'; import * as Ariakit from '@ariakit/react'; import { AnimatePresence, motion } from 'framer-motion'; +import { useDialogDepth, usePopoverZIndex } from './OriginalDialog'; import { cn } from '~/utils'; import './Tooltip.css'; @@ -35,6 +36,11 @@ const TooltipPopup = memo(function TooltipPopup({ }) { const mounted = Ariakit.useStoreState(store, (state) => state.mounted); const placement = Ariakit.useStoreState(store, (state) => state.placement); + /** Tooltips portal to body at z-150, which nested dialogs (z 200+) cover — + * inside a dialog, borrow the popover's depth-aware z-index; outside, keep + * the stylesheet default so tooltips never outrank freshly opened dialogs. */ + const dialogDepth = useDialogDepth(); + const popoverZIndex = usePopoverZIndex(); const sanitizer = useMemo(() => { const instance = DOMPurify(); @@ -89,6 +95,7 @@ const TooltipPopup = memo(function TooltipPopup({ className="tooltip" render={ 0 ? { zIndex: popoverZIndex } : undefined} initial={{ opacity: 0, x, y }} animate={{ opacity: 1, x: 0, y: 0 }} exit={{ opacity: 0, x, y }}