component.rs

  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}