Files
oak-gpui/crates/ui/src/components/badge.rs
T
Devdatta Talele 0a9023bce0 ui: Use hoverable tooltips for Badge component to fix tooltip behavior (#38387)
## 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
2025-09-18 15:15:10 +00:00

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(),
)
}
}