1use gpui::{
4 prelude::*, px, AnyElement, App, IntoElement, ParentElement, Pixels, RenderOnce, Window,
5};
6use herogpui_theme::ActiveTheme;
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub enum CardVariant {
12 Transparent,
14 #[default]
16 Default,
17 Secondary,
19 Tertiary,
21}
22
23impl CardVariant {
24 pub const ALL: [CardVariant; 4] = [
26 CardVariant::Transparent,
27 CardVariant::Default,
28 CardVariant::Secondary,
29 CardVariant::Tertiary,
30 ];
31
32 pub fn label(self) -> &'static str {
34 match self {
35 CardVariant::Transparent => "Transparent",
36 CardVariant::Default => "Default",
37 CardVariant::Secondary => "Secondary",
38 CardVariant::Tertiary => "Tertiary",
39 }
40 }
41}
42
43#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
45#[derive(IntoElement)]
46pub struct Card {
47 variant: CardVariant,
48 width: Option<Pixels>,
49 children: Vec<AnyElement>,
50 radius: Option<Pixels>,
52 sx: Option<Box<gpui::StyleRefinement>>,
54}
55
56impl Card {
57 pub fn new() -> Self {
59 Self {
60 variant: CardVariant::Default,
61 width: None,
62 children: Vec::new(),
63 radius: None,
64 sx: None,
65 }
66 }
67
68 pub fn variant(mut self, variant: CardVariant) -> Self {
70 self.variant = variant;
71 self
72 }
73
74 pub fn w(mut self, v: impl Into<Pixels>) -> Self {
76 self.width = Some(v.into());
77 self
78 }
79
80 pub fn radius(mut self, radius: impl Into<Pixels>) -> Self {
84 self.radius = Some(radius.into());
85 self
86 }
87
88 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
93 crate::util::refine_sx(&mut self.sx, style);
94 self
95 }
96}
97
98impl Default for Card {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104impl ParentElement for Card {
105 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
106 self.children.extend(elements);
107 }
108}
109
110impl RenderOnce for Card {
111 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
112 let colors = cx.colors();
113 let layout = cx.layout();
114 let mut el = gpui::div()
118 .flex()
119 .flex_col()
120 .gap(px(12.))
121 .p(px(16.))
122 .rounded(
123 self.radius
124 .unwrap_or_else(|| crate::util::container_radius(cx)),
125 )
126 .children(self.children);
127 if let Some(w) = self.width {
130 el = el.w(w);
131 }
132
133 el = match self.variant {
137 CardVariant::Transparent => el,
138 CardVariant::Default => el.bg(colors.surface.background),
139 CardVariant::Secondary => el.bg(colors.surface_secondary),
140 CardVariant::Tertiary => el.bg(colors.surface_tertiary),
141 };
142 if self.variant != CardVariant::Transparent && !layout.surface_shadow.is_empty() {
143 el = el.shadow(layout.surface_shadow.clone());
144 }
145
146 el = crate::util::apply_sx(el, &self.sx);
147 el
148 }
149}
150
151#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
153#[derive(IntoElement)]
154pub struct CardHeader {
155 children: Vec<AnyElement>,
156 sx: Option<Box<gpui::StyleRefinement>>,
158}
159
160impl CardHeader {
161 pub fn new() -> Self {
163 Self {
164 children: Vec::new(),
165 sx: None,
166 }
167 }
168
169 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
174 crate::util::refine_sx(&mut self.sx, style);
175 self
176 }
177}
178
179impl Default for CardHeader {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185impl ParentElement for CardHeader {
186 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
187 self.children.extend(elements);
188 }
189}
190
191impl RenderOnce for CardHeader {
192 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
193 let el = gpui::div().flex().flex_col().children(self.children);
197 crate::util::apply_sx(el, &self.sx)
198 }
199}
200
201#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
203#[derive(IntoElement)]
204pub struct CardTitle {
205 children: Vec<AnyElement>,
206 sx: Option<Box<gpui::StyleRefinement>>,
208}
209
210impl CardTitle {
211 pub fn new() -> Self {
213 Self {
214 children: Vec::new(),
215 sx: None,
216 }
217 }
218
219 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
224 crate::util::refine_sx(&mut self.sx, style);
225 self
226 }
227}
228
229impl Default for CardTitle {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl ParentElement for CardTitle {
236 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
237 self.children.extend(elements);
238 }
239}
240
241impl RenderOnce for CardTitle {
242 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
243 let colors = cx.colors();
245 let el = gpui::div()
246 .text_size(px(14.))
247 .line_height(px(24.))
248 .font_weight(gpui::FontWeight::MEDIUM)
249 .text_color(colors.foreground)
250 .children(self.children);
251 crate::util::apply_sx(el, &self.sx)
252 }
253}
254
255#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
257#[derive(IntoElement)]
258pub struct CardDescription {
259 children: Vec<AnyElement>,
260 sx: Option<Box<gpui::StyleRefinement>>,
262}
263
264impl CardDescription {
265 pub fn new() -> Self {
267 Self {
268 children: Vec::new(),
269 sx: None,
270 }
271 }
272
273 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
278 crate::util::refine_sx(&mut self.sx, style);
279 self
280 }
281}
282
283impl Default for CardDescription {
284 fn default() -> Self {
285 Self::new()
286 }
287}
288
289impl ParentElement for CardDescription {
290 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
291 self.children.extend(elements);
292 }
293}
294
295impl RenderOnce for CardDescription {
296 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
297 let colors = cx.colors();
299 let el = gpui::div()
300 .text_size(px(14.))
301 .line_height(px(20.))
302 .text_color(colors.muted)
303 .children(self.children);
304 crate::util::apply_sx(el, &self.sx)
305 }
306}
307
308#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
310#[derive(IntoElement)]
311pub struct CardContent {
312 children: Vec<AnyElement>,
313 sx: Option<Box<gpui::StyleRefinement>>,
315}
316
317impl CardContent {
318 pub fn new() -> Self {
320 Self {
321 children: Vec::new(),
322 sx: None,
323 }
324 }
325
326 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
331 crate::util::refine_sx(&mut self.sx, style);
332 self
333 }
334}
335
336impl Default for CardContent {
337 fn default() -> Self {
338 Self::new()
339 }
340}
341
342impl ParentElement for CardContent {
343 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
344 self.children.extend(elements);
345 }
346}
347
348impl RenderOnce for CardContent {
349 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
350 let el = gpui::div()
355 .flex()
356 .flex_col()
357 .gap(px(4.))
358 .children(self.children);
359 crate::util::apply_sx(el, &self.sx)
360 }
361}
362
363#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
365#[derive(IntoElement)]
366pub struct CardFooter {
367 children: Vec<AnyElement>,
368 sx: Option<Box<gpui::StyleRefinement>>,
370}
371
372impl CardFooter {
373 pub fn new() -> Self {
375 Self {
376 children: Vec::new(),
377 sx: None,
378 }
379 }
380
381 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
386 crate::util::refine_sx(&mut self.sx, style);
387 self
388 }
389}
390
391impl Default for CardFooter {
392 fn default() -> Self {
393 Self::new()
394 }
395}
396
397impl ParentElement for CardFooter {
398 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
399 self.children.extend(elements);
400 }
401}
402
403impl RenderOnce for CardFooter {
404 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
405 let el = gpui::div().flex().items_center().children(self.children);
409 crate::util::apply_sx(el, &self.sx)
410 }
411}
412
413crate::util::impl_component_styled!(
414 Card,
415 CardHeader,
416 CardTitle,
417 CardDescription,
418 CardContent,
419 CardFooter
420);