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