ui_storybook/components/menus/
popover_surface.rs1use leptos::prelude::*;
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10pub enum PopoverPlacement {
11 TopLeft,
13 TopRight,
15 #[default]
17 BottomLeft,
18 BottomRight,
20 Centered,
22}
23
24impl PopoverPlacement {
25 #[must_use]
27 pub fn css(self) -> &'static str {
28 match self {
29 PopoverPlacement::TopLeft => "popover-tl",
30 PopoverPlacement::TopRight => "popover-tr",
31 PopoverPlacement::BottomLeft => "popover-bl",
32 PopoverPlacement::BottomRight => "popover-br",
33 PopoverPlacement::Centered => "popover-center",
34 }
35 }
36}
37
38#[component]
39pub fn PopoverSurface(
40 #[prop(optional)]
43 placement: PopoverPlacement,
44 #[prop(optional)]
46 width_px: Option<u16>,
47 #[prop(optional, into)]
49 title: Option<String>,
50 #[prop(optional, into)]
52 description: Option<String>,
53 children: Children,
55 #[prop(optional)]
57 footer: Option<Children>,
58) -> impl IntoView {
59 let style = width_px.map(|w| format!("width:{w}px")).unwrap_or_default();
60 let class = format!("popover-surface {}", placement.css());
61 view! {
62 <div class=class role="dialog" style=style>
63 {title.map(|t| view! {
64 <header class="popover-header">
65 <div class="popover-title">{t}</div>
66 {description.map(|d| view! { <div class="popover-description">{d}</div> })}
67 </header>
68 })}
69 <div class="popover-body">{children()}</div>
70 {footer.map(|f| view! { <footer class="popover-footer">{f()}</footer> })}
71 </div>
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn each_placement_has_unique_class() {
81 let classes = [
82 PopoverPlacement::TopLeft.css(),
83 PopoverPlacement::TopRight.css(),
84 PopoverPlacement::BottomLeft.css(),
85 PopoverPlacement::BottomRight.css(),
86 PopoverPlacement::Centered.css(),
87 ];
88 let mut sorted = classes.to_vec();
89 sorted.sort_unstable();
90 sorted.dedup();
91 assert_eq!(sorted.len(), classes.len());
92 }
93}