Skip to content

Commit 4e3a1e5

Browse files
authored
Migrate to shadcn tooltips (#982)
It's nice to be on a standardized component, and it comes with the +1 of hover states.
1 parent 6cdbfd1 commit 4e3a1e5

36 files changed

+970
-1108
lines changed

src/browser/App.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { AuthTokenModal } from "@/browser/components/AuthTokenModal";
4141
import { SettingsProvider, useSettings } from "./contexts/SettingsContext";
4242
import { SettingsModal } from "./components/Settings/SettingsModal";
4343
import { TutorialProvider } from "./contexts/TutorialContext";
44+
import { TooltipProvider } from "./components/ui/tooltip";
4445

4546
const THINKING_LEVELS: ThinkingLevel[] = ["off", "low", "medium", "high"];
4647

@@ -690,13 +691,15 @@ function AppInner() {
690691
function App() {
691692
return (
692693
<ThemeProvider>
693-
<SettingsProvider>
694-
<TutorialProvider>
695-
<CommandRegistryProvider>
696-
<AppInner />
697-
</CommandRegistryProvider>
698-
</TutorialProvider>
699-
</SettingsProvider>
694+
<TooltipProvider delayDuration={200}>
695+
<SettingsProvider>
696+
<TutorialProvider>
697+
<CommandRegistryProvider>
698+
<AppInner />
699+
</CommandRegistryProvider>
700+
</TutorialProvider>
701+
</SettingsProvider>
702+
</TooltipProvider>
700703
</ThemeProvider>
701704
);
702705
}

src/browser/components/ChatInput/CreationControls.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Select } from "../Select";
44
import { RuntimeIconSelector } from "../RuntimeIconSelector";
55
import { Loader2, Wand2 } from "lucide-react";
66
import { cn } from "@/common/lib/utils";
7-
import { Tooltip, TooltipWrapper } from "../Tooltip";
7+
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
88
import type { WorkspaceNameState } from "@/browser/hooks/useWorkspaceName";
99

1010
interface CreationControlsProps {
@@ -80,27 +80,31 @@ export function CreationControls(props: CreationControlsProps) {
8080
{nameState.isGenerating ? (
8181
<Loader2 className="text-accent h-3.5 w-3.5 animate-spin" />
8282
) : (
83-
<TooltipWrapper inline>
84-
<button
85-
type="button"
86-
onClick={handleWandClick}
87-
disabled={props.disabled}
88-
className="flex h-full items-center disabled:opacity-50"
89-
aria-label={nameState.autoGenerate ? "Disable auto-naming" : "Enable auto-naming"}
90-
>
91-
<Wand2
92-
className={cn(
93-
"h-3.5 w-3.5 transition-colors",
94-
nameState.autoGenerate
95-
? "text-accent"
96-
: "text-muted-foreground opacity-50 hover:opacity-75"
97-
)}
98-
/>
99-
</button>
100-
<Tooltip className="tooltip" align="center">
83+
<Tooltip>
84+
<TooltipTrigger asChild>
85+
<button
86+
type="button"
87+
onClick={handleWandClick}
88+
disabled={props.disabled}
89+
className="flex h-full items-center disabled:opacity-50"
90+
aria-label={
91+
nameState.autoGenerate ? "Disable auto-naming" : "Enable auto-naming"
92+
}
93+
>
94+
<Wand2
95+
className={cn(
96+
"h-3.5 w-3.5 transition-colors",
97+
nameState.autoGenerate
98+
? "text-accent"
99+
: "text-muted-foreground opacity-50 hover:opacity-75"
100+
)}
101+
/>
102+
</button>
103+
</TooltipTrigger>
104+
<TooltipContent align="center">
101105
{nameState.autoGenerate ? "Auto-naming enabled" : "Click to enable auto-naming"}
102-
</Tooltip>
103-
</TooltipWrapper>
106+
</TooltipContent>
107+
</Tooltip>
104108
)}
105109
</div>
106110
</div>

src/browser/components/ChatInput/VoiceInputButton.tsx

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import React from "react";
77
import { Mic, Loader2 } from "lucide-react";
8-
import { TooltipWrapper, Tooltip } from "../Tooltip";
8+
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
99
import { formatKeybind, KEYBINDS } from "@/browser/utils/ui/keybinds";
1010
import { cn } from "@/common/lib/utils";
1111
import type { VoiceInputState } from "@/browser/hooks/useVoiceInput";
@@ -60,22 +60,24 @@ export const VoiceInputButton: React.FC<VoiceInputButtonProps> = (props) => {
6060
const isTranscribing = props.state === "transcribing";
6161

6262
return (
63-
<TooltipWrapper inline>
64-
<button
65-
type="button"
66-
onClick={props.onToggle}
67-
disabled={(props.disabled ?? false) || isTranscribing || isDisabled}
68-
aria-label={label}
69-
aria-pressed={props.state === "recording"}
70-
className={cn(
71-
"inline-flex items-center justify-center rounded p-0.5 transition-colors duration-150",
72-
"disabled:cursor-not-allowed disabled:opacity-40",
73-
colorClass
74-
)}
75-
>
76-
<Icon className={cn("h-4 w-4", isTranscribing && "animate-spin")} strokeWidth={1.5} />
77-
</button>
78-
<Tooltip className="tooltip" align="right">
63+
<Tooltip>
64+
<TooltipTrigger asChild>
65+
<button
66+
type="button"
67+
onClick={props.onToggle}
68+
disabled={(props.disabled ?? false) || isTranscribing || isDisabled}
69+
aria-label={label}
70+
aria-pressed={props.state === "recording"}
71+
className={cn(
72+
"inline-flex items-center justify-center rounded p-0.5 transition-colors duration-150",
73+
"disabled:cursor-not-allowed disabled:opacity-40",
74+
colorClass
75+
)}
76+
>
77+
<Icon className={cn("h-4 w-4", isTranscribing && "animate-spin")} strokeWidth={1.5} />
78+
</button>
79+
</TooltipTrigger>
80+
<TooltipContent>
7981
{needsHttps ? (
8082
<>
8183
Voice input requires a secure connection.
@@ -98,7 +100,7 @@ export const VoiceInputButton: React.FC<VoiceInputButtonProps> = (props) => {
98100
While recording: space sends, esc cancels
99101
</>
100102
)}
101-
</Tooltip>
102-
</TooltipWrapper>
103+
</TooltipContent>
104+
</Tooltip>
103105
);
104106
};

src/browser/components/ChatInput/index.tsx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
getSlashCommandSuggestions,
4343
type SlashSuggestion,
4444
} from "@/browser/utils/slashCommands/suggestions";
45-
import { TooltipWrapper, Tooltip, HelpIndicator } from "../Tooltip";
45+
import { Tooltip, TooltipTrigger, TooltipContent, HelpIndicator } from "../ui/tooltip";
4646
import { ModeSelector } from "../ModeSelector";
4747
import {
4848
matchesKeybind,
@@ -1398,9 +1398,11 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
13981398
defaultModel={defaultModel}
13991399
onSetDefaultModel={setDefaultModel}
14001400
/>
1401-
<TooltipWrapper inline>
1402-
<HelpIndicator>?</HelpIndicator>
1403-
<Tooltip className="tooltip" align="left" width="wide">
1401+
<Tooltip>
1402+
<TooltipTrigger asChild>
1403+
<HelpIndicator>?</HelpIndicator>
1404+
</TooltipTrigger>
1405+
<TooltipContent align="start" className="max-w-80 whitespace-normal">
14041406
<strong>Click to edit</strong> or use{" "}
14051407
{formatKeybind(KEYBINDS.OPEN_MODEL_SELECTOR)}
14061408
<br />
@@ -1418,8 +1420,8 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
14181420
<code>/model provider:model-name</code>
14191421
<br />
14201422
(e.g., <code>/model anthropic:claude-sonnet-4-5</code>)
1421-
</Tooltip>
1422-
</TooltipWrapper>
1423+
</TooltipContent>
1424+
</Tooltip>
14231425
</div>
14241426

14251427
{/* Thinking Slider - slider hidden on narrow containers, label always clickable */}
@@ -1457,25 +1459,27 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
14571459
data-tutorial="mode-selector"
14581460
>
14591461
<ModeSelector mode={mode} onChange={setMode} />
1460-
<TooltipWrapper inline>
1461-
<button
1462-
type="button"
1463-
onClick={() => void handleSend()}
1464-
disabled={!canSend}
1465-
aria-label="Send message"
1466-
className={cn(
1467-
"inline-flex items-center gap-1 rounded-sm border border-border-light px-1.5 py-0.5 text-[11px] font-medium text-white transition-colors duration-200 disabled:opacity-50",
1468-
mode === "plan"
1469-
? "bg-plan-mode hover:bg-plan-mode-hover disabled:hover:bg-plan-mode"
1470-
: "bg-exec-mode hover:bg-exec-mode-hover disabled:hover:bg-exec-mode"
1471-
)}
1472-
>
1473-
<SendHorizontal className="h-3.5 w-3.5" strokeWidth={2.5} />
1474-
</button>
1475-
<Tooltip className="tooltip" align="center">
1462+
<Tooltip>
1463+
<TooltipTrigger asChild>
1464+
<button
1465+
type="button"
1466+
onClick={() => void handleSend()}
1467+
disabled={!canSend}
1468+
aria-label="Send message"
1469+
className={cn(
1470+
"inline-flex items-center gap-1 rounded-sm border border-border-light px-1.5 py-0.5 text-[11px] font-medium text-white transition-colors duration-200 disabled:opacity-50",
1471+
mode === "plan"
1472+
? "bg-plan-mode hover:bg-plan-mode-hover disabled:hover:bg-plan-mode"
1473+
: "bg-exec-mode hover:bg-exec-mode-hover disabled:hover:bg-exec-mode"
1474+
)}
1475+
>
1476+
<SendHorizontal className="h-3.5 w-3.5" strokeWidth={2.5} />
1477+
</button>
1478+
</TooltipTrigger>
1479+
<TooltipContent align="center">
14761480
Send message ({formatKeybind(KEYBINDS.SEND_MESSAGE)})
1477-
</Tooltip>
1478-
</TooltipWrapper>
1481+
</TooltipContent>
1482+
</Tooltip>
14791483
</div>
14801484
</div>
14811485

src/browser/components/KebabMenu.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useRef, useEffect } from "react";
22
import { createPortal } from "react-dom";
3-
import { TooltipWrapper, Tooltip } from "./Tooltip";
3+
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
44
import { cn } from "@/common/lib/utils";
55

66
export interface KebabMenuItem {
@@ -69,29 +69,27 @@ export const KebabMenu: React.FC<KebabMenuProps> = ({ items, className }) => {
6969
setIsOpen(false);
7070
};
7171

72-
const button = (
73-
<button
74-
ref={buttonRef}
75-
onClick={() => setIsOpen(!isOpen)}
76-
className={cn(
77-
"border border-white/20 text-foreground text-[10px] py-0.5 px-2 rounded-[3px] cursor-pointer transition-all duration-200 font-primary flex items-center justify-center whitespace-nowrap",
78-
isOpen ? "bg-white/10" : "bg-none",
79-
"hover:bg-white/10 hover:border-white/30",
80-
"disabled:opacity-50 disabled:cursor-not-allowed",
81-
className
82-
)}
83-
>
84-
85-
</button>
86-
);
87-
8872
return (
8973
<>
9074
<div className="relative">
91-
<TooltipWrapper inline>
92-
{button}
93-
<Tooltip align="center">More actions</Tooltip>
94-
</TooltipWrapper>
75+
<Tooltip>
76+
<TooltipTrigger asChild>
77+
<button
78+
ref={buttonRef}
79+
onClick={() => setIsOpen(!isOpen)}
80+
className={cn(
81+
"border border-white/20 text-foreground text-[10px] py-0.5 px-2 rounded-[3px] cursor-pointer transition-all duration-200 font-primary flex items-center justify-center whitespace-nowrap",
82+
isOpen ? "bg-white/10" : "bg-none",
83+
"hover:bg-white/10 hover:border-white/30",
84+
"disabled:opacity-50 disabled:cursor-not-allowed",
85+
className
86+
)}
87+
>
88+
89+
</button>
90+
</TooltipTrigger>
91+
<TooltipContent align="center">More actions</TooltipContent>
92+
</Tooltip>
9593
</div>
9694

9795
{isOpen &&

src/browser/components/Messages/MessageWindow.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { formatTimestamp } from "@/browser/utils/ui/dateTime";
44
import { Code2Icon } from "lucide-react";
55
import type { ReactNode } from "react";
66
import React, { useMemo, useState } from "react";
7-
import { Tooltip, TooltipWrapper } from "../Tooltip";
7+
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
88
import { Button } from "../ui/button";
99

1010
export interface ButtonConfig {
@@ -153,10 +153,10 @@ const IconActionButton: React.FC<IconActionButtonProps> = ({ button }) => {
153153

154154
if (button.tooltip || button.label) {
155155
return (
156-
<TooltipWrapper inline>
157-
{content}
158-
<Tooltip align="center">{button.tooltip ?? button.label}</Tooltip>
159-
</TooltipWrapper>
156+
<Tooltip>
157+
<TooltipTrigger asChild>{content}</TooltipTrigger>
158+
<TooltipContent align="center">{button.tooltip ?? button.label}</TooltipContent>
159+
</Tooltip>
160160
);
161161
}
162162

src/browser/components/Messages/ModelDisplay.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import { TooltipWrapper, Tooltip } from "@/browser/components/Tooltip";
2+
import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/ui/tooltip";
33
import { ProviderIcon } from "@/browser/components/ProviderIcon";
44
import { formatModelDisplayName } from "@/common/utils/ai/modelDisplay";
55

@@ -68,11 +68,13 @@ export const ModelDisplay: React.FC<ModelDisplayProps> = ({ modelString, showToo
6868
}
6969

7070
return (
71-
<TooltipWrapper inline data-model-display-tooltip>
72-
{content}
73-
<Tooltip align="center" data-model-tooltip-text>
71+
<Tooltip>
72+
<TooltipTrigger asChild>
73+
<span data-model-display-tooltip>{content}</span>
74+
</TooltipTrigger>
75+
<TooltipContent align="center" data-model-tooltip-text>
7476
{modelString}
75-
</Tooltip>
76-
</TooltipWrapper>
77+
</TooltipContent>
78+
</Tooltip>
7779
);
7880
};

src/browser/components/Messages/QueuedMessage.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ButtonConfig } from "./MessageWindow";
33
import { MessageWindow } from "./MessageWindow";
44
import type { QueuedMessage as QueuedMessageType } from "@/common/types/message";
55
import { Pencil } from "lucide-react";
6-
import { Tooltip, TooltipWrapper } from "../Tooltip";
6+
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
77

88
interface QueuedMessageProps {
99
message: QueuedMessageType;
@@ -43,17 +43,19 @@ export const QueuedMessage: React.FC<QueuedMessageProps> = ({
4343

4444
// Clickable "Queued" label with tooltip
4545
const queuedLabel = onSendImmediately ? (
46-
<TooltipWrapper inline>
47-
<button
48-
type="button"
49-
onClick={() => void handleSendImmediately()}
50-
disabled={isSending}
51-
className="cursor-pointer hover:underline disabled:cursor-not-allowed disabled:opacity-50"
52-
>
53-
Queued
54-
</button>
55-
<Tooltip align="center">Click to send immediately</Tooltip>
56-
</TooltipWrapper>
46+
<Tooltip>
47+
<TooltipTrigger asChild>
48+
<button
49+
type="button"
50+
onClick={() => void handleSendImmediately()}
51+
disabled={isSending}
52+
className="cursor-pointer hover:underline disabled:cursor-not-allowed disabled:opacity-50"
53+
>
54+
Queued
55+
</button>
56+
</TooltipTrigger>
57+
<TooltipContent align="center">Click to send immediately</TooltipContent>
58+
</Tooltip>
5759
) : (
5860
"Queued"
5961
);

0 commit comments

Comments
 (0)