maud_ui/primitives/
skeleton.rs1use maud::{html, Markup};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Variant {
8 Text,
9 Circle,
10 Rect,
11}
12
13impl Variant {
14 fn class(&self) -> &'static str {
15 match self {
16 Self::Text => "mui-skeleton--text",
17 Self::Circle => "mui-skeleton--circle",
18 Self::Rect => "mui-skeleton--rect",
19 }
20 }
21}
22
23#[derive(Debug, Clone)]
25pub struct Props {
26 pub variant: Variant,
28 pub width: Option<String>,
30 pub height: Option<String>,
32}
33
34impl Default for Props {
35 fn default() -> Self {
36 Self {
37 variant: Variant::Rect,
38 width: None,
39 height: None,
40 }
41 }
42}
43
44pub fn render(props: Props) -> Markup {
46 let width_style = props.width.as_ref().map(|w| format!("width:{};", w));
47 let height_style = props.height.as_ref().map(|h| format!("height:{};", h));
48 let style = format!(
49 "{}{}",
50 width_style.unwrap_or_default(),
51 height_style.unwrap_or_default()
52 );
53
54 if style.is_empty() {
55 html! {
56 div class={"mui-skeleton " (props.variant.class())} aria-hidden="true" {}
57 }
58 } else {
59 html! {
60 div class={"mui-skeleton " (props.variant.class())} style=(style) aria-hidden="true" {}
61 }
62 }
63}
64
65pub fn showcase() -> Markup {
67 html! {
68 div.mui-showcase__grid {
69 div {
71 p.mui-showcase__caption { "Loading post" }
72 div style="display:flex;gap:0.75rem;padding:1rem;max-width:24rem;border:1px solid var(--mui-border,#e5e7eb);border-radius:var(--mui-radius-lg);" {
73 (render(Props {
75 variant: Variant::Circle,
76 width: Some("2.75rem".into()),
77 height: Some("2.75rem".into()),
78 }))
79 div.mui-showcase__column style="flex:1;gap:0.5rem;min-width:0;" {
80 div style="display:flex;gap:0.5rem;align-items:center;" {
82 (render(Props {
83 variant: Variant::Text,
84 width: Some("6rem".into()),
85 height: Some("0.875rem".into()),
86 }))
87 (render(Props {
88 variant: Variant::Text,
89 width: Some("3rem".into()),
90 height: Some("0.75rem".into()),
91 }))
92 }
93 (render(Props {
95 variant: Variant::Text,
96 width: Some("100%".into()),
97 height: None,
98 }))
99 (render(Props {
100 variant: Variant::Text,
101 width: Some("92%".into()),
102 height: None,
103 }))
104 (render(Props {
105 variant: Variant::Text,
106 width: Some("65%".into()),
107 height: None,
108 }))
109 div style="display:flex;gap:1.5rem;margin-top:0.25rem;" {
111 (render(Props {
112 variant: Variant::Text,
113 width: Some("2rem".into()),
114 height: Some("0.75rem".into()),
115 }))
116 (render(Props {
117 variant: Variant::Text,
118 width: Some("2rem".into()),
119 height: Some("0.75rem".into()),
120 }))
121 (render(Props {
122 variant: Variant::Text,
123 width: Some("2rem".into()),
124 height: Some("0.75rem".into()),
125 }))
126 }
127 }
128 }
129 }
130
131 div {
133 p.mui-showcase__caption { "Loading table row" }
134 div style="display:flex;flex-direction:column;gap:0;max-width:32rem;border:1px solid var(--mui-border);border-radius:var(--mui-radius-lg);overflow:hidden;" {
135 div style="display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:1rem;padding:0.625rem 0.875rem;background:var(--mui-bg-input);font-size:0.75rem;font-weight:600;color:var(--mui-text);text-transform:uppercase;letter-spacing:0.04em;" {
137 span { "Customer" }
138 span { "Plan" }
139 span { "Status" }
140 span { "MRR" }
141 }
142 @for _ in 0..3 {
144 div style="display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:1rem;padding:0.75rem 0.875rem;align-items:center;border-top:1px solid var(--mui-border);" {
145 div style="display:flex;align-items:center;gap:0.625rem;" {
146 (render(Props {
147 variant: Variant::Circle,
148 width: Some("1.75rem".into()),
149 height: Some("1.75rem".into()),
150 }))
151 (render(Props {
152 variant: Variant::Text,
153 width: Some("8rem".into()),
154 height: Some("0.875rem".into()),
155 }))
156 }
157 (render(Props {
158 variant: Variant::Text,
159 width: Some("4rem".into()),
160 height: Some("0.875rem".into()),
161 }))
162 (render(Props {
163 variant: Variant::Rect,
164 width: Some("4.5rem".into()),
165 height: Some("1.25rem".into()),
166 }))
167 (render(Props {
168 variant: Variant::Text,
169 width: Some("3rem".into()),
170 height: Some("0.875rem".into()),
171 }))
172 }
173 }
174 }
175 }
176
177 div {
179 p.mui-showcase__caption { "Loading product card" }
180 div style="display:flex;flex-direction:column;gap:0.75rem;max-width:16rem;padding:0.75rem;border:1px solid var(--mui-border,#e5e7eb);border-radius:var(--mui-radius-lg);" {
181 (render(Props {
183 variant: Variant::Rect,
184 width: Some("100%".into()),
185 height: Some("11rem".into()),
186 }))
187 (render(Props {
189 variant: Variant::Text,
190 width: Some("85%".into()),
191 height: Some("1rem".into()),
192 }))
193 (render(Props {
195 variant: Variant::Text,
196 width: Some("55%".into()),
197 height: Some("0.75rem".into()),
198 }))
199 div style="display:flex;justify-content:space-between;align-items:center;margin-top:0.25rem;" {
201 (render(Props {
202 variant: Variant::Text,
203 width: Some("4rem".into()),
204 height: Some("1.125rem".into()),
205 }))
206 (render(Props {
207 variant: Variant::Rect,
208 width: Some("2rem".into()),
209 height: Some("2rem".into()),
210 }))
211 }
212 }
213 }
214 }
215 }
216}