Merge Component and ComponentPreview trait (#28365)

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

Release Notes:

- N/A
This commit is contained in:
Nate Butler 2025-04-08 16:09:06 -06:00 committed by GitHub
parent b15ee1b1cc
commit c05bf096f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 3276 additions and 1848 deletions

50
Cargo.lock generated
View file

@ -3329,6 +3329,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 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]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.8.0" version = "0.8.0"
@ -4461,6 +4470,32 @@ dependencies = [
"workspace-hack", "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]] [[package]]
name = "dotenvy" name = "dotenvy"
version = "0.15.7" version = "0.15.7"
@ -7875,7 +7910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.48.5", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -9542,6 +9577,17 @@ dependencies = [
"vcpkg", "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]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@ -15349,6 +15395,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"component", "component",
"documented",
"gpui", "gpui",
"icons", "icons",
"itertools 0.14.0", "itertools 0.14.0",
@ -17614,6 +17661,7 @@ dependencies = [
"indexmap", "indexmap",
"inout", "inout",
"itertools 0.12.1", "itertools 0.12.1",
"itertools 0.13.0",
"lazy_static", "lazy_static",
"libc", "libc",
"libsqlite3-sys", "libsqlite3-sys",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,31 @@
use crate::{Avatar, prelude::*}; use crate::component_prelude::*;
use crate::prelude::*;
use gpui::{AnyElement, StyleRefinement}; use gpui::{AnyElement, StyleRefinement};
use smallvec::SmallVec; use smallvec::SmallVec;
/// A facepile is a collection of faces stacked horizontally use super::Avatar;
/// always with the leftmost face on top and descending in z-index
/// 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, /// Facepiles are used to display a group of people or things,
/// such as a list of participants in a collaboration session. /// 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 { pub struct Facepile {
base: Div, base: Div,
faces: SmallVec<[AnyElement; 2]>, faces: SmallVec<[AnyElement; 2]>,
@ -60,27 +78,37 @@ impl RenderOnce for Facepile {
} }
} }
impl ComponentPreview for Facepile { pub const EXAMPLE_FACES: [&'static str; 6] = [
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
let faces: [&'static str; 6] = [ "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
"https://avatars.githubusercontent.com/u/326587?s=60&v=4", "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4", "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
"https://avatars.githubusercontent.com/u/1789?s=60&v=4", "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4", "https://avatars.githubusercontent.com/u/1714999?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() impl Component for Facepile {
.gap_6() fn scope() -> ComponentScope {
.children(vec![ ComponentScope::Collaboration
example_group_with_title( }
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<AnyElement> {
Some(
v_flex()
.gap_6()
.children(vec![example_group_with_title(
"Facepile Examples", "Facepile Examples",
vec![ vec![
single_example( single_example(
"Default", "Default",
Facepile::new( Facepile::new(
faces EXAMPLE_FACES
.iter() .iter()
.map(|&url| Avatar::new(url).into_any_element()) .map(|&url| Avatar::new(url).into_any_element())
.collect(), .collect(),
@ -90,7 +118,7 @@ impl ComponentPreview for Facepile {
single_example( single_example(
"Custom Size", "Custom Size",
Facepile::new( Facepile::new(
faces EXAMPLE_FACES
.iter() .iter()
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element()) .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
.collect(), .collect(),
@ -98,19 +126,8 @@ impl ComponentPreview for Facepile {
.into_any_element(), .into_any_element(),
), ),
], ],
), )])
example_group_with_title( .into_any_element(),
"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()
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@ use crate::{Indicator, prelude::*};
use gpui::{AnyElement, FontWeight, IntoElement, Length, div}; use gpui::{AnyElement, FontWeight, IntoElement, Length, div};
/// A table component /// A table component
#[derive(IntoElement, IntoComponent)] #[derive(IntoElement, RegisterComponent)]
pub struct Table { pub struct Table {
column_headers: Vec<SharedString>, column_headers: Vec<SharedString>,
rows: Vec<Vec<TableCell>>, rows: Vec<Vec<TableCell>>,
@ -151,112 +151,121 @@ where
} }
} }
// View this component preview using `workspace: open component-preview` impl Component for Table {
impl ComponentPreview for Table { fn scope() -> ComponentScope {
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement { ComponentScope::Layout
v_flex() }
.gap_6()
.children(vec![ fn description() -> Option<&'static str> {
example_group_with_title( Some("A table component for displaying data in rows and columns with optional styling.")
"Basic Tables", }
vec![
single_example( fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
"Simple Table", Some(
Table::new(vec!["Name", "Age", "City"]) v_flex()
.width(px(400.)) .gap_6()
.row(vec!["Alice", "28", "New York"]) .children(vec![
.row(vec!["Bob", "32", "San Francisco"]) example_group_with_title(
.row(vec!["Charlie", "25", "London"]) "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(), .into_any_element(),
), )],
single_example( ),
"Two Column Table", ])
Table::new(vec!["Category", "Value"]) .into_any_element(),
.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()
} }
} }

View file

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

View file

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

View file

@ -8,9 +8,9 @@ pub use gpui::{
}; };
pub use component::{ 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::DynamicSpacing;
pub use crate::animation::{AnimationDirection, AnimationDuration, DefaultAnimations}; pub use crate::animation::{AnimationDirection, AnimationDuration, DefaultAnimations};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -267,6 +267,7 @@ impl Render for WelcomePage {
) )
.child( .child(
v_container() v_container()
.px_2()
.gap_2() .gap_2()
.child( .child(
h_flex() h_flex()

View file

@ -176,6 +176,7 @@ heck = { version = "0.4", features = ["unicode"] }
hmac = { version = "0.12", default-features = false, features = ["reset"] } hmac = { version = "0.12", default-features = false, features = ["reset"] }
hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] } hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
indexmap = { version = "2", features = ["serde"] } indexmap = { version = "2", features = ["serde"] }
itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" }
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
libc = { version = "0.2", features = ["extra_traits"] } libc = { version = "0.2", features = ["extra_traits"] }
libsqlite3-sys = { version = "0.30", features = ["bundled", "unlock_notify"] } 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"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } 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"] } 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"] } naga = { version = "23", features = ["msl-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal"] } nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } 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"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } 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"] } 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"] } naga = { version = "23", features = ["msl-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal"] } nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } 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"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } 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"] } 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"] } naga = { version = "23", features = ["msl-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal"] } nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } 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"] } getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } 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"] } 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"] } naga = { version = "23", features = ["msl-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal"] } nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] } 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"] } 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"] } 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"] } 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"] } 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"] } mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] } 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"] } 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"] } 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"] } 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"] } 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"] } mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] } 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"] } 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"] } 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"] } 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"] } 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"] } mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] } 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"] } 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"] } 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"] } 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"] } 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"] } mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] } 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-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } 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"] } 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"] } naga = { version = "23", features = ["spv-out", "wgsl-in"] }
ring = { version = "0.17", features = ["std"] } ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] } 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-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } 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"] } 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"] } naga = { version = "23", features = ["spv-out", "wgsl-in"] }
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
ring = { version = "0.17", features = ["std"] } 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"] } 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"] } 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"] } 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"] } 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"] } mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] } 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"] } 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"] } 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"] } 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"] } 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"] } mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] } naga = { version = "23", features = ["spv-out", "wgsl-in"] }