Skip to content

Commit dc4acc7

Browse files
authored
tooltips for tab bar buttons (AI and workspace switcher) (#2914)
1 parent 705563e commit dc4acc7

2 files changed

Lines changed: 65 additions & 15 deletions

File tree

frontend/app/element/tooltip.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
useHover,
1313
useInteractions,
1414
} from "@floating-ui/react";
15-
import { useEffect, useRef, useState } from "react";
15+
import { useCallback, useEffect, useRef, useState } from "react";
1616

1717
interface TooltipProps {
1818
children: React.ReactNode;
@@ -24,6 +24,8 @@ interface TooltipProps {
2424
divClassName?: string;
2525
divStyle?: React.CSSProperties;
2626
divOnClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
27+
divRef?: React.RefObject<HTMLDivElement>;
28+
hideOnClick?: boolean;
2729
}
2830

2931
function TooltipInner({
@@ -35,9 +37,12 @@ function TooltipInner({
3537
divClassName,
3638
divStyle,
3739
divOnClick,
40+
divRef,
41+
hideOnClick = false,
3842
}: Omit<TooltipProps, "disable">) {
3943
const [isOpen, setIsOpen] = useState(forceOpen);
4044
const [isVisible, setIsVisible] = useState(false);
45+
const [clickDisabled, setClickDisabled] = useState(false);
4146
const timeoutRef = useRef<number | null>(null);
4247
const prevForceOpenRef = useRef<boolean>(forceOpen);
4348

@@ -106,17 +111,42 @@ function TooltipInner({
106111
};
107112
}, []);
108113

109-
const hover = useHover(context);
114+
const hover = useHover(context, { enabled: !clickDisabled });
110115
const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
111116

117+
const handleClick = useCallback(
118+
(e: React.MouseEvent<HTMLDivElement>) => {
119+
if (hideOnClick) {
120+
setIsVisible(false);
121+
setIsOpen(false);
122+
if (timeoutRef.current !== null) {
123+
window.clearTimeout(timeoutRef.current);
124+
}
125+
setClickDisabled(true);
126+
}
127+
divOnClick?.(e);
128+
},
129+
[hideOnClick, divOnClick]
130+
);
131+
132+
const handlePointerEnter = useCallback(() => {
133+
if (hideOnClick && clickDisabled) {
134+
setClickDisabled(false);
135+
}
136+
}, [hideOnClick, clickDisabled]);
137+
112138
return (
113139
<>
114140
<div
115-
ref={refs.setReference}
116-
{...getReferenceProps()}
141+
ref={(node) => {
142+
refs.setReference(node);
143+
if (divRef) {
144+
divRef.current = node;
145+
}
146+
}}
147+
{...getReferenceProps({ onClick: handleClick, onPointerEnter: handlePointerEnter })}
117148
className={divClassName}
118149
style={divStyle}
119-
onClick={divOnClick}
120150
>
121151
{children}
122152
</div>
@@ -152,10 +182,12 @@ export function Tooltip({
152182
divClassName,
153183
divStyle,
154184
divOnClick,
185+
divRef,
186+
hideOnClick = false,
155187
}: TooltipProps) {
156188
if (disable) {
157189
return (
158-
<div className={divClassName} style={divStyle} onClick={divOnClick}>
190+
<div ref={divRef} className={divClassName} style={divStyle} onClick={divOnClick}>
159191
{children}
160192
</div>
161193
);
@@ -171,6 +203,8 @@ export function Tooltip({
171203
divClassName={divClassName}
172204
divStyle={divStyle}
173205
divOnClick={divOnClick}
206+
divRef={divRef}
207+
hideOnClick={hideOnClick}
174208
/>
175209
);
176210
}

frontend/app/tab/tabbar.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { Button } from "@/app/element/button";
5+
import { Tooltip } from "@/app/element/tooltip";
56
import { modalsModel } from "@/app/store/modalmodel";
67
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
78
import { deleteLayoutModelForTab } from "@/layout/index";
@@ -42,7 +43,7 @@ interface TabBarProps {
4243
workspace: Workspace;
4344
}
4445

45-
const WaveAIButton = memo(() => {
46+
const WaveAIButton = memo(({ divRef }: { divRef?: React.RefObject<HTMLDivElement> }) => {
4647
const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
4748
const hideAiButton = useAtomValue(getSettingsKeyAtom("app:hideaibutton"));
4849

@@ -56,14 +57,18 @@ const WaveAIButton = memo(() => {
5657
}
5758

5859
return (
59-
<div
60-
className={`flex h-[26px] px-1.5 justify-end items-center rounded-md mr-1 box-border cursor-pointer bg-hover hover:bg-hoverbg transition-colors text-[12px] ${aiPanelOpen ? "text-accent" : "text-secondary"}`}
61-
style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
62-
onClick={onClick}
60+
<Tooltip
61+
content="Toggle Wave AI Panel"
62+
placement="bottom"
63+
hideOnClick
64+
divClassName={`flex h-[26px] px-1.5 justify-end items-center rounded-md mr-1 box-border cursor-pointer bg-hover hover:bg-hoverbg transition-colors text-[12px] ${aiPanelOpen ? "text-accent" : "text-secondary"}`}
65+
divStyle={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
66+
divOnClick={onClick}
67+
divRef={divRef}
6368
>
6469
<i className="fa fa-sparkles" />
6570
<span className="font-bold ml-1 -top-px font-mono">AI</span>
66-
</div>
71+
</Tooltip>
6772
);
6873
});
6974
WaveAIButton.displayName = "WaveAIButton";
@@ -190,6 +195,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
190195
const draggerLeftRef = useRef<HTMLDivElement>(null);
191196
const draggerRightRef = useRef<HTMLDivElement>(null);
192197
const workspaceSwitcherRef = useRef<HTMLDivElement>(null);
198+
const waveAIButtonRef = useRef<HTMLDivElement>(null);
193199
const appMenuButtonRef = useRef<HTMLDivElement>(null);
194200
const tabWidthRef = useRef<number>(TabDefaultWidth);
195201
const scrollableRef = useRef<boolean>(false);
@@ -251,6 +257,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
251257
const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0;
252258
const appMenuButtonWidth = appMenuButtonRef.current?.getBoundingClientRect().width ?? 0;
253259
const workspaceSwitcherWidth = workspaceSwitcherRef.current?.getBoundingClientRect().width ?? 0;
260+
const waveAIButtonWidth = waveAIButtonRef.current?.getBoundingClientRect().width ?? 0;
254261

255262
const nonTabElementsWidth =
256263
windowDragLeftWidth +
@@ -259,7 +266,8 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
259266
updateStatusLabelWidth +
260267
configErrorWidth +
261268
appMenuButtonWidth +
262-
workspaceSwitcherWidth;
269+
workspaceSwitcherWidth +
270+
waveAIButtonWidth;
263271
const spaceForTabs = tabbarWrapperWidth - nonTabElementsWidth;
264272

265273
const numberOfTabs = tabIds.length;
@@ -670,8 +678,16 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
670678
<i className="fa fa-ellipsis" />
671679
</div>
672680
)}
673-
<WaveAIButton />
674-
<WorkspaceSwitcher ref={workspaceSwitcherRef} />
681+
<WaveAIButton divRef={waveAIButtonRef} />
682+
<Tooltip
683+
content="Workspace Switcher"
684+
placement="bottom"
685+
hideOnClick
686+
divRef={workspaceSwitcherRef}
687+
divClassName="flex items-center h-full"
688+
>
689+
<WorkspaceSwitcher />
690+
</Tooltip>
675691
<div className="tab-bar" ref={tabBarRef} data-overlayscrollbars-initialize>
676692
<div className="tabs-wrapper" ref={tabsWrapperRef} style={{ width: `${tabsWrapperWidth}px` }}>
677693
{tabIds.map((tabId, index) => {

0 commit comments

Comments
 (0)