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