1use crate::prelude::*;
2use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
3use smallvec::SmallVec;
4
5/// Creates a new [ContentGroup].
6pub fn content_group() -> ContentGroup {
7 ContentGroup::new()
8}
9
10/// A [ContentGroup] that vertically stacks its children.
11///
12/// This is a convenience function that simply combines [`ContentGroup`] and [`v_flex`](crate::v_flex).
13pub fn v_group() -> ContentGroup {
14 content_group().v_flex()
15}
16
17/// Creates a new horizontal [ContentGroup].
18///
19/// This is a convenience function that simply combines [`ContentGroup`] and [`h_flex`](crate::h_flex).
20pub fn h_group() -> ContentGroup {
21 content_group().h_flex()
22}
23
24/// A flexible container component that can hold other elements.
25#[derive(IntoElement)]
26pub struct ContentGroup {
27 base: Div,
28 border: bool,
29 fill: bool,
30 children: SmallVec<[AnyElement; 2]>,
31}
32
33impl ContentGroup {
34 /// Creates a new [`ContentGroup`].
35 pub fn new() -> Self {
36 Self {
37 base: div(),
38 border: true,
39 fill: true,
40 children: SmallVec::new(),
41 }
42 }
43
44 /// Removes the border from the [`ContentGroup`].
45 pub fn borderless(mut self) -> Self {
46 self.border = false;
47 self
48 }
49
50 /// Removes the background fill from the [`ContentGroup`].
51 pub fn unfilled(mut self) -> Self {
52 self.fill = false;
53 self
54 }
55}
56
57impl ParentElement for ContentGroup {
58 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
59 self.children.extend(elements)
60 }
61}
62
63impl Styled for ContentGroup {
64 fn style(&mut self) -> &mut StyleRefinement {
65 self.base.style()
66 }
67}
68
69impl RenderOnce for ContentGroup {
70 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
71 // TODO:
72 // Baked in padding will make scrollable views inside of content boxes awkward.
73 //
74 // Do we make the padding optional, or do we push to use a different component?
75
76 self.base
77 .when(self.fill, |this| {
78 this.bg(cx.theme().colors().text.opacity(0.05))
79 })
80 .when(self.border, |this| {
81 this.border_1().border_color(cx.theme().colors().border)
82 })
83 .rounded_md()
84 .p_2()
85 .children(self.children)
86 }
87}
88
89impl ComponentPreview for ContentGroup {
90 fn description() -> impl Into<Option<&'static str>> {
91 "A flexible container component that can hold other elements. It can be customized with or without a border and background fill."
92 }
93
94 fn example_label_side() -> ExampleLabelSide {
95 ExampleLabelSide::Bottom
96 }
97
98 fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
99 vec![example_group(vec![
100 single_example(
101 "Default",
102 ContentGroup::new()
103 .flex_1()
104 .items_center()
105 .justify_center()
106 .h_48()
107 .child(Label::new("Default ContentBox")),
108 )
109 .grow(),
110 single_example(
111 "Without Border",
112 ContentGroup::new()
113 .flex_1()
114 .items_center()
115 .justify_center()
116 .h_48()
117 .borderless()
118 .child(Label::new("Borderless ContentBox")),
119 )
120 .grow(),
121 single_example(
122 "Without Fill",
123 ContentGroup::new()
124 .flex_1()
125 .items_center()
126 .justify_center()
127 .h_48()
128 .unfilled()
129 .child(Label::new("Unfilled ContentBox")),
130 )
131 .grow(),
132 ])
133 .grow()]
134 }
135}