app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 7ca2fc3f3a81f4c6ccfb81dd574c86be3f17a244
parent cb862446796985476f03edaee9580bb44e146d82
Author: triesap <triesap@radroots.dev>
Date:   Thu, 22 Jan 2026 04:51:09 +0000

app: add spinner component for ui states

- add spinner component with blade rendering
- support white and centered variants via classes
- export spinner from ui-components
- cover blade count constant with unit test

Diffstat:
Mcrates/ui-components/src/lib.rs | 2++
Acrates/ui-components/src/spinner.rs | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 0 deletions(-)

diff --git a/crates/ui-components/src/lib.rs b/crates/ui-components/src/lib.rs @@ -6,6 +6,7 @@ mod label; mod list; mod list_types; mod separator; +mod spinner; mod dialog; mod sheet; @@ -70,6 +71,7 @@ pub use separator::{ RadrootsAppUiSeparator, RadrootsAppUiSeparatorOrientation, }; +pub use spinner::RadrootsAppUiSpinner; pub use dialog::{ radroots_app_ui_dialog_state_value, RadrootsAppUiDialogClose, diff --git a/crates/ui-components/src/spinner.rs b/crates/ui-components/src/spinner.rs @@ -0,0 +1,59 @@ +#![forbid(unsafe_code)] + +use leptos::prelude::*; + +const RADROOTS_APP_UI_SPINNER_BLADE_COUNT: usize = 8; + +fn radroots_app_ui_spinner_class_merge(parts: &[Option<&str>]) -> String { + let mut result = String::new(); + for part in parts { + if let Some(value) = part { + if value.is_empty() { + continue; + } + if !result.is_empty() { + result.push(' '); + } + result.push_str(value); + } + } + result +} + +#[component] +pub fn RadrootsAppUiSpinner( + #[prop(optional)] class: Option<String>, + #[prop(optional)] id: Option<String>, + #[prop(optional)] style: Option<String>, + #[prop(optional)] white: bool, + #[prop(optional)] center: bool, +) -> impl IntoView { + let class_value = radroots_app_ui_spinner_class_merge(&[ + Some("spinner8"), + if white { Some("spinner8-white") } else { None }, + if center { Some("center") } else { None }, + class.as_deref(), + ]); + let blade_class = radroots_app_ui_spinner_class_merge(&[ + Some("spinner8-blade"), + if white { Some("spinner8-blade-white") } else { None }, + ]); + let blades = (0..RADROOTS_APP_UI_SPINNER_BLADE_COUNT) + .map(|_| view! { <span class=blade_class.clone()></span> }) + .collect_view(); + view! { + <span id=id class=class_value style=style> + {blades} + </span> + } +} + +#[cfg(test)] +mod tests { + use super::RADROOTS_APP_UI_SPINNER_BLADE_COUNT; + + #[test] + fn spinner_blade_count_is_expected() { + assert_eq!(RADROOTS_APP_UI_SPINNER_BLADE_COUNT, 8); + } +}