Skip to main content

ui/components/
hover_card.rs

1use std::rc::Rc;
2
3use gpui::{Context, Render, StatefulInteractiveElement};
4
5use crate::{Popover, prelude::*};
6
7struct HoverCardTooltip {
8    content_builder: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
9}
10
11impl Render for HoverCardTooltip {
12    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
13        let content = (self.content_builder)(window, cx);
14        Popover::new().child(div().max_w(px(320.)).p_3().child(content))
15    }
16}
17
18/// Rich content shown on hover with delay, composing tooltip hover timing and
19/// [`Popover`] content styling.
20///
21/// Uses GPUI's `hoverable_tooltip` (500ms show delay, hoverable content) rather
22/// than inventing a third overlay primitive.
23#[derive(IntoElement, RegisterComponent)]
24pub struct HoverCard {
25    trigger: AnyElement,
26    content_builder: Rc<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
27}
28
29impl HoverCard {
30    pub fn new(trigger: impl IntoElement) -> Self {
31        Self {
32            trigger: trigger.into_any_element(),
33            content_builder: Rc::new(|_, _| div().into_any_element()),
34        }
35    }
36
37    pub fn content(
38        mut self,
39        content: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
40    ) -> Self {
41        self.content_builder = Rc::new(content);
42        self
43    }
44}
45
46impl RenderOnce for HoverCard {
47    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
48        let content_builder = self.content_builder.clone();
49        div()
50            .id("hover-card")
51            .child(self.trigger)
52            .hoverable_tooltip(move |_, cx| {
53                cx.new(|_| HoverCardTooltip {
54                    content_builder: content_builder.clone(),
55                })
56                .into()
57            })
58    }
59}
60
61impl Component for HoverCard {
62    fn scope() -> ComponentScope {
63        ComponentScope::Overlays
64    }
65
66    fn description() -> Option<&'static str> {
67        Some("Rich hover content with delay, composing tooltip timing and Popover styling.")
68    }
69
70    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
71        Some(
72            HoverCard::new(
73                Button::new("hover-card-trigger", "@nextjs").style(ButtonStyle::Transparent),
74            )
75            .content(|_, _| {
76                v_flex()
77                    .gap_2()
78                    .child(Label::new("Next.js").size(LabelSize::Small))
79                    .child(
80                        Label::new("The React Framework for the Web — built by Vercel.")
81                            .size(LabelSize::XSmall)
82                            .color(Color::Muted),
83                    )
84                    .child(
85                        h_flex()
86                            .gap_3()
87                            .child(Label::new("Joined December 2021").size(LabelSize::XSmall))
88                            .child(Label::new("1.2k followers").size(LabelSize::XSmall)),
89                    )
90                    .into_any_element()
91            })
92            .into_any_element(),
93        )
94    }
95}