Compare commits

...

1 Commits

Author SHA1 Message Date
203760ff14 Add form component
Some checks failed
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / clippy (push) Failing after 1m15s
Push Workflows / test (push) Failing after 1m22s
Push Workflows / docs (push) Successful in 1m25s
Push Workflows / build (push) Failing after 2m8s
Push Workflows / nix-build (push) Failing after 5m6s
2026-07-08 21:12:04 -04:00
2 changed files with 102 additions and 0 deletions

101
src/components/form.rs Normal file
View File

@@ -0,0 +1,101 @@
use dioxus::prelude::*;
use serde::de::DeserializeOwned;
use crate::util::error::Error;
/// A simple styled form card. Parses form fields into the provided generic type, and calls the
/// callback when submitted via button or enter key.
#[component]
pub fn Form<T>(
/// Heading above the form
title: String,
/// Form content, placed inside a `fieldset`
children: Element,
/// Text displayed on the form button
action_message: String,
/// Form-related error. Set to a parsing error on parse failure, or can be used to display any
/// submission error. Clears on each submit. Displays as a card between the inputs and action
/// button, opens a modal on click.
#[props(default)]
error: Signal<Option<Error>>,
/// Whether the form should display in a "loading" state, disables the action button and
/// displays a loading animation instead. For best UX, especially on pages the user might open
/// directly as opposed to navigate to within the app, initialize to true. This component will
/// set it to false when run. This sequence will discourage users from submitting the form
/// before the page is ready to handle it. Can also be used to indicate background work is
/// taking place in the `callback`, but this is not done automatically.
#[props(default)]
loading: Signal<bool>,
/// Content to display below the action button
#[props(default)]
extra_content: Option<Element>,
/// Form submission callback. Called with the deserialized form inputs.
onsubmit: Callback<T>,
) -> Element
where
T: DeserializeOwned + 'static,
{
// Button is initialized to loading, then set back when WASM is finished loading (use_effect
// runs only on the client)
use_effect(move || loading.set(false));
rsx! {
div {
class: "card card-xl w-full sm:w-100 sm:h-fit bg-base-200",
form {
onsubmit: move |evt| {
evt.prevent_default();
let data: T = match evt.data.parsed_values() {
Ok(data) => data,
Err(e) => {
let e = Error::message_here(e.to_string())
.with_context("Failed to parse form inputs");
error.set(Some(e));
return;
}
};
error.set(None);
onsubmit.call(data);
},
class: "card-body gap-3",
h1 {
class: "card-title",
{title}
}
fieldset {
class: "fieldset pt-0 gap-1",
{children}
}
{error().map(|e| e.as_alert())}
div {
class: "card-actions gap-4",
button {
class: "btn btn-primary btn-block",
disabled: loading(),
if loading() {
span {
class: "loading loading-dots"
}
} else {
{action_message}
}
}
{extra_content}
}
}
}
}
}

View File

@@ -1,2 +1,3 @@
pub mod footer;
pub mod form;
pub mod form_input;