yew_bs/components/
dropdown.rs1use yew::prelude::*;
2use web_sys::Element;
3use wasm_bindgen::prelude::*;
4use crate::components::common::Variant;
5use crate::interop::BsDropdown;
6#[derive(Properties, PartialEq)]
7pub struct DropdownProps {
8 #[prop_or_default]
9 pub children: Children,
10 #[prop_or_default]
11 pub label: Option<Html>,
12 #[prop_or(Variant::Primary)]
13 pub variant: Variant,
14 #[prop_or_default]
15 pub split: bool,
16 #[prop_or_default]
17 pub direction: Option<DropdownDirection>,
18 #[prop_or_default]
19 pub disabled: bool,
20 #[prop_or_default]
21 pub class: Option<AttrValue>,
22 #[prop_or_default]
23 pub node_ref: NodeRef,
24}
25#[derive(Clone, Copy, PartialEq, Debug)]
26pub enum DropdownDirection {
27 Up,
28 End,
29 Start,
30 Down,
31}
32impl DropdownDirection {
33 pub fn as_str(&self) -> &'static str {
34 match self {
35 DropdownDirection::Up => "dropup",
36 DropdownDirection::End => "dropend",
37 DropdownDirection::Start => "dropstart",
38 DropdownDirection::Down => "dropdown",
39 }
40 }
41}
42#[function_component(Dropdown)]
44pub fn dropdown(props: &DropdownProps) -> Html {
45 let dropdown_ref = use_node_ref();
46 let bs_dropdown = use_state(|| None::<BsDropdown>);
47 {
48 let dropdown_ref = dropdown_ref.clone();
49 let bs_dropdown = bs_dropdown.clone();
50 use_effect(move || {
51 let dropdown_ref = dropdown_ref.clone();
52 let bs_dropdown = bs_dropdown.clone();
53 let init_dropdown = Closure::wrap(
54 Box::new(move || {
55 if let Some(element) = dropdown_ref.cast::<Element>() {
56 let toggle_button = element
57 .query_selector("[data-bs-toggle='dropdown']")
58 .ok()
59 .flatten();
60 if let Some(toggle) = toggle_button {
61 bs_dropdown.set(Some(BsDropdown::new(&toggle, None)));
62 }
63 }
64 }) as Box<dyn FnMut()>,
65 );
66 if let Some(window) = web_sys::window() {
67 window
68 .set_timeout_with_callback_and_timeout_and_arguments_0(
69 init_dropdown.as_ref().unchecked_ref(),
70 50,
71 )
72 .unwrap();
73 init_dropdown.forget();
74 }
75 || {}
76 });
77 }
78 let mut container_classes = Classes::new();
79 if let Some(direction) = &props.direction {
80 container_classes.push(direction.as_str());
81 } else {
82 container_classes.push("dropdown");
83 }
84 if let Some(class) = &props.class {
85 container_classes.push(class.to_string());
86 }
87 let mut button_classes = Classes::new();
88 button_classes.push("btn");
89 button_classes.push(format!("btn-{}", props.variant.as_str()));
90 if props.split {
91 button_classes.push("dropdown-toggle-split");
92 } else {
93 button_classes.push("dropdown-toggle");
94 }
95 html! {
96 < div class = { container_classes } ref = { dropdown_ref.clone() } > if props
97 .split { < button type = "button" class = { format!("btn btn-{}", props.variant
98 .as_str()) } disabled = { props.disabled } > if let Some(label) = & props.label {
99 { label.clone() } } </ button > < button type = "button" class = { button_classes
100 .clone() } data - bs - toggle = "dropdown" aria - expanded = "false" disabled = {
101 props.disabled } > < span class = "visually-hidden" > { "Toggle Dropdown" } </
102 span > </ button > } else { < button type = "button" class = { button_classes
103 .clone() } data - bs - toggle = "dropdown" aria - expanded = "false" disabled = {
104 props.disabled } > if let Some(label) = & props.label { { label.clone() } } </
105 button > } < ul class = "dropdown-menu" > { for props.children.iter() } </ ul >
106 </ div >
107 }
108}
109#[derive(Properties, PartialEq)]
111pub struct DropdownItemProps {
112 #[prop_or_default]
114 pub children: Children,
115 #[prop_or_default]
117 pub active: bool,
118 #[prop_or_default]
120 pub disabled: bool,
121 #[prop_or_default]
123 pub onclick: Option<Callback<MouseEvent>>,
124 #[prop_or_default]
126 pub href: Option<AttrValue>,
127 #[prop_or_default]
129 pub class: Option<AttrValue>,
130 #[prop_or_default]
132 pub node_ref: NodeRef,
133}
134#[function_component(DropdownItem)]
136pub fn dropdown_item(props: &DropdownItemProps) -> Html {
137 let mut classes = Classes::new();
138 classes.push("dropdown-item");
139 if props.active {
140 classes.push("active");
141 }
142 if props.disabled {
143 classes.push("disabled");
144 }
145 if let Some(class) = &props.class {
146 classes.push(class.to_string());
147 }
148 if let Some(href) = &props.href {
149 html! {
150 < li > < a class = { classes } href = { href.clone() } onclick = { props
151 .onclick.clone() } aria - disabled = { props.disabled.to_string() } ref = {
152 props.node_ref.clone() } > { for props.children.iter() } </ a > </ li >
153 }
154 } else {
155 html! {
156 < li > < button class = { classes } type = "button" onclick = { props.onclick
157 .clone() } disabled = { props.disabled } ref = { props.node_ref.clone() } > {
158 for props.children.iter() } </ button > </ li >
159 }
160 }
161}
162#[derive(Properties, PartialEq)]
164pub struct DropdownDividerProps {
165 #[prop_or_default]
167 pub class: Option<AttrValue>,
168 #[prop_or_default]
170 pub node_ref: NodeRef,
171}
172#[function_component(DropdownDivider)]
174pub fn dropdown_divider(props: &DropdownDividerProps) -> Html {
175 let mut classes = Classes::new();
176 classes.push("dropdown-divider");
177 if let Some(class) = &props.class {
178 classes.push(class.to_string());
179 }
180 html! {
181 < li > < hr class = { classes } ref = { props.node_ref.clone() } /> </ li >
182 }
183}
184#[derive(Properties, PartialEq)]
186pub struct DropdownHeaderProps {
187 #[prop_or_default]
189 pub children: Children,
190 #[prop_or_default]
192 pub class: Option<AttrValue>,
193 #[prop_or_default]
195 pub node_ref: NodeRef,
196}
197#[function_component(DropdownHeader)]
199pub fn dropdown_header(props: &DropdownHeaderProps) -> Html {
200 let mut classes = Classes::new();
201 classes.push("dropdown-header");
202 if let Some(class) = &props.class {
203 classes.push(class.to_string());
204 }
205 html! {
206 < li > < h6 class = { classes } ref = { props.node_ref.clone() } > { for props
207 .children.iter() } </ h6 > </ li >
208 }
209}