collab: Improve project and call notification UI (#47964)
This PR moves the `CollabNotification` component from the collab_ui crate to the UI crate, so that we can add a component preview to it. It also fixes problems with text truncation and other little details. <img width="700" height="1674" alt="Screenshot 2026-01-29 at 4 07@2x" src="https://github.com/user-attachments/assets/6aca6cff-564a-4729-b505-c02d8c3af97f" /> Release Notes: - Collab: Fixed overflowing text in project sharing and call notifications.
This commit is contained in:
1 parent
ad69357968
commit
22e2c7ac8a
12 files changed
+151
-135
No files matched your search
Generated
-2
@@ -3448,7 +3448,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
"telemetry",
|
||||
"theme",
|
||||
"time",
|
||||
@@ -15917,7 +15916,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"collab_ui",
|
||||
"ctrlc",
|
||||
"dialoguer",
|
||||
"editor",
|
||||
|
||||
@@ -14,7 +14,6 @@ doctest = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
stories = ["dep:story"]
|
||||
test-support = [
|
||||
"call/test-support",
|
||||
"client/test-support",
|
||||
@@ -52,7 +51,6 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
telemetry.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
mod collab_notification;
|
||||
pub mod incoming_call_notification;
|
||||
pub mod project_shared_notification;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
mod stories;
|
||||
|
||||
use gpui::App;
|
||||
use std::sync::Arc;
|
||||
use workspace::AppState;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
|
||||
incoming_call_notification::init(app_state, cx);
|
||||
project_shared_notification::init(app_state, cx);
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
use gpui::{AnyElement, SharedUri, img, prelude::*};
|
||||
use smallvec::SmallVec;
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct CollabNotification {
|
||||
avatar_uri: SharedUri,
|
||||
accept_button: Button,
|
||||
dismiss_button: Button,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl CollabNotification {
|
||||
pub fn new(
|
||||
avatar_uri: impl Into<SharedUri>,
|
||||
accept_button: Button,
|
||||
dismiss_button: Button,
|
||||
) -> Self {
|
||||
Self {
|
||||
avatar_uri: avatar_uri.into(),
|
||||
accept_button,
|
||||
dismiss_button,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for CollabNotification {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CollabNotification {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
h_flex()
|
||||
.text_ui(cx)
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.elevation_3(cx)
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.child(img(self.avatar_uri).w_12().h_12().rounded_full())
|
||||
.child(v_flex().overflow_hidden().children(self.children))
|
||||
.child(
|
||||
v_flex()
|
||||
.child(self.accept_button)
|
||||
.child(self.dismiss_button),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::notification_window_options;
|
||||
use crate::notifications::collab_notification::CollabNotification;
|
||||
use call::{ActiveCall, IncomingCall};
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, WindowHandle, prelude::*};
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
use ui::{Button, Label, prelude::*};
|
||||
use ui::{CollabNotification, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
@@ -118,10 +117,10 @@ impl Render for IncomingCallNotification {
|
||||
move |_, _, cx| state.respond(false, cx)
|
||||
}),
|
||||
)
|
||||
.child(v_flex().overflow_hidden().child(Label::new(format!(
|
||||
.child(Label::new(format!(
|
||||
"{} is sharing a project in Zed",
|
||||
self.state.call.calling_user.github_login
|
||||
)))),
|
||||
))),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::notification_window_options;
|
||||
use crate::notifications::collab_notification::CollabNotification;
|
||||
use call::{ActiveCall, room};
|
||||
use client::User;
|
||||
use collections::HashMap;
|
||||
use gpui::{App, Size};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use ui::{Button, Label, prelude::*};
|
||||
use ui::{CollabNotification, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
@@ -122,6 +121,14 @@ impl ProjectSharedNotification {
|
||||
impl Render for ProjectSharedNotification {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
let no_worktree_root_names = self.worktree_root_names.is_empty();
|
||||
|
||||
let punctuation = if no_worktree_root_names { "" } else { ":" };
|
||||
let main_label = format!(
|
||||
"{} is sharing a project with you{}",
|
||||
self.owner.github_login.clone(),
|
||||
punctuation
|
||||
);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
@@ -135,19 +142,9 @@ impl Render for ProjectSharedNotification {
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(Label::new(self.owner.github_login.clone()))
|
||||
.child(Label::new(format!(
|
||||
"is sharing a project in Zed{}",
|
||||
if self.worktree_root_names.is_empty() {
|
||||
""
|
||||
} else {
|
||||
":"
|
||||
}
|
||||
)))
|
||||
.children(if self.worktree_root_names.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Label::new(self.worktree_root_names.join(", ")))
|
||||
.child(Label::new(main_label))
|
||||
.when(!no_worktree_root_names, |this| {
|
||||
this.child(Label::new(self.worktree_root_names.join(", ")).color(Color::Muted))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
use gpui::prelude::*;
|
||||
use story::{Story, StoryItem, StorySection};
|
||||
use ui::prelude::*;
|
||||
|
||||
use crate::notifications::collab_notification::CollabNotification;
|
||||
|
||||
pub struct CollabNotificationStory;
|
||||
|
||||
impl Render for CollabNotificationStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let window_container = |width, height| div().w(px(width)).h(px(height));
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<CollabNotification>(cx))
|
||||
.child(
|
||||
StorySection::new().child(StoryItem::new(
|
||||
"Incoming Call Notification",
|
||||
window_container(400., 72.).child(
|
||||
CollabNotification::new(
|
||||
"https://avatars.githubusercontent.com/u/1486634?v=4",
|
||||
Button::new("accept", "Accept"),
|
||||
Button::new("decline", "Decline"),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.overflow_hidden()
|
||||
.child(Label::new("maxdeviant is sharing a project in Zed")),
|
||||
),
|
||||
),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
StorySection::new().child(StoryItem::new(
|
||||
"Project Shared Notification",
|
||||
window_container(400., 72.).child(
|
||||
CollabNotification::new(
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
Button::new("open", "Open"),
|
||||
Button::new("dismiss", "Dismiss"),
|
||||
)
|
||||
.child(Label::new("iamnbutler"))
|
||||
.child(Label::new("is sharing a project in Zed:"))
|
||||
.child(Label::new("zed")),
|
||||
),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ path = "src/storybook.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap = { workspace = true, features = ["derive", "string"] }
|
||||
collab_ui = { workspace = true, features = ["stories"] }
|
||||
ctrlc = "3.4"
|
||||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||
editor.workspace = true
|
||||
|
||||
@@ -13,7 +13,6 @@ use ui::prelude::*;
|
||||
pub enum ComponentStory {
|
||||
ApplicationMenu,
|
||||
AutoHeightEditor,
|
||||
CollabNotification,
|
||||
ContextMenu,
|
||||
Cursor,
|
||||
Focus,
|
||||
@@ -33,9 +32,6 @@ impl ComponentStory {
|
||||
.new(|cx| title_bar::ApplicationMenuStory::new(window, cx))
|
||||
.into(),
|
||||
Self::AutoHeightEditor => AutoHeightEditorStory::new(window, cx).into(),
|
||||
Self::CollabNotification => cx
|
||||
.new(|_| collab_ui::notifications::CollabNotificationStory)
|
||||
.into(),
|
||||
Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
|
||||
Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
|
||||
Self::Focus => FocusStory::model(window, cx).into(),
|
||||
|
||||
@@ -4,6 +4,7 @@ mod banner;
|
||||
mod button;
|
||||
mod callout;
|
||||
mod chip;
|
||||
mod collab;
|
||||
mod content_group;
|
||||
mod context_menu;
|
||||
mod data_table;
|
||||
@@ -50,6 +51,7 @@ pub use banner::*;
|
||||
pub use button::*;
|
||||
pub use callout::*;
|
||||
pub use chip::*;
|
||||
pub use collab::*;
|
||||
pub use content_group::*;
|
||||
pub use context_menu::*;
|
||||
pub use data_table::*;
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,134 @@
|
||||
use gpui::{AnyElement, SharedUri, prelude::*};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{Avatar, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct CollabNotification {
|
||||
avatar_uri: SharedUri,
|
||||
accept_button: Button,
|
||||
dismiss_button: Button,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl CollabNotification {
|
||||
pub fn new(
|
||||
avatar_uri: impl Into<SharedUri>,
|
||||
accept_button: Button,
|
||||
dismiss_button: Button,
|
||||
) -> Self {
|
||||
Self {
|
||||
avatar_uri: avatar_uri.into(),
|
||||
accept_button,
|
||||
dismiss_button,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for CollabNotification {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CollabNotification {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
h_flex()
|
||||
.p_2()
|
||||
.size_full()
|
||||
.text_ui(cx)
|
||||
.justify_between()
|
||||
.overflow_hidden()
|
||||
.elevation_3(cx)
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.min_w_0()
|
||||
.gap_4()
|
||||
.child(Avatar::new(self.avatar_uri).size(px(40.)))
|
||||
.child(v_flex().truncate().children(self.children)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.items_center()
|
||||
.child(self.accept_button)
|
||||
.child(self.dismiss_button),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CollabNotification {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Collaboration
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let avatar = "https://avatars.githubusercontent.com/u/67129314?v=4";
|
||||
let container = || div().h(px(72.)).w(px(400.)); // Size of the actual notification window
|
||||
|
||||
let examples = vec![
|
||||
single_example(
|
||||
"Incoming Call",
|
||||
container()
|
||||
.child(
|
||||
CollabNotification::new(
|
||||
avatar,
|
||||
Button::new("accept", "Accept"),
|
||||
Button::new("decline", "Decline"),
|
||||
)
|
||||
.child(Label::new("the user is inviting you to a call")),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Screen Share Request",
|
||||
container()
|
||||
.child(
|
||||
CollabNotification::new(
|
||||
avatar,
|
||||
Button::new("accept", "View"),
|
||||
Button::new("decline", "Ignore"),
|
||||
)
|
||||
.child(Label::new("the user is sharing their screen")),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Project Shared",
|
||||
container()
|
||||
.child(
|
||||
CollabNotification::new(
|
||||
avatar,
|
||||
Button::new("accept", "Open"),
|
||||
Button::new("decline", "Dismiss"),
|
||||
)
|
||||
.child(Label::new("the user is sharing a project"))
|
||||
.child(Label::new("zed").color(Color::Muted)),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Overflowing Content",
|
||||
container()
|
||||
.child(
|
||||
CollabNotification::new(
|
||||
avatar,
|
||||
Button::new("accept", "Accept"),
|
||||
Button::new("decline", "Decline"),
|
||||
)
|
||||
.child(Label::new(
|
||||
"a_very_long_username_that_might_overflow is sharing a project in Zed:",
|
||||
))
|
||||
.child(
|
||||
Label::new("zed-cloud, zed, edit-prediction-bench, zed.dev")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
];
|
||||
|
||||
Some(example_group(examples).vertical().into_any_element())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user