dioxus_bootstrap_css/
popover.rs1use dioxus::prelude::*;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
5pub enum PopoverPlacement {
6 #[default]
7 Top,
8 Bottom,
9 Start,
10 End,
11}
12
13#[derive(Clone, PartialEq, Props)]
42pub struct PopoverProps {
43 #[props(default)]
45 pub title: String,
46 pub body: Element,
48 #[props(default)]
50 pub placement: PopoverPlacement,
51 #[props(default)]
53 pub class: String,
54 pub children: Element,
56}
57
58#[component]
59pub fn Popover(props: PopoverProps) -> Element {
60 let open = use_signal(|| false);
61 let is_open = *open.read();
62 let mut open_signal = open;
63
64 let placement_class = match props.placement {
65 PopoverPlacement::Top => "bs-popover-top",
66 PopoverPlacement::Bottom => "bs-popover-bottom",
67 PopoverPlacement::Start => "bs-popover-start",
68 PopoverPlacement::End => "bs-popover-end",
69 };
70
71 let popover_class = if props.class.is_empty() {
72 format!("popover fade {placement_class} show")
73 } else {
74 format!("popover fade {placement_class} show {}", props.class)
75 };
76
77 let position_style = match props.placement {
78 PopoverPlacement::Top => {
79 "position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 0.5rem;"
80 }
81 PopoverPlacement::Bottom => {
82 "position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 0.5rem;"
83 }
84 PopoverPlacement::Start => {
85 "position: absolute; right: 100%; top: 50%; transform: translateY(-50%); margin-right: 0.5rem;"
86 }
87 PopoverPlacement::End => {
88 "position: absolute; left: 100%; top: 50%; transform: translateY(-50%); margin-left: 0.5rem;"
89 }
90 };
91
92 let arrow_placement = match props.placement {
93 PopoverPlacement::Top => {
94 "bottom: calc(-0.5rem - 1px); left: 50%; transform: translateX(-50%);"
95 }
96 PopoverPlacement::Bottom => {
97 "top: calc(-0.5rem - 1px); left: 50%; transform: translateX(-50%);"
98 }
99 PopoverPlacement::Start => {
100 "right: calc(-0.5rem - 1px); top: 50%; transform: translateY(-50%);"
101 }
102 PopoverPlacement::End => {
103 "left: calc(-0.5rem - 1px); top: 50%; transform: translateY(-50%);"
104 }
105 };
106
107 rsx! {
108 if is_open {
110 div {
111 style: "position: fixed; inset: 0; z-index: 1069;",
112 onclick: move |_| open_signal.set(false),
113 }
114 }
115 div {
116 style: if is_open { "position: relative; display: inline-block; z-index: 1070;" } else { "position: relative; display: inline-block;" },
117 onclick: move |evt| {
118 evt.stop_propagation();
119 open_signal.set(!is_open);
120 },
121 {props.children}
122 if is_open {
123 div {
124 class: "{popover_class}",
125 role: "tooltip",
126 style: "{position_style} z-index: 1070; min-width: 200px;",
127 onclick: move |evt| evt.stop_propagation(),
128 div { class: "popover-arrow", style: "position: absolute; {arrow_placement}" }
129 if !props.title.is_empty() {
130 h3 { class: "popover-header", "{props.title}" }
131 }
132 div { class: "popover-body", {props.body} }
133 }
134 }
135 }
136 }
137}