stratum_components/data_display/
card.rs1use crate::common::merge_classes;
4use stratum_core::render::RenderOutput;
5
6#[derive(Debug, Clone, PartialEq, Default)]
9pub struct CardProps {
10 pub class: Option<String>,
11}
12
13pub struct Card;
14
15impl Card {
16 const BASE: &'static str = "rounded-xl border bg-card text-card-foreground shadow";
17
18 pub fn classes(props: &CardProps) -> String {
19 merge_classes(Self::BASE, &props.class)
20 }
21
22 pub fn render(props: &CardProps) -> RenderOutput {
23 RenderOutput::new()
24 .with_tag("div")
25 .with_class(Self::classes(props))
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Default)]
32pub struct CardHeaderProps {
33 pub class: Option<String>,
34}
35
36pub struct CardHeader;
37
38impl CardHeader {
39 const BASE: &'static str = "flex flex-col space-y-1.5 p-6";
40
41 pub fn classes(props: &CardHeaderProps) -> String {
42 merge_classes(Self::BASE, &props.class)
43 }
44
45 pub fn render(props: &CardHeaderProps) -> RenderOutput {
46 RenderOutput::new()
47 .with_tag("div")
48 .with_class(Self::classes(props))
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Default)]
55pub struct CardTitleProps {
56 pub class: Option<String>,
57}
58
59pub struct CardTitle;
60
61impl CardTitle {
62 const BASE: &'static str = "font-semibold leading-none tracking-tight";
63
64 pub fn classes(props: &CardTitleProps) -> String {
65 merge_classes(Self::BASE, &props.class)
66 }
67
68 pub fn render(props: &CardTitleProps) -> RenderOutput {
69 RenderOutput::new()
70 .with_tag("h3")
71 .with_class(Self::classes(props))
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Default)]
78pub struct CardDescriptionProps {
79 pub class: Option<String>,
80}
81
82pub struct CardDescription;
83
84impl CardDescription {
85 const BASE: &'static str = "text-sm text-muted-foreground";
86
87 pub fn classes(props: &CardDescriptionProps) -> String {
88 merge_classes(Self::BASE, &props.class)
89 }
90
91 pub fn render(props: &CardDescriptionProps) -> RenderOutput {
92 RenderOutput::new()
93 .with_tag("p")
94 .with_class(Self::classes(props))
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Default)]
101pub struct CardContentProps {
102 pub class: Option<String>,
103}
104
105pub struct CardContent;
106
107impl CardContent {
108 const BASE: &'static str = "p-6 pt-0";
109
110 pub fn classes(props: &CardContentProps) -> String {
111 merge_classes(Self::BASE, &props.class)
112 }
113
114 pub fn render(props: &CardContentProps) -> RenderOutput {
115 RenderOutput::new()
116 .with_tag("div")
117 .with_class(Self::classes(props))
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Default)]
124pub struct CardFooterProps {
125 pub class: Option<String>,
126}
127
128pub struct CardFooter;
129
130impl CardFooter {
131 const BASE: &'static str = "flex items-center p-6 pt-0";
132
133 pub fn classes(props: &CardFooterProps) -> String {
134 merge_classes(Self::BASE, &props.class)
135 }
136
137 pub fn render(props: &CardFooterProps) -> RenderOutput {
138 RenderOutput::new()
139 .with_tag("div")
140 .with_class(Self::classes(props))
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn card_classes() {
150 let props = CardProps::default();
151 let classes = Card::classes(&props);
152 assert!(classes.contains("rounded-xl"));
153 assert!(classes.contains("bg-card"));
154 }
155
156 #[test]
157 fn card_header_tag() {
158 let props = CardHeaderProps::default();
159 let output = CardHeader::render(&props);
160 assert_eq!(output.effective_tag(), "div");
161 assert!(output.class_string().contains("p-6"));
162 }
163
164 #[test]
165 fn card_title_tag() {
166 let props = CardTitleProps::default();
167 let output = CardTitle::render(&props);
168 assert_eq!(output.effective_tag(), "h3");
169 }
170
171 #[test]
172 fn card_description_tag() {
173 let props = CardDescriptionProps::default();
174 let output = CardDescription::render(&props);
175 assert_eq!(output.effective_tag(), "p");
176 }
177
178 #[test]
179 fn card_content_classes() {
180 let props = CardContentProps::default();
181 let classes = CardContent::classes(&props);
182 assert!(classes.contains("p-6"));
183 }
184
185 #[test]
186 fn card_footer_classes() {
187 let props = CardFooterProps::default();
188 let classes = CardFooter::classes(&props);
189 assert!(classes.contains("flex"));
190 }
191}