dioxus_ui_system/molecules/
skeleton.rs1use crate::atoms::{AlignItems, Box, HStack, SpacingSize, VStack};
6use crate::styles::Style;
7use crate::theme::{use_style, use_theme};
8use dioxus::prelude::*;
9
10#[derive(Props, Clone, PartialEq)]
12pub struct SkeletonProps {
13 #[props(default)]
15 pub width: Option<String>,
16 #[props(default)]
18 pub height: Option<String>,
19 #[props(default = true)]
21 pub animate: bool,
22 #[props(default)]
24 pub rounded: Option<String>,
25 #[props(default)]
27 pub style: Option<String>,
28 #[props(default)]
30 pub class: Option<String>,
31}
32
33#[component]
35pub fn Skeleton(props: SkeletonProps) -> Element {
36 let _theme = use_theme();
37
38 let width = props.width.unwrap_or_else(|| "100%".to_string());
39 let height = props.height.unwrap_or_else(|| "20px".to_string());
40 let rounded = props.rounded.unwrap_or_else(|| "4px".to_string());
41
42 let skeleton_style = use_style(move |t| Style::new().bg(&t.colors.muted).build());
43
44 let animation = if props.animate {
45 "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;"
46 } else {
47 ""
48 };
49
50 rsx! {
51 Box {
52 style: "{skeleton_style} width: {width}; height: {height}; border-radius: {rounded}; {animation} {props.style.clone().unwrap_or_default()}",
53 class: "{props.class.clone().unwrap_or_default()}",
54 }
55 }
56}
57
58#[derive(Props, Clone, PartialEq)]
60pub struct SkeletonCircleProps {
61 #[props(default = "40".to_string())]
63 pub size: String,
64 #[props(default = true)]
66 pub animate: bool,
67 #[props(default)]
69 pub style: Option<String>,
70}
71
72#[component]
74pub fn SkeletonCircle(props: SkeletonCircleProps) -> Element {
75 let _theme = use_theme();
76
77 let skeleton_style =
78 use_style(move |t| Style::new().bg(&t.colors.muted).rounded_full().build());
79
80 let animation = if props.animate {
81 "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;"
82 } else {
83 ""
84 };
85
86 rsx! {
87 Box {
88 style: "{skeleton_style} width: {props.size}px; height: {props.size}px; {animation} {props.style.clone().unwrap_or_default()}",
89 }
90 }
91}
92
93#[derive(Props, Clone, PartialEq)]
95pub struct SkeletonTextProps {
96 #[props(default = 3)]
98 pub lines: usize,
99 #[props(default = true)]
101 pub animate: bool,
102 #[props(default = 60)]
104 pub last_line_width: u8,
105 #[props(default)]
107 pub style: Option<String>,
108}
109
110#[component]
112pub fn SkeletonText(props: SkeletonTextProps) -> Element {
113 let _theme = use_theme();
114
115 rsx! {
116 VStack {
117 style: props.style.clone().unwrap_or_default(),
118 gap: SpacingSize::Sm,
119 align: AlignItems::Stretch,
120
121 for i in 0..props.lines {
122 Skeleton {
123 width: if i == props.lines - 1 {
124 Some(format!("{}%", props.last_line_width))
125 } else {
126 Some("100%".to_string())
127 },
128 height: Some("12px".to_string()),
129 animate: props.animate,
130 rounded: Some("6px".to_string()),
131 }
132 }
133 }
134 }
135}
136
137#[derive(Props, Clone, PartialEq)]
139pub struct SkeletonCardProps {
140 #[props(default = true)]
142 pub animate: bool,
143 #[props(default = true)]
145 pub show_avatar: bool,
146 #[props(default = 2)]
148 pub text_lines: usize,
149 #[props(default)]
151 pub style: Option<String>,
152}
153
154#[component]
156pub fn SkeletonCard(props: SkeletonCardProps) -> Element {
157 let _theme = use_theme();
158
159 let card_style = use_style(|t| {
160 Style::new()
161 .w_full()
162 .rounded(&t.radius, "lg")
163 .border(1, &t.colors.border)
164 .p(&t.spacing, "lg")
165 .build()
166 });
167
168 let custom_style = props.style.clone().unwrap_or_default();
169
170 rsx! {
171 VStack {
172 style: "{card_style} {custom_style}",
173 gap: SpacingSize::Md,
174 align: AlignItems::Stretch,
175
176 if props.show_avatar {
177 HStack {
178 gap: SpacingSize::Md,
179 align: AlignItems::Center,
180
181 SkeletonCircle {
182 size: "48".to_string(),
183 animate: props.animate,
184 }
185
186 Box {
187 style: "flex: 1;",
188 Skeleton {
189 width: Some("120px".to_string()),
190 height: Some("14px".to_string()),
191 animate: props.animate,
192 rounded: Some("4px".to_string()),
193 }
194 }
195 }
196 }
197
198 SkeletonText {
199 lines: props.text_lines,
200 animate: props.animate,
201 last_line_width: 80,
202 }
203 }
204 }
205}