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)]
195// pub enum ComponentStatus {
196// WorkInProgress,
197// EngineeringReady,
198// Live,
199// Deprecated,
200// }
201
202#[derive(Debug, Clone, PartialEq, Eq, Hash)]
203pub enum ComponentScope {
204 Collaboration,
205 DataDisplay,
206 Editor,
207 Images,
208 Input,
209 Layout,
210 Loading,
211 Navigation,
212 None,
213 Notification,
214 Overlays,
215 Status,
216 Typography,
217 VersionControl,
218}
219
220impl Display for ComponentScope {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 match self {
223 ComponentScope::Collaboration => write!(f, "Collaboration"),
224 ComponentScope::DataDisplay => write!(f, "Data Display"),
225 ComponentScope::Editor => write!(f, "Editor"),
226 ComponentScope::Images => write!(f, "Images & Icons"),
227 ComponentScope::Input => write!(f, "Forms & Input"),
228 ComponentScope::Layout => write!(f, "Layout & Structure"),
229 ComponentScope::Loading => write!(f, "Loading & Progress"),
230 ComponentScope::Navigation => write!(f, "Navigation"),
231 ComponentScope::None => write!(f, "Unsorted"),
232 ComponentScope::Notification => write!(f, "Notification"),
233 ComponentScope::Overlays => write!(f, "Overlays & Layering"),
234 ComponentScope::Status => write!(f, "Status"),
235 ComponentScope::Typography => write!(f, "Typography"),
236 ComponentScope::VersionControl => write!(f, "Version Control"),
237 }
238 }
239}
240
241/// A single example of a component.
242#[derive(IntoElement)]
243pub struct ComponentExample {
244 pub variant_name: SharedString,
245 pub description: Option<SharedString>,
246 pub element: AnyElement,
247}
248
249impl RenderOnce for ComponentExample {
250 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
251 div()
252 .pt_2()
253 .w_full()
254 .flex()
255 .flex_col()
256 .gap_3()
257 .child(
258 div()
259 .flex()
260 .flex_col()
261 .child(
262 div()
263 .child(self.variant_name.clone())
264 .text_size(rems(1.0))
265 .text_color(cx.theme().colors().text),
266 )
267 .when_some(self.description, |this, description| {
268 this.child(
269 div()
270 .text_size(rems(0.875))
271 .text_color(cx.theme().colors().text_muted)
272 .child(description.clone()),
273 )
274 }),
275 )
276 .child(
277 div()
278 .flex()
279 .w_full()
280 .rounded_xl()
281 .min_h(px(100.))
282 .justify_center()
283 .p_8()
284 .border_1()
285 .border_color(cx.theme().colors().border.opacity(0.5))
286 .bg(pattern_slash(
287 cx.theme().colors().surface_background.opacity(0.5),
288 12.0,
289 12.0,
290 ))
291 .shadow_sm()
292 .child(self.element),
293 )
294 .into_any_element()
295 }
296}
297
298impl ComponentExample {
299 pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
300 Self {
301 variant_name: variant_name.into(),
302 element,
303 description: None,
304 }
305 }
306
307 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
308 self.description = Some(description.into());
309 self
310 }
311}
312
313/// A group of component examples.
314#[derive(IntoElement)]
315pub struct ComponentExampleGroup {
316 pub title: Option<SharedString>,
317 pub examples: Vec<ComponentExample>,
318 pub grow: bool,
319 pub vertical: bool,
320}
321
322impl RenderOnce for ComponentExampleGroup {
323 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
324 div()
325 .flex_col()
326 .text_sm()
327 .text_color(cx.theme().colors().text_muted)
328 .w_full()
329 .when_some(self.title, |this, title| {
330 this.gap_4().child(
331 div()
332 .flex()
333 .items_center()
334 .gap_3()
335 .pb_1()
336 .child(div().h_px().w_4().bg(cx.theme().colors().border))
337 .child(
338 div()
339 .flex_none()
340 .text_size(px(10.))
341 .child(title.to_uppercase()),
342 )
343 .child(
344 div()
345 .h_px()
346 .w_full()
347 .flex_1()
348 .bg(cx.theme().colors().border),
349 ),
350 )
351 })
352 .child(
353 div()
354 .flex()
355 .flex_col()
356 .items_start()
357 .w_full()
358 .gap_6()
359 .children(self.examples)
360 .into_any_element(),
361 )
362 .into_any_element()
363 }
364}
365
366impl ComponentExampleGroup {
367 pub fn new(examples: Vec<ComponentExample>) -> Self {
368 Self {
369 title: None,
370 examples,
371 grow: false,
372 vertical: false,
373 }
374 }
375 pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
376 Self {
377 title: Some(title.into()),
378 examples,
379 grow: false,
380 vertical: false,
381 }
382 }
383 pub fn grow(mut self) -> Self {
384 self.grow = true;
385 self
386 }
387 pub fn vertical(mut self) -> Self {
388 self.vertical = true;
389 self
390 }
391}
392
393pub fn single_example(
394 variant_name: impl Into<SharedString>,
395 example: AnyElement,
396) -> ComponentExample {
397 ComponentExample::new(variant_name, example)
398}
399
400pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
401 ComponentExampleGroup::new(examples)
402}
403
404pub fn example_group_with_title(
405 title: impl Into<SharedString>,
406 examples: Vec<ComponentExample>,
407) -> ComponentExampleGroup {
408 ComponentExampleGroup::with_title(title, examples)
409}