diff --git a/Cargo.lock b/Cargo.lock index fe700aebb2..e9ed24ff14 100644 --- a/Cargo.lock +++ b/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", diff --git a/crates/component/src/component.rs b/crates/component/src/component.rs index 2ce6d9892f..31ed169743 100644 --- a/crates/component/src/component.rs +++ b/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; + fn scope() -> ComponentScope { + ComponentScope::None + } fn name() -> &'static str { std::any::type_name::() } + /// 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 { + None + } } #[distributed_slice] pub static __ALL_COMPONENTS: [fn()] = [..]; -#[distributed_slice] -pub static __ALL_PREVIEWS: [fn()] = [..]; - pub static COMPONENT_DATA: LazyLock> = LazyLock::new(|| RwLock::new(ComponentRegistry::new())); pub struct ComponentRegistry { - components: Vec<(Option, &'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>, } 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() { - let component_data = (T::scope(), T::name(), T::description()); - COMPONENT_DATA.write().components.push(component_data); -} - -pub fn register_preview() { - 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, + sort_name: SharedString, + scope: ComponentScope, description: Option, - preview: Option AnyElement>, + preview: Option Option>, } impl ComponentMetadata { pub fn id(&self) -> ComponentId { self.id.clone() } - pub fn name(&self) -> SharedString { self.name.clone() } - pub fn scope(&self) -> Option { - 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 { self.description.clone() } - - pub fn preview(&self) -> Option AnyElement> { + pub fn preview(&self) -> Option Option> { 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 { let mut previews: Vec = 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 { let mut components: Vec = 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; - 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, - Input, - Notification, - Editor, Collaboration, + DataDisplay, + Editor, + Images, + Input, + Layout, + Loading, + Navigation, + None, + Notification, + 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 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, + 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, 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) -> 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) -> 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, examples: Vec) -> 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, 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) -> ComponentExampleGroup { ComponentExampleGroup::new(examples) } -/// Create a group of examples with a title pub fn example_group_with_title( title: impl Into, examples: Vec, diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs index 48afadd7d8..45eda96caa 100644 --- a/crates/component_preview/src/component_preview.rs +++ b/crates/component_preview/src/component_preview.rs @@ -43,6 +43,7 @@ pub fn init(app_state: Arc, cx: &mut App) { language_registry, user_store, None, + None, cx, ) }); @@ -106,10 +107,12 @@ impl ComponentPreview { language_registry: Arc, user_store: Entity, selected_index: impl Into>, + active_page: Option, cx: &mut Context, ) -> 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 { use std::collections::HashMap; - let mut scope_groups: HashMap, Vec> = - HashMap::default(); + let mut scope_groups: HashMap> = 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, + _window: &mut Window, + _cx: &mut Context, ) -> 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, + component: ComponentMetadata, +} + +impl ComponentPreviewPage { + pub fn new( + component: ComponentMetadata, + // languages: Arc + ) -> 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)) + } +} diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 1abd3e7e44..be9f6e1615 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/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, @@ -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 { 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(), + ) } } diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index 7b5f343fbb..5edceb90fe 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/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 { 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(), + ) } } diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index fac11a5aa3..cc770055f3 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/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 { let unknown_upstream: Option = None; let ahead_of_upstream: Option = 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(), + ) } } } diff --git a/crates/notifications/src/status_toast.rs b/crates/notifications/src/status_toast.rs index e8e7876d99..f4b3f26572 100644 --- a/crates/notifications/src/status_toast.rs +++ b/crates/notifications/src/status_toast.rs @@ -33,8 +33,7 @@ impl From for ToastIcon { } } -#[derive(IntoComponent)] -#[component(scope = "Notification")] +#[derive(RegisterComponent)] pub struct StatusToast { icon: Option, text: SharedString, @@ -135,8 +134,12 @@ impl Focusable for StatusToast { impl EventEmitter 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 { 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(), + ) } } diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 492b06c6ac..fbcd328592 100644 --- a/crates/storybook/src/story_selector.rs +++ b/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(), } } } diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index 6f72674ed0..62549226ab 100644 --- a/crates/ui/Cargo.toml +++ b/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] diff --git a/crates/ui/src/component_prelude.rs b/crates/ui/src/component_prelude.rs new file mode 100644 index 0000000000..3a0a31b290 --- /dev/null +++ b/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; diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index 7bedd3e4d8..ca5f5d0237 100644 --- a/crates/ui/src/components.rs +++ b/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::*; diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index f7badf2ef0..668bdd5285 100644 --- a/crates/ui/src/components/avatar.rs +++ b/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, @@ -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 { 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(), + ) } } diff --git a/crates/ui/src/components/banner.rs b/crates/ui/src/components/banner.rs index f8567b3dc1..ec3d2b06d4 100644 --- a/crates/ui/src/components/banner.rs +++ b/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, @@ -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 { 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(), + ) } } diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 78fc882e37..cae5d0e2ca 100644 --- a/crates/ui/src/components/button/button.rs +++ b/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 { + 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(), + ) } } diff --git a/crates/ui/src/components/button/button_icon.rs b/crates/ui/src/components/button/button_icon.rs index 6c1bab316c..510c418714 100644 --- a/crates/ui/src/components/button/button_icon.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index f44c2e8b98..5c4ac21598 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index fb56980dca..050db6addd 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/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 { + 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(), + ) } } diff --git a/crates/ui/src/components/button/toggle_button.rs b/crates/ui/src/components/button/toggle_button.rs index 990c9c8c84..eca23fe6f7 100644 --- a/crates/ui/src/components/button/toggle_button.rs +++ b/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, @@ -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 { + 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(), + ) } } diff --git a/crates/ui/src/components/content_group.rs b/crates/ui/src/components/content_group.rs index 5b26793e56..f89d38d153 100644 --- a/crates/ui/src/components/content_group.rs +++ b/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 { + 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(), + ) } } diff --git a/crates/ui/src/components/disclosure.rs b/crates/ui/src/components/disclosure.rs index ce307e5cc2..6460d059a1 100644 --- a/crates/ui/src/components/disclosure.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index 6c53970423..e2bb234119 100644 --- a/crates/ui/src/components/divider.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/dropdown_menu.rs b/crates/ui/src/components/dropdown_menu.rs index 923577fe5d..8f191c5431 100644 --- a/crates/ui/src/components/dropdown_menu.rs +++ b/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 { + 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, diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs index 47be6a373d..879bfce041 100644 --- a/crates/ui/src/components/facepile.rs +++ b/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", +]; - v_flex() - .gap_6() - .children(vec![ - example_group_with_title( +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.", + ) + } + + fn preview(_window: &mut Window, _cx: &mut App) -> Option { + 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(), + ) } } diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 24170c1f11..7e353c82e9 100644 --- a/crates/ui/src/components/icon.rs +++ b/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 { + 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(), + ) } } diff --git a/crates/ui/src/components/icon/decorated_icon.rs b/crates/ui/src/components/icon/decorated_icon.rs index 99e20e6f02..e3599caaff 100644 --- a/crates/ui/src/components/icon/decorated_icon.rs +++ b/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, @@ -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 { 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)) + 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( - "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(), + ) } } diff --git a/crates/ui/src/components/image.rs b/crates/ui/src/components/image.rs index e20433a4ae..f22335b9cb 100644 --- a/crates/ui/src/components/image.rs +++ b/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>) -> 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; +impl Component for Vector { + fn scope() -> ComponentScope { + ComponentScope::Images + } - use crate::prelude::*; + fn name() -> &'static str { + "Vector" + } - use super::{Vector, VectorName}; + fn description() -> Option<&'static str> { + Some("A vector image component that can be displayed at specific sizes.") + } - pub struct VectorStory; - - impl Render for VectorStory { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> 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 { + 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(), + ) } } diff --git a/crates/ui/src/components/indicator.rs b/crates/ui/src/components/indicator.rs index 693ba80c9c..d319547bed 100644 --- a/crates/ui/src/components/indicator.rs +++ b/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, @@ -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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 1d3e80c4fd..db9bb3008f 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/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 { + 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::*; diff --git a/crates/ui/src/components/keybinding_hint.rs b/crates/ui/src/components/keybinding_hint.rs index 2423de5188..9cd63d544b 100644 --- a/crates/ui/src/components/keybinding_hint.rs +++ b/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, suffix: Option, @@ -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 { 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) + 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(), - ), - 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() + ), + 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(), + ) } } diff --git a/crates/ui/src/components/label/highlighted_label.rs b/crates/ui/src/components/label/highlighted_label.rs index 6362945091..1a6cd89d15 100644 --- a/crates/ui/src/components/label/highlighted_label.rs +++ b/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 { + 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() + ) + } +} diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 4fb280233a..e8a17aaebe 100644 --- a/crates/ui/src/components/label/label.rs +++ b/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 { + Some( v_flex() .gap_6() .children(vec![ @@ -251,6 +253,6 @@ mod label_preview { ), ]) .into_any_element() - } + ) } } diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index c9179cc923..d0a981ae1c 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/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 { + 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() + ) + } +} diff --git a/crates/ui/src/components/notification/alert_modal.rs b/crates/ui/src/components/notification/alert_modal.rs index 0145b7302a..25fd64135c 100644 --- a/crates/ui/src/components/notification/alert_modal.rs +++ b/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 { + 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() + ) } } diff --git a/crates/ui/src/components/settings_container.rs b/crates/ui/src/components/settings_container.rs index b8a4a021c6..31cb1b32f8 100644 --- a/crates/ui/src/components/settings_container.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/settings_group.rs b/crates/ui/src/components/settings_group.rs index 119727533d..1812a1bec4 100644 --- a/crates/ui/src/components/settings_group.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/stories.rs b/crates/ui/src/components/stories.rs index 69bd571586..05e8cd18d5 100644 --- a/crates/ui/src/components/stories.rs +++ b/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::*; diff --git a/crates/ui/src/components/stories/icon.rs b/crates/ui/src/components/stories/icon.rs deleted file mode 100644 index 402f4023cc..0000000000 --- a/crates/ui/src/components/stories/icon.rs +++ /dev/null @@ -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) -> impl IntoElement { - let icons = IconName::iter(); - - Story::container() - .child(Story::title_for::()) - .child(Story::label("DecoratedIcon")) - .child(Story::label("All Icons")) - .child(div().flex().gap_3().children(icons.map(Icon::new))) - } -} diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 60090f1267..b9b4cb43ce 100644 --- a/crates/ui/src/components/tab.rs +++ b/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
, 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 { + 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(), + ) } } diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 692e0433bc..3c467c06ce 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/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 { + 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(), + ) + } +} diff --git a/crates/ui/src/components/table.rs b/crates/ui/src/components/table.rs index 59bd5c390e..3f1b73e441 100644 --- a/crates/ui/src/components/table.rs +++ b/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, rows: Vec>, @@ -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"]) +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 { + 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(), - ), - 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(), - )], - ), - ]) - .into_any_element() + )], + ), + ]) + .into_any_element(), + ) } } diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index f9c62e3c2c..b9251bff6e 100644 --- a/crates/ui/src/components/toggle.rs +++ b/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( +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 { + 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) + .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(), + ) + } +} + +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 { + 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 { + 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(), + CheckboxWithLabel::new( + "checkbox_with_label_unselected", + Label::new("Always save on quit"), + ToggleState::Unselected, + |_, _, _| {}, + ) + .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) - .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") + CheckboxWithLabel::new( + "checkbox_with_label_indeterminate", + Label::new("Always save on quit"), + ToggleState::Indeterminate, + |_, _, _| {}, + ) .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( - "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(), + "Selected", + CheckboxWithLabel::new( + "checkbox_with_label_selected", + Label::new("Always save on quit"), + ToggleState::Selected, + |_, _, _| {}, + ) + .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() - } -} - -// 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(), + ) } } diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index b962b9167d..d692f45a33 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/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, @@ -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 { + 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(), + ) } } diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 2592e464af..5152751b7b 100644 --- a/crates/ui/src/prelude.rs +++ b/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}; diff --git a/crates/ui/src/styles/animation.rs b/crates/ui/src/styles/animation.rs index a4d5ba145d..50c4e0eb0d 100644 --- a/crates/ui/src/styles/animation.rs +++ b/crates/ui/src/styles/animation.rs @@ -94,183 +94,192 @@ pub trait DefaultAnimations: Styled + Sized { impl 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 { 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(), + ) } } diff --git a/crates/ui/src/styles/color.rs b/crates/ui/src/styles/color.rs index 1cd32f32b0..c7b995d39a 100644 --- a/crates/ui/src/styles/color.rs +++ b/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 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 { + 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(), + ) + } +} diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index c57d157c1a..a1a631deb2 100644 --- a/crates/ui/src/styles/typography.rs +++ b/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 { + 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(), + ) } } diff --git a/crates/ui/src/ui.rs b/crates/ui/src/ui.rs index 148ebda6ea..f0d93e2e27 100644 --- a/crates/ui/src/ui.rs +++ b/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; diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index 3bf2ad4a50..5f6ee8d0d9 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/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 { 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(), + ) } } diff --git a/crates/ui_macros/src/derive_register_component.rs b/crates/ui_macros/src/derive_register_component.rs new file mode 100644 index 0000000000..38f6c5018f --- /dev/null +++ b/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(::std::marker::PhantomData); + 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() +} diff --git a/crates/ui_macros/src/ui_macros.rs b/crates/ui_macros/src/ui_macros.rs index 7898f226b0..aa0b72455a 100644 --- a/crates/ui_macros/src/ui_macros.rs +++ b/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(Component)] -/// #[component(scope = "toggle", description = "A element that can be toggled on and off")] -/// struct Checkbox; +/// #[derive(RegisterComponent)] +/// struct MyComponent; +/// +/// 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) } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index aa69517c9f..8ae3a0cb19 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -267,6 +267,7 @@ impl Render for WelcomePage { ) .child( v_container() + .px_2() .gap_2() .child( h_flex() diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 5539a63afa..fac5fec310 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/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"] }