Start styling notification panel

Max Brunsfeld created

Change summary

crates/collab_ui/src/chat_panel.rs          |  3 
crates/collab_ui/src/collab_ui.rs           | 13 ++-
crates/collab_ui/src/notification_panel.rs  | 62 +++++++++++++---------
crates/theme/src/theme.rs                   |  3 +
styles/src/style_tree/notification_panel.ts | 39 +++++++++++--
5 files changed, 81 insertions(+), 39 deletions(-)

Detailed changes

crates/collab_ui/src/chat_panel.rs 🔗

@@ -443,7 +443,8 @@ impl ChatPanel {
                                 Flex::row()
                                     .with_child(render_avatar(
                                         message.sender.avatar.clone(),
-                                        &theme,
+                                        &theme.chat_panel.avatar,
+                                        theme.chat_panel.avatar_container,
                                     ))
                                     .with_child(
                                         Label::new(

crates/collab_ui/src/collab_ui.rs 🔗

@@ -11,7 +11,7 @@ use call::{report_call_event_for_room, ActiveCall, Room};
 use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
 use gpui::{
     actions,
-    elements::{Empty, Image},
+    elements::{ContainerStyle, Empty, Image},
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
@@ -20,7 +20,7 @@ use gpui::{
     AnyElement, AppContext, Element, ImageData, Task,
 };
 use std::{rc::Rc, sync::Arc};
-use theme::Theme;
+use theme::AvatarStyle;
 use time::{OffsetDateTime, UtcOffset};
 use util::ResultExt;
 use workspace::AppState;
@@ -133,8 +133,11 @@ fn notification_window_options(
     }
 }
 
-fn render_avatar<T: 'static>(avatar: Option<Arc<ImageData>>, theme: &Arc<Theme>) -> AnyElement<T> {
-    let avatar_style = theme.chat_panel.avatar;
+fn render_avatar<T: 'static>(
+    avatar: Option<Arc<ImageData>>,
+    avatar_style: &AvatarStyle,
+    container: ContainerStyle,
+) -> AnyElement<T> {
     avatar
         .map(|avatar| {
             Image::from_data(avatar)
@@ -154,7 +157,7 @@ fn render_avatar<T: 'static>(avatar: Option<Arc<ImageData>>, theme: &Arc<Theme>)
                 .into_any()
         })
         .contained()
-        .with_style(theme.chat_panel.avatar_container)
+        .with_style(container)
         .into_any()
 }
 

crates/collab_ui/src/notification_panel.rs 🔗

@@ -21,7 +21,7 @@ use rpc::proto;
 use serde::{Deserialize, Serialize};
 use settings::SettingsStore;
 use std::{sync::Arc, time::Duration};
-use theme::{IconButton, Theme};
+use theme::{ui, Theme};
 use time::{OffsetDateTime, UtcOffset};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
@@ -197,9 +197,9 @@ impl NotificationPanel {
         let NotificationPresenter {
             actor,
             text,
-            icon,
             needs_response,
             can_navigate,
+            ..
         } = self.present_notification(entry, cx)?;
 
         let theme = theme::current(cx);
@@ -227,17 +227,21 @@ impl NotificationPanel {
                 Flex::column()
                     .with_child(
                         Flex::row()
-                            .with_children(
-                                actor.map(|actor| render_avatar(actor.avatar.clone(), &theme)),
-                            )
-                            .with_child(render_icon_button(&theme.chat_panel.icon_button, icon))
+                            .with_children(actor.map(|actor| {
+                                render_avatar(
+                                    actor.avatar.clone(),
+                                    &style.avatar,
+                                    style.avatar_container,
+                                )
+                            }))
                             .with_child(
                                 Label::new(
                                     format_timestamp(timestamp, now, self.local_timezone),
                                     style.timestamp.text.clone(),
                                 )
                                 .contained()
-                                .with_style(style.timestamp.container),
+                                .with_style(style.timestamp.container)
+                                .flex_float(),
                             )
                             .align_children_center(),
                     )
@@ -245,8 +249,12 @@ impl NotificationPanel {
                     .with_children(if let Some(is_accepted) = response {
                         Some(
                             Label::new(
-                                if is_accepted { "Accepted" } else { "Declined" },
-                                style.button.text.clone(),
+                                if is_accepted {
+                                    "You Accepted"
+                                } else {
+                                    "You Declined"
+                                },
+                                style.read_text.text.clone(),
                             )
                             .into_any(),
                         )
@@ -573,19 +581,34 @@ impl View for NotificationPanel {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         let theme = theme::current(cx);
+        let style = &theme.notification_panel;
         let element = if self.client.user_id().is_none() {
             self.render_sign_in_prompt(&theme, cx)
         } else if self.notification_list.item_count() == 0 {
             self.render_empty_state(&theme, cx)
         } else {
-            List::new(self.notification_list.clone())
-                .contained()
-                .with_style(theme.chat_panel.list)
+            Flex::column()
+                .with_child(
+                    Flex::row()
+                        .with_child(Label::new("Notifications", style.title.text.clone()))
+                        .with_child(ui::svg(&style.title_icon).flex_float())
+                        .align_children_center()
+                        .contained()
+                        .with_style(style.title.container)
+                        .constrained()
+                        .with_height(style.title_height),
+                )
+                .with_child(
+                    List::new(self.notification_list.clone())
+                        .contained()
+                        .with_style(style.list)
+                        .flex(1., true),
+                )
                 .into_any()
         };
         element
             .contained()
-            .with_style(theme.chat_panel.container)
+            .with_style(style.container)
             .constrained()
             .with_min_width(150.)
             .into_any()
@@ -675,19 +698,6 @@ impl Panel for NotificationPanel {
     }
 }
 
-fn render_icon_button<V: View>(style: &IconButton, svg_path: &'static str) -> impl Element<V> {
-    Svg::new(svg_path)
-        .with_color(style.color)
-        .constrained()
-        .with_width(style.icon_width)
-        .aligned()
-        .constrained()
-        .with_width(style.button_width)
-        .with_height(style.button_width)
-        .contained()
-        .with_style(style.container)
-}
-
 pub struct NotificationToast {
     notification_id: u64,
     actor: Option<Arc<User>>,

crates/theme/src/theme.rs 🔗

@@ -653,6 +653,9 @@ pub struct ChatPanel {
 pub struct NotificationPanel {
     #[serde(flatten)]
     pub container: ContainerStyle,
+    pub title: ContainedText,
+    pub title_icon: SvgStyle,
+    pub title_height: f32,
     pub list: ContainerStyle,
     pub avatar: AvatarStyle,
     pub avatar_container: ContainerStyle,

styles/src/style_tree/notification_panel.ts 🔗

@@ -1,9 +1,9 @@
-import { background, text } from "./components"
+import { background, border, text } from "./components"
 import { icon_button } from "../component/icon_button"
 import { useTheme } from "../theme"
 import { interactive } from "../element"
 
-export default function chat_panel(): any {
+export default function (): any {
     const theme = useTheme()
     const layer = theme.middle
 
@@ -12,12 +12,32 @@ export default function chat_panel(): any {
         avatar: {
             icon_width: 24,
             icon_height: 24,
-            corner_radius: 4,
+            corner_radius: 12,
             outer_width: 24,
-            outer_corner_radius: 16,
+            outer_corner_radius: 24,
+        },
+        title: {
+            ...text(layer, "sans", "default"),
+            padding: { left: 8, right: 8 },
+            border: border(layer, { bottom: true }),
+        },
+        title_height: 32,
+        title_icon: {
+            asset: "icons/feedback.svg",
+            color: text(theme.lowest, "sans", "default").color,
+            dimensions: {
+                width: 16,
+                height: 16,
+            },
+        },
+        read_text: {
+            padding: { top: 4, bottom: 4 },
+            ...text(layer, "sans", "disabled"),
+        },
+        unread_text: {
+            padding: { top: 4, bottom: 4 },
+            ...text(layer, "sans", "base"),
         },
-        read_text: text(layer, "sans", "disabled"),
-        unread_text: text(layer, "sans", "base"),
         button: interactive({
             base: {
                 ...text(theme.lowest, "sans", "on", { size: "xs" }),
@@ -42,7 +62,12 @@ export default function chat_panel(): any {
                 bottom: 2,
             },
         },
-        list: {},
+        list: {
+            padding: {
+                left: 8,
+                right: 8,
+            },
+        },
         icon_button: icon_button({
             variant: "ghost",
             color: "variant",