diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 9f11870d98..077e08fac7 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -486,8 +486,12 @@ impl CollabTitlebarItem { .child( Avatar::new(user.avatar_uri.clone()) .grayscale(!is_present) - .when(is_speaking, |avatar| { - avatar.border_color(cx.theme().status().info_border) + .border_color(if is_speaking { + cx.theme().status().info_border + } else { + // We draw the border in a transparent color rather to avoid + // the layout shift that would come with adding/removing the border. + gpui::transparent_black() }) .when(is_muted, |avatar| { avatar.indicator( diff --git a/crates/gpui_macros/src/style_helpers.rs b/crates/gpui_macros/src/style_helpers.rs index b86bb2dfa6..00d3672033 100644 --- a/crates/gpui_macros/src/style_helpers.rs +++ b/crates/gpui_macros/src/style_helpers.rs @@ -85,6 +85,18 @@ fn generate_methods() -> Vec { } for (prefix, fields, prefix_doc_string) in border_prefixes() { + methods.push(generate_custom_value_setter( + // The plain method names (e.g., `border`, `border_t`, `border_r`, etc.) are special-cased + // versions of the 1px variants. This better matches Tailwind, but breaks our existing + // convention of the suffix-less variant of the method being the one that accepts a custom value + // + // To work around this, we're assigning a `_width` suffix here. + &format!("{prefix}_width"), + quote! { AbsoluteLength }, + &fields, + prefix_doc_string, + )); + for (suffix, width_tokens, suffix_doc_string) in border_suffixes() { methods.push(generate_predefined_setter( prefix, @@ -141,7 +153,7 @@ fn generate_predefined_setter( } fn generate_custom_value_setter( - prefix: &'static str, + prefix: &str, length_type: TokenStream2, fields: &[TokenStream2], doc_string: &str, diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs index 5154d90bd2..932cc9e243 100644 --- a/crates/ui/src/components/avatar/avatar.rs +++ b/crates/ui/src/components/avatar/avatar.rs @@ -99,20 +99,27 @@ impl RenderOnce for Avatar { self = self.shape(AvatarShape::Circle); } - let size = self.size.unwrap_or_else(|| cx.rem_size()); + let border_width = if self.border_color.is_some() { + px(2.) + } else { + px(0.) + }; + + let image_size = self.size.unwrap_or_else(|| cx.rem_size()); + let container_size = image_size + border_width * 2.; div() - .size(size + px(2.)) + .size(container_size) .map(|mut div| { div.style().corner_radii = self.image.style().corner_radii.clone(); div }) .when_some(self.border_color, |this, color| { - this.border().border_color(color) + this.border_width(border_width).border_color(color) }) .child( self.image - .size(size) + .size(image_size) .bg(cx.theme().colors().ghost_element_background), ) .children( diff --git a/crates/ui/src/components/stories/avatar.rs b/crates/ui/src/components/stories/avatar.rs index c3409b1ca8..9da475b0d9 100644 --- a/crates/ui/src/components/stories/avatar.rs +++ b/crates/ui/src/components/stories/avatar.rs @@ -1,5 +1,5 @@ use gpui::Render; -use story::Story; +use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, AudioStatus, Availability, AvatarAvailabilityIndicator}; use crate::{Avatar, AvatarAudioStatusIndicator}; @@ -7,31 +7,57 @@ use crate::{Avatar, AvatarAudioStatusIndicator}; pub struct AvatarStory; impl Render for AvatarStory { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - Story::container() - .child(Story::title_for::()) - .child(Story::label("Default")) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", - )) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/326587?v=4", - )) + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + StoryContainer::new("Avatar", "crates/ui/src/components/stories/avatar.rs") .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAvailabilityIndicator::new(Availability::Free)), + StorySection::new() + .child(StoryItem::new( + "Default", + Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4"), + )) + .child(StoryItem::new( + "Default", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4"), + )), ) .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAvailabilityIndicator::new(Availability::Busy)), + StorySection::new() + .child(StoryItem::new( + "With free availability indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAvailabilityIndicator::new(Availability::Free)), + )) + .child(StoryItem::new( + "With busy availability indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAvailabilityIndicator::new(Availability::Busy)), + )), ) .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)), + StorySection::new() + .child(StoryItem::new( + "With info border", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .border_color(cx.theme().status().info_border), + )) + .child(StoryItem::new( + "With error border", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .border_color(cx.theme().status().error_border), + )), ) .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)), + StorySection::new() + .child(StoryItem::new( + "With muted audio indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)), + )) + .child(StoryItem::new( + "With deafened audio indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)), + )), ) } }