feat(frontend): show placeholder + hotkey tooltip on InputSearch trigger

The collapsed trigger is just a magnifier icon — there's no visible
hint about what it does or which key reveals it. Added a tooltip that
combines the `placeholder` with the platform-appropriate hotkey glyph
(⌘ on Apple, Ctrl elsewhere) using the existing `isMac()` helper. When
`hotkey` is `null` the suffix drops and the tooltip shows just the
placeholder.

Tooltip wraps only the trigger button: once the input is expanded the
caret is in it and a hovering tooltip would obscure the very thing the
user is typing into.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-05-19 18:04:32 +07:00
co-authored by Claude Opus 4.7
parent dd786d4c9d
commit e2cd75720e
+31 -10
View File
@@ -1,9 +1,11 @@
import { Search } from 'lucide-react';
import { motion } from 'motion/react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
import { isMac } from '@/lib/utils/platform';
interface InputSearchProps {
/** Accessible label for the trigger button + the input. */
@@ -58,6 +60,20 @@ export function InputSearch({
const expand = useCallback(() => setIsExpanded(true), []);
// Hint shown over the collapsed trigger. The hotkey suffix mirrors the
// platform's modifier glyph (⌘ on Apple, Ctrl elsewhere) so the tooltip
// is actionable — the user sees the exact keys they can press without
// having to discover them by trial.
const tooltipText = useMemo(() => {
if (!hotkey) {
return placeholder;
}
const modifier = isMac() ? '⌘' : 'Ctrl';
return `${placeholder} (${modifier} ${hotkey.toUpperCase()})`;
}, [hotkey, placeholder]);
// Focus the input the frame after it appears. `rAF` defers past the same
// commit so motion's transform has started, otherwise focus can land on
// a still-zero-width box and the user's caret blinks invisibly until the
@@ -175,15 +191,20 @@ export function InputSearch({
className="text-muted-foreground"
/>
) : (
<InputGroupButton
aria-label={ariaLabel}
onClick={expand}
size="icon-sm"
type="button"
variant="ghost"
>
<Search aria-hidden="true" />
</InputGroupButton>
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton
aria-label={ariaLabel}
onClick={expand}
size="icon-sm"
type="button"
variant="ghost"
>
<Search aria-hidden="true" />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>{tooltipText}</TooltipContent>
</Tooltip>
)}
</InputGroupAddon>
<motion.div