ui/components/popover.rs
1use crate::prelude::*;
2use crate::v_flex;
3use gpui::{
4 AnyElement, App, Element, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window, div,
5};
6use smallvec::SmallVec;
7
8/// Y height added beyond the size of the contents.
9pub const POPOVER_Y_PADDING: Pixels = px(8.);
10
11/// A popover is used to display a menu or show some options.
12///
13/// Clicking the element that launches the popover should not change the current view,
14/// and the popover should be statically positioned relative to that element (not the
15/// user's mouse.)
16///
17/// Example: A "new" menu with options like "new file", "new folder", etc,
18/// Linear's "Display" menu, a profile menu that appears when you click your avatar.
19///
20/// Related elements:
21///
22/// [`ContextMenu`](crate::ContextMenu):
23///
24/// Used to display a popover menu that only contains a list of items. Context menus are always
25/// launched by secondary clicking on an element. The menu is positioned relative to the user's cursor.
26///
27/// Example: Right clicking a file in the file tree to get a list of actions, right clicking
28/// a tab to in the tab bar to get a list of actions.
29///
30/// `Dropdown`:
31///
32/// Used to display a list of options when the user clicks an element. The menu is
33/// positioned relative the element that was clicked, and clicking an item in the
34/// dropdown should change the value of the element that was clicked.
35///
36/// Example: A theme select control. Displays "One Dark", clicking it opens a list of themes.
37/// When one is selected, the theme select control displays the selected theme.
38#[derive(IntoElement, RegisterComponent)]
39pub struct Popover {
40 children: SmallVec<[AnyElement; 2]>,
41 aside: Option<AnyElement>,
42}
43
44impl RenderOnce for Popover {
45 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
46 div()
47 .flex()
48 .gap_1()
49 .child(
50 v_flex()
51 .bg(semantic::elevated_surface(cx))
52 .border_1()
53 .border_color(semantic::border(cx))
54 .rounded_md()
55 .shadow_level(Shadow::Lg)
56 .py(POPOVER_Y_PADDING / 2.)
57 .child(div().children(self.children)),
58 )
59 .when_some(self.aside, |this, aside| {
60 this.child(
61 v_flex()
62 .bg(semantic::elevated_surface(cx))
63 .border_1()
64 .border_color(semantic::border(cx))
65 .rounded_md()
66 .shadow_level(Shadow::Lg)
67 .px_1()
68 .child(aside),
69 )
70 })
71 }
72}
73
74impl Default for Popover {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl Popover {
81 pub fn new() -> Self {
82 Self {
83 children: SmallVec::new(),
84 aside: None,
85 }
86 }
87
88 pub fn aside(mut self, aside: impl IntoElement) -> Self
89 where
90 Self: Sized,
91 {
92 self.aside = Some(aside.into_element().into_any());
93 self
94 }
95}
96
97impl ParentElement for Popover {
98 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
99 self.children.extend(elements)
100 }
101}
102
103impl Component for Popover {
104 fn scope() -> ComponentScope {
105 ComponentScope::Overlays
106 }
107
108 fn description() -> Option<&'static str> {
109 Some("A raised surface for displaying contextual content, styled like the dropdown family.")
110 }
111
112 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
113 Some(
114 example_group(vec![single_example(
115 "Basic",
116 Popover::new()
117 .child(div().p_2().child(Label::new("Popover content")))
118 .into_any_element(),
119 )])
120 .into_any_element(),
121 )
122 }
123}