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(DynamicSpacing::Base24.px(cx))
83 .py(DynamicSpacing::Base16.px(cx))
84 .border_b_1()
85 .border_color(semantic::border(cx))
86 .child(header),
87 )
88 })
89 .child(
90 v_flex()
91 .p(DynamicSpacing::Base24.px(cx))
92 .gap(DynamicSpacing::Base16.rems(cx))
93 .children(self.children),
94 )
95 .when_some(self.footer, |this, footer| {
96 this.child(
97 div()
98 .px(DynamicSpacing::Base24.px(cx))
99 .py(DynamicSpacing::Base16.px(cx))
100 .border_t_1()
101 .border_color(semantic::border(cx))
102 .child(footer),
103 )
104 })
105 }
106}
107
108impl Component for Card {
109 fn scope() -> ComponentScope {
110 ComponentScope::Layout
111 }
112
113 fn description() -> Option<&'static str> {
114 Some("A surface container for grouping content, with optional header and footer.")
115 }
116
117 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
118 Some(
119 h_flex()
120 .gap(DynamicSpacing::Base16.rems(cx))
121 .child(
122 Card::new()
123 .header(Label::new("Bordered"))
124 .child(Label::new("Body content"))
125 .footer(Label::new("Footer")),
126 )
127 .child(
128 Card::new()
129 .variant(CardVariant::Elevated)
130 .header(Label::new("Elevated"))
131 .child(Label::new("With shadow")),
132 )
133 .child(
134 Card::new()
135 .variant(CardVariant::Flat)
136 .child(Label::new("Flat card")),
137 )
138 .into_any_element(),
139 )
140 }
141}