Skip to main content

dioxus_bootstrap_css/
placeholder.rs

1use dioxus::prelude::*;
2
3use crate::types::{Color, Size};
4
5/// Bootstrap Placeholder component — loading skeleton.
6///
7/// ```rust
8/// rsx! {
9///     Placeholder { width: 75 }
10///     Placeholder { width: 50, color: Color::Primary, size: Size::Lg }
11///     // Placeholder card
12///     Card {
13///         body: rsx! {
14///             PlaceholderParagraph { lines: 3 }
15///             Placeholder { width: 30, tag: "button" }
16///         },
17///     }
18/// }
19/// ```
20#[derive(Clone, PartialEq, Props)]
21pub struct PlaceholderProps {
22    /// Width percentage (1-100). Maps to Bootstrap's col-* classes.
23    #[props(default = 100)]
24    pub width: u8,
25    /// Placeholder color.
26    #[props(default)]
27    pub color: Option<Color>,
28    /// Placeholder size.
29    #[props(default)]
30    pub size: Size,
31    /// Use wave animation.
32    #[props(default)]
33    pub wave: bool,
34    /// Use glow animation.
35    #[props(default)]
36    pub glow: bool,
37    /// HTML tag to render (default: "span").
38    #[props(default = "span".to_string())]
39    pub tag: String,
40    /// Additional CSS classes.
41    #[props(default)]
42    pub class: String,
43}
44
45#[component]
46pub fn Placeholder(props: PlaceholderProps) -> Element {
47    let col = format!("col-{}", props.width.min(12));
48    let color_class = match &props.color {
49        Some(c) => format!(" bg-{c}"),
50        None => String::new(),
51    };
52    let size_class = match props.size {
53        Size::Sm => " placeholder-sm",
54        Size::Lg => " placeholder-lg",
55        Size::Md => "",
56    };
57
58    let full_class = if props.class.is_empty() {
59        format!("placeholder {col}{color_class}{size_class}")
60    } else {
61        format!(
62            "placeholder {col}{color_class}{size_class} {}",
63            props.class
64        )
65    };
66
67    let animation = if props.wave {
68        "placeholder-wave"
69    } else if props.glow {
70        "placeholder-glow"
71    } else {
72        ""
73    };
74
75    if animation.is_empty() {
76        rsx! {
77            span { class: "{full_class}", "aria-hidden": "true" }
78        }
79    } else {
80        rsx! {
81            p { class: "{animation}",
82                span { class: "{full_class}", "aria-hidden": "true" }
83            }
84        }
85    }
86}
87
88/// Quick placeholder paragraph with multiple lines.
89///
90/// ```rust
91/// rsx! {
92///     PlaceholderParagraph { lines: 3, glow: true }
93/// }
94/// ```
95#[derive(Clone, PartialEq, Props)]
96pub struct PlaceholderParagraphProps {
97    /// Number of placeholder lines.
98    #[props(default = 3)]
99    pub lines: usize,
100    /// Use wave animation.
101    #[props(default)]
102    pub wave: bool,
103    /// Use glow animation.
104    #[props(default)]
105    pub glow: bool,
106    /// Additional CSS classes.
107    #[props(default)]
108    pub class: String,
109}
110
111#[component]
112pub fn PlaceholderParagraph(props: PlaceholderParagraphProps) -> Element {
113    let animation = if props.wave {
114        " placeholder-wave"
115    } else if props.glow {
116        " placeholder-glow"
117    } else {
118        ""
119    };
120
121    let full_class = if props.class.is_empty() {
122        animation.trim().to_string()
123    } else {
124        format!("{} {}", animation.trim(), props.class)
125    };
126
127    // Vary widths for a natural look
128    let widths = [7, 9, 6, 8, 5, 10, 7, 4, 11, 6];
129
130    rsx! {
131        p { class: "{full_class}",
132            for i in 0..props.lines {
133                {
134                    let w = widths[i % widths.len()];
135                    let cls = format!("placeholder col-{w}");
136                    rsx! {
137                        span {
138                            class: "{cls}",
139                            "aria-hidden": "true",
140                        }
141                        " "
142                    }
143                }
144            }
145        }
146    }
147}