//! Generic loading spinner component use yew::prelude::*; /// Size options for the loading spinner #[derive(Clone, PartialEq)] pub enum SpinnerSize { Small, Medium, Large, } impl SpinnerSize { pub fn get_class(&self) -> &'static str { match self { SpinnerSize::Small => "spinner-border-sm", SpinnerSize::Medium => "", SpinnerSize::Large => "spinner-border-lg", } } pub fn get_style(&self) -> &'static str { match self { SpinnerSize::Small => "width: 1rem; height: 1rem;", SpinnerSize::Medium => "width: 1.5rem; height: 1.5rem;", SpinnerSize::Large => "width: 2rem; height: 2rem;", } } } /// Color options for the loading spinner #[derive(Clone, PartialEq)] pub enum SpinnerColor { Primary, Secondary, Success, Danger, Warning, Info, Light, Dark, } impl SpinnerColor { pub fn get_class(&self) -> &'static str { match self { SpinnerColor::Primary => "text-primary", SpinnerColor::Secondary => "text-secondary", SpinnerColor::Success => "text-success", SpinnerColor::Danger => "text-danger", SpinnerColor::Warning => "text-warning", SpinnerColor::Info => "text-info", SpinnerColor::Light => "text-light", SpinnerColor::Dark => "text-dark", } } } /// Properties for LoadingSpinner component #[derive(Properties, PartialEq)] pub struct LoadingSpinnerProps { /// Size of the spinner #[prop_or(SpinnerSize::Medium)] pub size: SpinnerSize, /// Color of the spinner #[prop_or(SpinnerColor::Primary)] pub color: SpinnerColor, /// Loading message to display #[prop_or_default] pub message: Option, /// Whether to center the spinner #[prop_or(true)] pub centered: bool, /// Custom CSS class for container #[prop_or_default] pub container_class: Option, /// Whether to show as inline spinner #[prop_or(false)] pub inline: bool, } /// LoadingSpinner component #[function_component(LoadingSpinner)] pub fn loading_spinner(props: &LoadingSpinnerProps) -> Html { let container_class = if props.inline { "d-inline-flex align-items-center" } else if props.centered { "d-flex flex-column align-items-center justify-content-center" } else { "d-flex align-items-center" }; let final_container_class = if let Some(custom_class) = &props.container_class { format!("{} {}", container_class, custom_class.as_str()) } else { container_class.to_string() }; let spinner_classes = format!( "spinner-border {} {}", props.size.get_class(), props.color.get_class() ); html! {
{if let Some(message) = &props.message { let message_class = if props.inline { "ms-2" } else { "mt-2" }; html! {
{message.as_str()}
} } else { html! {} }}
} } /// Convenience component for common loading scenarios #[derive(Properties, PartialEq)] pub struct LoadingOverlayProps { /// Loading message #[prop_or("Loading...".to_string())] pub message: String, /// Whether the overlay is visible #[prop_or(true)] pub show: bool, /// Background opacity (0.0 to 1.0) #[prop_or(0.8)] pub opacity: f64, /// Spinner color #[prop_or(SpinnerColor::Primary)] pub spinner_color: SpinnerColor, } /// LoadingOverlay component for full-screen loading #[function_component(LoadingOverlay)] pub fn loading_overlay(props: &LoadingOverlayProps) -> Html { if !props.show { return html! {}; } let background_style = format!( "position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, {}); z-index: 9999;", props.opacity ); html! {
} }