Skip to main content

stratum_components/data_display/
skeleton.rs

1//! Skeleton loader component for content placeholders.
2
3use crate::common::merge_classes;
4use stratum_core::aria::AriaAttributes;
5use stratum_core::render::RenderOutput;
6
7/// Properties for the Skeleton component.
8#[derive(Debug, Clone, PartialEq, Default)]
9pub struct SkeletonProps {
10    pub class: Option<String>,
11}
12
13pub struct Skeleton;
14
15impl Skeleton {
16    const BASE: &'static str = "animate-pulse rounded-md bg-primary/10";
17
18    pub fn classes(props: &SkeletonProps) -> String {
19        merge_classes(Self::BASE, &props.class)
20    }
21
22    pub fn render(props: &SkeletonProps) -> RenderOutput {
23        let classes = Self::classes(props);
24        let mut aria = AriaAttributes::new();
25        aria.busy = Some(true);
26        aria.hidden = Some(true);
27
28        RenderOutput::new()
29            .with_tag("div")
30            .with_class(classes)
31            .with_aria(aria)
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn skeleton_classes() {
41        let props = SkeletonProps::default();
42        let classes = Skeleton::classes(&props);
43        assert!(classes.contains("animate-pulse"));
44        assert!(classes.contains("rounded-md"));
45    }
46
47    #[test]
48    fn skeleton_aria_busy() {
49        let props = SkeletonProps::default();
50        let output = Skeleton::render(&props);
51        assert_eq!(output.aria.busy, Some(true));
52        assert_eq!(output.aria.hidden, Some(true));
53    }
54}