Skip to content

Commit 87b9b4b

Browse files
committed
feat: 统一 tooltip 样式并优化工具箱 toast 提示
- IconButton 组件添加自定义 tooltip 支持,支持 top/bottom/left/right 四个方向 - 侧边栏图标添加 tooltip,窄布局时显示在右侧,宽布局时显示在上方 - header 图标 tooltip 显示在下方,避免被顶部边缘截断 - 工具箱文本处理(翻译/OCR/优化提示词)改用持续 toast,显示模型名称和计时 - 思考过程复制按钮添加自定义 tooltip
1 parent 4cc0151 commit 87b9b4b

File tree

8 files changed

+425
-47
lines changed

8 files changed

+425
-47
lines changed

app/components/button.module.scss

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,94 @@
142142
margin-left: 5px;
143143
}
144144
}
145+
146+
.icon-button-wrapper {
147+
position: relative;
148+
display: inline-flex;
149+
flex: inherit;
150+
flex-grow: inherit;
151+
152+
.icon-button {
153+
flex-grow: inherit;
154+
}
155+
}
156+
157+
.icon-button-tooltip {
158+
position: absolute;
159+
padding: 4px 8px;
160+
background-color: var(--primary);
161+
color: var(--white);
162+
font-size: var(--text-xs);
163+
border-radius: 4px;
164+
white-space: nowrap;
165+
z-index: 100;
166+
pointer-events: none;
167+
animation: tooltip-fade-in ease 0.15s;
168+
169+
&::after {
170+
content: "";
171+
position: absolute;
172+
border: 5px solid transparent;
173+
}
174+
175+
&.tooltip-top {
176+
bottom: calc(100% + 8px);
177+
left: 50%;
178+
transform: translateX(-50%);
179+
180+
&::after {
181+
top: 100%;
182+
left: 50%;
183+
transform: translateX(-50%);
184+
border-top-color: var(--primary);
185+
}
186+
}
187+
188+
&.tooltip-bottom {
189+
top: calc(100% + 8px);
190+
left: 50%;
191+
transform: translateX(-50%);
192+
193+
&::after {
194+
bottom: 100%;
195+
left: 50%;
196+
transform: translateX(-50%);
197+
border-bottom-color: var(--primary);
198+
}
199+
}
200+
201+
&.tooltip-right {
202+
left: calc(100% + 8px);
203+
top: 50%;
204+
transform: translateY(-50%);
205+
206+
&::after {
207+
right: 100%;
208+
top: 50%;
209+
transform: translateY(-50%);
210+
border-right-color: var(--primary);
211+
}
212+
}
213+
214+
&.tooltip-left {
215+
right: calc(100% + 8px);
216+
top: 50%;
217+
transform: translateY(-50%);
218+
219+
&::after {
220+
left: 100%;
221+
top: 50%;
222+
transform: translateY(-50%);
223+
border-left-color: var(--primary);
224+
}
225+
}
226+
}
227+
228+
@keyframes tooltip-fade-in {
229+
from {
230+
opacity: 0;
231+
}
232+
to {
233+
opacity: 1;
234+
}
235+
}

app/components/button.tsx

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from "react";
2+
import { useState } from "react";
23

34
import styles from "./button.module.scss";
45
import { CSSProperties } from "react";
@@ -19,44 +20,62 @@ export function IconButton(props: {
1920
autoFocus?: boolean;
2021
style?: CSSProperties;
2122
aria?: string;
23+
tooltipPosition?: "top" | "bottom" | "left" | "right";
2224
}) {
25+
const [showTooltip, setShowTooltip] = useState(false);
26+
const tooltipPosition = props.tooltipPosition || "top";
27+
2328
return (
24-
<button
25-
className={
26-
styles["icon-button"] +
27-
` ${props.bordered && styles.border} ${props.shadow && styles.shadow} ${
28-
props.className ?? ""
29-
} clickable ${styles[props.type ?? ""]}`
30-
}
31-
onClick={props.onClick}
32-
title={props.title}
33-
disabled={props.disabled}
34-
role="button"
35-
tabIndex={props.tabIndex}
36-
autoFocus={props.autoFocus}
37-
style={props.style}
38-
aria-label={props.aria}
29+
<div
30+
className={`${styles["icon-button-wrapper"]} ${props.className ?? ""}`}
3931
>
40-
{props.icon && (
41-
<div
42-
aria-label={props.text || props.title}
43-
className={
44-
styles["icon-button-icon"] +
45-
` ${props.type === "primary" && "no-dark"}`
46-
}
47-
>
48-
{props.icon}
49-
</div>
50-
)}
32+
<button
33+
className={
34+
styles["icon-button"] +
35+
` ${props.bordered && styles.border} ${
36+
props.shadow && styles.shadow
37+
} clickable ${styles[props.type ?? ""]}`
38+
}
39+
onClick={props.onClick}
40+
disabled={props.disabled}
41+
role="button"
42+
tabIndex={props.tabIndex}
43+
autoFocus={props.autoFocus}
44+
style={props.style}
45+
aria-label={props.aria || props.title}
46+
onMouseEnter={() => props.title && setShowTooltip(true)}
47+
onMouseLeave={() => setShowTooltip(false)}
48+
>
49+
{props.icon && (
50+
<div
51+
aria-label={props.text || props.title}
52+
className={
53+
styles["icon-button-icon"] +
54+
` ${props.type === "primary" && "no-dark"}`
55+
}
56+
>
57+
{props.icon}
58+
</div>
59+
)}
5160

52-
{props.text && (
61+
{props.text && (
62+
<div
63+
aria-label={props.text || props.title}
64+
className={styles["icon-button-text"]}
65+
>
66+
{props.text}
67+
</div>
68+
)}
69+
</button>
70+
{showTooltip && props.title && !props.text && (
5371
<div
54-
aria-label={props.text || props.title}
55-
className={styles["icon-button-text"]}
72+
className={`${styles["icon-button-tooltip"]} ${
73+
styles[`tooltip-${tooltipPosition}`]
74+
}`}
5675
>
57-
{props.text}
76+
{props.title}
5877
</div>
5978
)}
60-
</button>
79+
</div>
6180
);
6281
}

0 commit comments

Comments
 (0)