adui_dioxus/components/
card.rs1use crate::components::config_provider::ComponentSize;
2use crate::components::skeleton::Skeleton;
3use dioxus::prelude::*;
4
5#[derive(Props, Clone, PartialEq)]
7pub struct CardProps {
8 #[props(optional)]
10 pub title: Option<Element>,
11 #[props(optional)]
13 pub extra: Option<Element>,
14 #[props(default = true)]
16 pub bordered: bool,
17 #[props(optional)]
19 pub size: Option<ComponentSize>,
20 #[props(default)]
23 pub loading: bool,
24 #[props(default)]
26 pub hoverable: bool,
27 #[props(optional)]
29 pub class: Option<String>,
30 #[props(optional)]
32 pub style: Option<String>,
33 pub children: Element,
35}
36
37fn build_card_classes(
38 bordered: bool,
39 size: Option<ComponentSize>,
40 hoverable: bool,
41 extra_class: Option<String>,
42) -> String {
43 let mut class_list = vec!["adui-card".to_string()];
44 if bordered {
45 class_list.push("adui-card-bordered".into());
46 }
47 if hoverable {
48 class_list.push("adui-card-hoverable".into());
49 }
50 if let Some(sz) = size {
51 match sz {
52 ComponentSize::Small => class_list.push("adui-card-sm".into()),
53 ComponentSize::Middle => {}
54 ComponentSize::Large => class_list.push("adui-card-lg".into()),
55 }
56 }
57 if let Some(extra) = extra_class {
58 class_list.push(extra);
59 }
60 class_list.join(" ")
61}
62
63#[component]
65pub fn Card(props: CardProps) -> Element {
66 let CardProps {
67 title,
68 extra,
69 bordered,
70 size,
71 loading,
72 hoverable,
73 class,
74 style,
75 children,
76 } = props;
77
78 let class_attr = build_card_classes(bordered, size, hoverable, class);
79 let style_attr = style.unwrap_or_default();
80
81 rsx! {
82 div { class: "{class_attr}", style: "{style_attr}",
83 if title.is_some() || extra.is_some() {
84 div { class: "adui-card-head",
85 if let Some(head_title) = title {
86 div { class: "adui-card-head-title", {head_title} }
87 }
88 if let Some(head_extra) = extra {
89 div { class: "adui-card-head-extra", {head_extra} }
90 }
91 }
92 }
93
94 div { class: "adui-card-body",
95 if loading {
96 Skeleton {
97 loading: Some(true),
98 active: true,
99 paragraph_rows: Some(3),
100 }
101 } else {
102 {children}
103 }
104 }
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn build_card_classes_includes_flags() {
115 let classes =
116 build_card_classes(true, Some(ComponentSize::Small), true, Some("extra".into()));
117
118 assert!(classes.contains("adui-card"));
119 assert!(classes.contains("adui-card-bordered"));
120 assert!(classes.contains("adui-card-hoverable"));
121 assert!(classes.contains("adui-card-sm"));
122 assert!(classes.contains("extra"));
123 }
124
125 #[test]
126 fn build_card_classes_handles_minimal_case() {
127 let classes = build_card_classes(false, None, false, None);
128 assert_eq!(classes, "adui-card");
129 }
130
131 #[test]
132 fn build_card_classes_bordered_only() {
133 let classes = build_card_classes(true, None, false, None);
134 assert!(classes.contains("adui-card"));
135 assert!(classes.contains("adui-card-bordered"));
136 assert!(!classes.contains("adui-card-hoverable"));
137 }
138
139 #[test]
140 fn build_card_classes_hoverable_only() {
141 let classes = build_card_classes(false, None, true, None);
142 assert!(classes.contains("adui-card"));
143 assert!(!classes.contains("adui-card-bordered"));
144 assert!(classes.contains("adui-card-hoverable"));
145 }
146
147 #[test]
148 fn build_card_classes_size_small() {
149 let classes = build_card_classes(false, Some(ComponentSize::Small), false, None);
150 assert!(classes.contains("adui-card"));
151 assert!(classes.contains("adui-card-sm"));
152 }
153
154 #[test]
155 fn build_card_classes_size_middle() {
156 let classes = build_card_classes(false, Some(ComponentSize::Middle), false, None);
157 assert!(classes.contains("adui-card"));
158 assert!(!classes.contains("adui-card-sm"));
159 assert!(!classes.contains("adui-card-lg"));
160 }
161
162 #[test]
163 fn build_card_classes_size_large() {
164 let classes = build_card_classes(false, Some(ComponentSize::Large), false, None);
165 assert!(classes.contains("adui-card"));
166 assert!(classes.contains("adui-card-lg"));
167 }
168
169 #[test]
170 fn build_card_classes_all_combinations() {
171 let classes = build_card_classes(true, None, true, None);
173 assert!(classes.contains("adui-card-bordered"));
174 assert!(classes.contains("adui-card-hoverable"));
175
176 let classes = build_card_classes(true, Some(ComponentSize::Small), false, None);
178 assert!(classes.contains("adui-card-bordered"));
179 assert!(classes.contains("adui-card-sm"));
180
181 let classes = build_card_classes(false, Some(ComponentSize::Large), true, None);
183 assert!(classes.contains("adui-card-hoverable"));
184 assert!(classes.contains("adui-card-lg"));
185
186 let classes = build_card_classes(
188 true,
189 Some(ComponentSize::Small),
190 true,
191 Some("custom-class".into()),
192 );
193 assert!(classes.contains("adui-card"));
194 assert!(classes.contains("adui-card-bordered"));
195 assert!(classes.contains("adui-card-hoverable"));
196 assert!(classes.contains("adui-card-sm"));
197 assert!(classes.contains("custom-class"));
198 }
199
200 #[test]
201 fn build_card_classes_with_extra_class() {
202 let classes = build_card_classes(false, None, false, Some("my-custom-class".into()));
203 assert!(classes.contains("adui-card"));
204 assert!(classes.contains("my-custom-class"));
205 }
206
207 #[test]
208 fn build_card_classes_multiple_extra_classes() {
209 let classes = build_card_classes(false, None, false, Some("class1 class2".into()));
211 assert!(classes.contains("adui-card"));
212 assert!(classes.contains("class1 class2"));
213 }
214
215 #[test]
216 fn build_card_classes_empty_extra_class() {
217 let classes = build_card_classes(false, None, false, Some(String::new()));
218 assert!(classes.contains("adui-card"));
219 let parts: Vec<&str> = classes.split(' ').collect();
221 assert!(parts.len() >= 1);
222 }
223
224 #[test]
225 fn card_props_defaults() {
226 }
231}