From 28f74d475899091d69733aae71d41c80774142cb Mon Sep 17 00:00:00 2001 From: safina57 Date: Wed, 11 Nov 2026 15:42:34 +0100 Subject: [PATCH] feat(frontend): green icon hover on action buttons and tooltips on icon-only buttons --- .../bookmarks/BookmarkRow/BookmarkRow.tsx | 37 ++++++++------- .../BookmarksSheet/BookmarksSheet.tsx | 15 +++--- .../components/chat/ChatHeader/ChatHeader.tsx | 20 ++++---- .../chat/MessageFeedback/MessageFeedback.tsx | 23 +++++---- .../chat/SelectedTrials/SelectedTrials.tsx | 28 +++++++---- .../chat/TrialCitation/TrialCitation.tsx | 6 +-- .../common/ActionTooltip/ActionTooltip.tsx | 21 +++++++++ .../components/layout/AppHeader/AppHeader.tsx | 10 ++-- .../layout/LanguagePicker/LanguagePicker.tsx | 2 +- .../TrialSummaryHeader/TrialSummaryHeader.tsx | 47 ++++++++----------- frontend/src/components/ui/button.tsx | 2 + frontend/src/lib/utils.ts | 4 ++ 12 files changed, 125 insertions(+), 90 deletions(-) create mode 100644 frontend/src/components/common/ActionTooltip/ActionTooltip.tsx diff --git a/frontend/src/components/bookmarks/BookmarkRow/BookmarkRow.tsx b/frontend/src/components/bookmarks/BookmarkRow/BookmarkRow.tsx index 40505b4..38a4bf6 100644 --- a/frontend/src/components/bookmarks/BookmarkRow/BookmarkRow.tsx +++ b/frontend/src/components/bookmarks/BookmarkRow/BookmarkRow.tsx @@ -2,6 +2,7 @@ import { BookmarkX, FileDown } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Item, ItemActions, ItemContent } from '@/components/ui/item'; +import ActionTooltip from '@/components/common/ActionTooltip/ActionTooltip'; import BookmarkRowSkeleton from '@/components/bookmarks/BookmarkRowSkeleton/BookmarkRowSkeleton'; import TrialBadges from '@/components/bookmarks/TrialBadges/TrialBadges'; import { publicTrialId, trialTitle } from '@/lib/trial'; @@ -58,27 +59,29 @@ function BookmarkRow({ {onExport && ( + + + + )} + - )} - + ); diff --git a/frontend/src/components/bookmarks/BookmarksSheet/BookmarksSheet.tsx b/frontend/src/components/bookmarks/BookmarksSheet/BookmarksSheet.tsx index 5417fec..b69f6c3 100644 --- a/frontend/src/components/bookmarks/BookmarksSheet/BookmarksSheet.tsx +++ b/frontend/src/components/bookmarks/BookmarksSheet/BookmarksSheet.tsx @@ -14,6 +14,7 @@ import { SheetTitle, } from '@/components/ui/sheet'; import BookmarkRow from '@/components/bookmarks/BookmarkRow/BookmarkRow'; +import ActionTooltip from '@/components/common/ActionTooltip/ActionTooltip'; import { useCachedTrialTranslations } from '@/hooks/useTrialTranslation'; import { useTrialPdfExport } from '@/hooks/useTrialPdfExport'; import { trialQuery } from '@/services/trials'; @@ -52,12 +53,14 @@ function BookmarksSheet({ open, onOpenChange }: BookmarksSheetProps) { return ( - - - + + + + + diff --git a/frontend/src/components/chat/ChatHeader/ChatHeader.tsx b/frontend/src/components/chat/ChatHeader/ChatHeader.tsx index d4a6c4d..e7823a9 100644 --- a/frontend/src/components/chat/ChatHeader/ChatHeader.tsx +++ b/frontend/src/components/chat/ChatHeader/ChatHeader.tsx @@ -1,6 +1,7 @@ import { SquarePen } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; +import ActionTooltip from '@/components/common/ActionTooltip/ActionTooltip'; interface ChatHeaderProps { onNewConversation: () => void; @@ -11,15 +12,16 @@ function ChatHeader({ onNewConversation }: ChatHeaderProps) { return (
- + + +
); } diff --git a/frontend/src/components/chat/MessageFeedback/MessageFeedback.tsx b/frontend/src/components/chat/MessageFeedback/MessageFeedback.tsx index b6c4972..2ef7725 100644 --- a/frontend/src/components/chat/MessageFeedback/MessageFeedback.tsx +++ b/frontend/src/components/chat/MessageFeedback/MessageFeedback.tsx @@ -5,6 +5,7 @@ import { toast } from 'sonner'; import { MessageAction, MessageActions } from '@/components/ai-elements/message'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import ActionTooltip from '@/components/common/ActionTooltip/ActionTooltip'; import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; @@ -138,16 +139,18 @@ function MessageFeedback({ sessionId, observationId }: MessageFeedbackProps) { className="gap-1 py-0.5 pr-1 pl-2 font-mono" > {nct} - + + + ))} diff --git a/frontend/src/components/chat/SelectedTrials/SelectedTrials.tsx b/frontend/src/components/chat/SelectedTrials/SelectedTrials.tsx index 748dc61..fdd2b47 100644 --- a/frontend/src/components/chat/SelectedTrials/SelectedTrials.tsx +++ b/frontend/src/components/chat/SelectedTrials/SelectedTrials.tsx @@ -2,9 +2,13 @@ import { MapPin, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import ActionTooltip from '@/components/common/ActionTooltip/ActionTooltip'; import { trialTitle } from '@/lib/trial'; +import { truncate } from '@/lib/utils'; import type { Trial } from '@/types/trial'; +const TOOLTIP_TITLE_MAX_LENGTH = 40; + interface SelectedTrialsProps { trials: Trial[]; onRemove: (trialRef: string) => void; @@ -25,16 +29,22 @@ function SelectedTrials({ trials, onRemove }: SelectedTrialsProps) { {label} - + + ); })} diff --git a/frontend/src/components/chat/TrialCitation/TrialCitation.tsx b/frontend/src/components/chat/TrialCitation/TrialCitation.tsx index 604a0d5..3332631 100644 --- a/frontend/src/components/chat/TrialCitation/TrialCitation.tsx +++ b/frontend/src/components/chat/TrialCitation/TrialCitation.tsx @@ -11,17 +11,13 @@ import { import { useIsMobile } from '@/hooks/useIsMobile'; import { useCachedTrialTranslations } from '@/hooks/useTrialTranslation'; import { trialTitle } from '@/lib/trial'; -import { cn } from '@/lib/utils'; +import { cn, truncate } from '@/lib/utils'; import { trialQuery } from '@/services/trials'; const CITATION_TITLE_MAX_LENGTH = 50; const COMPACT_TITLE_MAX_LENGTH = 28; const CITATION_DESCRIPTION_MAX_LENGTH = 200; -function truncate(text: string, max: number): string { - return text.length > max ? `${text.slice(0, max).trimEnd()}…` : text; -} - interface TrialCitationProps { trialRef: string; onSelect: (trialRef: string) => void; diff --git a/frontend/src/components/common/ActionTooltip/ActionTooltip.tsx b/frontend/src/components/common/ActionTooltip/ActionTooltip.tsx new file mode 100644 index 0000000..6b45281 --- /dev/null +++ b/frontend/src/components/common/ActionTooltip/ActionTooltip.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; + +interface ActionTooltipProps { + label: string; + children: ReactNode; +} + +/** Hover tooltip for a single action button, which must forward refs and props. */ +function ActionTooltip({ label, children }: ActionTooltipProps) { + return ( + + + {children} + {label} + + + ); +} + +export default ActionTooltip; diff --git a/frontend/src/components/layout/AppHeader/AppHeader.tsx b/frontend/src/components/layout/AppHeader/AppHeader.tsx index 47f11e9..e9bed64 100644 --- a/frontend/src/components/layout/AppHeader/AppHeader.tsx +++ b/frontend/src/components/layout/AppHeader/AppHeader.tsx @@ -44,14 +44,14 @@ function AppHeader({ onOpenBookmarks, onStartTour }: AppHeaderProps) { - - + + - - + + - + {registryLinks.map(({ source, href }, index) => { const label = t('summary.viewOn', { registry: TRIAL_SOURCES[source].registry }); return ( - + - + ); })}
{!hideActions && } - + + +
); diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 75b8c3d..7e2781f 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -19,6 +19,8 @@ const buttonVariants = cva( destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", link: "text-primary underline-offset-4 hover:underline", + action: + "hover:text-foreground aria-expanded:text-foreground [&_svg]:transition-colors hover:[&_svg]:text-highlight focus-visible:[&_svg]:text-highlight aria-expanded:[&_svg]:text-highlight", }, size: { default: diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 2819a83..67248cc 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -4,3 +4,7 @@ import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function truncate(text: string, max: number): string { + return text.length > max ? `${text.slice(0, max).trimEnd()}…` : text; +}