1use gpui::{
2 AnyElement, App, IntoElement, Pixels, RenderOnce, SharedString, Window, div, pattern_slash,
3 prelude::*, px, rems,
4};
5use theme::ActiveTheme;
6
7/// A single example of a component.
8#[derive(IntoElement)]
9pub struct ComponentExample {
10 pub variant_name: SharedString,
11 pub description: Option<SharedString>,
12 pub element: AnyElement,
13 pub width: Option<Pixels>,
14}
15
16impl RenderOnce for ComponentExample {
17 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
18 div()
19 .pt_2()
20 .map(|this| {
21 if let Some(width) = self.width {
22 this.w(width)
23 } else {
24 this.w_full()
25 }
26 })
27 .flex()
28 .flex_col()
29 .gap_3()
30 .child(
31 div()
32 .flex()
33 .flex_col()
34 .child(
35 div()
36 .child(self.variant_name.clone())
37 .text_size(rems(1.0))
38 .text_color(cx.theme().colors().text),
39 )
40 .when_some(self.description, |this, description| {
41 this.child(
42 div()
43 .text_size(rems(0.875))
44 .text_color(cx.theme().colors().text_muted)
45 .child(description),
46 )
47 }),
48 )
49 .child(
50 div()
51 .min_h(px(100.))
52 .w_full()
53 .p_8()
54 .flex()
55 .items_center()
56 .justify_center()
57 .rounded_xl()
58 .border_1()
59 .border_color(cx.theme().colors().border.opacity(0.5))
60 .bg(pattern_slash(
61 cx.theme().colors().surface_background.opacity(0.25),
62 12.0,
63 12.0,
64 ))
65 .child(self.element),
66 )
67 .into_any_element()
68 }
69}
70
71impl ComponentExample {
72 pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
73 Self {
74 variant_name: variant_name.into(),
75 element,
76 description: None,
77 width: None,
78 }
79 }
80
81 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
82 self.description = Some(description.into());
83 self
84 }
85
86 pub const fn width(mut self, width: Pixels) -> Self {
87 self.width = Some(width);
88 self
89 }
90}
91
92/// A group of component examples.
93#[derive(IntoElement)]
94pub struct ComponentExampleGroup {
95 pub title: Option<SharedString>,
96 pub examples: Vec<ComponentExample>,
97 pub width: Option<Pixels>,
98 pub grow: bool,
99 pub vertical: bool,
100}
101
102impl RenderOnce for ComponentExampleGroup {
103 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
104 div()
105 .flex_col()
106 .text_sm()
107 .text_color(cx.theme().colors().text_muted)
108 .map(|this| {
109 if let Some(width) = self.width {
110 this.w(width)
111 } else {
112 this.w_full()
113 }
114 })
115 .when_some(self.title, |this, title| {
116 this.gap_4().child(
117 div()
118 .flex()
119 .items_center()
120 .gap_3()
121 .mt_4()
122 .mb_1()
123 .child(
124 div()
125 .flex_none()
126 .text_size(px(10.))
127 .child(title.to_uppercase()),
128 )
129 .child(
130 div()
131 .h_px()
132 .w_full()
133 .flex_1()
134 .bg(cx.theme().colors().border),
135 ),
136 )
137 })
138 .child(
139 div()
140 .flex()
141 .flex_col()
142 .items_start()
143 .w_full()
144 .gap_6()
145 .children(self.examples)
146 .into_any_element(),
147 )
148 .into_any_element()
149 }
150}
151
152impl ComponentExampleGroup {
153 pub const fn new(examples: Vec<ComponentExample>) -> Self {
154 Self {
155 title: None,
156 examples,
157 width: None,
158 grow: false,
159 vertical: false,
160 }
161 }
162 pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
163 Self {
164 title: Some(title.into()),
165 examples,
166 width: None,
167 grow: false,
168 vertical: false,
169 }
170 }
171 pub const fn width(mut self, width: Pixels) -> Self {
172 self.width = Some(width);
173 self
174 }
175 pub const fn grow(mut self) -> Self {
176 self.grow = true;
177 self
178 }
179 pub const fn vertical(mut self) -> Self {
180 self.vertical = true;
181 self
182 }
183}
184
185pub fn single_example(
186 variant_name: impl Into<SharedString>,
187 example: AnyElement,
188) -> ComponentExample {
189 ComponentExample::new(variant_name, example)
190}
191
192pub fn empty_example(variant_name: impl Into<SharedString>) -> ComponentExample {
193 ComponentExample::new(variant_name, div().w_full().text_center().items_center().text_xs().opacity(0.4).child("This space is intentionally left blank. It indicates a case that should render nothing.").into_any_element())
194}
195
196pub const fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
197 ComponentExampleGroup::new(examples)
198}
199
200pub fn example_group_with_title(
201 title: impl Into<SharedString>,
202 examples: Vec<ComponentExample>,
203) -> ComponentExampleGroup {
204 ComponentExampleGroup::with_title(title, examples)
205}