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    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}