1use std::fmt::Display;
2use std::ops::{Deref, DerefMut};
3use std::sync::LazyLock;
4
5use collections::HashMap;
6use gpui::{
7 AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, pattern_slash, prelude::*,
8 px, rems,
9};
10use linkme::distributed_slice;
11use parking_lot::RwLock;
12use theme::ActiveTheme;
13
14pub trait Component {
15 fn scope() -> ComponentScope {
16 ComponentScope::None
17 }
18 fn name() -> &'static str {
19 std::any::type_name::<Self>()
20 }
21 /// Returns a name that the component should be sorted by.
22 ///
23 /// Implement this if the component should be sorted in an alternate order than its name.
24 ///
25 /// Example:
26 ///
27 /// For example, to group related components together when sorted:
28 ///
29 /// - Button -> ButtonA
30 /// - IconButton -> ButtonBIcon
31 /// - ToggleButton -> ButtonCToggle
32 ///
33 /// This naming scheme keeps these components together and allows them to /// be sorted in a logical order.
34 fn sort_name() -> &'static str {
35 Self::name()
36 }
37 fn description() -> Option<&'static str> {
38 None
39 }
40 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
41 None
42 }
43}
44
45#[distributed_slice]
46pub static __ALL_COMPONENTS: [fn()] = [..];
47
48pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
49 LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
50
51pub struct ComponentRegistry {
52 components: Vec<(
53 ComponentScope,
54 // name
55 &'static str,
56 // sort name
57 &'static str,
58 // description
59 Option<&'static str>,
60 )>,
61 previews: HashMap<&'static str, fn(&mut Window, &mut App) -> Option<AnyElement>>,
62}
63
64impl ComponentRegistry {
65 fn new() -> Self {
66 ComponentRegistry {
67 components: Vec::new(),
68 previews: HashMap::default(),
69 }
70 }
71}
72
73pub fn init() {
74 let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
75 for f in component_fns {
76 f();
77 }
78}
79
80pub fn register_component<T: Component>() {
81 let component_data = (T::scope(), T::name(), T::sort_name(), T::description());
82 let mut data = COMPONENT_DATA.write();
83 data.components.push(component_data);
84 data.previews.insert(T::name(), T::preview);
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88pub struct ComponentId(pub &'static str);
89
90#[derive(Clone)]
91pub struct ComponentMetadata {
92 id: ComponentId,
93 name: SharedString,
94 sort_name: SharedString,
95 scope: ComponentScope,
96 description: Option<SharedString>,
97 preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
98}
99
100impl ComponentMetadata {
101 pub fn id(&self) -> ComponentId {
102 self.id.clone()
103 }
104 pub fn name(&self) -> SharedString {
105 self.name.clone()
106 }
107
108 pub fn sort_name(&self) -> SharedString {
109 self.sort_name.clone()
110 }
111
112 pub fn scopeless_name(&self) -> SharedString {
113 self.name
114 .clone()
115 .split("::")
116 .last()
117 .unwrap_or(&self.name)
118 .to_string()
119 .into()
120 }
121
122 pub fn scope(&self) -> ComponentScope {
123 self.scope.clone()
124 }
125 pub fn description(&self) -> Option<SharedString> {
126 self.description.clone()
127 }
128 pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
129 self.preview
130 }
131}
132
133pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
134
135impl AllComponents {
136 pub fn new() -> Self {
137 AllComponents(HashMap::default())
138 }
139 pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
140 self.0.values().filter(|c| c.preview.is_some()).collect()
141 }
142 pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
143 let mut previews: Vec<ComponentMetadata> =
144 self.all_previews().into_iter().cloned().collect();
145 previews.sort_by_key(|a| a.name());
146 previews
147 }
148 pub fn all(&self) -> Vec<&ComponentMetadata> {
149 self.0.values().collect()
150 }
151 pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
152 let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
153 components.sort_by_key(|a| a.name());
154 components
155 }
156}
157
158impl Deref for AllComponents {
159 type Target = HashMap<ComponentId, ComponentMetadata>;
160 fn deref(&self) -> &Self::Target {
161 &self.0
162 }
163}
164
165impl DerefMut for AllComponents {
166 fn deref_mut(&mut self) -> &mut Self::Target {
167 &mut self.0
168 }
169}
170
171pub fn components() -> AllComponents {
172 let data = COMPONENT_DATA.read();
173 let mut all_components = AllComponents::new();
174 for (scope, name, sort_name, description) in &data.components {
175 let preview = data.previews.get(name).cloned();
176 let component_name = SharedString::new_static(name);
177 let sort_name = SharedString::new_static(sort_name);
178 let id = ComponentId(name);
179 all_components.insert(
180 id.clone(),
181 ComponentMetadata {
182 id,
183 name: component_name,
184 sort_name,
185 scope: scope.clone(),
186 description: description.map(Into::into),
187 preview,
188 },
189 );
190 }
191 all_components
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195pub enum ComponentScope {
196 Collaboration,
197 DataDisplay,
198 Editor,
199 Images,
200 Input,
201 Layout,
202 Loading,
203 Navigation,
204 None,
205 Notification,
206 Overlays,
207 Status,
208 Typography,
209 VersionControl,
210}
211
212impl Display for ComponentScope {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 match self {
215 ComponentScope::Collaboration => write!(f, "Collaboration"),
216 ComponentScope::DataDisplay => write!(f, "Data Display"),
217 ComponentScope::Editor => write!(f, "Editor"),
218 ComponentScope::Images => write!(f, "Images & Icons"),
219 ComponentScope::Input => write!(f, "Forms & Input"),
220 ComponentScope::Layout => write!(f, "Layout & Structure"),
221 ComponentScope::Loading => write!(f, "Loading & Progress"),
222 ComponentScope::Navigation => write!(f, "Navigation"),
223 ComponentScope::None => write!(f, "Unsorted"),
224 ComponentScope::Notification => write!(f, "Notification"),
225 ComponentScope::Overlays => write!(f, "Overlays & Layering"),
226 ComponentScope::Status => write!(f, "Status"),
227 ComponentScope::Typography => write!(f, "Typography"),
228 ComponentScope::VersionControl => write!(f, "Version Control"),
229 }
230 }
231}
232
233/// A single example of a component.
234#[derive(IntoElement)]
235pub struct ComponentExample {
236 pub variant_name: SharedString,
237 pub description: Option<SharedString>,
238 pub element: AnyElement,
239}
240
241impl RenderOnce for ComponentExample {
242 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
243 div()
244 .w_full()
245 .flex()
246 .flex_col()
247 .gap_3()
248 .child(
249 div()
250 .child(self.variant_name.clone())
251 .text_size(rems(1.25))
252 .text_color(cx.theme().colors().text),
253 )
254 .when_some(self.description, |this, description| {
255 this.child(
256 div()
257 .text_size(rems(0.9375))
258 .text_color(cx.theme().colors().text_muted)
259 .child(description.clone()),
260 )
261 })
262 .child(
263 div()
264 .flex()
265 .w_full()
266 .rounded_xl()
267 .min_h(px(100.))
268 .justify_center()
269 .p_8()
270 .border_1()
271 .border_color(cx.theme().colors().border)
272 .bg(pattern_slash(
273 cx.theme().colors().surface_background.opacity(0.5),
274 24.0,
275 24.0,
276 ))
277 .shadow_sm()
278 .child(self.element),
279 )
280 .into_any_element()
281 }
282}
283
284impl ComponentExample {
285 pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
286 Self {
287 variant_name: variant_name.into(),
288 element,
289 description: None,
290 }
291 }
292
293 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
294 self.description = Some(description.into());
295 self
296 }
297}
298
299/// A group of component examples.
300#[derive(IntoElement)]
301pub struct ComponentExampleGroup {
302 pub title: Option<SharedString>,
303 pub examples: Vec<ComponentExample>,
304 pub grow: bool,
305 pub vertical: bool,
306}
307
308impl RenderOnce for ComponentExampleGroup {
309 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
310 div()
311 .flex_col()
312 .text_sm()
313 .text_color(cx.theme().colors().text_muted)
314 .w_full()
315 .when_some(self.title, |this, title| {
316 this.gap_4().child(
317 div()
318 .flex()
319 .items_center()
320 .gap_3()
321 .pb_1()
322 .child(div().h_px().w_4().bg(cx.theme().colors().border))
323 .child(
324 div()
325 .flex_none()
326 .text_size(px(10.))
327 .child(title.to_uppercase()),
328 )
329 .child(
330 div()
331 .h_px()
332 .w_full()
333 .flex_1()
334 .bg(cx.theme().colors().border),
335 ),
336 )
337 })
338 .child(
339 div()
340 .flex()
341 .flex_col()
342 .items_start()
343 .w_full()
344 .gap_6()
345 .children(self.examples)
346 .into_any_element(),
347 )
348 .into_any_element()
349 }
350}
351
352impl ComponentExampleGroup {
353 pub fn new(examples: Vec<ComponentExample>) -> Self {
354 Self {
355 title: None,
356 examples,
357 grow: false,
358 vertical: false,
359 }
360 }
361 pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
362 Self {
363 title: Some(title.into()),
364 examples,
365 grow: false,
366 vertical: false,
367 }
368 }
369 pub fn grow(mut self) -> Self {
370 self.grow = true;
371 self
372 }
373 pub fn vertical(mut self) -> Self {
374 self.vertical = true;
375 self
376 }
377}
378
379pub fn single_example(
380 variant_name: impl Into<SharedString>,
381 example: AnyElement,
382) -> ComponentExample {
383 ComponentExample::new(variant_name, example)
384}
385
386pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
387 ComponentExampleGroup::new(examples)
388}
389
390pub fn example_group_with_title(
391 title: impl Into<SharedString>,
392 examples: Vec<ComponentExample>,
393) -> ComponentExampleGroup {
394 ComponentExampleGroup::with_title(title, examples)
395}