orbital-base-components 0.1.1

Headless base primitives for Orbital — structure, semantics, and accessibility
Documentation
use leptos::{either::Either, ev, prelude::*};

use super::{
    dismiss::BaseTagDismissIcon,
    surface::{tag_surface_classes, TagMediaView, TagSurfaceClassOptions},
    TagGroupInjection,
};
use crate::Handler;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TagSize {
    #[default]
    Medium,
    Small,
    ExtraSmall,
}

impl TagSize {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Medium => "medium",
            Self::Small => "small",
            Self::ExtraSmall => "extra-small",
        }
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TagAppearance {
    #[default]
    Filled,
    Outline,
    Brand,
}

impl TagAppearance {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Filled => "filled",
            Self::Outline => "outline",
            Self::Brand => "brand",
        }
    }
}

#[component]
pub fn BaseTag(
    #[prop(optional, into)] class: MaybeProp<String>,
    #[prop(optional, into)] size: Option<Signal<TagSize>>,
    #[prop(optional, into)] appearance: Option<Signal<TagAppearance>>,
    #[prop(optional, into)] icon: MaybeProp<icondata_core::Icon>,
    #[prop(optional, into)] dismissible: Signal<bool>,
    #[prop(optional, into)] on_dismiss: Option<Handler<ev::MouseEvent>>,
    #[prop(optional, into)] value: Option<String>,
    children: Children,
) -> impl IntoView {
    let group = TagGroupInjection::use_context();
    let group_size = group.as_ref().map(|g| g.size);
    let group_appearance = group.as_ref().map(|g| g.appearance);
    let (group_on_dismiss, group_dismissible) = group
        .map(
            |TagGroupInjection {
                 on_dismiss,
                 dismissible,
                 ..
             }| {
                if value.is_none() {
                    (None, None)
                } else {
                    (on_dismiss, Some(dismissible))
                }
            },
        )
        .unwrap_or((None, None));

    let size_class = size.or(group_size);
    let appearance_class = appearance.or(group_appearance);

    let is_dismissible = move || group_dismissible.map_or_else(|| dismissible.get(), |d| d.get());

    let has_icon = move || icon.get().is_some();

    view! {
        <span
            class=move || {
                tag_surface_classes(TagSurfaceClassOptions {
                    appearance: appearance_class
                        .map_or(TagAppearance::Filled, |a| a.get()),
                    size: size_class.map(|size| size.get()),
                    dismissible: is_dismissible(),
                    with_media: has_icon(),
                    primary_action: false,
                    with_secondary: false,
                    secondary_action: false,
                    extra: class.get(),
                })
            }
        >
            {move || icon.get().map(|icon| view! { <TagMediaView icon=icon /> })}
            <span class="orbital-tag__primary-text">{children()}</span>
            {move || {
                if is_dismissible() {
                    let on_dismiss = on_dismiss.clone();
                    let group_on_dismiss = group_on_dismiss.clone();
                    let value = value.clone();
                    let on_click = move |event: ev::MouseEvent| {
                        if let Some(on_dismiss) = group_on_dismiss.as_ref() {
                            event.prevent_default();
                            if let Some(value) = value.clone() {
                                on_dismiss.run(value);
                            }
                        }
                        if let Some(on_dismiss) = on_dismiss.as_ref() {
                            on_dismiss.run(event);
                        }
                    };
                    Either::Left(view! {
                        <button class="orbital-tag__dismiss" on:click=on_click>
                            <BaseTagDismissIcon />
                        </button>
                    })
                } else {
                    Either::Right(())
                }
            }}
        </span>
    }
}