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