leptix-password-toggle 0.1.4

Leptix Password Toggle Field — a password input with integrated visibility toggle.
Documentation
use leptix_core::primitive::{Primitive, VoidPrimitive};
use leptos::{context::Provider, html, prelude::*};
use leptos_node_ref::AnyNodeRef;

#[derive(Clone, Debug)]
struct PasswordToggleContextValue {
    visible: RwSignal<bool>,
}

#[component]
pub fn PasswordToggleField(
    #[prop(into, optional)] default_visible: MaybeProp<bool>,
    #[prop(into, optional)] as_child: MaybeProp<bool>,
    #[prop(into, optional)] node_ref: AnyNodeRef,
    children: TypedChildrenFn<impl IntoView + 'static>,
) -> impl IntoView {
    let children = StoredValue::new(children.into_inner());
    let visible = RwSignal::new(default_visible.get().unwrap_or(false));
    let ctx = PasswordToggleContextValue { visible };

    view! {
        <Provider value=ctx>
            <Primitive element=html::div as_child=as_child node_ref=node_ref
                attr:data-state=move || if visible.get() { "visible" } else { "hidden" }
            >
                {children.with_value(|c| c())}
            </Primitive>
        </Provider>
    }
}

#[component]
pub fn PasswordToggleFieldInput(
    #[prop(into, optional)] as_child: MaybeProp<bool>,
    #[prop(into, optional)] node_ref: AnyNodeRef,
) -> impl IntoView {
    let ctx = expect_context::<PasswordToggleContextValue>();

    view! {
        <VoidPrimitive element=html::input as_child=as_child node_ref=node_ref
            attr:r#type=move || if ctx.visible.get() { "text" } else { "password" }
            attr:data-state=move || if ctx.visible.get() { "visible" } else { "hidden" }
        >
            {""}
        </VoidPrimitive>
    }
}

#[component]
pub fn PasswordToggleFieldToggle(
    #[prop(into, optional)] as_child: MaybeProp<bool>,
    #[prop(into, optional)] node_ref: AnyNodeRef,
    children: TypedChildrenFn<impl IntoView + 'static>,
) -> impl IntoView {
    let children = StoredValue::new(children.into_inner());
    let ctx = expect_context::<PasswordToggleContextValue>();

    view! {
        <Primitive element=html::button as_child=as_child node_ref=node_ref
            attr:r#type="button"
            attr:aria-label=move || if ctx.visible.get() { "Hide password" } else { "Show password" }
            attr:aria-pressed=move || ctx.visible.get().to_string()
            attr:data-state=move || if ctx.visible.get() { "visible" } else { "hidden" }
            on:click=move |_| ctx.visible.set(!ctx.visible.get())
        >
            {children.with_value(|c| c())}
        </Primitive>
    }
}

#[component]
pub fn PasswordToggleFieldIcon(
    #[prop(into, optional)] as_child: MaybeProp<bool>,
    #[prop(into, optional)] node_ref: AnyNodeRef,
    #[prop(optional)] children: Option<ChildrenFn>,
) -> impl IntoView {
    let children = StoredValue::new(children);
    let ctx = expect_context::<PasswordToggleContextValue>();

    view! {
        <Primitive element=html::span as_child=as_child node_ref=node_ref
            attr:aria-hidden="true"
            attr:data-state=move || if ctx.visible.get() { "visible" } else { "hidden" }
        >
            {children.with_value(|c| c.as_ref().map(|c| c()))}
        </Primitive>
    }
}

#[component]
pub fn PasswordToggleFieldSlot(
    #[prop(into, optional)] as_child: MaybeProp<bool>,
    #[prop(into, optional)] node_ref: AnyNodeRef,
    children: TypedChildrenFn<impl IntoView + 'static>,
) -> impl IntoView {
    let children = StoredValue::new(children.into_inner());

    view! {
        <Primitive element=html::div as_child=as_child node_ref=node_ref>
            {children.with_value(|c| c())}
        </Primitive>
    }
}