use icondata::Icon as IconId;
use leptos::ev;
use leptos::prelude::*;
use leptos_icons::Icon;
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
pub enum ButtonType {
#[default]
Button,
Submit,
Reset,
}
#[component]
pub fn BasicButton(
#[prop(into, optional)] button_text: MaybeProp<String>,
#[prop(into, optional)] style_ext: MaybeProp<String>,
#[prop(into, optional)] children_style_ext: MaybeProp<String>,
#[prop(default = Callback::new(|_| {}))] onclick: Callback<ev::MouseEvent>,
#[prop(default = None)] icon: Option<IconId>,
#[prop(into, default = MaybeProp::derive(move || Some(false)))] disabled: MaybeProp<bool>,
#[prop(into, default = ButtonType::Button)] button_type: ButtonType,
#[prop(default = false)] icon_before: bool,
#[prop(optional)] children: Option<ChildrenFn>,
) -> impl IntoView {
let button_text_styles = button_text.clone();
let button_content_styles = move || {
if button_text_styles.get().is_none() {
""
} else if icon_before {
"gap-2"
} else {
"gap-2 flex-row-reverse"
}
};
view! {
<button
type={
match button_type {
ButtonType::Button => "button",
ButtonType::Submit => "submit",
ButtonType::Reset => "reset"
}
}
class=move || format!(
"font-bold py-2 px-4 cursor-pointer rounded-[5px] disabled:opacity-50 disabled:cursor-not-allowed {}",
style_ext.get().unwrap_or_default(),
)
on:click=move |ev| onclick.run(ev)
disabled={disabled}
>
{
if let Some(children) = &children {
Some(children())
} else {
None
}
}
{
if children.is_none() {
Some(view! {
<span class=move || format!("flex flex-row items-center justify-center {} {}", button_content_styles(), children_style_ext.get().unwrap_or_default())>
{move || match icon {
Some(button_icon) => view! {
<Icon width="1em" height="1em" icon=button_icon />
}.into(),
None => None,
}}
<span>{button_text}</span>
</span>
})
} else {
None
}
}
</button>
}
}
#[component]
pub fn ButtonGroup(
#[prop(into, optional)]
style_ext: String,
mut children: ChildrenFragmentMut,
) -> impl IntoView {
view! {
<div class="flex" role="group">
{
let style_ext_view = style_ext.clone();
let children_len = children().nodes.iter().collect::<Vec<_>>().len();
children()
.nodes
.into_iter()
.enumerate()
.map(|(index, child)| {
let style_ext_view = style_ext_view.clone();
let class_name = move || {
let mut base = format!(
"font-bold py-2 px-4 border border-light-gray border-l-0 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed {}",
style_ext_view
);
if index == 0 {
base.push_str(" rounded-l-[5px]");
}
if index == children_len - 1 {
base.push_str(" rounded-r-[5px]");
}
base
};
view! {
{child.attr("class", class_name())}
}
})
.collect::<Vec<_>>()}
</div>
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn button_type_default_is_button() {
assert!(matches!(ButtonType::default(), ButtonType::Button));
}
#[test]
fn button_type_clone() {
assert!(matches!(ButtonType::Submit.clone(), ButtonType::Submit));
}
fn content_styles(has_text: bool, icon_before: bool) -> &'static str {
if !has_text {
""
} else if icon_before {
"gap-2"
} else {
"gap-2 flex-row-reverse"
}
}
#[test]
fn no_text_produces_no_layout_class() {
assert_eq!(content_styles(false, true), "");
assert_eq!(content_styles(false, false), "");
}
#[test]
fn icon_before_true_places_icon_first() {
assert!(!content_styles(true, true).contains("flex-row-reverse"));
}
#[test]
fn icon_before_false_reverses_order() {
assert!(content_styles(true, false).contains("flex-row-reverse"));
}
fn is_first(index: usize) -> bool {
index == 0
}
fn is_last(index: usize, len: usize) -> bool {
index == len - 1
}
#[test]
fn single_button_is_both_first_and_last() {
assert!(is_first(0));
assert!(is_last(0, 1));
}
#[test]
fn first_button_is_not_last_in_group() {
assert!(is_first(0));
assert!(!is_last(0, 3));
}
#[test]
fn middle_button_is_neither_first_nor_last() {
assert!(!is_first(1));
assert!(!is_last(1, 3));
}
#[test]
fn last_button_is_not_first_in_group() {
assert!(!is_first(2));
assert!(is_last(2, 3));
}
#[test]
fn disabled_defaults_to_false() {
let disabled: MaybeProp<bool> = MaybeProp::derive(move || Some(false));
assert_eq!(disabled.get(), Some(false));
}
#[test]
fn disabled_can_be_set_to_true() {
let disabled: MaybeProp<bool> = MaybeProp::from(true);
assert_eq!(disabled.get(), Some(true));
}
#[test]
fn disabled_accepts_signal() {
let sig = RwSignal::new(false);
let disabled: MaybeProp<bool> = MaybeProp::derive(move || Some(sig.get()));
assert_eq!(disabled.get(), Some(false));
sig.set(true);
assert_eq!(disabled.get(), Some(true));
}
}