Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions frontend/src/components/bookmarks/BookmarkRow/BookmarkRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,27 +59,29 @@ function BookmarkRow({

<ItemActions className="gap-0.5">
{onExport && (
<ActionTooltip label={t('bookmarks.exportOne')}>
<Button
variant="action"
size="icon"
disabled={!trial || isExporting}
aria-label={`${t('bookmarks.exportOne')} (${title})`}
onClick={() => trial && onExport(trial)}
>
<FileDown />
</Button>
</ActionTooltip>
)}
<ActionTooltip label={t('bookmarks.remove')}>
<Button
variant="ghost"
variant="action"
size="icon"
disabled={!trial || isExporting}
aria-label={`${t('bookmarks.exportOne')} (${title})`}
title={t('bookmarks.exportOne')}
onClick={() => trial && onExport(trial)}
disabled={isPending}
aria-label={`${t('bookmarks.remove')} (${title})`}
onClick={() => onRemove(trialRef)}
>
<FileDown />
<BookmarkX />
</Button>
)}
<Button
variant="ghost"
size="icon"
disabled={isPending}
aria-label={`${t('bookmarks.remove')} (${title})`}
title={t('bookmarks.remove')}
onClick={() => onRemove(trialRef)}
>
<BookmarkX />
</Button>
</ActionTooltip>
</ItemActions>
</Item>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -52,12 +53,14 @@ function BookmarksSheet({ open, onOpenChange }: BookmarksSheetProps) {
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="right" showCloseButton={false} className="w-full gap-0 p-0 sm:max-w-md">
<SheetClose
aria-label={t('bookmarks.close')}
className="border-border bg-popover text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute top-1/2 -left-4 flex h-16 w-4 -translate-y-1/2 cursor-pointer items-center justify-center rounded-l-full border border-r-0 shadow-sm transition-colors focus-visible:ring-2 focus-visible:outline-none"
>
<ChevronRight className="size-3" />
</SheetClose>
<ActionTooltip label={t('bookmarks.close')}>
<SheetClose
aria-label={t('bookmarks.close')}
className="border-border bg-popover text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute top-1/2 -left-4 flex h-16 w-4 -translate-y-1/2 cursor-pointer items-center justify-center rounded-l-full border border-r-0 shadow-sm transition-colors focus-visible:ring-2 focus-visible:outline-none"
>
<ChevronRight className="size-3" />
</SheetClose>
</ActionTooltip>

<SheetHeader className="border-border border-b">
<SheetTitle>
Expand Down
20 changes: 11 additions & 9 deletions frontend/src/components/chat/ChatHeader/ChatHeader.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,15 +12,16 @@ function ChatHeader({ onNewConversation }: ChatHeaderProps) {

return (
<header className="border-border flex items-center justify-end border-b px-2 py-1">
<Button
variant="ghost"
size="icon"
aria-label={t('chat.newConversation')}
title={t('chat.newConversation')}
onClick={onNewConversation}
>
<SquarePen />
</Button>
<ActionTooltip label={t('chat.newConversation')}>
<Button
variant="action"
size="icon"
aria-label={t('chat.newConversation')}
onClick={onNewConversation}
>
<SquarePen />
</Button>
</ActionTooltip>
</header>
);
}
Expand Down
23 changes: 13 additions & 10 deletions frontend/src/components/chat/MessageFeedback/MessageFeedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -138,16 +139,18 @@ function MessageFeedback({ sessionId, observationId }: MessageFeedbackProps) {
className="gap-1 py-0.5 pr-1 pl-2 font-mono"
>
<span>{nct}</span>
<Button
type="button"
variant="ghost"
size="icon"
aria-label={t('feedback.removeNct', { nct })}
onClick={() => removeNct(nct)}
className="size-4 rounded-full hover:bg-transparent"
>
<X className="size-3" />
</Button>
<ActionTooltip label={t('feedback.removeNct', { nct })}>
<Button
type="button"
variant="ghost"
size="icon"
aria-label={t('feedback.removeNct', { nct })}
onClick={() => removeNct(nct)}
className="size-4 rounded-full hover:bg-transparent"
>
<X className="size-3" />
</Button>
</ActionTooltip>
</Badge>
))}
</div>
Expand Down
28 changes: 19 additions & 9 deletions frontend/src/components/chat/SelectedTrials/SelectedTrials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,16 +29,22 @@ function SelectedTrials({ trials, onRemove }: SelectedTrialsProps) {
<Badge key={trial.trialRef} variant="secondary" className="gap-1.5 py-1 pr-1 pl-2">
<MapPin className="size-3 shrink-0" />
<span className="max-w-[12rem] truncate">{label}</span>
<Button
type="button"
variant="ghost"
size="icon"
aria-label={t('chat.removeFromContext', { trialRef: label })}
onClick={() => onRemove(trial.trialRef)}
className="size-4 rounded-full hover:bg-transparent"
<ActionTooltip
label={t('chat.removeFromContext', {
trialRef: truncate(label, TOOLTIP_TITLE_MAX_LENGTH),
})}
>
<X className="size-3" />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
aria-label={t('chat.removeFromContext', { trialRef: label })}
onClick={() => onRemove(trial.trialRef)}
className="size-4 rounded-full hover:bg-transparent"
>
<X className="size-3" />
</Button>
</ActionTooltip>
</Badge>
);
})}
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/components/chat/TrialCitation/TrialCitation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/common/ActionTooltip/ActionTooltip.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent>{label}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

export default ActionTooltip;
10 changes: 5 additions & 5 deletions frontend/src/components/layout/AppHeader/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ function AppHeader({ onOpenBookmarks, onStartTour }: AppHeaderProps) {
<button
type="button"
aria-label={notice}
className="text-header-foreground/80 hover:text-header-foreground flex items-center gap-1 rounded-md px-1.5 py-0.5 transition-colors"
className="group/updated text-header-foreground/80 hover:text-header-foreground flex items-center gap-1 rounded-md px-1.5 py-0.5 transition-colors"
>
{lastUpdated && (
<span className="text-eyebrow text-[0.65rem] font-bold">{lastUpdated}</span>
)}
<span
aria-hidden
className="border-header-foreground/40 flex size-3 items-center justify-center rounded-full border text-[0.55rem] leading-none font-bold"
className="border-header-foreground/40 group-hover/updated:border-highlight group-hover/updated:text-highlight flex size-3 items-center justify-center rounded-full border text-[0.55rem] leading-none font-bold transition-colors"
>
?
</span>
Expand Down Expand Up @@ -79,7 +79,7 @@ function AppHeader({ onOpenBookmarks, onStartTour }: AppHeaderProps) {
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
variant="action"
size="icon"
data-tour="bookmarks"
onClick={onOpenBookmarks}
Expand All @@ -100,7 +100,7 @@ function AppHeader({ onOpenBookmarks, onStartTour }: AppHeaderProps) {
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
variant="action"
size="icon"
onClick={onStartTour}
aria-label={t('header.takeTour')}
Expand All @@ -114,7 +114,7 @@ function AppHeader({ onOpenBookmarks, onStartTour }: AppHeaderProps) {
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
variant="action"
size="icon"
onClick={toggleTheme}
aria-label={dark ? t('header.switchToLight') : t('header.switchToDark')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function LanguagePicker() {
<TooltipTrigger asChild>
<PopoverTrigger asChild>
<Button
variant="ghost"
variant="action"
size="icon"
data-tour="language"
aria-label={t('header.language')}
Expand Down
Loading
Loading