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