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