yew-duskmoon 0.9.4

DuskMoon, a Yew component libaray.
Documentation
use yew::prelude::*;
use yew::virtual_dom::AttrValue;

use super::variants;

#[derive(Properties, Clone, PartialEq)]
pub struct ToggleProps {
    #[prop_or_default]
    pub class: Classes,
    #[prop_or_default]
    pub children: Children,
    #[prop_or_default]
    pub variant: Option<String>,
    #[prop_or_default]
    pub options: Vec<AttrValue>,
    #[prop_or_default]
    pub active: usize,
    #[prop_or_default]
    pub disabled: bool,
    #[prop_or(AttrValue::from("Toggle options"))]
    pub aria_label: AttrValue,
    #[prop_or_default]
    pub onclick: Callback<usize>,
}

#[function_component(Toggle)]
pub fn toggle(props: &ToggleProps) -> Html {
    let active = use_state_eq(|| props.active);

    let mut classes = classes!("toggle-group");
    if let Some(variant) = &props.variant {
        classes.push(format!("toggle-group-{}", variant));
    }
    classes.push(props.class.clone());

    let group_style = variants::style(
        props.variant.as_deref(),
        "border-color: color-mix(in oklch, var(--component-color, var(--color-primary)) 42%, var(--dm-line)); background: color-mix(in oklch, var(--component-container, var(--color-surface-container)) 34%, var(--color-surface));",
    );

    let options = if props.options.is_empty() && props.children.is_empty() {
        vec![
            AttrValue::from("List"),
            AttrValue::from("Grid"),
            AttrValue::from("Details"),
        ]
    } else {
        props.options.clone()
    };

    html! {
        <div class={classes} role="group" aria-label={props.aria_label.clone()} style={group_style}>
            {
                if options.is_empty() {
                    let mut button_classes = toggle_button_classes(props.variant.as_deref(), true);
                    let button_style = toggle_button_style(props.variant.as_deref(), true);
                    if props.disabled {
                        button_classes.push("toggle-btn-disabled");
                    }

                    html! {
                        <button
                            type="button"
                            class={button_classes}
                            style={button_style}
                            aria-pressed="true"
                            disabled={props.disabled}
                        >
                            { for props.children.iter() }
                        </button>
                    }
                } else {
                    html! {
                        <>
                            {
                                for options.into_iter().enumerate().map(|(index, option)| {
                                    let is_active = index == *active;
                                    let mut button_classes = toggle_button_classes(props.variant.as_deref(), is_active);
                                    let button_style = toggle_button_style(props.variant.as_deref(), is_active);
                                    if props.disabled {
                                        button_classes.push("toggle-btn-disabled");
                                    }

                                    let onclick = {
                                        let active = active.clone();
                                        let callback = props.onclick.clone();
                                        Callback::from(move |_| {
                                            active.set(index);
                                            callback.emit(index);
                                        })
                                    };

                                    html! {
                                        <button
                                            type="button"
                                            class={button_classes}
                                            style={button_style}
                                            aria-pressed={if is_active { "true" } else { "false" }}
                                            disabled={props.disabled}
                                            onclick={onclick}
                                        >
                                            { option }
                                        </button>
                                    }
                                })
                            }
                        </>
                    }
                }
            }
        </div>
    }
}

fn toggle_button_classes(variant: Option<&str>, active: bool) -> Classes {
    let mut classes = classes!("toggle-btn");

    if let Some(variant) = variant {
        classes.push(format!("toggle-btn-{}", variant));
    }

    if active {
        classes.push("toggle-btn-active");
        classes.push("active");
    }

    classes
}

fn toggle_button_style(variant: Option<&str>, active: bool) -> AttrValue {
    if active {
        variants::style(
            variant,
            "color: var(--component-on-container, var(--dm-ink)); background: var(--component-container, var(--color-primary-container)); box-shadow: 0 1px 2px color-mix(in oklch, var(--component-color, var(--color-primary)) 28%, transparent);",
        )
    } else {
        variants::style(
            variant,
            "color: var(--component-color, var(--color-primary));",
        )
    }
}