Merge `Component` and `ComponentPreview` trait (#28365)

Nate Butler created

- Merge `Component` and `ComponentPreview` trait
- Adds a number of component previews
- Removes a number of stories

Release Notes:

- N/A

Change summary

Cargo.lock                                           |  50 +
crates/component/src/component.rs                    | 268 ++++----
crates/component_preview/src/component_preview.rs    | 155 ++++-
crates/git_ui/src/git_panel.rs                       | 371 ++++++------
crates/git_ui/src/git_ui.rs                          |  38 
crates/git_ui/src/project_diff.rs                    |  91 +-
crates/notifications/src/status_toast.rs             |  70 +
crates/storybook/src/story_selector.rs               |   6 
crates/ui/Cargo.toml                                 |   1 
crates/ui/src/component_prelude.rs                   |   5 
crates/ui/src/components.rs                          |   2 
crates/ui/src/components/avatar.rs                   | 175 +++--
crates/ui/src/components/banner.rs                   |  19 
crates/ui/src/components/button/button.rs            | 253 ++++----
crates/ui/src/components/button/button_icon.rs       |  83 ++
crates/ui/src/components/button/button_like.rs       | 105 +++
crates/ui/src/components/button/icon_button.rs       | 321 +++++-----
crates/ui/src/components/button/toggle_button.rs     | 261 ++++----
crates/ui/src/components/content_group.rs            |  94 +-
crates/ui/src/components/disclosure.rs               |  54 +
crates/ui/src/components/divider.rs                  |  89 +++
crates/ui/src/components/dropdown_menu.rs            |  65 ++
crates/ui/src/components/facepile.rs                 |  85 +-
crates/ui/src/components/icon.rs                     |  89 +-
crates/ui/src/components/icon/decorated_icon.rs      |  75 +-
crates/ui/src/components/image.rs                    |  82 ++
crates/ui/src/components/indicator.rs                |  97 +++
crates/ui/src/components/keybinding.rs               |  89 +++
crates/ui/src/components/keybinding_hint.rs          | 127 ++-
crates/ui/src/components/label/highlighted_label.rs  |  98 +++
crates/ui/src/components/label/label.rs              |  22 
crates/ui/src/components/label/label_like.rs         |  67 ++
crates/ui/src/components/notification/alert_modal.rs |  49 +
crates/ui/src/components/settings_container.rs       |  56 +
crates/ui/src/components/settings_group.rs           |  76 ++
crates/ui/src/components/stories.rs                  |   4 
crates/ui/src/components/stories/icon.rs             |  20 
crates/ui/src/components/tab.rs                      |  99 +-
crates/ui/src/components/tab_bar.rs                  |  56 +
crates/ui/src/components/table.rs                    | 223 ++++---
crates/ui/src/components/toggle.rs                   | 402 +++++++------
crates/ui/src/components/tooltip.rs                  |  33 
crates/ui/src/prelude.rs                             |   4 
crates/ui/src/styles/animation.rs                    | 355 ++++++------
crates/ui/src/styles/color.rs                        | 136 ++++
crates/ui/src/styles/typography.rs                   |  83 +-
crates/ui/src/ui.rs                                  |   1 
crates/ui_input/src/ui_input.rs                      |  29 
crates/ui_macros/src/derive_register_component.rs    |  25 
crates/ui_macros/src/ui_macros.rs                    |  32 
crates/welcome/src/welcome.rs                        |   1 
tooling/workspace-hack/Cargo.toml                    |  25 
52 files changed, 3,272 insertions(+), 1,844 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3329,6 +3329,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.8.0"
@@ -4461,6 +4470,32 @@ dependencies = [
  "workspace-hack",
 ]
 
+[[package]]
+name = "documented"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6db32f0995bc4553d2de888999075acd0dbeef75ba923503f6a724263dc6f3"
+dependencies = [
+ "documented-macros",
+ "phf",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "documented-macros"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a394bb35929b58f9a5fd418f7c6b17a4b616efcc1e53e6995ca123948f87e5fa"
+dependencies = [
+ "convert_case 0.6.0",
+ "itertools 0.13.0",
+ "optfield",
+ "proc-macro2",
+ "quote",
+ "strum",
+ "syn 2.0.100",
+]
+
 [[package]]
 name = "dotenvy"
 version = "0.15.7"
@@ -7875,7 +7910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
 dependencies = [
  "cfg-if",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -9542,6 +9577,17 @@ dependencies = [
  "vcpkg",
 ]
 
+[[package]]
+name = "optfield"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa59f025cde9c698fcb4fcb3533db4621795374065bee908215263488f2d2a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
 [[package]]
 name = "option-ext"
 version = "0.2.0"
@@ -15349,6 +15395,7 @@ version = "0.1.0"
 dependencies = [
  "chrono",
  "component",
+ "documented",
  "gpui",
  "icons",
  "itertools 0.14.0",
@@ -17614,6 +17661,7 @@ dependencies = [
  "indexmap",
  "inout",
  "itertools 0.12.1",
+ "itertools 0.13.0",
  "lazy_static",
  "libc",
  "libsqlite3-sys",

crates/component/src/component.rs 🔗

@@ -3,37 +3,62 @@ use std::ops::{Deref, DerefMut};
 use std::sync::LazyLock;
 
 use collections::HashMap;
-use gpui::{AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
+use gpui::{
+    AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, pattern_slash, prelude::*,
+    px, rems,
+};
 use linkme::distributed_slice;
 use parking_lot::RwLock;
 use theme::ActiveTheme;
 
 pub trait Component {
-    fn scope() -> Option<ComponentScope>;
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
     fn name() -> &'static str {
         std::any::type_name::<Self>()
     }
+    /// Returns a name that the component should be sorted by.
+    ///
+    /// Implement this if the component should be sorted in an alternate order than its name.
+    ///
+    /// Example:
+    ///
+    /// For example, to group related components together when sorted:
+    ///
+    /// - Button      -> ButtonA
+    /// - IconButton  -> ButtonBIcon
+    /// - ToggleButton -> ButtonCToggle
+    ///
+    /// This naming scheme keeps these components together and allows them to /// be sorted in a logical order.
+    fn sort_name() -> &'static str {
+        Self::name()
+    }
     fn description() -> Option<&'static str> {
         None
     }
-}
-
-pub trait ComponentPreview: Component {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement;
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        None
+    }
 }
 
 #[distributed_slice]
 pub static __ALL_COMPONENTS: [fn()] = [..];
 
-#[distributed_slice]
-pub static __ALL_PREVIEWS: [fn()] = [..];
-
 pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
     LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
 
 pub struct ComponentRegistry {
-    components: Vec<(Option<ComponentScope>, &'static str, Option<&'static str>)>,
-    previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>,
+    components: Vec<(
+        ComponentScope,
+        // name
+        &'static str,
+        // sort name
+        &'static str,
+        // description
+        Option<&'static str>,
+    )>,
+    previews: HashMap<&'static str, fn(&mut Window, &mut App) -> Option<AnyElement>>,
 }
 
 impl ComponentRegistry {
@@ -47,30 +72,16 @@ impl ComponentRegistry {
 
 pub fn init() {
     let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
-    let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
-
     for f in component_fns {
         f();
     }
-    for f in preview_fns {
-        f();
-    }
 }
 
 pub fn register_component<T: Component>() {
-    let component_data = (T::scope(), T::name(), T::description());
-    COMPONENT_DATA.write().components.push(component_data);
-}
-
-pub fn register_preview<T: ComponentPreview>() {
-    let preview_data = (
-        T::name(),
-        T::preview as fn(&mut Window, &mut App) -> AnyElement,
-    );
-    COMPONENT_DATA
-        .write()
-        .previews
-        .insert(preview_data.0, preview_data.1);
+    let component_data = (T::scope(), T::name(), T::sort_name(), T::description());
+    let mut data = COMPONENT_DATA.write();
+    data.components.push(component_data);
+    data.previews.insert(T::name(), T::preview);
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -80,29 +91,41 @@ pub struct ComponentId(pub &'static str);
 pub struct ComponentMetadata {
     id: ComponentId,
     name: SharedString,
-    scope: Option<ComponentScope>,
+    sort_name: SharedString,
+    scope: ComponentScope,
     description: Option<SharedString>,
-    preview: Option<fn(&mut Window, &mut App) -> AnyElement>,
+    preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
 }
 
 impl ComponentMetadata {
     pub fn id(&self) -> ComponentId {
         self.id.clone()
     }
-
     pub fn name(&self) -> SharedString {
         self.name.clone()
     }
 
-    pub fn scope(&self) -> Option<ComponentScope> {
-        self.scope.clone()
+    pub fn sort_name(&self) -> SharedString {
+        self.sort_name.clone()
+    }
+
+    pub fn scopeless_name(&self) -> SharedString {
+        self.name
+            .clone()
+            .split("::")
+            .last()
+            .unwrap_or(&self.name)
+            .to_string()
+            .into()
     }
 
+    pub fn scope(&self) -> ComponentScope {
+        self.scope.clone()
+    }
     pub fn description(&self) -> Option<SharedString> {
         self.description.clone()
     }
-
-    pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> AnyElement> {
+    pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
         self.preview
     }
 }
@@ -113,26 +136,18 @@ impl AllComponents {
     pub fn new() -> Self {
         AllComponents(HashMap::default())
     }
-
-    /// Returns all components with previews
     pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
         self.0.values().filter(|c| c.preview.is_some()).collect()
     }
-
-    /// Returns all components with previews sorted by name
     pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
         let mut previews: Vec<ComponentMetadata> =
             self.all_previews().into_iter().cloned().collect();
         previews.sort_by_key(|a| a.name());
         previews
     }
-
-    /// Returns all components
     pub fn all(&self) -> Vec<&ComponentMetadata> {
         self.0.values().collect()
     }
-
-    /// Returns all components sorted by name
     pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
         let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
         components.sort_by_key(|a| a.name());
@@ -142,7 +157,6 @@ impl AllComponents {
 
 impl Deref for AllComponents {
     type Target = HashMap<ComponentId, ComponentMetadata>;
-
     fn deref(&self) -> &Self::Target {
         &self.0
     }
@@ -157,139 +171,127 @@ impl DerefMut for AllComponents {
 pub fn components() -> AllComponents {
     let data = COMPONENT_DATA.read();
     let mut all_components = AllComponents::new();
-
-    for (scope, name, description) in &data.components {
+    for (scope, name, sort_name, description) in &data.components {
         let preview = data.previews.get(name).cloned();
         let component_name = SharedString::new_static(name);
+        let sort_name = SharedString::new_static(sort_name);
         let id = ComponentId(name);
         all_components.insert(
             id.clone(),
             ComponentMetadata {
                 id,
                 name: component_name,
+                sort_name,
                 scope: scope.clone(),
                 description: description.map(Into::into),
                 preview,
             },
         );
     }
-
     all_components
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum ComponentScope {
-    Layout,
+    Collaboration,
+    DataDisplay,
+    Editor,
+    Images,
     Input,
+    Layout,
+    Loading,
+    Navigation,
+    None,
     Notification,
-    Editor,
-    Collaboration,
+    Overlays,
+    Status,
+    Typography,
     VersionControl,
-    Unknown(SharedString),
 }
 
 impl Display for ComponentScope {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            ComponentScope::Layout => write!(f, "Layout"),
-            ComponentScope::Input => write!(f, "Input"),
-            ComponentScope::Notification => write!(f, "Notification"),
-            ComponentScope::Editor => write!(f, "Editor"),
             ComponentScope::Collaboration => write!(f, "Collaboration"),
+            ComponentScope::DataDisplay => write!(f, "Data Display"),
+            ComponentScope::Editor => write!(f, "Editor"),
+            ComponentScope::Images => write!(f, "Images & Icons"),
+            ComponentScope::Input => write!(f, "Forms & Input"),
+            ComponentScope::Layout => write!(f, "Layout & Structure"),
+            ComponentScope::Loading => write!(f, "Loading & Progress"),
+            ComponentScope::Navigation => write!(f, "Navigation"),
+            ComponentScope::None => write!(f, "Unsorted"),
+            ComponentScope::Notification => write!(f, "Notification"),
+            ComponentScope::Overlays => write!(f, "Overlays & Layering"),
+            ComponentScope::Status => write!(f, "Status"),
+            ComponentScope::Typography => write!(f, "Typography"),
             ComponentScope::VersionControl => write!(f, "Version Control"),
-            ComponentScope::Unknown(name) => write!(f, "Unknown: {}", name),
         }
     }
 }
 
-impl From<&str> for ComponentScope {
-    fn from(value: &str) -> Self {
-        match value {
-            "Layout" => ComponentScope::Layout,
-            "Input" => ComponentScope::Input,
-            "Notification" => ComponentScope::Notification,
-            "Editor" => ComponentScope::Editor,
-            "Collaboration" => ComponentScope::Collaboration,
-            "Version Control" | "VersionControl" => ComponentScope::VersionControl,
-            _ => ComponentScope::Unknown(SharedString::new(value)),
-        }
-    }
-}
-
-impl From<String> for ComponentScope {
-    fn from(value: String) -> Self {
-        match value.as_str() {
-            "Layout" => ComponentScope::Layout,
-            "Input" => ComponentScope::Input,
-            "Notification" => ComponentScope::Notification,
-            "Editor" => ComponentScope::Editor,
-            "Collaboration" => ComponentScope::Collaboration,
-            "Version Control" | "VersionControl" => ComponentScope::VersionControl,
-            _ => ComponentScope::Unknown(SharedString::new(value)),
-        }
-    }
-}
-
-/// Which side of the preview to show labels on
-#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ExampleLabelSide {
-    /// Left side
-    Left,
-    /// Right side
-    Right,
-    /// Top side
-    #[default]
-    Top,
-    /// Bottom side
-    Bottom,
-}
-
 /// A single example of a component.
 #[derive(IntoElement)]
 pub struct ComponentExample {
-    variant_name: SharedString,
-    element: AnyElement,
-    label_side: ExampleLabelSide,
-    grow: bool,
+    pub variant_name: SharedString,
+    pub description: Option<SharedString>,
+    pub element: AnyElement,
 }
 
 impl RenderOnce for ComponentExample {
     fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
-        let base = div().flex();
-
-        let base = match self.label_side {
-            ExampleLabelSide::Right => base.flex_row(),
-            ExampleLabelSide::Left => base.flex_row_reverse(),
-            ExampleLabelSide::Bottom => base.flex_col(),
-            ExampleLabelSide::Top => base.flex_col_reverse(),
-        };
-
-        base.gap_2()
-            .p_2()
-            .text_size(px(10.))
-            .text_color(cx.theme().colors().text_muted)
-            .when(self.grow, |this| this.flex_1())
-            .when(!self.grow, |this| this.flex_none())
-            .child(self.element)
-            .child(self.variant_name)
+        div()
+            .w_full()
+            .flex()
+            .flex_col()
+            .gap_3()
+            .child(
+                div()
+                    .child(self.variant_name.clone())
+                    .text_size(rems(1.25))
+                    .text_color(cx.theme().colors().text),
+            )
+            .when_some(self.description, |this, description| {
+                this.child(
+                    div()
+                        .text_size(rems(0.9375))
+                        .text_color(cx.theme().colors().text_muted)
+                        .child(description.clone()),
+                )
+            })
+            .child(
+                div()
+                    .flex()
+                    .w_full()
+                    .rounded_xl()
+                    .min_h(px(100.))
+                    .justify_center()
+                    .p_8()
+                    .border_1()
+                    .border_color(cx.theme().colors().border)
+                    .bg(pattern_slash(
+                        cx.theme().colors().surface_background.opacity(0.5),
+                        24.0,
+                        24.0,
+                    ))
+                    .shadow_sm()
+                    .child(self.element),
+            )
             .into_any_element()
     }
 }
 
 impl ComponentExample {
-    /// Create a new example with the given variant name and example value.
     pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
         Self {
             variant_name: variant_name.into(),
             element,
-            label_side: ExampleLabelSide::default(),
-            grow: false,
+            description: None,
         }
     }
 
-    /// Set the example to grow to fill the available horizontal space.
-    pub fn grow(mut self) -> Self {
-        self.grow = true;
+    pub fn description(mut self, description: impl Into<SharedString>) -> Self {
+        self.description = Some(description.into());
         self
     }
 }
@@ -309,7 +311,7 @@ impl RenderOnce for ComponentExampleGroup {
             .flex_col()
             .text_sm()
             .text_color(cx.theme().colors().text_muted)
-            .when(self.grow, |this| this.w_full().flex_1())
+            .w_full()
             .when_some(self.title, |this, title| {
                 this.gap_4().child(
                     div()
@@ -336,7 +338,7 @@ impl RenderOnce for ComponentExampleGroup {
             .child(
                 div()
                     .flex()
-                    .when(self.vertical, |this| this.flex_col())
+                    .flex_col()
                     .items_start()
                     .w_full()
                     .gap_6()
@@ -348,7 +350,6 @@ impl RenderOnce for ComponentExampleGroup {
 }
 
 impl ComponentExampleGroup {
-    /// Create a new group of examples with the given title.
     pub fn new(examples: Vec<ComponentExample>) -> Self {
         Self {
             title: None,
@@ -357,8 +358,6 @@ impl ComponentExampleGroup {
             vertical: false,
         }
     }
-
-    /// Create a new group of examples with the given title.
     pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
         Self {
             title: Some(title.into()),
@@ -367,21 +366,16 @@ impl ComponentExampleGroup {
             vertical: false,
         }
     }
-
-    /// Set the group to grow to fill the available horizontal space.
     pub fn grow(mut self) -> Self {
         self.grow = true;
         self
     }
-
-    /// Lay the group out vertically.
     pub fn vertical(mut self) -> Self {
         self.vertical = true;
         self
     }
 }
 
-/// Create a single example
 pub fn single_example(
     variant_name: impl Into<SharedString>,
     example: AnyElement,
@@ -389,12 +383,10 @@ pub fn single_example(
     ComponentExample::new(variant_name, example)
 }
 
-/// Create a group of examples without a title
 pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
     ComponentExampleGroup::new(examples)
 }
 
-/// Create a group of examples with a title
 pub fn example_group_with_title(
     title: impl Into<SharedString>,
     examples: Vec<ComponentExample>,

crates/component_preview/src/component_preview.rs 🔗

@@ -43,6 +43,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
                         language_registry,
                         user_store,
                         None,
+                        None,
                         cx,
                     )
                 });
@@ -106,10 +107,12 @@ impl ComponentPreview {
         language_registry: Arc<LanguageRegistry>,
         user_store: Entity<UserStore>,
         selected_index: impl Into<Option<usize>>,
+        active_page: Option<PreviewPage>,
         cx: &mut Context<Self>,
     ) -> Self {
         let sorted_components = components().all_sorted();
         let selected_index = selected_index.into().unwrap_or(0);
+        let active_page = active_page.unwrap_or(PreviewPage::AllComponents);
 
         let component_list = ListState::new(
             sorted_components.len(),
@@ -135,7 +138,7 @@ impl ComponentPreview {
             language_registry,
             user_store,
             workspace,
-            active_page: PreviewPage::AllComponents,
+            active_page,
             component_map: components().0,
             components: sorted_components,
             component_list,
@@ -169,8 +172,7 @@ impl ComponentPreview {
     fn scope_ordered_entries(&self) -> Vec<PreviewEntry> {
         use std::collections::HashMap;
 
-        let mut scope_groups: HashMap<Option<ComponentScope>, Vec<ComponentMetadata>> =
-            HashMap::default();
+        let mut scope_groups: HashMap<ComponentScope, Vec<ComponentMetadata>> = HashMap::default();
 
         for component in &self.components {
             scope_groups
@@ -192,6 +194,7 @@ impl ComponentPreview {
             ComponentScope::Notification,
             ComponentScope::Collaboration,
             ComponentScope::VersionControl,
+            ComponentScope::None,
         ];
 
         // Always show all components first
@@ -199,38 +202,27 @@ impl ComponentPreview {
         entries.push(PreviewEntry::Separator);
 
         for scope in known_scopes.iter() {
-            let scope_key = Some(scope.clone());
-            if let Some(components) = scope_groups.remove(&scope_key) {
+            if let Some(components) = scope_groups.remove(scope) {
                 if !components.is_empty() {
                     entries.push(PreviewEntry::SectionHeader(scope.to_string().into()));
+                    let mut sorted_components = components;
+                    sorted_components.sort_by_key(|component| component.sort_name());
 
-                    for component in components {
+                    for component in sorted_components {
                         entries.push(PreviewEntry::Component(component));
                     }
                 }
             }
         }
 
-        for (scope, components) in &scope_groups {
-            if let Some(ComponentScope::Unknown(_)) = scope {
-                if !components.is_empty() {
-                    if let Some(scope_value) = scope {
-                        entries.push(PreviewEntry::SectionHeader(scope_value.to_string().into()));
-                    }
-
-                    for component in components {
-                        entries.push(PreviewEntry::Component(component.clone()));
-                    }
-                }
-            }
-        }
-
-        if let Some(components) = scope_groups.get(&None) {
+        if let Some(components) = scope_groups.get(&ComponentScope::None) {
             if !components.is_empty() {
                 entries.push(PreviewEntry::Separator);
                 entries.push(PreviewEntry::SectionHeader("Uncategorized".into()));
+                let mut sorted_components = components.clone();
+                sorted_components.sort_by_key(|c| c.sort_name());
 
-                for component in components {
+                for component in sorted_components {
                     entries.push(PreviewEntry::Component(component.clone()));
                 }
             }
@@ -250,7 +242,10 @@ impl ComponentPreview {
                 let id = component_metadata.id();
                 let selected = self.active_page == PreviewPage::Component(id.clone());
                 ListItem::new(ix)
-                    .child(Label::new(component_metadata.name().clone()).color(Color::Default))
+                    .child(
+                        Label::new(component_metadata.scopeless_name().clone())
+                            .color(Color::Default),
+                    )
                     .selectable(true)
                     .toggle_state(selected)
                     .inset(true)
@@ -333,7 +328,7 @@ impl ComponentPreview {
         window: &mut Window,
         cx: &mut App,
     ) -> impl IntoElement {
-        let name = component.name();
+        let name = component.scopeless_name();
         let scope = component.scope();
 
         let description = component.description();
@@ -354,13 +349,12 @@ impl ComponentPreview {
                         v_flex()
                             .gap_1()
                             .child(
-                                h_flex()
-                                    .gap_1()
-                                    .text_xl()
-                                    .child(div().child(name))
-                                    .when_some(scope, |this, scope| {
+                                h_flex().gap_1().text_xl().child(div().child(name)).when(
+                                    !matches!(scope, ComponentScope::None),
+                                    |this| {
                                         this.child(div().opacity(0.5).child(format!("({})", scope)))
-                                    }),
+                                    },
+                                ),
                             )
                             .when_some(description, |this, description| {
                                 this.child(
@@ -373,7 +367,7 @@ impl ComponentPreview {
                             }),
                     )
                     .when_some(component.preview(), |this, preview| {
-                        this.child(preview(window, cx))
+                        this.children(preview(window, cx))
                     }),
             )
             .into_any_element()
@@ -395,17 +389,16 @@ impl ComponentPreview {
     fn render_component_page(
         &mut self,
         component_id: &ComponentId,
-        window: &mut Window,
-        cx: &mut Context<Self>,
+        _window: &mut Window,
+        _cx: &mut Context<Self>,
     ) -> impl IntoElement {
         let component = self.component_map.get(&component_id);
 
         if let Some(component) = component {
             v_flex()
-                .w_full()
-                .flex_initial()
-                .min_h_full()
-                .child(self.render_preview(component, window, cx))
+                .id("render-component-page")
+                .size_full()
+                .child(ComponentPreviewPage::new(component.clone()))
                 .into_any_element()
         } else {
             v_flex()
@@ -445,10 +438,11 @@ impl Render for ComponentPreview {
             .overflow_hidden()
             .size_full()
             .track_focus(&self.focus_handle)
-            .px_2()
             .bg(cx.theme().colors().editor_background)
             .child(
                 v_flex()
+                    .border_r_1()
+                    .border_color(cx.theme().colors().border)
                     .h_full()
                     .child(
                         uniform_list(
@@ -465,6 +459,7 @@ impl Render for ComponentPreview {
                         )
                         .track_scroll(self.nav_scroll_handle.clone())
                         .pt_4()
+                        .px_4()
                         .w(px(240.))
                         .h_full()
                         .flex_1(),
@@ -527,6 +522,7 @@ impl Item for ComponentPreview {
         let user_store = self.user_store.clone();
         let weak_workspace = self.workspace.clone();
         let selected_index = self.cursor_index;
+        let active_page = self.active_page.clone();
 
         Some(cx.new(|cx| {
             Self::new(
@@ -534,6 +530,7 @@ impl Item for ComponentPreview {
                 language_registry,
                 user_store,
                 selected_index,
+                Some(active_page),
                 cx,
             )
         }))
@@ -566,7 +563,14 @@ impl SerializableItem for ComponentPreview {
             let weak_workspace = workspace.clone();
             cx.update(|_, cx| {
                 Ok(cx.new(|cx| {
-                    ComponentPreview::new(weak_workspace, language_registry, user_store, None, cx)
+                    ComponentPreview::new(
+                        weak_workspace,
+                        language_registry,
+                        user_store,
+                        None,
+                        None,
+                        cx,
+                    )
                 }))
             })?
         })
@@ -600,3 +604,76 @@ impl SerializableItem for ComponentPreview {
         false
     }
 }
+
+#[derive(IntoElement)]
+pub struct ComponentPreviewPage {
+    // languages: Arc<LanguageRegistry>,
+    component: ComponentMetadata,
+}
+
+impl ComponentPreviewPage {
+    pub fn new(
+        component: ComponentMetadata,
+        // languages: Arc<LanguageRegistry>
+    ) -> Self {
+        Self {
+            // languages,
+            component,
+        }
+    }
+
+    fn render_header(&self, _: &Window, cx: &App) -> impl IntoElement {
+        v_flex()
+            .px_12()
+            .pt_16()
+            .pb_12()
+            .gap_6()
+            .bg(cx.theme().colors().surface_background)
+            .border_b_1()
+            .border_color(cx.theme().colors().border)
+            .child(
+                v_flex()
+                    .gap_0p5()
+                    .child(
+                        Label::new(self.component.scope().to_string())
+                            .size(LabelSize::Small)
+                            .color(Color::Muted),
+                    )
+                    .child(
+                        Headline::new(self.component.scopeless_name()).size(HeadlineSize::XLarge),
+                    ),
+            )
+            .when_some(self.component.description(), |this, description| {
+                this.child(div().text_sm().child(description))
+            })
+    }
+
+    fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+        v_flex()
+            .flex_1()
+            .px_12()
+            .py_6()
+            .bg(cx.theme().colors().editor_background)
+            .child(if let Some(preview) = self.component.preview() {
+                preview(window, cx).unwrap_or_else(|| {
+                    div()
+                        .child("Failed to load preview. This path should be unreachable")
+                        .into_any_element()
+                })
+            } else {
+                div().child("No preview available").into_any_element()
+            })
+    }
+}
+
+impl RenderOnce for ComponentPreviewPage {
+    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+        v_flex()
+            .id("component-preview-page")
+            .overflow_y_scroll()
+            .overflow_x_hidden()
+            .w_full()
+            .child(self.render_header(window, cx))
+            .child(self.render_preview(window, cx))
+    }
+}

crates/git_ui/src/git_panel.rs 🔗

@@ -3953,8 +3953,7 @@ impl Render for GitPanelMessageTooltip {
     }
 }
 
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Version Control")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct PanelRepoFooter {
     active_repository: SharedString,
     branch: Option<Branch>,
@@ -4134,8 +4133,12 @@ impl RenderOnce for PanelRepoFooter {
     }
 }
 
-impl ComponentPreview for PanelRepoFooter {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+impl Component for PanelRepoFooter {
+    fn scope() -> ComponentScope {
+        ComponentScope::VersionControl
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
         let unknown_upstream = None;
         let no_remote_upstream = Some(UpstreamTracking::Gone);
         let ahead_of_upstream = Some(
@@ -4207,192 +4210,180 @@ impl ComponentPreview for PanelRepoFooter {
         }
 
         let example_width = px(340.);
-
-        v_flex()
-            .gap_6()
-            .w_full()
-            .flex_none()
-            .children(vec![
-                example_group_with_title(
-                    "Action Button States",
-                    vec![
-                        single_example(
-                            "No Branch",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(1).clone(),
-                                    None,
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Remote status unknown",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(2).clone(),
-                                    Some(branch(unknown_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "No Remote Upstream",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(3).clone(),
-                                    Some(branch(no_remote_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Not Ahead or Behind",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(4).clone(),
-                                    Some(branch(not_ahead_or_behind_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Behind remote",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(5).clone(),
-                                    Some(branch(behind_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Ahead of remote",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(6).clone(),
-                                    Some(branch(ahead_of_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Ahead and behind remote",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    active_repository(7).clone(),
-                                    Some(branch(ahead_and_behind_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                    ],
-                )
-                .grow()
-                .vertical(),
-            ])
-            .children(vec![
-                example_group_with_title(
-                    "Labels",
-                    vec![
-                        single_example(
-                            "Short Branch & Repo",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    SharedString::from("zed"),
-                                    Some(custom("main", behind_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Long Branch",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    SharedString::from("zed"),
-                                    Some(custom(
-                                        "redesign-and-update-git-ui-list-entry-style",
-                                        behind_upstream,
-                                    )),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Long Repo",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    SharedString::from("zed-industries-community-examples"),
-                                    Some(custom("gpui", ahead_of_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Long Repo & Branch",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    SharedString::from("zed-industries-community-examples"),
-                                    Some(custom(
-                                        "redesign-and-update-git-ui-list-entry-style",
-                                        behind_upstream,
-                                    )),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Uppercase Repo",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    SharedString::from("LICENSES"),
-                                    Some(custom("main", ahead_of_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                        single_example(
-                            "Uppercase Branch",
-                            div()
-                                .w(example_width)
-                                .overflow_hidden()
-                                .child(PanelRepoFooter::new_preview(
-                                    SharedString::from("zed"),
-                                    Some(custom("update-README", behind_upstream)),
-                                ))
-                                .into_any_element(),
-                        )
-                        .grow(),
-                    ],
-                )
-                .grow()
-                .vertical(),
-            ])
-            .into_any_element()
+        Some(
+            v_flex()
+                .gap_6()
+                .w_full()
+                .flex_none()
+                .children(vec![
+                    example_group_with_title(
+                        "Action Button States",
+                        vec![
+                            single_example(
+                                "No Branch",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(1).clone(),
+                                        None,
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Remote status unknown",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(2).clone(),
+                                        Some(branch(unknown_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "No Remote Upstream",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(3).clone(),
+                                        Some(branch(no_remote_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Not Ahead or Behind",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(4).clone(),
+                                        Some(branch(not_ahead_or_behind_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Behind remote",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(5).clone(),
+                                        Some(branch(behind_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Ahead of remote",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(6).clone(),
+                                        Some(branch(ahead_of_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Ahead and behind remote",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        active_repository(7).clone(),
+                                        Some(branch(ahead_and_behind_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    )
+                    .grow()
+                    .vertical(),
+                ])
+                .children(vec![
+                    example_group_with_title(
+                        "Labels",
+                        vec![
+                            single_example(
+                                "Short Branch & Repo",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        SharedString::from("zed"),
+                                        Some(custom("main", behind_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Long Branch",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        SharedString::from("zed"),
+                                        Some(custom(
+                                            "redesign-and-update-git-ui-list-entry-style",
+                                            behind_upstream,
+                                        )),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Long Repo",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        SharedString::from("zed-industries-community-examples"),
+                                        Some(custom("gpui", ahead_of_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Long Repo & Branch",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        SharedString::from("zed-industries-community-examples"),
+                                        Some(custom(
+                                            "redesign-and-update-git-ui-list-entry-style",
+                                            behind_upstream,
+                                        )),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Uppercase Repo",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        SharedString::from("LICENSES"),
+                                        Some(custom("main", ahead_of_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Uppercase Branch",
+                                div()
+                                    .w(example_width)
+                                    .overflow_hidden()
+                                    .child(PanelRepoFooter::new_preview(
+                                        SharedString::from("zed"),
+                                        Some(custom("update-README", behind_upstream)),
+                                    ))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    )
+                    .grow()
+                    .vertical(),
+                ])
+                .into_any_element(),
+        )
     }
 }
 

crates/git_ui/src/git_ui.rs 🔗

@@ -441,8 +441,8 @@ mod remote_button {
     }
 }
 
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Version Control")]
+/// A visual representation of a file's Git status.
+#[derive(IntoElement, RegisterComponent)]
 pub struct GitStatusIcon {
     status: FileStatus,
 }
@@ -484,8 +484,12 @@ impl RenderOnce for GitStatusIcon {
 }
 
 // View this component preview using `workspace: open component-preview`
-impl ComponentPreview for GitStatusIcon {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+impl Component for GitStatusIcon {
+    fn scope() -> ComponentScope {
+        ComponentScope::VersionControl
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
         fn tracked_file_status(code: StatusCode) -> FileStatus {
             FileStatus::Tracked(git::status::TrackedStatus {
                 index_status: code,
@@ -502,17 +506,19 @@ impl ComponentPreview for GitStatusIcon {
         }
         .into();
 
-        v_flex()
-            .gap_6()
-            .children(vec![example_group(vec![
-                single_example("Modified", GitStatusIcon::new(modified).into_any_element()),
-                single_example("Added", GitStatusIcon::new(added).into_any_element()),
-                single_example("Deleted", GitStatusIcon::new(deleted).into_any_element()),
-                single_example(
-                    "Conflicted",
-                    GitStatusIcon::new(conflict).into_any_element(),
-                ),
-            ])])
-            .into_any_element()
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![example_group(vec![
+                    single_example("Modified", GitStatusIcon::new(modified).into_any_element()),
+                    single_example("Added", GitStatusIcon::new(added).into_any_element()),
+                    single_example("Deleted", GitStatusIcon::new(deleted).into_any_element()),
+                    single_example(
+                        "Conflicted",
+                        GitStatusIcon::new(conflict).into_any_element(),
+                    ),
+                ])])
+                .into_any_element(),
+        )
     }
 }

crates/git_ui/src/project_diff.rs 🔗

@@ -1005,8 +1005,7 @@ impl Render for ProjectDiffToolbar {
     }
 }
 
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Version Control")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct ProjectDiffEmptyState {
     pub no_repo: bool,
     pub can_push_and_pull: bool,
@@ -1178,8 +1177,12 @@ mod preview {
     use super::ProjectDiffEmptyState;
 
     // View this component preview using `workspace: open component-preview`
-    impl ComponentPreview for ProjectDiffEmptyState {
-        fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+    impl Component for ProjectDiffEmptyState {
+        fn scope() -> ComponentScope {
+            ComponentScope::VersionControl
+        }
+
+        fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
             let unknown_upstream: Option<UpstreamTracking> = None;
             let ahead_of_upstream: Option<UpstreamTracking> = Some(
                 UpstreamTrackingStatus {
@@ -1244,46 +1247,48 @@ mod preview {
 
             let (width, height) = (px(480.), px(320.));
 
-            v_flex()
-                .gap_6()
-                .children(vec![
-                    example_group(vec![
-                        single_example(
-                            "No Repo",
-                            div()
-                                .w(width)
-                                .h(height)
-                                .child(no_repo_state)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "No Changes",
-                            div()
-                                .w(width)
-                                .h(height)
-                                .child(no_changes_state)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Unknown Upstream",
-                            div()
-                                .w(width)
-                                .h(height)
-                                .child(unknown_upstream_state)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Ahead of Remote",
-                            div()
-                                .w(width)
-                                .h(height)
-                                .child(ahead_of_upstream_state)
-                                .into_any_element(),
-                        ),
+            Some(
+                v_flex()
+                    .gap_6()
+                    .children(vec![
+                        example_group(vec![
+                            single_example(
+                                "No Repo",
+                                div()
+                                    .w(width)
+                                    .h(height)
+                                    .child(no_repo_state)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "No Changes",
+                                div()
+                                    .w(width)
+                                    .h(height)
+                                    .child(no_changes_state)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Unknown Upstream",
+                                div()
+                                    .w(width)
+                                    .h(height)
+                                    .child(unknown_upstream_state)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Ahead of Remote",
+                                div()
+                                    .w(width)
+                                    .h(height)
+                                    .child(ahead_of_upstream_state)
+                                    .into_any_element(),
+                            ),
+                        ])
+                        .vertical(),
                     ])
-                    .vertical(),
-                ])
-                .into_any_element()
+                    .into_any_element(),
+            )
         }
     }
 }

crates/notifications/src/status_toast.rs 🔗

@@ -33,8 +33,7 @@ impl From<IconName> for ToastIcon {
     }
 }
 
-#[derive(IntoComponent)]
-#[component(scope = "Notification")]
+#[derive(RegisterComponent)]
 pub struct StatusToast {
     icon: Option<ToastIcon>,
     text: SharedString,
@@ -135,8 +134,12 @@ impl Focusable for StatusToast {
 
 impl EventEmitter<DismissEvent> for StatusToast {}
 
-impl ComponentPreview for StatusToast {
-    fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
+impl Component for StatusToast {
+    fn scope() -> ComponentScope {
+        ComponentScope::Notification
+    }
+
+    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
         let text_example = StatusToast::new("Operation completed", cx, |this, _| this);
 
         let action_example = StatusToast::new("Update ready to install", cx, |this, _cx| {
@@ -175,29 +178,40 @@ impl ComponentPreview for StatusToast {
                     })
             });
 
-        v_flex()
-            .gap_6()
-            .p_4()
-            .children(vec![
-                example_group_with_title(
-                    "Basic Toast",
-                    vec![
-                        single_example("Text", div().child(text_example).into_any_element()),
-                        single_example("Action", div().child(action_example).into_any_element()),
-                        single_example("Icon", div().child(icon_example).into_any_element()),
-                    ],
-                ),
-                example_group_with_title(
-                    "Examples",
-                    vec![
-                        single_example("Success", div().child(success_example).into_any_element()),
-                        single_example("Error", div().child(error_example).into_any_element()),
-                        single_example("Warning", div().child(warning_example).into_any_element()),
-                        single_example("Create PR", div().child(pr_example).into_any_element()),
-                    ],
-                )
-                .vertical(),
-            ])
-            .into_any_element()
+        Some(
+            v_flex()
+                .gap_6()
+                .p_4()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Toast",
+                        vec![
+                            single_example("Text", div().child(text_example).into_any_element()),
+                            single_example(
+                                "Action",
+                                div().child(action_example).into_any_element(),
+                            ),
+                            single_example("Icon", div().child(icon_example).into_any_element()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Examples",
+                        vec![
+                            single_example(
+                                "Success",
+                                div().child(success_example).into_any_element(),
+                            ),
+                            single_example("Error", div().child(error_example).into_any_element()),
+                            single_example(
+                                "Warning",
+                                div().child(warning_example).into_any_element(),
+                            ),
+                            single_example("Create PR", div().child(pr_example).into_any_element()),
+                        ],
+                    )
+                    .vertical(),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/storybook/src/story_selector.rs 🔗

@@ -18,9 +18,7 @@ pub enum ComponentStory {
     ContextMenu,
     Cursor,
     DefaultColors,
-    Disclosure,
     Focus,
-    Icon,
     IconButton,
     Keybinding,
     List,
@@ -35,7 +33,6 @@ pub enum ComponentStory {
     ToggleButton,
     ViewportUnits,
     WithRemSize,
-    Vector,
 }
 
 impl ComponentStory {
@@ -51,9 +48,7 @@ impl ComponentStory {
             Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
             Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
             Self::DefaultColors => DefaultColorsStory::model(cx).into(),
-            Self::Disclosure => cx.new(|_| ui::DisclosureStory).into(),
             Self::Focus => FocusStory::model(window, cx).into(),
-            Self::Icon => cx.new(|_| ui::IconStory).into(),
             Self::IconButton => cx.new(|_| ui::IconButtonStory).into(),
             Self::Keybinding => cx.new(|_| ui::KeybindingStory).into(),
             Self::List => cx.new(|_| ui::ListStory).into(),
@@ -68,7 +63,6 @@ impl ComponentStory {
             Self::ToggleButton => cx.new(|_| ui::ToggleButtonStory).into(),
             Self::ViewportUnits => cx.new(|_| crate::stories::ViewportUnitsStory).into(),
             Self::WithRemSize => cx.new(|_| crate::stories::WithRemSizeStory).into(),
-            Self::Vector => cx.new(|_| ui::VectorStory).into(),
         }
     }
 }

crates/ui/Cargo.toml 🔗

@@ -28,6 +28,7 @@ strum.workspace = true
 theme.workspace = true
 ui_macros.workspace = true
 util.workspace = true
+documented = "0.9.1"
 workspace-hack.workspace = true
 
 [target.'cfg(windows)'.dependencies]

crates/ui/src/component_prelude.rs 🔗

@@ -0,0 +1,5 @@
+pub use component::{
+    Component, ComponentScope, example_group, example_group_with_title, single_example,
+};
+pub use documented::Documented;
+pub use ui_macros::RegisterComponent;

crates/ui/src/components.rs 🔗

@@ -73,7 +73,5 @@ pub use table::*;
 pub use toggle::*;
 pub use tooltip::*;
 
-#[cfg(feature = "stories")]
-pub use image::story::*;
 #[cfg(feature = "stories")]
 pub use stories::*;

crates/ui/src/components/avatar.rs 🔗

@@ -1,5 +1,6 @@
 use crate::prelude::*;
 
+use documented::Documented;
 use gpui::{AnyElement, Hsla, ImageSource, Img, IntoElement, Styled, img};
 
 /// An element that renders a user avatar with customizable appearance options.
@@ -14,7 +15,7 @@ use gpui::{AnyElement, Hsla, ImageSource, Img, IntoElement, Styled, img};
 ///     .grayscale(true)
 ///     .border_color(gpui::red());
 /// ```
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, Documented, RegisterComponent)]
 pub struct Avatar {
     image: Img,
     size: Option<AbsoluteLength>,
@@ -219,84 +220,102 @@ impl RenderOnce for AvatarAvailabilityIndicator {
 }
 
 // View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Avatar {
-    fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
+impl Component for Avatar {
+    fn scope() -> ComponentScope {
+        ComponentScope::Collaboration
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(Avatar::DOCS)
+    }
+
+    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
         let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
 
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Sizes",
-                    vec![
-                        single_example("Default", Avatar::new(example_avatar).into_any_element()),
-                        single_example(
-                            "Small",
-                            Avatar::new(example_avatar).size(px(24.)).into_any_element(),
-                        ),
-                        single_example(
-                            "Large",
-                            Avatar::new(example_avatar).size(px(48.)).into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Styles",
-                    vec![
-                        single_example("Default", Avatar::new(example_avatar).into_any_element()),
-                        single_example(
-                            "Grayscale",
-                            Avatar::new(example_avatar)
-                                .grayscale(true)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "With Border",
-                            Avatar::new(example_avatar)
-                                .border_color(cx.theme().colors().border)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Audio Status",
-                    vec![
-                        single_example(
-                            "Muted",
-                            Avatar::new(example_avatar)
-                                .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Deafened",
-                            Avatar::new(example_avatar)
-                                .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened))
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Availability",
-                    vec![
-                        single_example(
-                            "Free",
-                            Avatar::new(example_avatar)
-                                .indicator(AvatarAvailabilityIndicator::new(
-                                    CollaboratorAvailability::Free,
-                                ))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Busy",
-                            Avatar::new(example_avatar)
-                                .indicator(AvatarAvailabilityIndicator::new(
-                                    CollaboratorAvailability::Busy,
-                                ))
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Sizes",
+                        vec![
+                            single_example(
+                                "Default",
+                                Avatar::new(example_avatar).into_any_element(),
+                            ),
+                            single_example(
+                                "Small",
+                                Avatar::new(example_avatar).size(px(24.)).into_any_element(),
+                            ),
+                            single_example(
+                                "Large",
+                                Avatar::new(example_avatar).size(px(48.)).into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Styles",
+                        vec![
+                            single_example(
+                                "Default",
+                                Avatar::new(example_avatar).into_any_element(),
+                            ),
+                            single_example(
+                                "Grayscale",
+                                Avatar::new(example_avatar)
+                                    .grayscale(true)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "With Border",
+                                Avatar::new(example_avatar)
+                                    .border_color(cx.theme().colors().border)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Audio Status",
+                        vec![
+                            single_example(
+                                "Muted",
+                                Avatar::new(example_avatar)
+                                    .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Deafened",
+                                Avatar::new(example_avatar)
+                                    .indicator(AvatarAudioStatusIndicator::new(
+                                        AudioStatus::Deafened,
+                                    ))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Availability",
+                        vec![
+                            single_example(
+                                "Free",
+                                Avatar::new(example_avatar)
+                                    .indicator(AvatarAvailabilityIndicator::new(
+                                        CollaboratorAvailability::Free,
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Busy",
+                                Avatar::new(example_avatar)
+                                    .indicator(AvatarAvailabilityIndicator::new(
+                                        CollaboratorAvailability::Busy,
+                                    ))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/banner.rs 🔗

@@ -28,8 +28,7 @@ pub enum Severity {
 ///             .icon_position(IconPosition::End),
 ///     )
 /// ```
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Notification")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Banner {
     severity: Severity,
     children: Option<AnyElement>,
@@ -137,8 +136,12 @@ impl RenderOnce for Banner {
     }
 }
 
-impl ComponentPreview for Banner {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+impl Component for Banner {
+    fn scope() -> ComponentScope {
+        ComponentScope::Notification
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
         let severity_examples = vec![
             single_example(
                 "Default",
@@ -185,8 +188,10 @@ impl ComponentPreview for Banner {
             ),
         ];
 
-        example_group(severity_examples)
-            .vertical()
-            .into_any_element()
+        Some(
+            example_group(severity_examples)
+                .vertical()
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/button/button.rs 🔗

@@ -1,6 +1,6 @@
-use component::{ComponentPreview, example_group_with_title, single_example};
+use crate::component_prelude::*;
 use gpui::{AnyElement, AnyView, DefiniteLength};
-use ui_macros::IntoComponent;
+use ui_macros::RegisterComponent;
 
 use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label};
 use crate::{
@@ -77,8 +77,7 @@ use super::button_icon::ButtonIcon;
 ///     });
 /// ```
 ///
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Input")]
+#[derive(IntoElement, Documented, RegisterComponent)]
 pub struct Button {
     base: ButtonLike,
     label: SharedString,
@@ -466,121 +465,135 @@ impl RenderOnce for Button {
 }
 
 // View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Button {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Button Styles",
-                    vec![
-                        single_example(
-                            "Default",
-                            Button::new("default", "Default").into_any_element(),
-                        ),
-                        single_example(
-                            "Filled",
-                            Button::new("filled", "Filled")
-                                .style(ButtonStyle::Filled)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Subtle",
-                            Button::new("outline", "Subtle")
-                                .style(ButtonStyle::Subtle)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Tinted",
-                            Button::new("tinted_accent_style", "Accent")
-                                .style(ButtonStyle::Tinted(TintColor::Accent))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Transparent",
-                            Button::new("transparent", "Transparent")
-                                .style(ButtonStyle::Transparent)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Tint Styles",
-                    vec![
-                        single_example(
-                            "Accent",
-                            Button::new("tinted_accent", "Accent")
-                                .style(ButtonStyle::Tinted(TintColor::Accent))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Error",
-                            Button::new("tinted_negative", "Error")
-                                .style(ButtonStyle::Tinted(TintColor::Error))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Warning",
-                            Button::new("tinted_warning", "Warning")
-                                .style(ButtonStyle::Tinted(TintColor::Warning))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Success",
-                            Button::new("tinted_positive", "Success")
-                                .style(ButtonStyle::Tinted(TintColor::Success))
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Special States",
-                    vec![
-                        single_example(
-                            "Default",
-                            Button::new("default_state", "Default").into_any_element(),
-                        ),
-                        single_example(
-                            "Disabled",
-                            Button::new("disabled", "Disabled")
-                                .disabled(true)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Selected",
-                            Button::new("selected", "Selected")
-                                .toggle_state(true)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Buttons with Icons",
-                    vec![
-                        single_example(
-                            "Icon Start",
-                            Button::new("icon_start", "Icon Start")
-                                .icon(IconName::Check)
-                                .icon_position(IconPosition::Start)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Icon End",
-                            Button::new("icon_end", "Icon End")
-                                .icon(IconName::Check)
-                                .icon_position(IconPosition::End)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Icon Color",
-                            Button::new("icon_color", "Icon Color")
-                                .icon(IconName::Check)
-                                .icon_color(Color::Accent)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+impl Component for Button {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn sort_name() -> &'static str {
+        "ButtonA"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A button triggers an event or action.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Button Styles",
+                        vec![
+                            single_example(
+                                "Default",
+                                Button::new("default", "Default").into_any_element(),
+                            ),
+                            single_example(
+                                "Filled",
+                                Button::new("filled", "Filled")
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Subtle",
+                                Button::new("outline", "Subtle")
+                                    .style(ButtonStyle::Subtle)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Tinted",
+                                Button::new("tinted_accent_style", "Accent")
+                                    .style(ButtonStyle::Tinted(TintColor::Accent))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Transparent",
+                                Button::new("transparent", "Transparent")
+                                    .style(ButtonStyle::Transparent)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Tint Styles",
+                        vec![
+                            single_example(
+                                "Accent",
+                                Button::new("tinted_accent", "Accent")
+                                    .style(ButtonStyle::Tinted(TintColor::Accent))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Error",
+                                Button::new("tinted_negative", "Error")
+                                    .style(ButtonStyle::Tinted(TintColor::Error))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Warning",
+                                Button::new("tinted_warning", "Warning")
+                                    .style(ButtonStyle::Tinted(TintColor::Warning))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Success",
+                                Button::new("tinted_positive", "Success")
+                                    .style(ButtonStyle::Tinted(TintColor::Success))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Special States",
+                        vec![
+                            single_example(
+                                "Default",
+                                Button::new("default_state", "Default").into_any_element(),
+                            ),
+                            single_example(
+                                "Disabled",
+                                Button::new("disabled", "Disabled")
+                                    .disabled(true)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Selected",
+                                Button::new("selected", "Selected")
+                                    .toggle_state(true)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Buttons with Icons",
+                        vec![
+                            single_example(
+                                "Icon Start",
+                                Button::new("icon_start", "Icon Start")
+                                    .icon(IconName::Check)
+                                    .icon_position(IconPosition::Start)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Icon End",
+                                Button::new("icon_end", "Icon End")
+                                    .icon(IconName::Check)
+                                    .icon_position(IconPosition::End)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Icon Color",
+                                Button::new("icon_color", "Icon Color")
+                                    .icon(IconName::Check)
+                                    .icon_color(Color::Accent)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/button/button_icon.rs 🔗

@@ -5,7 +5,7 @@ use gpui::Hsla;
 ///
 /// Can be used as either an icon alongside a label, like in [`Button`](crate::Button),
 /// or as a standalone icon, like in [`IconButton`](crate::IconButton).
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub(super) struct ButtonIcon {
     icon: IconName,
     size: IconSize,
@@ -39,7 +39,6 @@ impl ButtonIcon {
         if let Some(size) = size.into() {
             self.size = size;
         }
-
         self
     }
 
@@ -47,7 +46,6 @@ impl ButtonIcon {
         if let Some(color) = color.into() {
             self.color = color;
         }
-
         self
     }
 
@@ -120,3 +118,82 @@ impl RenderOnce for ButtonIcon {
         }
     }
 }
+
+impl Component for ButtonIcon {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn name() -> &'static str {
+        "ButtonIcon"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("An icon component specifically designed for use within buttons.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Default",
+                                ButtonIcon::new(IconName::Star).into_any_element(),
+                            ),
+                            single_example(
+                                "Custom Size",
+                                ButtonIcon::new(IconName::Star)
+                                    .size(IconSize::Medium)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Custom Color",
+                                ButtonIcon::new(IconName::Star)
+                                    .color(Color::Accent)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "States",
+                        vec![
+                            single_example(
+                                "Selected",
+                                ButtonIcon::new(IconName::Star)
+                                    .toggle_state(true)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Disabled",
+                                ButtonIcon::new(IconName::Star)
+                                    .disabled(true)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "With Indicator",
+                        vec![
+                            single_example(
+                                "Default Indicator",
+                                ButtonIcon::new(IconName::Star)
+                                    .indicator(Indicator::dot())
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Custom Indicator",
+                                ButtonIcon::new(IconName::Star)
+                                    .indicator(Indicator::dot().color(Color::Error))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/button/button_like.rs 🔗

@@ -1,5 +1,8 @@
-use gpui::{AnyElement, AnyView, ClickEvent, Hsla, Rems, transparent_black};
-use gpui::{CursorStyle, DefiniteLength, MouseButton, MouseDownEvent, MouseUpEvent, relative};
+use documented::Documented;
+use gpui::{
+    AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, Hsla, MouseButton,
+    MouseDownEvent, MouseUpEvent, Rems, relative, transparent_black,
+};
 use smallvec::SmallVec;
 
 use crate::{DynamicSpacing, ElevationIndex, prelude::*};
@@ -343,7 +346,7 @@ impl ButtonSize {
 /// unconstrained and may make the UI feel less consistent.
 ///
 /// This is also used to build the prebuilt buttons.
-#[derive(IntoElement)]
+#[derive(IntoElement, Documented, RegisterComponent)]
 pub struct ButtonLike {
     pub(super) base: Div,
     id: ElementId,
@@ -585,3 +588,99 @@ impl RenderOnce for ButtonLike {
             .children(self.children)
     }
 }
+
+impl Component for ButtonLike {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn sort_name() -> &'static str {
+        // ButtonLike should be at the bottom of the button list
+        "ButtonZ"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(ButtonLike::DOCS)
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group(vec![
+                        single_example(
+                            "Default",
+                            ButtonLike::new("default")
+                                .child(Label::new("Default"))
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Filled",
+                            ButtonLike::new("filled")
+                                .style(ButtonStyle::Filled)
+                                .child(Label::new("Filled"))
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Subtle",
+                            ButtonLike::new("outline")
+                                .style(ButtonStyle::Subtle)
+                                .child(Label::new("Subtle"))
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Tinted",
+                            ButtonLike::new("tinted_accent_style")
+                                .style(ButtonStyle::Tinted(TintColor::Accent))
+                                .child(Label::new("Accent"))
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Transparent",
+                            ButtonLike::new("transparent")
+                                .style(ButtonStyle::Transparent)
+                                .child(Label::new("Transparent"))
+                                .into_any_element(),
+                        ),
+                    ]),
+                    example_group_with_title(
+                        "Button Group Constructors",
+                        vec![
+                            single_example(
+                                "Left Rounded",
+                                ButtonLike::new_rounded_left("left_rounded")
+                                    .child(Label::new("Left Rounded"))
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Right Rounded",
+                                ButtonLike::new_rounded_right("right_rounded")
+                                    .child(Label::new("Right Rounded"))
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Button Group",
+                                h_flex()
+                                    .gap_px()
+                                    .child(
+                                        ButtonLike::new_rounded_left("bg_left")
+                                            .child(Label::new("Left"))
+                                            .style(ButtonStyle::Filled),
+                                    )
+                                    .child(
+                                        ButtonLike::new_rounded_right("bg_right")
+                                            .child(Label::new("Right"))
+                                            .style(ButtonStyle::Filled),
+                                    )
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/button/icon_button.rs 🔗

@@ -13,8 +13,7 @@ pub enum IconButtonShape {
     Wide,
 }
 
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Input")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct IconButton {
     base: ButtonLike,
     shape: IconButtonShape,
@@ -210,159 +209,169 @@ impl RenderOnce for IconButton {
     }
 }
 
-impl ComponentPreview for IconButton {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Icon Button Styles",
-                    vec![
-                        single_example(
-                            "Default",
-                            IconButton::new("default", IconName::Check)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Filled",
-                            IconButton::new("filled", IconName::Check)
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Filled)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Subtle",
-                            IconButton::new("subtle", IconName::Check)
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Subtle)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Tinted",
-                            IconButton::new("tinted", IconName::Check)
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Tinted(TintColor::Accent))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Transparent",
-                            IconButton::new("transparent", IconName::Check)
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Transparent)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Icon Button Shapes",
-                    vec![
-                        single_example(
-                            "Square",
-                            IconButton::new("square", IconName::Check)
-                                .shape(IconButtonShape::Square)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Wide",
-                            IconButton::new("wide", IconName::Check)
-                                .shape(IconButtonShape::Wide)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Icon Button Sizes",
-                    vec![
-                        single_example(
-                            "Small",
-                            IconButton::new("small", IconName::Check)
-                                .icon_size(IconSize::XSmall)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Small",
-                            IconButton::new("small", IconName::Check)
-                                .icon_size(IconSize::Small)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Medium",
-                            IconButton::new("medium", IconName::Check)
-                                .icon_size(IconSize::Medium)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "XLarge",
-                            IconButton::new("xlarge", IconName::Check)
-                                .icon_size(IconSize::XLarge)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Special States",
-                    vec![
-                        single_example(
-                            "Disabled",
-                            IconButton::new("disabled", IconName::Check)
-                                .disabled(true)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Selected",
-                            IconButton::new("selected", IconName::Check)
-                                .toggle_state(true)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "With Indicator",
-                            IconButton::new("indicator", IconName::Check)
-                                .indicator(Indicator::dot().color(Color::Success))
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Custom Colors",
-                    vec![
-                        single_example(
-                            "Custom Icon Color",
-                            IconButton::new("custom_color", IconName::Check)
-                                .icon_color(Color::Accent)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "With Alpha",
-                            IconButton::new("alpha", IconName::Check)
-                                .alpha(0.5)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::Background)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+impl Component for IconButton {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn sort_name() -> &'static str {
+        "ButtonB"
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Icon Button Styles",
+                        vec![
+                            single_example(
+                                "Default",
+                                IconButton::new("default", IconName::Check)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Filled",
+                                IconButton::new("filled", IconName::Check)
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Subtle",
+                                IconButton::new("subtle", IconName::Check)
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Subtle)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Tinted",
+                                IconButton::new("tinted", IconName::Check)
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Tinted(TintColor::Accent))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Transparent",
+                                IconButton::new("transparent", IconName::Check)
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Transparent)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Icon Button Shapes",
+                        vec![
+                            single_example(
+                                "Square",
+                                IconButton::new("square", IconName::Check)
+                                    .shape(IconButtonShape::Square)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Wide",
+                                IconButton::new("wide", IconName::Check)
+                                    .shape(IconButtonShape::Wide)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Icon Button Sizes",
+                        vec![
+                            single_example(
+                                "XSmall",
+                                IconButton::new("xsmall", IconName::Check)
+                                    .icon_size(IconSize::XSmall)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Small",
+                                IconButton::new("small", IconName::Check)
+                                    .icon_size(IconSize::Small)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Medium",
+                                IconButton::new("medium", IconName::Check)
+                                    .icon_size(IconSize::Medium)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "XLarge",
+                                IconButton::new("xlarge", IconName::Check)
+                                    .icon_size(IconSize::XLarge)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Special States",
+                        vec![
+                            single_example(
+                                "Disabled",
+                                IconButton::new("disabled", IconName::Check)
+                                    .disabled(true)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Selected",
+                                IconButton::new("selected", IconName::Check)
+                                    .toggle_state(true)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "With Indicator",
+                                IconButton::new("indicator", IconName::Check)
+                                    .indicator(Indicator::dot().color(Color::Success))
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Custom Colors",
+                        vec![
+                            single_example(
+                                "Custom Icon Color",
+                                IconButton::new("custom_color", IconName::Check)
+                                    .icon_color(Color::Accent)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "With Alpha",
+                                IconButton::new("alpha", IconName::Check)
+                                    .alpha(0.5)
+                                    .style(ButtonStyle::Filled)
+                                    .layer(ElevationIndex::Background)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/button/toggle_button.rs 🔗

@@ -15,8 +15,7 @@ pub enum ToggleButtonPosition {
     Last,
 }
 
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Input")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct ToggleButton {
     base: ButtonLike,
     position_in_group: Option<ToggleButtonPosition>,
@@ -155,129 +154,139 @@ impl RenderOnce for ToggleButton {
     }
 }
 
-impl ComponentPreview for ToggleButton {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Button Styles",
-                    vec![
-                        single_example(
-                            "Off",
-                            ToggleButton::new("off", "Off")
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Filled)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "On",
-                            ToggleButton::new("on", "On")
-                                .layer(ElevationIndex::Background)
-                                .toggle_state(true)
-                                .style(ButtonStyle::Filled)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Off – Disabled",
-                            ToggleButton::new("disabled_off", "Disabled Off")
-                                .layer(ElevationIndex::Background)
-                                .disabled(true)
-                                .style(ButtonStyle::Filled)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "On – Disabled",
-                            ToggleButton::new("disabled_on", "Disabled On")
-                                .layer(ElevationIndex::Background)
-                                .disabled(true)
-                                .toggle_state(true)
-                                .style(ButtonStyle::Filled)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Button Group",
-                    vec![
-                        single_example(
-                            "Three Buttons",
-                            h_flex()
-                                .child(
-                                    ToggleButton::new("three_btn_first", "First")
-                                        .layer(ElevationIndex::Background)
-                                        .style(ButtonStyle::Filled)
-                                        .first()
-                                        .into_any_element(),
-                                )
-                                .child(
-                                    ToggleButton::new("three_btn_middle", "Middle")
-                                        .layer(ElevationIndex::Background)
-                                        .style(ButtonStyle::Filled)
-                                        .middle()
-                                        .toggle_state(true)
-                                        .into_any_element(),
-                                )
-                                .child(
-                                    ToggleButton::new("three_btn_last", "Last")
-                                        .layer(ElevationIndex::Background)
-                                        .style(ButtonStyle::Filled)
-                                        .last()
-                                        .into_any_element(),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Two Buttons",
-                            h_flex()
-                                .child(
-                                    ToggleButton::new("two_btn_first", "First")
-                                        .layer(ElevationIndex::Background)
-                                        .style(ButtonStyle::Filled)
-                                        .first()
-                                        .into_any_element(),
-                                )
-                                .child(
-                                    ToggleButton::new("two_btn_last", "Last")
-                                        .layer(ElevationIndex::Background)
-                                        .style(ButtonStyle::Filled)
-                                        .last()
-                                        .into_any_element(),
-                                )
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Alternate Sizes",
-                    vec![
-                        single_example(
-                            "None",
-                            ToggleButton::new("none", "None")
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::None)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Compact",
-                            ToggleButton::new("compact", "Compact")
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Compact)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Large",
-                            ToggleButton::new("large", "Large")
-                                .layer(ElevationIndex::Background)
-                                .style(ButtonStyle::Filled)
-                                .size(ButtonSize::Large)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+impl Component for ToggleButton {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn sort_name() -> &'static str {
+        "ButtonC"
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Button Styles",
+                        vec![
+                            single_example(
+                                "Off",
+                                ToggleButton::new("off", "Off")
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "On",
+                                ToggleButton::new("on", "On")
+                                    .layer(ElevationIndex::Background)
+                                    .toggle_state(true)
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Off – Disabled",
+                                ToggleButton::new("disabled_off", "Disabled Off")
+                                    .layer(ElevationIndex::Background)
+                                    .disabled(true)
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "On – Disabled",
+                                ToggleButton::new("disabled_on", "Disabled On")
+                                    .layer(ElevationIndex::Background)
+                                    .disabled(true)
+                                    .toggle_state(true)
+                                    .style(ButtonStyle::Filled)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Button Group",
+                        vec![
+                            single_example(
+                                "Three Buttons",
+                                h_flex()
+                                    .child(
+                                        ToggleButton::new("three_btn_first", "First")
+                                            .layer(ElevationIndex::Background)
+                                            .style(ButtonStyle::Filled)
+                                            .first()
+                                            .into_any_element(),
+                                    )
+                                    .child(
+                                        ToggleButton::new("three_btn_middle", "Middle")
+                                            .layer(ElevationIndex::Background)
+                                            .style(ButtonStyle::Filled)
+                                            .middle()
+                                            .toggle_state(true)
+                                            .into_any_element(),
+                                    )
+                                    .child(
+                                        ToggleButton::new("three_btn_last", "Last")
+                                            .layer(ElevationIndex::Background)
+                                            .style(ButtonStyle::Filled)
+                                            .last()
+                                            .into_any_element(),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Two Buttons",
+                                h_flex()
+                                    .child(
+                                        ToggleButton::new("two_btn_first", "First")
+                                            .layer(ElevationIndex::Background)
+                                            .style(ButtonStyle::Filled)
+                                            .first()
+                                            .into_any_element(),
+                                    )
+                                    .child(
+                                        ToggleButton::new("two_btn_last", "Last")
+                                            .layer(ElevationIndex::Background)
+                                            .style(ButtonStyle::Filled)
+                                            .last()
+                                            .into_any_element(),
+                                    )
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Alternate Sizes",
+                        vec![
+                            single_example(
+                                "None",
+                                ToggleButton::new("none", "None")
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::None)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Compact",
+                                ToggleButton::new("compact", "Compact")
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Compact)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Large",
+                                ToggleButton::new("large", "Large")
+                                    .layer(ElevationIndex::Background)
+                                    .style(ButtonStyle::Filled)
+                                    .size(ButtonSize::Large)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/content_group.rs 🔗

@@ -1,5 +1,5 @@
+use crate::component_prelude::*;
 use crate::prelude::*;
-use component::{ComponentPreview, example_group, single_example};
 use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
 use smallvec::SmallVec;
 
@@ -23,8 +23,7 @@ pub fn h_container() -> ContentGroup {
 }
 
 /// A flexible container component that can hold other elements.
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Layout")]
+#[derive(IntoElement, Documented, RegisterComponent)]
 pub struct ContentGroup {
     base: Div,
     border: bool,
@@ -83,51 +82,56 @@ impl RenderOnce for ContentGroup {
                 this.border_1().border_color(cx.theme().colors().border)
             })
             .rounded_sm()
-            .p_2()
             .children(self.children)
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for ContentGroup {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        example_group(vec![
-            single_example(
-                "Default",
-                ContentGroup::new()
-                    .flex_1()
-                    .items_center()
-                    .justify_center()
-                    .h_48()
-                    .child(Label::new("Default ContentBox"))
-                    .into_any_element(),
-            )
-            .grow(),
-            single_example(
-                "Without Border",
-                ContentGroup::new()
-                    .flex_1()
-                    .items_center()
-                    .justify_center()
-                    .h_48()
-                    .borderless()
-                    .child(Label::new("Borderless ContentBox"))
-                    .into_any_element(),
-            )
-            .grow(),
-            single_example(
-                "Without Fill",
-                ContentGroup::new()
-                    .flex_1()
-                    .items_center()
-                    .justify_center()
-                    .h_48()
-                    .unfilled()
-                    .child(Label::new("Unfilled ContentBox"))
-                    .into_any_element(),
-            )
-            .grow(),
-        ])
-        .into_any_element()
+impl Component for ContentGroup {
+    fn scope() -> ComponentScope {
+        ComponentScope::Layout
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(ContentGroup::DOCS)
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            example_group(vec![
+                single_example(
+                    "Default",
+                    ContentGroup::new()
+                        .flex_1()
+                        .items_center()
+                        .justify_center()
+                        .h_48()
+                        .child(Label::new("Default ContentGroup"))
+                        .into_any_element(),
+                ).description("A contained style for laying out groups of content. Has a default background and border color."),
+                single_example(
+                    "Without Border",
+                    ContentGroup::new()
+                        .flex_1()
+                        .items_center()
+                        .justify_center()
+                        .h_48()
+                        .borderless()
+                        .child(Label::new("Borderless ContentGroup"))
+                        .into_any_element(),
+                ),
+                single_example(
+                    "Without Fill",
+                    ContentGroup::new()
+                        .flex_1()
+                        .items_center()
+                        .justify_center()
+                        .h_48()
+                        .unfilled()
+                        .child(Label::new("Unfilled ContentGroup"))
+                        .into_any_element(),
+                ),
+            ])
+            .into_any_element(),
+        )
     }
 }

crates/ui/src/components/disclosure.rs 🔗

@@ -4,7 +4,7 @@ use gpui::{ClickEvent, CursorStyle};
 
 use crate::{Color, IconButton, IconButtonShape, IconName, IconSize, prelude::*};
 
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Disclosure {
     id: ElementId,
     is_open: bool,
@@ -84,3 +84,55 @@ impl RenderOnce for Disclosure {
         })
     }
 }
+
+impl Component for Disclosure {
+    fn scope() -> ComponentScope {
+        ComponentScope::Navigation
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "An interactive element used to show or hide content, typically used in expandable sections or tree-like structures.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Disclosure States",
+                        vec![
+                            single_example(
+                                "Closed",
+                                Disclosure::new("closed", false).into_any_element(),
+                            ),
+                            single_example(
+                                "Open",
+                                Disclosure::new("open", true).into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Interactive Example",
+                        vec![single_example(
+                            "Toggleable",
+                            v_flex()
+                                .gap_2()
+                                .child(
+                                    Disclosure::new("interactive", false)
+                                        // .on_toggle(Some(Arc::new(|_, _, cx| {
+                                        //     cx.refresh();
+                                        // })))
+                                        .into_any_element(),
+                                )
+                                .child(Label::new("Click to toggle"))
+                                .into_any_element(),
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/divider.rs 🔗

@@ -49,7 +49,7 @@ impl DividerColor {
     }
 }
 
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Divider {
     style: DividerStyle,
     direction: DividerDirection,
@@ -158,3 +158,90 @@ impl Divider {
             )
     }
 }
+
+impl Component for Divider {
+    fn scope() -> ComponentScope {
+        ComponentScope::Layout
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "Visual separator used to create divisions between groups of content or sections in a layout.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Horizontal Dividers",
+                        vec![
+                            single_example("Default", Divider::horizontal().into_any_element()),
+                            single_example(
+                                "Border Color",
+                                Divider::horizontal()
+                                    .color(DividerColor::Border)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Inset",
+                                Divider::horizontal().inset().into_any_element(),
+                            ),
+                            single_example(
+                                "Dashed",
+                                Divider::horizontal_dashed().into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Vertical Dividers",
+                        vec![
+                            single_example(
+                                "Default",
+                                div().h_16().child(Divider::vertical()).into_any_element(),
+                            ),
+                            single_example(
+                                "Border Color",
+                                div()
+                                    .h_16()
+                                    .child(Divider::vertical().color(DividerColor::Border))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Inset",
+                                div()
+                                    .h_16()
+                                    .child(Divider::vertical().inset())
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Dashed",
+                                div()
+                                    .h_16()
+                                    .child(Divider::vertical_dashed())
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Example Usage",
+                        vec![single_example(
+                            "Between Content",
+                            v_flex()
+                                .gap_4()
+                                .px_4()
+                                .child(Label::new("Section One"))
+                                .child(Divider::horizontal())
+                                .child(Label::new("Section Two"))
+                                .child(Divider::horizontal_dashed())
+                                .child(Label::new("Section Three"))
+                                .into_any_element(),
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/dropdown_menu.rs 🔗

@@ -7,7 +7,7 @@ enum LabelKind {
     Element(AnyElement),
 }
 
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct DropdownMenu {
     id: ElementId,
     label: LabelKind,
@@ -72,6 +72,69 @@ impl RenderOnce for DropdownMenu {
     }
 }
 
+impl Component for DropdownMenu {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn name() -> &'static str {
+        "DropdownMenu"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "A dropdown menu displays a list of actions or options. A dropdown menu is always activated by clicking a trigger (or via a keybinding).",
+        )
+    }
+
+    fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
+        let menu = ContextMenu::build(window, cx, |this, _, _| {
+            this.entry("Option 1", None, |_, _| {})
+                .entry("Option 2", None, |_, _| {})
+                .entry("Option 3", None, |_, _| {})
+                .separator()
+                .entry("Option 4", None, |_, _| {})
+        });
+
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Default",
+                                DropdownMenu::new("default", "Select an option", menu.clone())
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Full Width",
+                                DropdownMenu::new(
+                                    "full-width",
+                                    "Full Width Dropdown",
+                                    menu.clone(),
+                                )
+                                .full_width(true)
+                                .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "States",
+                        vec![single_example(
+                            "Disabled",
+                            DropdownMenu::new("disabled", "Disabled Dropdown", menu.clone())
+                                .disabled(true)
+                                .into_any_element(),
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}
+
 #[derive(IntoElement)]
 struct DropdownMenuTrigger {
     label: LabelKind,

crates/ui/src/components/facepile.rs 🔗

@@ -1,13 +1,31 @@
-use crate::{Avatar, prelude::*};
+use crate::component_prelude::*;
+use crate::prelude::*;
 use gpui::{AnyElement, StyleRefinement};
 use smallvec::SmallVec;
 
-/// A facepile is a collection of faces stacked horizontally–
-/// always with the leftmost face on top and descending in z-index
+use super::Avatar;
+
+/// An element that displays a collection of (usually) faces stacked
+/// horizontally, with the left-most face on top, visually descending
+/// from left to right.
 ///
 /// Facepiles are used to display a group of people or things,
 /// such as a list of participants in a collaboration session.
-#[derive(IntoElement, IntoComponent)]
+///
+/// # Examples
+///
+/// ## Default
+///
+/// A default, horizontal facepile.
+///
+/// ```
+/// use ui::{Avatar, Facepile, EXAMPLE_FACES};
+///
+/// Facepile::new(
+/// EXAMPLE_FACES.iter().take(3).iter().map(|&url|
+///    Avatar::new(url).into_any_element()).collect())
+/// ```
+#[derive(IntoElement, Documented, RegisterComponent)]
 pub struct Facepile {
     base: Div,
     faces: SmallVec<[AnyElement; 2]>,
@@ -60,27 +78,37 @@ impl RenderOnce for Facepile {
     }
 }
 
-impl ComponentPreview for Facepile {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        let faces: [&'static str; 6] = [
-            "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
-            "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
-            "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
-            "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
-            "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
-            "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
-        ];
+pub const EXAMPLE_FACES: [&'static str; 6] = [
+    "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
+    "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
+    "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
+    "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
+    "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
+    "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
+];
+
+impl Component for Facepile {
+    fn scope() -> ComponentScope {
+        ComponentScope::Collaboration
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "Displays a collection of avatars or initials in a compact format. Often used to represent active collaborators or a subset of contributors.",
+        )
+    }
 
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![example_group_with_title(
                     "Facepile Examples",
                     vec![
                         single_example(
                             "Default",
                             Facepile::new(
-                                faces
+                                EXAMPLE_FACES
                                     .iter()
                                     .map(|&url| Avatar::new(url).into_any_element())
                                     .collect(),
@@ -90,7 +118,7 @@ impl ComponentPreview for Facepile {
                         single_example(
                             "Custom Size",
                             Facepile::new(
-                                faces
+                                EXAMPLE_FACES
                                     .iter()
                                     .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
                                     .collect(),
@@ -98,19 +126,8 @@ impl ComponentPreview for Facepile {
                             .into_any_element(),
                         ),
                     ],
-                ),
-                example_group_with_title(
-                    "Special Cases",
-                    vec![
-                        single_example("Empty Facepile", Facepile::empty().into_any_element()),
-                        single_example(
-                            "Single Face",
-                            Facepile::new(vec![Avatar::new(faces[0]).into_any_element()].into())
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+                )])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/icon.rs 🔗

@@ -136,7 +136,7 @@ impl IconSource {
     }
 }
 
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Icon {
     source: IconSource,
     color: Color,
@@ -265,43 +265,54 @@ impl RenderOnce for IconWithIndicator {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Icon {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Sizes",
-                    vec![
-                        single_example("Default", Icon::new(IconName::Star).into_any_element()),
-                        single_example(
-                            "Small",
-                            Icon::new(IconName::Star)
-                                .size(IconSize::Small)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Large",
-                            Icon::new(IconName::Star)
-                                .size(IconSize::XLarge)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Colors",
-                    vec![
-                        single_example("Default", Icon::new(IconName::Bell).into_any_element()),
-                        single_example(
-                            "Custom Color",
-                            Icon::new(IconName::Bell)
-                                .color(Color::Error)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+impl Component for Icon {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "A versatile icon component that supports SVG and image-based icons with customizable size, color, and transformations.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Sizes",
+                        vec![
+                            single_example("Default", Icon::new(IconName::Star).into_any_element()),
+                            single_example(
+                                "Small",
+                                Icon::new(IconName::Star)
+                                    .size(IconSize::Small)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Large",
+                                Icon::new(IconName::Star)
+                                    .size(IconSize::XLarge)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Colors",
+                        vec![
+                            single_example("Default", Icon::new(IconName::Bell).into_any_element()),
+                            single_example(
+                                "Custom Color",
+                                Icon::new(IconName::Bell)
+                                    .color(Color::Error)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/icon/decorated_icon.rs 🔗

@@ -2,7 +2,7 @@ use gpui::{AnyElement, IntoElement, Point};
 
 use crate::{IconDecoration, IconDecorationKind, prelude::*};
 
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct DecoratedIcon {
     icon: Icon,
     decoration: Option<IconDecoration>,
@@ -24,9 +24,18 @@ impl RenderOnce for DecoratedIcon {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for DecoratedIcon {
-    fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
+impl Component for DecoratedIcon {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "An icon with an optional decoration overlay (like an X, triangle, or dot) that can be positioned relative to the icon",
+        )
+    }
+
+    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
         let decoration_x = IconDecoration::new(
             IconDecorationKind::X,
             cx.theme().colors().surface_background,
@@ -60,32 +69,38 @@ impl ComponentPreview for DecoratedIcon {
             y: px(-2.),
         });
 
-        v_flex()
-            .gap_6()
-            .children(vec![example_group_with_title(
-                "Decorations",
-                vec![
-                    single_example(
-                        "No Decoration",
-                        DecoratedIcon::new(Icon::new(IconName::FileDoc), None).into_any_element(),
-                    ),
-                    single_example(
-                        "X Decoration",
-                        DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
-                            .into_any_element(),
-                    ),
-                    single_example(
-                        "Triangle Decoration",
-                        DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_triangle))
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![example_group_with_title(
+                    "Decorations",
+                    vec![
+                        single_example(
+                            "No Decoration",
+                            DecoratedIcon::new(Icon::new(IconName::FileDoc), None)
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "X Decoration",
+                            DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Triangle Decoration",
+                            DecoratedIcon::new(
+                                Icon::new(IconName::FileDoc),
+                                Some(decoration_triangle),
+                            )
                             .into_any_element(),
-                    ),
-                    single_example(
-                        "Dot Decoration",
-                        DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_dot))
-                            .into_any_element(),
-                    ),
-                ],
-            )])
-            .into_any_element()
+                        ),
+                        single_example(
+                            "Dot Decoration",
+                            DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_dot))
+                                .into_any_element(),
+                        ),
+                    ],
+                )])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/image.rs 🔗

@@ -4,6 +4,7 @@ use strum::{EnumIter, EnumString, IntoStaticStr};
 use ui_macros::{DerivePathStr, path_str};
 
 use crate::Color;
+use crate::prelude::*;
 
 #[derive(
     Debug,
@@ -30,7 +31,7 @@ pub enum VectorName {
 /// A [`Vector`] is different from an [`crate::Icon`] in that it is intended
 /// to be displayed at a specific size, or series of sizes, rather
 /// than conforming to the standard size of an icon.
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Vector {
     path: &'static str,
     color: Color,
@@ -61,7 +62,6 @@ impl Vector {
     /// Sets the vector size.
     pub fn size(mut self, size: impl Into<Size<Rems>>) -> Self {
         let size = size.into();
-
         self.size = size;
         self
     }
@@ -83,24 +83,72 @@ impl RenderOnce for Vector {
     }
 }
 
-#[cfg(feature = "stories")]
-pub mod story {
-    use gpui::Render;
-    use story::{Story, StoryItem, StorySection};
-    use strum::IntoEnumIterator;
-
-    use crate::prelude::*;
+impl Component for Vector {
+    fn scope() -> ComponentScope {
+        ComponentScope::Images
+    }
 
-    use super::{Vector, VectorName};
+    fn name() -> &'static str {
+        "Vector"
+    }
 
-    pub struct VectorStory;
+    fn description() -> Option<&'static str> {
+        Some("A vector image component that can be displayed at specific sizes.")
+    }
 
-    impl Render for VectorStory {
-        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-            Story::container().child(StorySection::new().children(VectorName::iter().map(
-                |vector| StoryItem::new(format!("{:?}", vector), Vector::square(vector, rems(8.))),
-            )))
-        }
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Default",
+                                Vector::square(VectorName::ZedLogo, rems(8.)).into_any_element(),
+                            ),
+                            single_example(
+                                "Custom Size",
+                                Vector::new(VectorName::ZedLogo, rems(12.), rems(6.))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Colored",
+                        vec![
+                            single_example(
+                                "Accent Color",
+                                Vector::square(VectorName::ZedLogo, rems(8.))
+                                    .color(Color::Accent)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Error Color",
+                                Vector::square(VectorName::ZedLogo, rems(8.))
+                                    .color(Color::Error)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Different Vectors",
+                        vec![
+                            single_example(
+                                "Zed Logo",
+                                Vector::square(VectorName::ZedLogo, rems(8.)).into_any_element(),
+                            ),
+                            single_example(
+                                "Zed X Copilot",
+                                Vector::square(VectorName::ZedXCopilot, rems(8.))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }
 

crates/ui/src/components/indicator.rs 🔗

@@ -1,4 +1,5 @@
-use crate::{AnyIcon, prelude::*};
+use super::AnyIcon;
+use crate::prelude::*;
 
 #[derive(Default)]
 enum IndicatorKind {
@@ -8,7 +9,7 @@ enum IndicatorKind {
     Icon(AnyIcon),
 }
 
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Indicator {
     kind: IndicatorKind,
     border_color: Option<Color>,
@@ -82,3 +83,95 @@ impl RenderOnce for Indicator {
         }
     }
 }
+
+impl Component for Indicator {
+    fn scope() -> ComponentScope {
+        ComponentScope::Status
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "Visual indicators used to represent status, notifications, or draw attention to specific elements.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Dot Indicators",
+                        vec![
+                            single_example("Default", Indicator::dot().into_any_element()),
+                            single_example(
+                                "Success",
+                                Indicator::dot().color(Color::Success).into_any_element(),
+                            ),
+                            single_example(
+                                "Warning",
+                                Indicator::dot().color(Color::Warning).into_any_element(),
+                            ),
+                            single_example(
+                                "Error",
+                                Indicator::dot().color(Color::Error).into_any_element(),
+                            ),
+                            single_example(
+                                "With Border",
+                                Indicator::dot()
+                                    .color(Color::Accent)
+                                    .border_color(Color::Default)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Bar Indicators",
+                        vec![
+                            single_example("Default", Indicator::bar().into_any_element()),
+                            single_example(
+                                "Success",
+                                Indicator::bar().color(Color::Success).into_any_element(),
+                            ),
+                            single_example(
+                                "Warning",
+                                Indicator::bar().color(Color::Warning).into_any_element(),
+                            ),
+                            single_example(
+                                "Error",
+                                Indicator::bar().color(Color::Error).into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Icon Indicators",
+                        vec![
+                            single_example(
+                                "Default",
+                                Indicator::icon(Icon::new(IconName::Circle)).into_any_element(),
+                            ),
+                            single_example(
+                                "Success",
+                                Indicator::icon(Icon::new(IconName::Check))
+                                    .color(Color::Success)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Warning",
+                                Indicator::icon(Icon::new(IconName::Warning))
+                                    .color(Color::Warning)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Error",
+                                Indicator::icon(Icon::new(IconName::X))
+                                    .color(Color::Error)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/keybinding.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
 };
 use itertools::Itertools;
 
-#[derive(Debug, IntoElement, Clone)]
+#[derive(Debug, IntoElement, Clone, RegisterComponent)]
 pub struct KeyBinding {
     /// A keybinding consists of a key and a set of modifier keys.
     /// More then one keybinding produces a chord.
@@ -449,6 +449,93 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
     text
 }
 
+impl Component for KeyBinding {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn name() -> &'static str {
+        "KeyBinding"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "A component that displays a key binding, supporting different platform styles and vim mode.",
+        )
+    }
+
+    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Default",
+                                KeyBinding::new(
+                                    gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
+                                    cx,
+                                )
+                                .into_any_element(),
+                            ),
+                            single_example(
+                                "Mac Style",
+                                KeyBinding::new(
+                                    gpui::KeyBinding::new("cmd-s", gpui::NoAction, None),
+                                    cx,
+                                )
+                                .platform_style(PlatformStyle::Mac)
+                                .into_any_element(),
+                            ),
+                            single_example(
+                                "Windows Style",
+                                KeyBinding::new(
+                                    gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
+                                    cx,
+                                )
+                                .platform_style(PlatformStyle::Windows)
+                                .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Vim Mode",
+                        vec![single_example(
+                            "Vim Mode Enabled",
+                            KeyBinding::new(gpui::KeyBinding::new("dd", gpui::NoAction, None), cx)
+                                .vim_mode(true)
+                                .into_any_element(),
+                        )],
+                    ),
+                    example_group_with_title(
+                        "Complex Bindings",
+                        vec![
+                            single_example(
+                                "Multiple Keys",
+                                KeyBinding::new(
+                                    gpui::KeyBinding::new("ctrl-k ctrl-b", gpui::NoAction, None),
+                                    cx,
+                                )
+                                .into_any_element(),
+                            ),
+                            single_example(
+                                "With Shift",
+                                KeyBinding::new(
+                                    gpui::KeyBinding::new("shift-cmd-p", gpui::NoAction, None),
+                                    cx,
+                                )
+                                .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

crates/ui/src/components/keybinding_hint.rs 🔗

@@ -18,7 +18,7 @@ use theme::Appearance;
 ///     .prefix("Save:")
 ///     .size(Pixels::from(14.0));
 /// ```
-#[derive(Debug, IntoElement, IntoComponent)]
+#[derive(Debug, IntoElement, RegisterComponent)]
 pub struct KeybindingHint {
     prefix: Option<SharedString>,
     suffix: Option<SharedString>,
@@ -205,68 +205,81 @@ impl RenderOnce for KeybindingHint {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for KeybindingHint {
-    fn preview(window: &mut Window, cx: &mut App) -> AnyElement {
+impl Component for KeybindingHint {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("Displays a keyboard shortcut hint with optional prefix and suffix text")
+    }
+
+    fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
         let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
         let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
             .unwrap_or(KeyBinding::new(enter_fallback, cx));
 
         let bg_color = cx.theme().colors().surface_background;
 
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Basic",
-                    vec![
-                        single_example(
-                            "With Prefix",
-                            KeybindingHint::with_prefix("Go to Start:", enter.clone(), bg_color)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "With Suffix",
-                            KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "With Prefix and Suffix",
-                            KeybindingHint::new(enter.clone(), bg_color)
-                                .prefix("Confirm:")
-                                .suffix("Execute selected action")
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Sizes",
-                    vec![
-                        single_example(
-                            "Small",
-                            KeybindingHint::new(enter.clone(), bg_color)
-                                .size(Pixels::from(12.0))
-                                .prefix("Small:")
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Medium",
-                            KeybindingHint::new(enter.clone(), bg_color)
-                                .size(Pixels::from(16.0))
-                                .suffix("Medium")
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Large",
-                            KeybindingHint::new(enter.clone(), bg_color)
-                                .size(Pixels::from(20.0))
-                                .prefix("Large:")
-                                .suffix("Size")
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic",
+                        vec![
+                            single_example(
+                                "With Prefix",
+                                KeybindingHint::with_prefix(
+                                    "Go to Start:",
+                                    enter.clone(),
+                                    bg_color,
+                                )
                                 .into_any_element(),
-                        ),
-                    ],
-                ),
-            ])
-            .into_any_element()
+                            ),
+                            single_example(
+                                "With Suffix",
+                                KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "With Prefix and Suffix",
+                                KeybindingHint::new(enter.clone(), bg_color)
+                                    .prefix("Confirm:")
+                                    .suffix("Execute selected action")
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Sizes",
+                        vec![
+                            single_example(
+                                "Small",
+                                KeybindingHint::new(enter.clone(), bg_color)
+                                    .size(Pixels::from(12.0))
+                                    .prefix("Small:")
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Medium",
+                                KeybindingHint::new(enter.clone(), bg_color)
+                                    .size(Pixels::from(16.0))
+                                    .suffix("Medium")
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Large",
+                                KeybindingHint::new(enter.clone(), bg_color)
+                                    .size(Pixels::from(20.0))
+                                    .prefix("Large:")
+                                    .suffix("Size")
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/label/highlighted_label.rs 🔗

@@ -4,7 +4,7 @@ use gpui::{FontWeight, HighlightStyle, StyledText};
 
 use crate::{LabelCommon, LabelLike, LabelSize, LineHeightStyle, prelude::*};
 
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct HighlightedLabel {
     base: LabelLike,
     label: SharedString,
@@ -129,3 +129,99 @@ impl RenderOnce for HighlightedLabel {
             .child(StyledText::new(self.label).with_default_highlights(&text_style, highlights))
     }
 }
+
+impl Component for HighlightedLabel {
+    fn scope() -> ComponentScope {
+        ComponentScope::Typography
+    }
+
+    fn name() -> &'static str {
+        "HighlightedLabel"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A label with highlighted characters based on specified indices.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Default",
+                                HighlightedLabel::new("Highlighted Text", vec![0, 1, 2, 3]).into_any_element(),
+                            ),
+                            single_example(
+                                "Custom Color",
+                                HighlightedLabel::new("Colored Highlight", vec![0, 1, 7, 8, 9])
+                                    .color(Color::Accent)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Styles",
+                        vec![
+                            single_example(
+                                "Bold",
+                                HighlightedLabel::new("Bold Highlight", vec![0, 1, 2, 3])
+                                    .weight(FontWeight::BOLD)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Italic",
+                                HighlightedLabel::new("Italic Highlight", vec![0, 1, 6, 7, 8])
+                                    .italic()
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Underline",
+                                HighlightedLabel::new("Underlined Highlight", vec![0, 1, 10, 11, 12])
+                                    .underline()
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Sizes",
+                        vec![
+                            single_example(
+                                "Small",
+                                HighlightedLabel::new("Small Highlight", vec![0, 1, 5, 6, 7])
+                                    .size(LabelSize::Small)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Large",
+                                HighlightedLabel::new("Large Highlight", vec![0, 1, 5, 6, 7])
+                                    .size(LabelSize::Large)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Special Cases",
+                        vec![
+                            single_example(
+                                "Single Line",
+                                HighlightedLabel::new("Single Line Highlight\nWith Newline", vec![0, 1, 7, 8, 9])
+                                    .single_line()
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Truncate",
+                                HighlightedLabel::new("This is a very long text that should be truncated with highlights", vec![0, 1, 2, 3, 4, 5])
+                                    .truncate()
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                ])
+                .into_any_element()
+        )
+    }
+}

crates/ui/src/components/label/label.rs 🔗

@@ -29,7 +29,7 @@ use gpui::StyleRefinement;
 ///
 /// let my_label = Label::new("Deleted").strikethrough(true);
 /// ```
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Label {
     base: LabelLike,
     label: SharedString,
@@ -58,9 +58,6 @@ impl Label {
     }
 }
 
-// nate: If we are going to do this, we might as well just
-// impl Styled for Label and not constrain styles
-
 // Style methods.
 impl Label {
     fn style(&mut self) -> &mut StyleRefinement {
@@ -200,12 +197,17 @@ impl RenderOnce for Label {
     }
 }
 
-mod label_preview {
-    use crate::prelude::*;
+impl Component for Label {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
 
-    // View this component preview using `workspace: open component-preview`
-    impl ComponentPreview for Label {
-        fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+    fn description() -> Option<&'static str> {
+        Some("A text label component that supports various styles, sizes, and formatting options.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
             v_flex()
                 .gap_6()
                 .children(vec![
@@ -251,6 +253,6 @@ mod label_preview {
                     ),
                 ])
                 .into_any_element()
-        }
+        )
     }
 }

crates/ui/src/components/label/label_like.rs 🔗

@@ -232,3 +232,70 @@ impl RenderOnce for LabelLike {
             .children(self.children)
     }
 }
+
+impl Component for LabelLike {
+    fn scope() -> ComponentScope {
+        ComponentScope::Typography
+    }
+
+    fn name() -> &'static str {
+        "LabelLike"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "A flexible, customizable label-like component that serves as a base for other label types.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Sizes",
+                        vec![
+                            single_example("Default", LabelLike::new().child("Default size").into_any_element()),
+                            single_example("Large", LabelLike::new().size(LabelSize::Large).child("Large size").into_any_element()),
+                            single_example("Small", LabelLike::new().size(LabelSize::Small).child("Small size").into_any_element()),
+                            single_example("XSmall", LabelLike::new().size(LabelSize::XSmall).child("Extra small size").into_any_element()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Styles",
+                        vec![
+                            single_example("Bold", LabelLike::new().weight(FontWeight::BOLD).child("Bold text").into_any_element()),
+                            single_example("Italic", LabelLike::new().italic().child("Italic text").into_any_element()),
+                            single_example("Underline", LabelLike::new().underline().child("Underlined text").into_any_element()),
+                            single_example("Strikethrough", LabelLike::new().strikethrough().child("Strikethrough text").into_any_element()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Colors",
+                        vec![
+                            single_example("Default", LabelLike::new().child("Default color").into_any_element()),
+                            single_example("Accent", LabelLike::new().color(Color::Accent).child("Accent color").into_any_element()),
+                            single_example("Error", LabelLike::new().color(Color::Error).child("Error color").into_any_element()),
+                            single_example("Alpha", LabelLike::new().alpha(0.5).child("50% opacity").into_any_element()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Line Height",
+                        vec![
+                            single_example("Default", LabelLike::new().child("Default line height\nMulti-line text").into_any_element()),
+                            single_example("UI Label", LabelLike::new().line_height_style(LineHeightStyle::UiLabel).child("UI label line height\nMulti-line text").into_any_element()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Special Cases",
+                        vec![
+                            single_example("Single Line", LabelLike::new().single_line().child("This is a very long text that should be displayed in a single line").into_any_element()),
+                            single_example("Truncate", LabelLike::new().truncate().child("This is a very long text that should be truncated with an ellipsis").into_any_element()),
+                        ],
+                    ),
+                ])
+                .into_any_element()
+        )
+    }
+}

crates/ui/src/components/notification/alert_modal.rs 🔗

@@ -2,8 +2,7 @@ use crate::prelude::*;
 use gpui::IntoElement;
 use smallvec::{SmallVec, smallvec};
 
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Notification")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct AlertModal {
     id: ElementId,
     children: SmallVec<[AnyElement; 2]>,
@@ -77,23 +76,33 @@ impl ParentElement for AlertModal {
     }
 }
 
-impl ComponentPreview for AlertModal {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .p_4()
-            .children(vec![example_group(
-                vec![
-                    single_example(
-                        "Basic Alert",
-                        AlertModal::new("simple-modal", "Do you want to leave the current call?")
-                            .child("The current window will be closed, and connections to any shared projects will be terminated."
-                            )
-                            .primary_action("Leave Call")
-                            .into_any_element(),
-                    )
-                ],
-            )])
-            .into_any_element()
+impl Component for AlertModal {
+    fn scope() -> ComponentScope {
+        ComponentScope::Notification
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A modal dialog that presents an alert message with primary and dismiss actions.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .p_4()
+                .children(vec![example_group(
+                    vec![
+                        single_example(
+                            "Basic Alert",
+                            AlertModal::new("simple-modal", "Do you want to leave the current call?")
+                                .child("The current window will be closed, and connections to any shared projects will be terminated."
+                                )
+                                .primary_action("Leave Call")
+                                .into_any_element(),
+                        )
+                    ],
+                )])
+                .into_any_element()
+        )
     }
 }

crates/ui/src/components/settings_container.rs 🔗

@@ -3,7 +3,9 @@ use smallvec::SmallVec;
 
 use crate::prelude::*;
 
-#[derive(IntoElement)]
+use super::Checkbox;
+
+#[derive(IntoElement, RegisterComponent)]
 pub struct SettingsContainer {
     children: SmallVec<[AnyElement; 2]>,
 }
@@ -33,3 +35,55 @@ impl RenderOnce for SettingsContainer {
         v_flex().px_2().gap_1().children(self.children)
     }
 }
+
+impl Component for SettingsContainer {
+    fn scope() -> ComponentScope {
+        ComponentScope::Layout
+    }
+
+    fn name() -> &'static str {
+        "SettingsContainer"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A container for organizing and displaying settings in a structured manner.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Empty Container",
+                                SettingsContainer::new().into_any_element(),
+                            ),
+                            single_example(
+                                "With Content",
+                                SettingsContainer::new()
+                                    .child(Label::new("Setting 1"))
+                                    .child(Label::new("Setting 2"))
+                                    .child(Label::new("Setting 3"))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "With Different Elements",
+                        vec![single_example(
+                            "Mixed Content",
+                            SettingsContainer::new()
+                                .child(Label::new("Text Setting"))
+                                .child(Checkbox::new("checkbox", ToggleState::Unselected))
+                                .child(Button::new("button", "Click me"))
+                                .into_any_element(),
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/settings_group.rs 🔗

@@ -3,8 +3,10 @@ use smallvec::SmallVec;
 
 use crate::{ListHeader, prelude::*};
 
+use super::Checkbox;
+
 /// A group of settings.
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct SettingsGroup {
     header: SharedString,
     children: SmallVec<[AnyElement; 2]>,
@@ -34,3 +36,75 @@ impl RenderOnce for SettingsGroup {
             .children(self.children)
     }
 }
+
+impl Component for SettingsGroup {
+    fn scope() -> ComponentScope {
+        ComponentScope::Layout
+    }
+
+    fn name() -> &'static str {
+        "SettingsGroup"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A group of settings with a header, used to organize related settings.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Empty Group",
+                                SettingsGroup::new("General Settings").into_any_element(),
+                            ),
+                            single_example(
+                                "With Children",
+                                SettingsGroup::new("Appearance")
+                                    .child(
+                                        Checkbox::new("dark_mode", ToggleState::Unselected)
+                                            .label("Dark Mode"),
+                                    )
+                                    .child(
+                                        Checkbox::new("high_contrast", ToggleState::Unselected)
+                                            .label("High Contrast"),
+                                    )
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Multiple Groups",
+                        vec![single_example(
+                            "Two Groups",
+                            v_flex()
+                                .gap_4()
+                                .child(
+                                    SettingsGroup::new("General").child(
+                                        Checkbox::new("auto_update", ToggleState::Selected)
+                                            .label("Auto Update"),
+                                    ),
+                                )
+                                .child(
+                                    SettingsGroup::new("Editor")
+                                        .child(
+                                            Checkbox::new("line_numbers", ToggleState::Selected)
+                                                .label("Show Line Numbers"),
+                                        )
+                                        .child(
+                                            Checkbox::new("word_wrap", ToggleState::Unselected)
+                                                .label("Word Wrap"),
+                                        ),
+                                )
+                                .into_any_element(),
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/stories.rs 🔗

@@ -1,6 +1,4 @@
 mod context_menu;
-mod disclosure;
-mod icon;
 mod icon_button;
 mod keybinding;
 mod list;
@@ -11,8 +9,6 @@ mod tab_bar;
 mod toggle_button;
 
 pub use context_menu::*;
-pub use disclosure::*;
-pub use icon::*;
 pub use icon_button::*;
 pub use keybinding::*;
 pub use list::*;

crates/ui/src/components/stories/icon.rs 🔗

@@ -1,20 +0,0 @@
-use gpui::Render;
-use story::Story;
-use strum::IntoEnumIterator;
-
-use crate::prelude::*;
-use crate::{Icon, IconName};
-
-pub struct IconStory;
-
-impl Render for IconStory {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        let icons = IconName::iter();
-
-        Story::container()
-            .child(Story::title_for::<Icon>())
-            .child(Story::label("DecoratedIcon"))
-            .child(Story::label("All Icons"))
-            .child(div().flex().gap_3().children(icons.map(Icon::new)))
-    }
-}

crates/ui/src/components/tab.rs 🔗

@@ -26,7 +26,7 @@ pub enum TabCloseSide {
     End,
 }
 
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Tab {
     div: Stateful<Div>,
     selected: bool,
@@ -171,48 +171,59 @@ impl RenderOnce for Tab {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Tab {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![example_group_with_title(
-                "Variations",
-                vec![
-                    single_example(
-                        "Default",
-                        Tab::new("default").child("Default Tab").into_any_element(),
-                    ),
-                    single_example(
-                        "Selected",
-                        Tab::new("selected")
-                            .toggle_state(true)
-                            .child("Selected Tab")
-                            .into_any_element(),
-                    ),
-                    single_example(
-                        "First",
-                        Tab::new("first")
-                            .position(TabPosition::First)
-                            .child("First Tab")
-                            .into_any_element(),
-                    ),
-                    single_example(
-                        "Middle",
-                        Tab::new("middle")
-                            .position(TabPosition::Middle(Ordering::Equal))
-                            .child("Middle Tab")
-                            .into_any_element(),
-                    ),
-                    single_example(
-                        "Last",
-                        Tab::new("last")
-                            .position(TabPosition::Last)
-                            .child("Last Tab")
-                            .into_any_element(),
-                    ),
-                ],
-            )])
-            .into_any_element()
+impl Component for Tab {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "A tab component that can be used in a tabbed interface, supporting different positions and states.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![example_group_with_title(
+                    "Variations",
+                    vec![
+                        single_example(
+                            "Default",
+                            Tab::new("default").child("Default Tab").into_any_element(),
+                        ),
+                        single_example(
+                            "Selected",
+                            Tab::new("selected")
+                                .toggle_state(true)
+                                .child("Selected Tab")
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "First",
+                            Tab::new("first")
+                                .position(TabPosition::First)
+                                .child("First Tab")
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Middle",
+                            Tab::new("middle")
+                                .position(TabPosition::Middle(Ordering::Equal))
+                                .child("Middle Tab")
+                                .into_any_element(),
+                        ),
+                        single_example(
+                            "Last",
+                            Tab::new("last")
+                                .position(TabPosition::Last)
+                                .child("Last Tab")
+                                .into_any_element(),
+                        ),
+                    ],
+                )])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/tab_bar.rs 🔗

@@ -4,7 +4,7 @@ use smallvec::SmallVec;
 use crate::Tab;
 use crate::prelude::*;
 
-#[derive(IntoElement)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct TabBar {
     id: ElementId,
     start_children: SmallVec<[AnyElement; 2]>,
@@ -151,3 +151,57 @@ impl RenderOnce for TabBar {
             })
     }
 }
+
+impl Component for TabBar {
+    fn scope() -> ComponentScope {
+        ComponentScope::Navigation
+    }
+
+    fn name() -> &'static str {
+        "TabBar"
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A horizontal bar containing tabs for navigation between different views or sections.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Usage",
+                        vec![
+                            single_example(
+                                "Empty TabBar",
+                                TabBar::new("empty_tab_bar").into_any_element(),
+                            ),
+                            single_example(
+                                "With Tabs",
+                                TabBar::new("tab_bar_with_tabs")
+                                    .child(Tab::new("tab1"))
+                                    .child(Tab::new("tab2"))
+                                    .child(Tab::new("tab3"))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "With Start and End Children",
+                        vec![single_example(
+                            "Full TabBar",
+                            TabBar::new("full_tab_bar")
+                                .start_child(Button::new("start_button", "Start"))
+                                .child(Tab::new("tab1"))
+                                .child(Tab::new("tab2"))
+                                .child(Tab::new("tab3"))
+                                .end_child(Button::new("end_button", "End"))
+                                .into_any_element(),
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/components/table.rs 🔗

@@ -2,7 +2,7 @@ use crate::{Indicator, prelude::*};
 use gpui::{AnyElement, FontWeight, IntoElement, Length, div};
 
 /// A table component
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Table {
     column_headers: Vec<SharedString>,
     rows: Vec<Vec<TableCell>>,
@@ -151,112 +151,121 @@ where
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Table {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Basic Tables",
-                    vec![
-                        single_example(
-                            "Simple Table",
-                            Table::new(vec!["Name", "Age", "City"])
-                                .width(px(400.))
-                                .row(vec!["Alice", "28", "New York"])
-                                .row(vec!["Bob", "32", "San Francisco"])
-                                .row(vec!["Charlie", "25", "London"])
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Two Column Table",
-                            Table::new(vec!["Category", "Value"])
-                                .width(px(300.))
-                                .row(vec!["Revenue", "$100,000"])
-                                .row(vec!["Expenses", "$75,000"])
-                                .row(vec!["Profit", "$25,000"])
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Styled Tables",
-                    vec![
-                        single_example(
-                            "Default",
-                            Table::new(vec!["Product", "Price", "Stock"])
-                                .width(px(400.))
-                                .row(vec!["Laptop", "$999", "In Stock"])
-                                .row(vec!["Phone", "$599", "Low Stock"])
-                                .row(vec!["Tablet", "$399", "Out of Stock"])
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Striped",
-                            Table::new(vec!["Product", "Price", "Stock"])
-                                .width(px(400.))
-                                .striped()
-                                .row(vec!["Laptop", "$999", "In Stock"])
-                                .row(vec!["Phone", "$599", "Low Stock"])
-                                .row(vec!["Tablet", "$399", "Out of Stock"])
-                                .row(vec!["Headphones", "$199", "In Stock"])
+impl Component for Table {
+    fn scope() -> ComponentScope {
+        ComponentScope::Layout
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A table component for displaying data in rows and columns with optional styling.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Basic Tables",
+                        vec![
+                            single_example(
+                                "Simple Table",
+                                Table::new(vec!["Name", "Age", "City"])
+                                    .width(px(400.))
+                                    .row(vec!["Alice", "28", "New York"])
+                                    .row(vec!["Bob", "32", "San Francisco"])
+                                    .row(vec!["Charlie", "25", "London"])
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Two Column Table",
+                                Table::new(vec!["Category", "Value"])
+                                    .width(px(300.))
+                                    .row(vec!["Revenue", "$100,000"])
+                                    .row(vec!["Expenses", "$75,000"])
+                                    .row(vec!["Profit", "$25,000"])
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Styled Tables",
+                        vec![
+                            single_example(
+                                "Default",
+                                Table::new(vec!["Product", "Price", "Stock"])
+                                    .width(px(400.))
+                                    .row(vec!["Laptop", "$999", "In Stock"])
+                                    .row(vec!["Phone", "$599", "Low Stock"])
+                                    .row(vec!["Tablet", "$399", "Out of Stock"])
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Striped",
+                                Table::new(vec!["Product", "Price", "Stock"])
+                                    .width(px(400.))
+                                    .striped()
+                                    .row(vec!["Laptop", "$999", "In Stock"])
+                                    .row(vec!["Phone", "$599", "Low Stock"])
+                                    .row(vec!["Tablet", "$399", "Out of Stock"])
+                                    .row(vec!["Headphones", "$199", "In Stock"])
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Mixed Content Table",
+                        vec![single_example(
+                            "Table with Elements",
+                            Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
+                                .width(px(840.))
+                                .row(vec![
+                                    element_cell(
+                                        Indicator::dot().color(Color::Success).into_any_element(),
+                                    ),
+                                    string_cell("Project A"),
+                                    string_cell("High"),
+                                    string_cell("2023-12-31"),
+                                    element_cell(
+                                        Button::new("view_a", "View")
+                                            .style(ButtonStyle::Filled)
+                                            .full_width()
+                                            .into_any_element(),
+                                    ),
+                                ])
+                                .row(vec![
+                                    element_cell(
+                                        Indicator::dot().color(Color::Warning).into_any_element(),
+                                    ),
+                                    string_cell("Project B"),
+                                    string_cell("Medium"),
+                                    string_cell("2024-03-15"),
+                                    element_cell(
+                                        Button::new("view_b", "View")
+                                            .style(ButtonStyle::Filled)
+                                            .full_width()
+                                            .into_any_element(),
+                                    ),
+                                ])
+                                .row(vec![
+                                    element_cell(
+                                        Indicator::dot().color(Color::Error).into_any_element(),
+                                    ),
+                                    string_cell("Project C"),
+                                    string_cell("Low"),
+                                    string_cell("2024-06-30"),
+                                    element_cell(
+                                        Button::new("view_c", "View")
+                                            .style(ButtonStyle::Filled)
+                                            .full_width()
+                                            .into_any_element(),
+                                    ),
+                                ])
                                 .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Mixed Content Table",
-                    vec![single_example(
-                        "Table with Elements",
-                        Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
-                            .width(px(840.))
-                            .row(vec![
-                                element_cell(
-                                    Indicator::dot().color(Color::Success).into_any_element(),
-                                ),
-                                string_cell("Project A"),
-                                string_cell("High"),
-                                string_cell("2023-12-31"),
-                                element_cell(
-                                    Button::new("view_a", "View")
-                                        .style(ButtonStyle::Filled)
-                                        .full_width()
-                                        .into_any_element(),
-                                ),
-                            ])
-                            .row(vec![
-                                element_cell(
-                                    Indicator::dot().color(Color::Warning).into_any_element(),
-                                ),
-                                string_cell("Project B"),
-                                string_cell("Medium"),
-                                string_cell("2024-03-15"),
-                                element_cell(
-                                    Button::new("view_b", "View")
-                                        .style(ButtonStyle::Filled)
-                                        .full_width()
-                                        .into_any_element(),
-                                ),
-                            ])
-                            .row(vec![
-                                element_cell(
-                                    Indicator::dot().color(Color::Error).into_any_element(),
-                                ),
-                                string_cell("Project C"),
-                                string_cell("Low"),
-                                string_cell("2024-06-30"),
-                                element_cell(
-                                    Button::new("view_c", "View")
-                                        .style(ButtonStyle::Filled)
-                                        .full_width()
-                                        .into_any_element(),
-                                ),
-                            ])
-                            .into_any_element(),
-                    )],
-                ),
-            ])
-            .into_any_element()
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/toggle.rs 🔗

@@ -38,8 +38,7 @@ pub enum ToggleStyle {
 /// Checkboxes are used for multiple choices, not for mutually exclusive choices.
 /// Each checkbox works independently from other checkboxes in the list,
 /// therefore checking an additional box does not affect any other selections.
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Input")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Checkbox {
     id: ElementId,
     toggle_state: ToggleState,
@@ -244,8 +243,7 @@ impl RenderOnce for Checkbox {
 }
 
 /// A [`Checkbox`] that has a [`Label`].
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Input")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct CheckboxWithLabel {
     id: ElementId,
     label: Label,
@@ -344,8 +342,7 @@ impl RenderOnce for CheckboxWithLabel {
 /// # Switch
 ///
 /// Switches are used to represent opposite states, such as enabled or disabled.
-#[derive(IntoElement, IntoComponent)]
-#[component(scope = "Input")]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Switch {
     id: ElementId,
     toggle_state: ToggleState,
@@ -479,7 +476,6 @@ impl RenderOnce for Switch {
 
 /// A [`Switch`] that has a [`Label`].
 #[derive(IntoElement)]
-// #[component(scope = "input")]
 pub struct SwitchWithLabel {
     id: ElementId,
     label: Label,
@@ -535,200 +531,232 @@ impl RenderOnce for SwitchWithLabel {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Checkbox {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "States",
-                    vec![
-                        single_example(
-                            "Unselected",
-                            Checkbox::new("checkbox_unselected", ToggleState::Unselected)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Placeholder",
-                            Checkbox::new("checkbox_indeterminate", ToggleState::Selected)
-                                .placeholder(true)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Indeterminate",
-                            Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Selected",
-                            Checkbox::new("checkbox_selected", ToggleState::Selected)
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Styles",
-                    vec![
-                        single_example(
-                            "Default",
-                            Checkbox::new("checkbox_default", ToggleState::Selected)
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Filled",
-                            Checkbox::new("checkbox_filled", ToggleState::Selected)
-                                .fill()
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "ElevationBased",
-                            Checkbox::new("checkbox_elevation", ToggleState::Selected)
-                                .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface))
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "Custom Color",
-                            Checkbox::new("checkbox_custom", ToggleState::Selected)
-                                .style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7)))
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Disabled",
-                    vec![
-                        single_example(
-                            "Unselected",
-                            Checkbox::new("checkbox_disabled_unselected", ToggleState::Unselected)
+impl Component for Checkbox {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A checkbox component that can be used for multiple choice selections")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "States",
+                        vec![
+                            single_example(
+                                "Unselected",
+                                Checkbox::new("checkbox_unselected", ToggleState::Unselected)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Placeholder",
+                                Checkbox::new("checkbox_indeterminate", ToggleState::Selected)
+                                    .placeholder(true)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Indeterminate",
+                                Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Selected",
+                                Checkbox::new("checkbox_selected", ToggleState::Selected)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Styles",
+                        vec![
+                            single_example(
+                                "Default",
+                                Checkbox::new("checkbox_default", ToggleState::Selected)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Filled",
+                                Checkbox::new("checkbox_filled", ToggleState::Selected)
+                                    .fill()
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "ElevationBased",
+                                Checkbox::new("checkbox_elevation", ToggleState::Selected)
+                                    .style(ToggleStyle::ElevationBased(
+                                        ElevationIndex::EditorSurface,
+                                    ))
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Custom Color",
+                                Checkbox::new("checkbox_custom", ToggleState::Selected)
+                                    .style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7)))
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Disabled",
+                        vec![
+                            single_example(
+                                "Unselected",
+                                Checkbox::new(
+                                    "checkbox_disabled_unselected",
+                                    ToggleState::Unselected,
+                                )
                                 .disabled(true)
                                 .into_any_element(),
-                        ),
-                        single_example(
-                            "Selected",
-                            Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
-                                .disabled(true)
+                            ),
+                            single_example(
+                                "Selected",
+                                Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
+                                    .disabled(true)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "With Label",
+                        vec![single_example(
+                            "Default",
+                            Checkbox::new("checkbox_with_label", ToggleState::Selected)
+                                .label("Always save on quit")
                                 .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "With Label",
-                    vec![single_example(
-                        "Default",
-                        Checkbox::new("checkbox_with_label", ToggleState::Selected)
-                            .label("Always save on quit")
-                            .into_any_element(),
-                    )],
-                ),
-            ])
-            .into_any_element()
+                        )],
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Switch {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
+impl Component for Switch {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A switch component that represents binary states like on/off")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "States",
+                        vec![
+                            single_example(
+                                "Off",
+                                Switch::new("switch_off", ToggleState::Unselected)
+                                    .on_click(|_, _, _cx| {})
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "On",
+                                Switch::new("switch_on", ToggleState::Selected)
+                                    .on_click(|_, _, _cx| {})
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Disabled",
+                        vec![
+                            single_example(
+                                "Off",
+                                Switch::new("switch_disabled_off", ToggleState::Unselected)
+                                    .disabled(true)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "On",
+                                Switch::new("switch_disabled_on", ToggleState::Selected)
+                                    .disabled(true)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "With Label",
+                        vec![
+                            single_example(
+                                "Label",
+                                Switch::new("switch_with_label", ToggleState::Selected)
+                                    .label("Always save on quit")
+                                    .into_any_element(),
+                            ),
+                            // TODO: Where did theme_preview_keybinding go?
+                            // single_example(
+                            //     "Keybinding",
+                            //     Switch::new("switch_with_keybinding", ToggleState::Selected)
+                            //         .key_binding(theme_preview_keybinding("cmd-shift-e"))
+                            //         .into_any_element(),
+                            // ),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}
+
+impl Component for CheckboxWithLabel {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A checkbox component with an attached label")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![example_group_with_title(
                     "States",
                     vec![
                         single_example(
-                            "Off",
-                            Switch::new("switch_off", ToggleState::Unselected)
-                                .on_click(|_, _, _cx| {})
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "On",
-                            Switch::new("switch_on", ToggleState::Selected)
-                                .on_click(|_, _, _cx| {})
-                                .into_any_element(),
-                        ),
-                    ],
-                ),
-                example_group_with_title(
-                    "Disabled",
-                    vec![
-                        single_example(
-                            "Off",
-                            Switch::new("switch_disabled_off", ToggleState::Unselected)
-                                .disabled(true)
-                                .into_any_element(),
+                            "Unselected",
+                            CheckboxWithLabel::new(
+                                "checkbox_with_label_unselected",
+                                Label::new("Always save on quit"),
+                                ToggleState::Unselected,
+                                |_, _, _| {},
+                            )
+                            .into_any_element(),
                         ),
                         single_example(
-                            "On",
-                            Switch::new("switch_disabled_on", ToggleState::Selected)
-                                .disabled(true)
-                                .into_any_element(),
+                            "Indeterminate",
+                            CheckboxWithLabel::new(
+                                "checkbox_with_label_indeterminate",
+                                Label::new("Always save on quit"),
+                                ToggleState::Indeterminate,
+                                |_, _, _| {},
+                            )
+                            .into_any_element(),
                         ),
-                    ],
-                ),
-                example_group_with_title(
-                    "With Label",
-                    vec![
                         single_example(
-                            "Label",
-                            Switch::new("switch_with_label", ToggleState::Selected)
-                                .label("Always save on quit")
-                                .into_any_element(),
+                            "Selected",
+                            CheckboxWithLabel::new(
+                                "checkbox_with_label_selected",
+                                Label::new("Always save on quit"),
+                                ToggleState::Selected,
+                                |_, _, _| {},
+                            )
+                            .into_any_element(),
                         ),
-                        // TODO: Where did theme_preview_keybinding go?
-                        // single_example(
-                        //     "Keybinding",
-                        //     Switch::new("switch_with_keybinding", ToggleState::Selected)
-                        //         .key_binding(theme_preview_keybinding("cmd-shift-e"))
-                        //         .into_any_element(),
-                        // ),
                     ],
-                ),
-            ])
-            .into_any_element()
-    }
-}
-
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for CheckboxWithLabel {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_6()
-            .children(vec![example_group_with_title(
-                "States",
-                vec![
-                    single_example(
-                        "Unselected",
-                        CheckboxWithLabel::new(
-                            "checkbox_with_label_unselected",
-                            Label::new("Always save on quit"),
-                            ToggleState::Unselected,
-                            |_, _, _| {},
-                        )
-                        .into_any_element(),
-                    ),
-                    single_example(
-                        "Indeterminate",
-                        CheckboxWithLabel::new(
-                            "checkbox_with_label_indeterminate",
-                            Label::new("Always save on quit"),
-                            ToggleState::Indeterminate,
-                            |_, _, _| {},
-                        )
-                        .into_any_element(),
-                    ),
-                    single_example(
-                        "Selected",
-                        CheckboxWithLabel::new(
-                            "checkbox_with_label_selected",
-                            Label::new("Always save on quit"),
-                            ToggleState::Selected,
-                            |_, _, _| {},
-                        )
-                        .into_any_element(),
-                    ),
-                ],
-            )])
-            .into_any_element()
+                )])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/components/tooltip.rs 🔗

@@ -5,7 +5,7 @@ use theme::ThemeSettings;
 use crate::prelude::*;
 use crate::{Color, KeyBinding, Label, LabelSize, StyledExt, h_flex, v_flex};
 
-#[derive(IntoComponent)]
+#[derive(RegisterComponent)]
 pub struct Tooltip {
     title: SharedString,
     meta: Option<SharedString>,
@@ -222,15 +222,26 @@ impl Render for LinkPreview {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Tooltip {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        example_group(vec![single_example(
-            "Text only",
-            Button::new("delete-example", "Delete")
-                .tooltip(Tooltip::text("This is a tooltip!"))
-                .into_any_element(),
-        )])
-        .into_any_element()
+impl Component for Tooltip {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(
+            "A tooltip that appears when hovering over an element, optionally showing a keybinding or additional metadata.",
+        )
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            example_group(vec![single_example(
+                "Text only",
+                Button::new("delete-example", "Delete")
+                    .tooltip(Tooltip::text("This is a tooltip!"))
+                    .into_any_element(),
+            )])
+            .into_any_element(),
+        )
     }
 }

crates/ui/src/prelude.rs 🔗

@@ -8,9 +8,9 @@ pub use gpui::{
 };
 
 pub use component::{
-    ComponentPreview, ComponentScope, example_group, example_group_with_title, single_example,
+    Component, ComponentScope, example_group, example_group_with_title, single_example,
 };
-pub use ui_macros::IntoComponent;
+pub use ui_macros::RegisterComponent;
 
 pub use crate::DynamicSpacing;
 pub use crate::animation::{AnimationDirection, AnimationDuration, DefaultAnimations};

crates/ui/src/styles/animation.rs 🔗

@@ -94,183 +94,192 @@ pub trait DefaultAnimations: Styled + Sized {
 impl<E: Styled> DefaultAnimations for E {}
 
 // Don't use this directly, it only exists to show animation previews
-#[derive(IntoComponent)]
+#[derive(RegisterComponent)]
 struct Animation {}
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Animation {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
+impl Component for Animation {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("Demonstrates various animation patterns and transitions available in the UI system.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
         let container_size = 128.0;
         let element_size = 32.0;
         let left_offset = element_size - container_size / 2.0;
-        v_flex()
-            .gap_6()
-            .children(vec![
-                example_group_with_title(
-                    "Animate In",
-                    vec![
-                        single_example(
-                            "From Bottom",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("animate-in-from-bottom")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::red())
-                                        .animate_in(AnimationDirection::FromBottom, false),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "From Top",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("animate-in-from-top")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::blue())
-                                        .animate_in(AnimationDirection::FromTop, false),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "From Left",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("animate-in-from-left")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::green())
-                                        .animate_in(AnimationDirection::FromLeft, false),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "From Right",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("animate-in-from-right")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::yellow())
-                                        .animate_in(AnimationDirection::FromRight, false),
-                                )
-                                .into_any_element(),
-                        ),
-                    ],
-                )
-                .grow(),
-                example_group_with_title(
-                    "Fade and Animate In",
-                    vec![
-                        single_example(
-                            "From Bottom",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("fade-animate-in-from-bottom")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::red())
-                                        .animate_in(AnimationDirection::FromBottom, true),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "From Top",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("fade-animate-in-from-top")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::blue())
-                                        .animate_in(AnimationDirection::FromTop, true),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "From Left",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("fade-animate-in-from-left")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::green())
-                                        .animate_in(AnimationDirection::FromLeft, true),
-                                )
-                                .into_any_element(),
-                        ),
-                        single_example(
-                            "From Right",
-                            ContentGroup::new()
-                                .relative()
-                                .items_center()
-                                .justify_center()
-                                .size(px(container_size))
-                                .child(
-                                    div()
-                                        .id("fade-animate-in-from-right")
-                                        .absolute()
-                                        .size(px(element_size))
-                                        .left(px(left_offset))
-                                        .rounded_md()
-                                        .bg(gpui::yellow())
-                                        .animate_in(AnimationDirection::FromRight, true),
-                                )
-                                .into_any_element(),
-                        ),
-                    ],
-                )
-                .grow(),
-            ])
-            .into_any_element()
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Animate In",
+                        vec![
+                            single_example(
+                                "From Bottom",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("animate-in-from-bottom")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::red())
+                                            .animate_in(AnimationDirection::FromBottom, false),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "From Top",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("animate-in-from-top")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::blue())
+                                            .animate_in(AnimationDirection::FromTop, false),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "From Left",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("animate-in-from-left")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::green())
+                                            .animate_in(AnimationDirection::FromLeft, false),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "From Right",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("animate-in-from-right")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::yellow())
+                                            .animate_in(AnimationDirection::FromRight, false),
+                                    )
+                                    .into_any_element(),
+                            ),
+                        ],
+                    )
+                    .grow(),
+                    example_group_with_title(
+                        "Fade and Animate In",
+                        vec![
+                            single_example(
+                                "From Bottom",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("fade-animate-in-from-bottom")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::red())
+                                            .animate_in(AnimationDirection::FromBottom, true),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "From Top",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("fade-animate-in-from-top")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::blue())
+                                            .animate_in(AnimationDirection::FromTop, true),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "From Left",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("fade-animate-in-from-left")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::green())
+                                            .animate_in(AnimationDirection::FromLeft, true),
+                                    )
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "From Right",
+                                ContentGroup::new()
+                                    .relative()
+                                    .items_center()
+                                    .justify_center()
+                                    .size(px(container_size))
+                                    .child(
+                                        div()
+                                            .id("fade-animate-in-from-right")
+                                            .absolute()
+                                            .size(px(element_size))
+                                            .left(px(left_offset))
+                                            .rounded_md()
+                                            .bg(gpui::yellow())
+                                            .animate_in(AnimationDirection::FromRight, true),
+                                    )
+                                    .into_any_element(),
+                            ),
+                        ],
+                    )
+                    .grow(),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/styles/color.rs 🔗

@@ -1,8 +1,21 @@
-use gpui::{App, Hsla};
+use crate::{Label, LabelCommon, component_prelude::*, v_flex};
+use documented::{DocumentedFields, DocumentedVariants};
+use gpui::{App, Hsla, IntoElement, ParentElement, Styled};
 use theme::ActiveTheme;
 
 /// Sets a color that has a consistent meaning across all themes.
-#[derive(Debug, Default, Eq, PartialEq, Copy, Clone)]
+#[derive(
+    Debug,
+    Default,
+    Eq,
+    PartialEq,
+    Copy,
+    Clone,
+    RegisterComponent,
+    Documented,
+    DocumentedFields,
+    DocumentedVariants,
+)]
 pub enum Color {
     #[default]
     /// The default text color. Might be known as "foreground" or "primary" in
@@ -110,3 +123,122 @@ impl From<Hsla> for Color {
         Color::Custom(color)
     }
 }
+
+impl Component for Color {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some(Color::DOCS)
+    }
+
+    fn preview(_window: &mut gpui::Window, _cx: &mut App) -> Option<gpui::AnyElement> {
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![
+                    example_group_with_title(
+                        "Text Colors",
+                        vec![
+                            single_example(
+                                "Default",
+                                Label::new("Default text color")
+                                    .color(Color::Default)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Default.get_variant_docs()),
+                            single_example(
+                                "Muted",
+                                Label::new("Muted text color")
+                                    .color(Color::Muted)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Muted.get_variant_docs()),
+                            single_example(
+                                "Accent",
+                                Label::new("Accent text color")
+                                    .color(Color::Accent)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Accent.get_variant_docs()),
+                            single_example(
+                                "Disabled",
+                                Label::new("Disabled text color")
+                                    .color(Color::Disabled)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Disabled.get_variant_docs()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Status Colors",
+                        vec![
+                            single_example(
+                                "Success",
+                                Label::new("Success status")
+                                    .color(Color::Success)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Success.get_variant_docs()),
+                            single_example(
+                                "Warning",
+                                Label::new("Warning status")
+                                    .color(Color::Warning)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Warning.get_variant_docs()),
+                            single_example(
+                                "Error",
+                                Label::new("Error status")
+                                    .color(Color::Error)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Error.get_variant_docs()),
+                            single_example(
+                                "Info",
+                                Label::new("Info status")
+                                    .color(Color::Info)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Info.get_variant_docs()),
+                        ],
+                    ),
+                    example_group_with_title(
+                        "Version Control Colors",
+                        vec![
+                            single_example(
+                                "Created",
+                                Label::new("Created item")
+                                    .color(Color::Created)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Created.get_variant_docs()),
+                            single_example(
+                                "Modified",
+                                Label::new("Modified item")
+                                    .color(Color::Modified)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Modified.get_variant_docs()),
+                            single_example(
+                                "Deleted",
+                                Label::new("Deleted item")
+                                    .color(Color::Deleted)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Deleted.get_variant_docs()),
+                            single_example(
+                                "Conflict",
+                                Label::new("Conflict item")
+                                    .color(Color::Conflict)
+                                    .into_any_element(),
+                            )
+                            .description(Color::Conflict.get_variant_docs()),
+                        ],
+                    ),
+                ])
+                .into_any_element(),
+        )
+    }
+}

crates/ui/src/styles/typography.rs 🔗

@@ -190,7 +190,7 @@ impl HeadlineSize {
 
 /// A headline element, used to emphasize some text and
 /// create a visual hierarchy.
-#[derive(IntoElement, IntoComponent)]
+#[derive(IntoElement, RegisterComponent)]
 pub struct Headline {
     size: HeadlineSize,
     text: SharedString,
@@ -233,41 +233,50 @@ impl Headline {
     }
 }
 
-// View this component preview using `workspace: open component-preview`
-impl ComponentPreview for Headline {
-    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
-        v_flex()
-            .gap_1()
-            .children(vec![
-                single_example(
-                    "XLarge",
-                    Headline::new("XLarge Headline")
-                        .size(HeadlineSize::XLarge)
-                        .into_any_element(),
-                ),
-                single_example(
-                    "Large",
-                    Headline::new("Large Headline")
-                        .size(HeadlineSize::Large)
-                        .into_any_element(),
-                ),
-                single_example(
-                    "Medium (Default)",
-                    Headline::new("Medium Headline").into_any_element(),
-                ),
-                single_example(
-                    "Small",
-                    Headline::new("Small Headline")
-                        .size(HeadlineSize::Small)
-                        .into_any_element(),
-                ),
-                single_example(
-                    "XSmall",
-                    Headline::new("XSmall Headline")
-                        .size(HeadlineSize::XSmall)
-                        .into_any_element(),
-                ),
-            ])
-            .into_any_element()
+impl Component for Headline {
+    fn scope() -> ComponentScope {
+        ComponentScope::None
+    }
+
+    fn description() -> Option<&'static str> {
+        Some("A headline element used to emphasize text and create visual hierarchy in the UI.")
+    }
+
+    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+        Some(
+            v_flex()
+                .gap_1()
+                .children(vec![
+                    single_example(
+                        "XLarge",
+                        Headline::new("XLarge Headline")
+                            .size(HeadlineSize::XLarge)
+                            .into_any_element(),
+                    ),
+                    single_example(
+                        "Large",
+                        Headline::new("Large Headline")
+                            .size(HeadlineSize::Large)
+                            .into_any_element(),
+                    ),
+                    single_example(
+                        "Medium (Default)",
+                        Headline::new("Medium Headline").into_any_element(),
+                    ),
+                    single_example(
+                        "Small",
+                        Headline::new("Small Headline")
+                            .size(HeadlineSize::Small)
+                            .into_any_element(),
+                    ),
+                    single_example(
+                        "XSmall",
+                        Headline::new("XSmall Headline")
+                            .size(HeadlineSize::XSmall)
+                            .into_any_element(),
+                    ),
+                ])
+                .into_any_element(),
+        )
     }
 }

crates/ui/src/ui.rs 🔗

@@ -7,6 +7,7 @@
 //! - [`ui_macros`] - proc_macros support for this crate
 //! - `ui_input` - the single line input component
 
+pub mod component_prelude;
 mod components;
 pub mod prelude;
 mod styles;

crates/ui_input/src/ui_input.rs 🔗

@@ -5,7 +5,7 @@
 //! It can't be located in the `ui` crate because it depends on `editor`.
 //!
 
-use component::{ComponentPreview, example_group, single_example};
+use component::{example_group, single_example};
 use editor::{Editor, EditorElement, EditorStyle};
 use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle};
 use settings::Settings;
@@ -21,8 +21,7 @@ pub struct SingleLineInputStyle {
 /// A Text Field that can be used to create text fields like search inputs, form fields, etc.
 ///
 /// It wraps a single line [`Editor`] and allows for common field properties like labels, placeholders, icons, etc.
-#[derive(IntoComponent)]
-#[component(scope = "Input")]
+#[derive(RegisterComponent)]
 pub struct SingleLineInput {
     /// An optional label for the text field.
     ///
@@ -168,17 +167,23 @@ impl Render for SingleLineInput {
     }
 }
 
-impl ComponentPreview for SingleLineInput {
-    fn preview(window: &mut Window, cx: &mut App) -> AnyElement {
+impl Component for SingleLineInput {
+    fn scope() -> ComponentScope {
+        ComponentScope::Input
+    }
+
+    fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
         let input_1 =
             cx.new(|cx| SingleLineInput::new(window, cx, "placeholder").label("Some Label"));
 
-        v_flex()
-            .gap_6()
-            .children(vec![example_group(vec![single_example(
-                "Default",
-                div().child(input_1.clone()).into_any_element(),
-            )])])
-            .into_any_element()
+        Some(
+            v_flex()
+                .gap_6()
+                .children(vec![example_group(vec![single_example(
+                    "Default",
+                    div().child(input_1.clone()).into_any_element(),
+                )])])
+                .into_any_element(),
+        )
     }
 }

crates/ui_macros/src/derive_register_component.rs 🔗

@@ -0,0 +1,25 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{DeriveInput, parse_macro_input};
+
+pub fn derive_register_component(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let name = input.ident;
+    let reg_fn_name = syn::Ident::new(
+        &format!("__component_registry_internal_register_{}", name),
+        name.span(),
+    );
+    let expanded = quote! {
+        const _: () = {
+            struct AssertComponent<T: component::Component>(::std::marker::PhantomData<T>);
+            let _ = AssertComponent::<#name>(::std::marker::PhantomData);
+        };
+
+        #[allow(non_snake_case)]
+        #[linkme::distributed_slice(component::__ALL_COMPONENTS)]
+        fn #reg_fn_name() {
+            component::register_component::<#name>();
+        }
+    };
+    expanded.into()
+}

crates/ui_macros/src/ui_macros.rs 🔗

@@ -1,5 +1,5 @@
-mod derive_component;
 mod derive_path_str;
+mod derive_register_component;
 mod dynamic_spacing;
 
 use proc_macro::TokenStream;
@@ -60,26 +60,28 @@ pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
     dynamic_spacing::derive_spacing(input)
 }
 
-/// Derives the `Component` trait for a struct.
+/// Registers components that implement the `Component` trait.
 ///
-/// This macro generates implementations for the `Component` trait and associated
-/// registration functions for the component system.
+/// This proc macro is used to automatically register structs that implement
+/// the `Component` trait with the [`component::ComponentRegistry`].
 ///
-/// # Attributes
-///
-/// - `#[component(scope = "...")]`: Required. Specifies the scope of the component.
-/// - `#[component(description = "...")]`: Optional. Provides a description for the component.
+/// If the component trait is not implemented, it will generate a compile-time error.
 ///
 /// # Example
 ///
 /// ```
-/// use ui_macros::Component;
+/// use ui_macros::RegisterComponent;
+///
+/// #[derive(RegisterComponent)]
+/// struct MyComponent;
 ///
-/// #[derive(Component)]
-/// #[component(scope = "toggle", description = "A element that can be toggled on and off")]
-/// struct Checkbox;
+/// impl Component for MyComponent {
+///     // Component implementation
+/// }
 /// ```
-#[proc_macro_derive(IntoComponent, attributes(component))]
-pub fn derive_component(input: TokenStream) -> TokenStream {
-    derive_component::derive_into_component(input)
+///
+/// This example will add MyComponent to the ComponentRegistry.
+#[proc_macro_derive(RegisterComponent)]
+pub fn derive_register_component(input: TokenStream) -> TokenStream {
+    derive_register_component::derive_register_component(input)
 }

tooling/workspace-hack/Cargo.toml 🔗

@@ -176,6 +176,7 @@ heck = { version = "0.4", features = ["unicode"] }
 hmac = { version = "0.12", default-features = false, features = ["reset"] }
 hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
 indexmap = { version = "2", features = ["serde"] }
+itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" }
 lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
 libc = { version = "0.2", features = ["extra_traits"] }
 libsqlite3-sys = { version = "0.30", features = ["bundled", "unlock_notify"] }
@@ -252,7 +253,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -276,7 +277,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -300,7 +301,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -324,7 +325,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -354,7 +355,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -394,7 +395,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -432,7 +433,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -472,7 +473,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -502,7 +503,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
 ring = { version = "0.17", features = ["std"] }
 rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] }
@@ -522,7 +523,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
 proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
 ring = { version = "0.17", features = ["std"] }
@@ -551,7 +552,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -591,7 +592,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
-itertools = { version = "0.12" }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }