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