дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
fix(workbench): 优化侧栏悬停、选中和提示框样式 (#2094)
Sidebar buttons used a larger radius than the rest of the IDE, and their tooltips opened below the button, often over neighbouring stripe buttons. Stripe buttons now use a 6px radius, and their tooltips open toward the inside of the window, with a dark background in light themes. Siblings covered: every theme and plugin theme falls back to its base tooltip tokens, long tooltips flip correctly at the window edge, the shortcut hint stays readable, and the More button gets the same tooltip. Co-authored-by: PLZY <[email protected]>
Цей коміт міститься в:
1 джерело
f04164d7ee
коміт
bb86e626a2
9 files changed
+233
-12
No files matched your search
@@ -300,6 +300,10 @@ html {
|
||||
--dur: 140ms;
|
||||
|
||||
/* additive: overlays, layers, controls */
|
||||
--bg-stripe-tooltip: var(--bg-tooltip);
|
||||
--fg-stripe-tooltip: var(--fg-tooltip);
|
||||
--border-stripe-tooltip: var(--border-tooltip);
|
||||
--shadow-stripe-tooltip: var(--shadow-tooltip);
|
||||
--bg-tooltip: #33353b;
|
||||
--fg-tooltip: #d1d3d9;
|
||||
--border-tooltip: #33353b;
|
||||
@@ -337,7 +341,6 @@ html {
|
||||
--fg-stripe-label: #9fa2a8;
|
||||
--fs-stripe-label: 11px;
|
||||
--stripe-button-w: 58px;
|
||||
--stripe-button-radius: 6px;
|
||||
--bg-tab-selected: #233558;
|
||||
--border-tab-selected: #2e4d89;
|
||||
--bg-tab-selected-inactive: #26282c;
|
||||
@@ -650,6 +653,10 @@ html {
|
||||
--traffic-off-border: #c8c8c8;
|
||||
|
||||
/* additive */
|
||||
--bg-stripe-tooltip: #27282e;
|
||||
--fg-stripe-tooltip: #ffffff;
|
||||
--border-stripe-tooltip: #383a42;
|
||||
--shadow-stripe-tooltip: 0 2px 8px rgba(0, 0, 0, 0.24);
|
||||
--bg-tooltip: #ffffff;
|
||||
--fg-tooltip: #000000;
|
||||
--border-tooltip: #d1d3d9;
|
||||
@@ -909,6 +916,10 @@ html {
|
||||
--terminal-ansi-bright-cyan: var(--ansi-bright-cyan);
|
||||
--terminal-ansi-bright-white: var(--ansi-bright-white);
|
||||
|
||||
--bg-stripe-tooltip: var(--bg-tooltip);
|
||||
--fg-stripe-tooltip: var(--fg-tooltip);
|
||||
--border-stripe-tooltip: var(--border-tooltip);
|
||||
--shadow-stripe-tooltip: var(--shadow-tooltip);
|
||||
--bg-tooltip: #24262b;
|
||||
--fg-tooltip: #ffffff;
|
||||
--border-tooltip: #6f737a;
|
||||
@@ -985,6 +996,10 @@ html {
|
||||
* Keep before appearance options so accessibility preferences still win.
|
||||
*/
|
||||
:root[data-color-theme] {
|
||||
--bg-stripe-tooltip: var(--bg-tooltip);
|
||||
--fg-stripe-tooltip: var(--fg-tooltip);
|
||||
--border-stripe-tooltip: var(--border-tooltip);
|
||||
--shadow-stripe-tooltip: var(--shadow-tooltip);
|
||||
--bg-app: var(--palette-bg);
|
||||
--bg-island: var(--palette-bg);
|
||||
--bg-input: var(--palette-bg);
|
||||
@@ -1230,6 +1245,14 @@ html {
|
||||
--terminal-ansi-bright-white: var(--ansi-bright-white);
|
||||
}
|
||||
|
||||
/* Plugin themes can opt into stripe colours; otherwise their base tooltip tokens win. */
|
||||
:root[data-plugin-theme] {
|
||||
--bg-stripe-tooltip: var(--bg-tooltip);
|
||||
--fg-stripe-tooltip: var(--fg-tooltip);
|
||||
--border-stripe-tooltip: var(--border-tooltip);
|
||||
--shadow-stripe-tooltip: var(--shadow-tooltip);
|
||||
}
|
||||
|
||||
:root[data-color-theme="solarized-light"] {
|
||||
--palette-yellow: #b58900;
|
||||
--palette-orange: #cb4b16;
|
||||
|
||||
@@ -34,6 +34,18 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stripe {
|
||||
color: var(--fg-stripe-tooltip, var(--fg-tooltip));
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-stripe-tooltip, var(--bg-tooltip));
|
||||
border-color: var(--border-stripe-tooltip, var(--border-tooltip));
|
||||
box-shadow: var(--shadow-stripe-tooltip, var(--shadow-tooltip));
|
||||
line-height: 18px;
|
||||
}
|
||||
.stripe .shortcut {
|
||||
color: color-mix(in srgb, var(--fg-stripe-tooltip, var(--fg-tooltip)) 80%, transparent);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.tooltip[data-placed] {
|
||||
transition: none;
|
||||
|
||||
@@ -64,4 +64,31 @@ describe("TooltipHost", () => {
|
||||
fireEvent.pointerOut(button);
|
||||
expect(screen.queryByRole("tooltip")).toBeNull();
|
||||
});
|
||||
|
||||
it.each(["left", "right", undefined, "invalid"])("keeps dismissal behavior for %s tooltips", (stripe) => {
|
||||
render(
|
||||
<>
|
||||
<TooltipHost />
|
||||
<button type="button" data-tooltip-stripe={stripe} {...tooltipAttrs("Project", "⌘1", true)}>
|
||||
Project
|
||||
</button>
|
||||
</>,
|
||||
);
|
||||
const button = screen.getByRole("button");
|
||||
for (const dismiss of [
|
||||
() => fireEvent.pointerOut(button),
|
||||
() => fireEvent.pointerDown(button),
|
||||
() => fireEvent.keyDown(button, { key: "Escape" }),
|
||||
() => fireEvent.scroll(document),
|
||||
() => fireEvent.wheel(document),
|
||||
() => fireEvent.blur(window),
|
||||
]) {
|
||||
fireEvent.pointerOver(button);
|
||||
act(() => vi.advanceTimersByTime(TOOLTIP_DELAY));
|
||||
const tooltip = screen.getByRole("tooltip");
|
||||
expect(tooltip.classList.contains(styles.stripe!)).toBe(stripe === "left" || stripe === "right");
|
||||
dismiss();
|
||||
expect(screen.queryByRole("tooltip")).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@
|
||||
* 600ms of hover. Hidden on scroll, key press, pointer down or leave.
|
||||
*/
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { createPortal } from "react-dom";
|
||||
import { computePosition, flip, offset, shift } from "@floating-ui/react-dom";
|
||||
import styles from "./Tooltip.module.css";
|
||||
@@ -27,6 +28,7 @@ interface Shown {
|
||||
x: number;
|
||||
y: number;
|
||||
placed: boolean;
|
||||
stripe?: "left" | "right";
|
||||
}
|
||||
|
||||
export function TooltipHost() {
|
||||
@@ -52,7 +54,15 @@ export function TooltipHost() {
|
||||
if (!text || !el.isConnected) return;
|
||||
visible = true;
|
||||
anchorRef.current = el;
|
||||
setShown({ text, shortcut: el.dataset.tooltipShortcut, x: -9999, y: -9999, placed: false });
|
||||
const stripe = el.dataset.tooltipStripe;
|
||||
setShown({
|
||||
text,
|
||||
shortcut: el.dataset.tooltipShortcut,
|
||||
x: -9999,
|
||||
y: -9999,
|
||||
placed: false,
|
||||
stripe: stripe === "left" || stripe === "right" ? stripe : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const onOver = (e: PointerEvent) => {
|
||||
@@ -103,9 +113,13 @@ export function TooltipHost() {
|
||||
if (!anchor || !tip) return;
|
||||
let cancelled = false;
|
||||
void computePosition(anchor, tip, {
|
||||
placement: "bottom",
|
||||
placement: shown.stripe === "right" ? "left" : shown.stripe === "left" ? "right" : "bottom",
|
||||
strategy: "fixed",
|
||||
middleware: [offset(6), flip({ padding: 6 }), shift({ padding: 6 })],
|
||||
middleware: [
|
||||
offset(6),
|
||||
flip({ padding: 6, ...(shown.stripe && { fallbackAxisSideDirection: "end" }) }),
|
||||
shift({ padding: 6 }),
|
||||
],
|
||||
}).then(({ x, y }) => {
|
||||
if (!cancelled) setShown((s) => (s ? { ...s, x, y, placed: true } : s));
|
||||
});
|
||||
@@ -119,7 +133,7 @@ export function TooltipHost() {
|
||||
<div
|
||||
ref={tipRef}
|
||||
role="tooltip"
|
||||
className={styles.tooltip}
|
||||
className={clsx(styles.tooltip, shown.stripe && styles.stripe)}
|
||||
data-placed={shown.placed || undefined}
|
||||
style={{ left: shown.x, top: shown.y }}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.frame {
|
||||
--stripe-button-radius: var(--radius-popup);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
@@ -246,7 +245,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
border-radius: var(--stripe-button-radius);
|
||||
border-radius: var(--radius-toolbar-button);
|
||||
color: var(--fg-muted);
|
||||
transition:
|
||||
background var(--dur-fast) var(--ease),
|
||||
@@ -267,8 +266,6 @@
|
||||
.stripeButton:hover,
|
||||
.stripeButton[aria-expanded="true"] {
|
||||
color: var(--fg-secondary);
|
||||
}
|
||||
.stripeButton[aria-expanded="true"] {
|
||||
background: var(--bg-stripe-hover);
|
||||
}
|
||||
.stripeActive,
|
||||
@@ -278,8 +275,8 @@
|
||||
}
|
||||
.stripeFocused,
|
||||
.stripeFocused:hover {
|
||||
background: var(--bg-button-primary);
|
||||
color: var(--fg-button-primary);
|
||||
background: var(--bg-stripe-selected);
|
||||
color: var(--fg-emphasis);
|
||||
--icon-color: currentColor;
|
||||
}
|
||||
.stripeButton:focus-visible {
|
||||
@@ -466,3 +463,9 @@
|
||||
:global(:root[data-tool-window-bg="different"]) .anchor {
|
||||
background: var(--bg-elev);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.stripeButton {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@@ -420,6 +420,7 @@ function StripeButton({
|
||||
e.preventDefault();
|
||||
showContextMenu(e, stripeButtonMenu(tw, showName));
|
||||
}}
|
||||
data-tooltip-stripe={side}
|
||||
{...tooltipAttrs(titleOf(tw), shortcut, true)}
|
||||
>
|
||||
<span className={styles.stripeIcon}>
|
||||
@@ -463,6 +464,7 @@ function StripeMore({ windows }: { windows: ToolWindowContribution[] }) {
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
data-tooltip-stripe="left"
|
||||
{...tooltipAttrs("More Tool Windows")}
|
||||
>
|
||||
<Icon name="more-horizontal" size={20} />
|
||||
|
||||
Reference in new issue
Заблокувати користувача