use yew::prelude::*;
#[derive(Properties, Clone, PartialEq, Debug, Default)]
pub struct NavDropdownProps {
#[prop_or_default]
pub classes: Classes,
#[prop_or("dropdown")]
pub toggle_text: &'static str,
#[prop_or_default]
pub id: Option<&'static str>,
#[prop_or_default]
pub children: Children
}
#[function_component]
pub fn NavDropdown(props: &NavDropdownProps) -> Html {
let mut classes = props.classes.clone();
classes.push("nav-dropdown");
let open = use_state(|| false);
let on_toggle = {
let open = open.clone();
Callback::from(move |e: MouseEvent| {
e.stop_propagation();
open.set(!*open);
})
};
let menu_class = if *open {
"nav-dropdown-menu open"
} else {
"nav-dropdown-menu"
};
html! {
<li {classes} role="presentation">
<button
type="button"
class="nav-dropdown-toggle"
aria-expanded={if *open { "true" } else { "false" }}
aria-haspopup="true"
onclick={on_toggle}
>
{ props.toggle_text }
<span class="nav-dropdown-caret">{" ▼"}</span>
</button>
<ul class={menu_class} role="menu">
{ for props.children.iter() }
</ul>
</li>
}
}
#[derive(Properties, Clone, PartialEq, Debug, Default)]
pub struct NavDropdownItemProps {
#[prop_or_default]
pub classes: Classes,
#[prop_or_default]
pub disabled: bool,
pub children: Children
}
#[function_component]
pub fn NavDropdownItem(props: &NavDropdownItemProps) -> Html {
let mut classes = props.classes.clone();
classes.push("nav-dropdown-item");
if props.disabled {
classes.push("disabled");
}
html! {
<li class={classes} role="menuitem">
{ for props.children.iter() }
</li>
}
}
#[derive(Properties, Clone, PartialEq, Eq, Debug, Default)]
pub struct NavDropdownDividerProps {
#[prop_or_default]
pub classes: Classes
}
#[function_component]
pub fn NavDropdownDivider(props: &NavDropdownDividerProps) -> Html {
let mut classes = props.classes.clone();
classes.push("nav-dropdown-divider");
html! {
<li class={classes} role="separator" />
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nav_dropdown_props_default() {
let props = NavDropdownProps {
classes: Classes::default(),
toggle_text: "Menu",
id: None,
children: Children::new(vec![])
};
assert_eq!(props.toggle_text, "Menu");
assert!(props.id.is_none());
}
#[test]
fn nav_dropdown_item_default() {
let props = NavDropdownItemProps {
classes: Classes::default(),
disabled: false,
children: Children::new(vec![])
};
assert!(!props.disabled);
}
#[test]
fn nav_dropdown_item_disabled() {
let props = NavDropdownItemProps {
classes: Classes::default(),
disabled: true,
children: Children::new(vec![])
};
assert!(props.disabled);
}
#[test]
fn nav_dropdown_divider_props() {
let props = NavDropdownDividerProps {
classes: Classes::default()
};
assert!(props.classes.is_empty());
}
#[test]
fn nav_dropdown_with_custom_id() {
let props = NavDropdownProps {
classes: Classes::default(),
toggle_text: "Menu",
id: Some("my-dropdown"),
children: Children::new(vec![])
};
assert_eq!(props.id, Some("my-dropdown"));
}
#[test]
fn nav_dropdown_item_with_classes() {
let mut classes = Classes::new();
classes.push("custom-item");
let props = NavDropdownItemProps {
classes,
disabled: false,
children: Children::new(vec![])
};
assert!(props.classes.contains("custom-item"));
}
#[test]
fn nav_dropdown_disabled_item() {
let props = NavDropdownItemProps {
classes: Classes::default(),
disabled: true,
children: Children::new(vec![])
};
assert!(props.disabled);
}
#[test]
fn nav_dropdown_with_children() {
let children = Children::new(vec![html! { <div>{ "child" }</div> }]);
let props = NavDropdownProps {
classes: Classes::default(),
toggle_text: "Test",
id: None,
children
};
assert_eq!(props.children.len(), 1);
}
}