1use gpui::AnyElement;
2use smallvec::SmallVec;
3
4use crate::prelude::*;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum CardVariant {
9 #[default]
11 Bordered,
12 Elevated,
14 Flat,
16}
17
18#[derive(IntoElement, RegisterComponent)]
21pub struct Card {
22 variant: CardVariant,
23 header: Option<AnyElement>,
24 footer: Option<AnyElement>,
25 children: SmallVec<[AnyElement; 2]>,
26}
27
28impl Card {
29 pub fn new() -> Self {
30 Self {
31 variant: CardVariant::default(),
32 header: None,
33 footer: None,
34 children: SmallVec::new(),
35 }
36 }
37
38 pub fn variant(mut self, variant: CardVariant) -> Self {
39 self.variant = variant;
40 self
41 }
42
43 pub fn header(mut self, header: impl IntoElement) -> Self {
44 self.header = Some(header.into_any_element());
45 self
46 }
47
48 pub fn footer(mut self, footer: impl IntoElement) -> Self {
49 self.footer = Some(footer.into_any_element());
50 self
51 }
52}
53
54impl Default for Card {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl ParentElement for Card {
61 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
62 self.children.extend(elements);
63 }
64}
65
66impl RenderOnce for Card {
67 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
68 let mut base = v_flex()
69 .bg(semantic::surface(cx))
70 .rounded_lg()
71 .text_color(semantic::text(cx));
72
73 base = match self.variant {
74 CardVariant::Bordered => base.border_1().border_color(semantic::border(cx)),
75 CardVariant::Elevated => base.shadow_level(Shadow::Md),
76 CardVariant::Flat => base,
77 };
78
79 base.when_some(self.header, |this, header| {
80 this.child(
81 div()
82 .px_6()
83 .py_4()
84 .border_b_1()
85 .border_color(semantic::border(cx))
86 .child(header),
87 )
88 })
89 .child(v_flex().p_6().gap_4().children(self.children))
90 .when_some(self.footer, |this, footer| {
91 this.child(
92 div()
93 .px_6()
94 .py_4()
95 .border_t_1()
96 .border_color(semantic::border(cx))
97 .child(footer),
98 )
99 })
100 }
101}
102
103impl Component for Card {
104 fn scope() -> ComponentScope {
105 ComponentScope::Layout
106 }
107
108 fn description() -> Option<&'static str> {
109 Some("A surface container for grouping content, with optional header and footer.")
110 }
111
112 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
113 Some(
114 h_flex()
115 .gap_4()
116 .child(
117 Card::new()
118 .header(Label::new("Bordered"))
119 .child(Label::new("Body content"))
120 .footer(Label::new("Footer")),
121 )
122 .child(
123 Card::new()
124 .variant(CardVariant::Elevated)
125 .header(Label::new("Elevated"))
126 .child(Label::new("With shadow")),
127 )
128 .child(
129 Card::new()
130 .variant(CardVariant::Flat)
131 .child(Label::new("Flat card")),
132 )
133 .into_any_element(),
134 )
135 }
136}