1use crate::{
3 core::{assets, attrs::component_attributes},
4 prelude::*,
5 theme::use_stylesheets,
6};
7const BEM: Bem = Bem::new("fc-card");
8const FOOTER: Bem = Bem::new("fc-card__footer");
9fc_modifier! {
10 pub enum CardVariant for BEM {
12 Default => None,
14 Elevated => "elevated",
16 Subtle => "subtle",
18 Feature => "feature",
20 }
21}
22fc_modifier! { enum FooterDivider for FOOTER { None => None, Divided => "divided" } }
23#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
25#[non_exhaustive]
26pub enum CardTag {
27 #[default]
29 Article,
30 Div,
32 Section,
34 Li,
36 Label,
38}
39fn stylesheet() -> Element {
40 rsx! { if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_CARD_CSS } } }
41}
42#[component]
44pub fn Card(
45 #[props(default)] variant: CardVariant,
46 #[props(default)] as_: CardTag,
47 #[props(default)] class: Option<String>,
48 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
49 children: Element,
50) -> Element {
51 let attributes = component_attributes(
52 attributes,
53 ClassList::new(BEM.block())
54 .with_modifier(BEM, variant)
55 .extend(class.as_deref()),
56 &[],
57 );
58 rsx! { {stylesheet()} match as_ {
59 CardTag::Article => rsx! { article { ..attributes, {children} } },
60 CardTag::Div => rsx! { div { ..attributes, {children} } },
61 CardTag::Section => rsx! { section { ..attributes, {children} } },
62 CardTag::Li => rsx! { li { ..attributes, {children} } },
63 CardTag::Label => rsx! { label { ..attributes, {children} } },
64 } }
65}
66#[component]
68pub fn CardHeader(
69 #[props(default)] class: Option<String>,
70 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
71 children: Element,
72) -> Element {
73 let attributes = component_attributes(
74 attributes,
75 ClassList::new(&BEM.element("header")).extend(class.as_deref()),
76 &[],
77 );
78 rsx! { {stylesheet()} div { ..attributes, {children} } }
79}
80#[component]
82pub fn CardHeading(
83 #[props(default)] class: Option<String>,
84 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
85 children: Element,
86) -> Element {
87 let attributes = component_attributes(
88 attributes,
89 ClassList::new(&BEM.element("heading")).extend(class.as_deref()),
90 &[],
91 );
92 rsx! { {stylesheet()} div { ..attributes, {children} } }
93}
94#[component]
96pub fn CardBody(
97 #[props(default)] class: Option<String>,
98 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
99 children: Element,
100) -> Element {
101 let attributes = component_attributes(
102 attributes,
103 ClassList::new(&BEM.element("body")).extend(class.as_deref()),
104 &[],
105 );
106 rsx! { {stylesheet()} div { ..attributes, {children} } }
107}
108#[component]
110pub fn CardLayout(
111 #[props(default)] class: Option<String>,
112 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
113 children: Element,
114) -> Element {
115 let attributes = component_attributes(
116 attributes,
117 ClassList::new(&BEM.element("layout")).extend(class.as_deref()),
118 &[],
119 );
120 rsx! { {stylesheet()} div { ..attributes, {children} } }
121}
122#[component]
124pub fn CardTitle(
125 #[props(default = Some(HeadingLevel::H3))] level: Option<HeadingLevel>,
126 #[props(default)] class: Option<String>,
127 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
128 children: Element,
129) -> Element {
130 let attributes = component_attributes(
131 attributes,
132 ClassList::new(&BEM.element("title")).extend(class.as_deref()),
133 &[],
134 );
135 rsx! { {stylesheet()} match level {
136 Some(HeadingLevel::H1) => rsx! { h1 { ..attributes, {children} } },
137 Some(HeadingLevel::H2) => rsx! { h2 { ..attributes, {children} } },
138 Some(HeadingLevel::H3) => rsx! { h3 { ..attributes, {children} } },
139 Some(HeadingLevel::H4) => rsx! { h4 { ..attributes, {children} } },
140 Some(HeadingLevel::H5) => rsx! { h5 { ..attributes, {children} } },
141 Some(HeadingLevel::H6) => rsx! { h6 { ..attributes, {children} } },
142 None => rsx! { span { ..attributes, {children} } },
143 } }
144}
145#[component]
147pub fn CardSubtitle(
148 #[props(default)] inline: bool,
149 #[props(default)] class: Option<String>,
150 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
151 children: Element,
152) -> Element {
153 let attributes = component_attributes(
154 attributes,
155 ClassList::new(&BEM.element("subtitle")).extend(class.as_deref()),
156 &[],
157 );
158 rsx! { {stylesheet()} if inline { span { ..attributes, {children} } } else { p { ..attributes, {children} } } }
159}
160#[component]
162pub fn CardMedia(
163 alt: Alt,
164 #[props(default)] src: Option<String>,
165 #[props(default)] class: Option<String>,
166 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
167) -> Element {
168 let attributes = component_attributes(
169 attributes,
170 ClassList::new(&BEM.element("media")).extend(class.as_deref()),
171 &["alt", "src", "aria-hidden", "aria-label", "role"],
172 );
173 let label = match &alt {
174 Alt::Text(name) => name.to_string(),
175 Alt::Decorative => String::new(),
176 };
177 rsx! { {stylesheet()}
178 if let Some(src) = src { img { src, alt: label, ..attributes } }
179 else if alt == Alt::Decorative { div { aria_hidden: "true", ..attributes } }
180 else { div { role: "img", aria_label: label, ..attributes } }
181 }
182}
183#[component]
185pub fn CardFooter(
186 #[props(default)] divided: bool,
187 #[props(default)] class: Option<String>,
188 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
189 children: Element,
190) -> Element {
191 let classes = ClassList::new(FOOTER.block())
192 .with_modifier(
193 FOOTER,
194 if divided {
195 FooterDivider::Divided
196 } else {
197 FooterDivider::None
198 },
199 )
200 .extend(class.as_deref());
201 let attributes = component_attributes(attributes, classes, &[]);
202 rsx! { {stylesheet()} div { ..attributes, {children} } }
203}
204#[component]
206pub fn CardLink(
207 href: String,
208 #[props(default)] class: Option<String>,
209 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
210 children: Element,
211) -> Element {
212 let attributes = component_attributes(
213 attributes,
214 ClassList::new(&BEM.element("link")).extend(class.as_deref()),
215 &["href", "role"],
216 );
217 rsx! { {stylesheet()} a { href, ..attributes, {children} } }
218}