dioxus_ui_system/molecules/
skeleton.rs1use dioxus::prelude::*;
6use crate::theme::{use_theme, use_style};
7use crate::styles::Style;
8
9#[derive(Props, Clone, PartialEq)]
11pub struct SkeletonProps {
12 #[props(default)]
14 pub width: Option<String>,
15 #[props(default)]
17 pub height: Option<String>,
18 #[props(default = true)]
20 pub animate: bool,
21 #[props(default)]
23 pub rounded: Option<String>,
24 #[props(default)]
26 pub style: Option<String>,
27 #[props(default)]
29 pub class: Option<String>,
30}
31
32#[component]
34pub fn Skeleton(props: SkeletonProps) -> Element {
35 let _theme = use_theme();
36
37 let width = props.width.unwrap_or_else(|| "100%".to_string());
38 let height = props.height.unwrap_or_else(|| "20px".to_string());
39 let rounded = props.rounded.unwrap_or_else(|| "4px".to_string());
40
41 let skeleton_style = use_style(move |t| {
42 Style::new()
43 .bg(&t.colors.muted)
44 .build()
45 });
46
47 let animation = if props.animate {
48 "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;"
49 } else {
50 ""
51 };
52
53 rsx! {
54 div {
55 style: "{skeleton_style} width: {width}; height: {height}; border-radius: {rounded}; {animation} {props.style.clone().unwrap_or_default()}",
56 class: "{props.class.clone().unwrap_or_default()}",
57 }
58 }
59}
60
61#[derive(Props, Clone, PartialEq)]
63pub struct SkeletonCircleProps {
64 #[props(default = "40".to_string())]
66 pub size: String,
67 #[props(default = true)]
69 pub animate: bool,
70 #[props(default)]
72 pub style: Option<String>,
73}
74
75#[component]
77pub fn SkeletonCircle(props: SkeletonCircleProps) -> Element {
78 let _theme = use_theme();
79
80 let skeleton_style = use_style(move |t| {
81 Style::new()
82 .bg(&t.colors.muted)
83 .rounded_full()
84 .build()
85 });
86
87 let animation = if props.animate {
88 "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;"
89 } else {
90 ""
91 };
92
93 rsx! {
94 div {
95 style: "{skeleton_style} width: {props.size}px; height: {props.size}px; {animation} {props.style.clone().unwrap_or_default()}",
96 }
97 }
98}
99
100#[derive(Props, Clone, PartialEq)]
102pub struct SkeletonTextProps {
103 #[props(default = 3)]
105 pub lines: usize,
106 #[props(default = true)]
108 pub animate: bool,
109 #[props(default = 60)]
111 pub last_line_width: u8,
112 #[props(default)]
114 pub style: Option<String>,
115}
116
117#[component]
119pub fn SkeletonText(props: SkeletonTextProps) -> Element {
120 let _theme = use_theme();
121
122 let container_style = use_style(|t| {
123 Style::new()
124 .flex()
125 .flex_col()
126 .gap(&t.spacing, "sm")
127 .build()
128 });
129
130 rsx! {
131 div {
132 style: "{container_style} {props.style.clone().unwrap_or_default()}",
133
134 for i in 0..props.lines {
135 Skeleton {
136 width: if i == props.lines - 1 {
137 Some(format!("{}%", props.last_line_width))
138 } else {
139 Some("100%".to_string())
140 },
141 height: Some("12px".to_string()),
142 animate: props.animate,
143 rounded: Some("6px".to_string()),
144 }
145 }
146 }
147 }
148}
149
150#[derive(Props, Clone, PartialEq)]
152pub struct SkeletonCardProps {
153 #[props(default = true)]
155 pub animate: bool,
156 #[props(default = true)]
158 pub show_avatar: bool,
159 #[props(default = 2)]
161 pub text_lines: usize,
162 #[props(default)]
164 pub style: Option<String>,
165}
166
167#[component]
169pub fn SkeletonCard(props: SkeletonCardProps) -> Element {
170 let _theme = use_theme();
171
172 let card_style = use_style(|t| {
173 Style::new()
174 .w_full()
175 .rounded(&t.radius, "lg")
176 .border(1, &t.colors.border)
177 .p(&t.spacing, "lg")
178 .flex()
179 .flex_col()
180 .gap(&t.spacing, "md")
181 .build()
182 });
183
184 rsx! {
185 div {
186 style: "{card_style} {props.style.clone().unwrap_or_default()}",
187
188 if props.show_avatar {
189 div {
190 style: "display: flex; align-items: center; gap: 12px;",
191
192 SkeletonCircle {
193 size: "48".to_string(),
194 animate: props.animate,
195 }
196
197 div {
198 style: "flex: 1;",
199 Skeleton {
200 width: Some("120px".to_string()),
201 height: Some("14px".to_string()),
202 animate: props.animate,
203 rounded: Some("4px".to_string()),
204 }
205 }
206 }
207 }
208
209 SkeletonText {
210 lines: props.text_lines,
211 animate: props.animate,
212 last_line_width: 80,
213 }
214 }
215 }
216}