soma-ui 0.1.1

A Leptos 0.8 component library: 160+ accessible UI components, blocks, charts, and a ChartDB-style schema diagram — with a prebuilt stylesheet.
Documentation
use crate::components::shared::{CONTROL_MOTION, FOCUS_RING_INSET};
use leptos::prelude::*;

#[component]
pub fn Tabs(
    #[prop(into)] value: RwSignal<String>,
    #[prop(default = String::new())] class: String,
    children: Children,
) -> impl IntoView {
    provide_context(value);
    let combined = format!("w-full {}", class);
    view! {
        <div class=combined>{children()}</div>
    }
}

#[component]
pub fn TabsList(
    #[prop(default = String::new())] class: String,
    children: Children,
) -> impl IntoView {
    let combined = format!(
        "inline-flex h-9 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground {}",
        class
    );
    view! {
        <div role="tablist" class=combined>{children()}</div>
    }
}

#[component]
pub fn TabsTrigger(
    #[prop(into)] value: String,
    #[prop(default = String::new())] class: String,
    children: Children,
) -> impl IntoView {
    let active = use_context::<RwSignal<String>>().expect("TabsTrigger must be used inside Tabs");
    let val = StoredValue::new(value);
    view! {
        <button
            role="tab"
            class=move || {
                let base = format!("inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1 text-sm font-medium {} {}", FOCUS_RING_INSET, CONTROL_MOTION);
                let active_cls = "bg-background text-foreground shadow-elev-sm";
                let inactive_cls = "hover:bg-muted/50 hover:text-foreground";
                let extra = if class.is_empty() { "" } else { &class };
                if active.get() == val.get_value() {
                    format!("{} {} {}", base, active_cls, extra)
                } else {
                    format!("{} {} {}", base, inactive_cls, extra)
                }
            }
            aria-selected=move || if active.get() == val.get_value() { "true" } else { "false" }
            on:click=move |_| active.set(val.get_value())
        >
            {children()}
        </button>
    }
}

#[component]
pub fn TabsContent(
    #[prop(into)] value: String,
    #[prop(default = String::new())] class: String,
    children: ChildrenFn,
) -> impl IntoView {
    let active = use_context::<RwSignal<String>>().expect("TabsContent must be used inside Tabs");
    let val = StoredValue::new(value);
    let combined = format!("mt-2 focus:outline-none {}", class);
    view! {
        <Show when=move || active.get() == val.get_value()>
            <div role="tabpanel" class=combined.clone()>
                {children()}
            </div>
        </Show>
    }
}