## Summary Fixes #38362 - Privacy tooltip behavior issues in AI Setup onboarding ## Problem The Privacy tooltip in AI Setup onboarding had incorrect behavior: 1. Tooltip remained visible after mouse left the Privacy button 2. Clicking the button didn't toggle tooltip properly 3. Clicking in intersection area between tooltip and button didn't work ## Root Cause Badge component used `tooltip()` instead of `hoverable_tooltip()`, causing: - Immediate tooltip hiding when mouse left triggering element - No support for tooltip content interaction - Poor intersection area click handling ## Solution **Single line change** in `crates/ui/src/components/badge.rs:61`: ```rust // Before: this.tooltip(move |window, cx| tooltip(window, cx)) // After: this.hoverable_tooltip(move |window, cx| tooltip(window, cx)) ``` ## Technical Details - Leverages existing GPUI `hoverable_tooltip()` infrastructure - Enables 500ms grace period before tooltip hiding - Allows hovering over tooltip content without disappearing - Uses proper tooltip bounds detection for click handling - Affects all Badge tooltips system-wide (positive improvement) - Full backward compatibility - no API changes ## Test Plan - [x] Hover over Privacy badge → tooltip appears - [x] Move mouse away → tooltip stays visible for 500ms - [x] Move mouse to tooltip content → tooltip remains visible - [x] Click on tooltip content → properly handled - [x] Move mouse completely away → tooltip hides after delay - [x] Verify no regression in other Badge tooltip usage Release Notes: - N/A
95 lines
2.6 KiB
Rust
95 lines
2.6 KiB
Rust
use std::rc::Rc;
|
|
|
|
use crate::Divider;
|
|
use crate::DividerColor;
|
|
use crate::Tooltip;
|
|
use crate::component_prelude::*;
|
|
use crate::prelude::*;
|
|
use gpui::AnyView;
|
|
use gpui::{AnyElement, IntoElement, SharedString, Window};
|
|
|
|
#[derive(IntoElement, RegisterComponent)]
|
|
pub struct Badge {
|
|
label: SharedString,
|
|
icon: IconName,
|
|
tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
|
|
}
|
|
|
|
impl Badge {
|
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
|
Self {
|
|
label: label.into(),
|
|
icon: IconName::Check,
|
|
tooltip: None,
|
|
}
|
|
}
|
|
|
|
pub fn icon(mut self, icon: IconName) -> Self {
|
|
self.icon = icon;
|
|
self
|
|
}
|
|
|
|
pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
|
|
self.tooltip = Some(Rc::new(tooltip));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for Badge {
|
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let tooltip = self.tooltip;
|
|
|
|
h_flex()
|
|
.id(self.label.clone())
|
|
.h_full()
|
|
.gap_1()
|
|
.pl_1()
|
|
.pr_2()
|
|
.border_1()
|
|
.border_color(cx.theme().colors().border.opacity(0.6))
|
|
.bg(cx.theme().colors().element_background)
|
|
.rounded_sm()
|
|
.overflow_hidden()
|
|
.child(
|
|
Icon::new(self.icon)
|
|
.size(IconSize::XSmall)
|
|
.color(Color::Muted),
|
|
)
|
|
.child(Divider::vertical().color(DividerColor::Border))
|
|
.child(Label::new(self.label.clone()).size(LabelSize::Small).ml_1())
|
|
.when_some(tooltip, |this, tooltip| {
|
|
this.hoverable_tooltip(move |window, cx| tooltip(window, cx))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Component for Badge {
|
|
fn scope() -> ComponentScope {
|
|
ComponentScope::DataDisplay
|
|
}
|
|
|
|
fn description() -> Option<&'static str> {
|
|
Some(
|
|
"A compact, labeled component with optional icon for displaying status, categories, or metadata.",
|
|
)
|
|
}
|
|
|
|
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
|
Some(
|
|
v_flex()
|
|
.gap_6()
|
|
.child(single_example(
|
|
"Basic Badge",
|
|
Badge::new("Default").into_any_element(),
|
|
))
|
|
.child(single_example(
|
|
"With Tooltip",
|
|
Badge::new("Tooltip")
|
|
.tooltip(Tooltip::text("This is a tooltip."))
|
|
.into_any_element(),
|
|
))
|
|
.into_any_element(),
|
|
)
|
|
}
|
|
}
|