Skip to main content

fc_dioxus/components/
spinner.rs

1//! Indeterminate activity indicators and shimmering status text, from spinner.css.
2use crate::{
3    core::{assets, attrs::component_attributes},
4    prelude::*,
5    theme::use_stylesheets,
6};
7const BEM: Bem = Bem::new("fc-spinner");
8const SHIMMER: Bem = Bem::new("fc-shimmer-text");
9const HIDDEN: Bem = Bem::new("fc-u-visually-hidden");
10fc_modifier! {
11    /// Which drawing, chosen by what the work is.
12    pub enum SpinnerKind for BEM {
13        /// Generic loading: a page, a request, a save.
14        Ring => None,
15        /// A softer tapering arc.
16        Arc => "arc",
17        /// Someone is composing a reply.
18        Dots => "dots",
19        /// Something is live and listening.
20        Pulse => "pulse",
21        /// A stream is flowing.
22        Bars => "bars",
23        /// Background work with several parts.
24        Orbit => "orbit",
25        /// A model is thinking.
26        Sparkle => "sparkle",
27    }
28}
29fc_modifier! {
30    /// Spinner sizes.
31    pub enum SpinnerSize for BEM {
32        /// Default.
33        Md => None,
34        /// Extra small, for inline use.
35        Xs => "xs",
36        /// Small.
37        Sm => "sm",
38        /// Large.
39        Lg => "lg",
40        /// Extra large.
41        Xl => "xl",
42    }
43}
44fc_modifier! {
45    /// Spinner color.
46    pub enum SpinnerTone for BEM {
47        /// The accent color.
48        Accent => None,
49        /// Muted, for background work.
50        Muted => "muted",
51        /// The surrounding text color.
52        Current => "current",
53    }
54}
55
56/// What a spinner says. There is no default: a spinner either decorates text that already
57/// says what is happening, or it is the status and has to say so in words.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum SpinnerLabel {
60    /// Visible text beside the spinner describes the work.
61    Decorative,
62    /// The spinner is the status; the name is announced politely.
63    Status(AccessibleName),
64}
65
66/// An animated activity indicator. The drawing is always hidden from assistive technology.
67#[component]
68pub fn Spinner(
69    #[props(default)] kind: SpinnerKind,
70    #[props(default)] size: SpinnerSize,
71    #[props(default)] tone: SpinnerTone,
72    label: SpinnerLabel,
73    #[props(default)] class: Option<String>,
74    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
75) -> Element {
76    let classes = ClassList::new(BEM.block())
77        .with_modifier(BEM, kind)
78        .with_modifier(BEM, size)
79        .with_modifier(BEM, tone)
80        .extend(class.as_deref());
81    let attributes = component_attributes(attributes, classes, &["aria-hidden", "role"]);
82    // Dots and bars animate their child spans; the other drawings are pseudo-elements.
83    let parts = match kind {
84        SpinnerKind::Dots => 3,
85        SpinnerKind::Bars => 4,
86        _ => 0,
87    };
88    let drawing = rsx! {
89        span { aria_hidden: "true", ..attributes,
90            for index in 0..parts { span { key: "{index}" } }
91        }
92    };
93    rsx! {
94        if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_SPINNER_CSS } }
95        match label {
96            SpinnerLabel::Decorative => drawing,
97            SpinnerLabel::Status(name) => rsx! {
98                span { role: "status", {drawing} span { class: HIDDEN.block(), "{name}" } }
99            },
100        }
101    }
102}
103
104/// Status text with a shimmer sweeping across it, e.g. "Running tests".
105#[component]
106pub fn ShimmerText(
107    #[props(default)] class: Option<String>,
108    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
109    children: Element,
110) -> Element {
111    let attributes = component_attributes(
112        attributes,
113        ClassList::new(SHIMMER.block()).extend(class.as_deref()),
114        &[],
115    );
116    rsx! {
117        if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_SPINNER_CSS } }
118        span { ..attributes, {children} }
119    }
120}