formidable 0.1.0

Easily derive forms from structs for Leptos.
Documentation
use leptos::prelude::*;

use crate::{
    components::{Description, ErrorMessage},
    FieldError, Name,
};
use std::fmt::Display;

pub use formidable_derive::Form;

#[component]
pub fn Checkbox<T>(
    #[prop(into)] label: TextProp,
    #[prop(into, default = None)] description: Option<TextProp>,
    #[prop(into)] name: Name,
    #[prop(into)] value: Option<T>,
    #[prop(into)] callback: Option<Callback<Result<T, FieldError>>>,
) -> impl IntoView
where
    T: Clone + Into<bool> + TryFrom<bool> + Send + Sync + 'static,
    T::Error: Clone + Display + Send + Sync + 'static,
{
    let node_ref = NodeRef::new();
    let touched = RwSignal::<bool>::new(false);
    let raw_value = RwSignal::new(value.map(Into::into).unwrap_or_default());
    let value = Signal::derive(move || T::try_from(raw_value.get()));

    if let Some(callback) = callback {
        Effect::new(move |_| {
            callback.run(value.get().map_err(|err| FieldError::new(name, err)));
        });
    }

    node_ref.on_load(move |elem: leptos::web_sys::HtmlInputElement| {
        let input_value = elem.checked();
        if input_value != raw_value.get_untracked() {
            raw_value.set(input_value);
        }
    });

    view! {
        <div class:error={move || touched.get() && value.get().is_err()} class="field checkbox-field">
            <label for=name.to_string()>
                <span class="custom custom-checkbox"></span>
                <input
                    node_ref=node_ref
                    type="checkbox"
                    name=name.to_string()
                    id=name.to_string()
                    checked=move || raw_value.get()
                    on:focus=move |_| {
                        touched.set(true);
                    }
                    on:change=move |ev| {
                        touched.set(true);
                        let checked = event_target_checked(&ev);
                        raw_value.set(checked);
                    }
                />
                {label.get()}
            </label>
            <ErrorMessage touched={touched} value={value} />
            <Description description={description} />
        </div>
    }
}