🎨 fix: Gate message hover-reveal controls on hover capability, not width (#13712)

Apply the MessageTimestamp transform to all remaining hover-reveal
controls in the message UI: replace the md: breakpoint proxy with a
(hover: hover) media query so action buttons stay visible on touch and
other non-hover devices (e.g. tablets wider than md), while still
hiding until row hover/focus where a pointer supports it.

The group-hover:visible reveal trio keeps no hidden base state, so it
only drops the md: prefixes; the actual hide-until-hover mechanism is
the opacity variant, which stays focusable and in the accessibility
tree while hidden.
This commit is contained in:
Marco Beretta 2026-06-14 15:03:37 +01:00 committed by GitHub
parent fc2ae89aa6
commit 16bbc4b97e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 24 additions and 19 deletions

View file

@ -1,14 +1,6 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import * as Ariakit from '@ariakit/react';
import { TFeedback, TFeedbackTag, getTagsForRating } from 'librechat-data-provider';
import {
Button,
OGDialog,
OGDialogContent,
OGDialogTitle,
ThumbUpIcon,
ThumbDownIcon,
} from '@librechat/client';
import {
AlertCircle,
PenTool,
@ -19,6 +11,14 @@ import {
Lightbulb,
Search,
} from 'lucide-react';
import {
Button,
OGDialog,
OGDialogContent,
OGDialogTitle,
ThumbUpIcon,
ThumbDownIcon,
} from '@librechat/client';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
@ -218,8 +218,9 @@ function buttonClasses(isActive: boolean, isLast: boolean) {
return cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
!isLast && 'md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100',
'group-hover:visible group-focus-within:visible group-[.final-completion]:visible',
!isLast &&
'group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:hover)]:opacity-0',
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',
isActive && 'active text-text-primary bg-surface-hover',
);

View file

@ -229,8 +229,9 @@ export default function Fork({
const buttonStyle = cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
!isLast && 'md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100',
'group-hover:visible group-focus-within:visible group-[.final-completion]:visible',
!isLast &&
'group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:hover)]:opacity-0',
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',
isActive && 'active text-text-primary bg-surface-hover',
);

View file

@ -1,7 +1,7 @@
import React, { useState, useMemo, memo } from 'react';
import { useRecoilState } from 'recoil';
import type { TConversation, TMessage, TFeedback } from 'librechat-data-provider';
import { EditIcon, Clipboard, CheckMark, ContinueIcon, RegenerateIcon } from '@librechat/client';
import type { TConversation, TMessage, TFeedback } from 'librechat-data-provider';
import { useGenerationsByLatest, useLocalize } from '~/hooks';
import { Fork } from '~/components/Conversations';
import MessageAudio from './MessageAudio';
@ -84,8 +84,9 @@ const HoverButton = memo(
const buttonStyle = cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
!isLast && 'md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100',
'group-hover:visible group-focus-within:visible group-[.final-completion]:visible',
!isLast &&
'group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:hover)]:opacity-0',
!isVisible && 'opacity-0',
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',
isActive && isVisible && 'active text-text-primary bg-surface-hover',
@ -215,7 +216,9 @@ const HoverButtons = ({
isLast={isLast}
className={cn(
'ml-0 flex items-center gap-1.5 text-xs',
isSubmitting && isCreatedByUser ? 'md:opacity-0 md:group-hover:opacity-100' : '',
isSubmitting && isCreatedByUser
? 'group-hover:opacity-100 [@media(hover:hover)]:opacity-0'
: '',
)}
/>

View file

@ -1,6 +1,6 @@
import { useState } from 'react';
import { Clipboard, CheckMark } from '@librechat/client';
import type { TMessage, TAttachment, SearchResultData } from 'librechat-data-provider';
import type { TMessage, SearchResultData } from 'librechat-data-provider';
import { useLocalize, useCopyToClipboard } from '~/hooks';
type THoverButtons = {
@ -20,7 +20,7 @@ export default function MinimalHoverButtons({ message, searchResults }: THoverBu
return (
<div className="visible mt-1 flex justify-center gap-1 self-end text-gray-400 lg:justify-start">
<button
className="ml-0 flex items-center gap-1.5 rounded-lg p-1.5 text-xs text-text-secondary-alt transition-colors duration-200 hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white md:opacity-0 md:group-focus-within:opacity-100 md:group-hover:opacity-100"
className="ml-0 flex items-center gap-1.5 rounded-lg p-1.5 text-xs text-text-secondary-alt transition-colors duration-200 hover:bg-surface-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black group-focus-within:opacity-100 group-hover:opacity-100 dark:focus-visible:ring-white [@media(hover:hover)]:opacity-0"
onClick={() => copyToClipboard(setIsCopied)}
type="button"
title={

View file

@ -26,7 +26,7 @@ export default function SiblingSwitch({
const buttonStyle = cn(
'hover-button rounded-lg p-1.5 text-text-secondary-alt',
'hover:text-text-primary hover:bg-surface-hover',
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
'group-hover:visible group-focus-within:visible group-[.final-completion]:visible',
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',
);