Show incoming request notification and implement dismissal

Antonio Scandurra created

Change summary

assets/themes/cave-dark.json                        |  61 +++++++
assets/themes/cave-light.json                       |  61 +++++++
assets/themes/dark.json                             |  61 +++++++
assets/themes/light.json                            |  61 +++++++
assets/themes/solarized-dark.json                   |  61 +++++++
assets/themes/solarized-light.json                  |  61 +++++++
assets/themes/sulphurpool-dark.json                 |  61 +++++++
assets/themes/sulphurpool-light.json                |  61 +++++++
crates/client/src/user.rs                           |  18 ++
crates/collab/src/rpc.rs                            |  63 ++++---
crates/contacts_panel/src/contact_notifications.rs  | 113 ++++++++++++++
crates/contacts_panel/src/contacts_panel.rs         |   1 
crates/rpc/proto/zed.proto                          |   1 
crates/theme/src/theme.rs                           |  11 +
styles/src/styleTree/app.ts                         |   4 
styles/src/styleTree/incomingRequestNotification.ts |  35 ++++
styles/src/styleTree/workspace.ts                   |   8 
17 files changed, 689 insertions(+), 53 deletions(-)

Detailed changes

assets/themes/cave-dark.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#26232a",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#19171c",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000003d",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#e2dfe7",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#8b8792",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#e2dfe7",
+      "size": 12,
+      "background": "#19171c",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#8b8792",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/cave-light.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#e2dfe7",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#efecf4",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000001f",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#26232a",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#585260",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#26232a",
+      "size": 12,
+      "background": "#efecf4",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#585260",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/dark.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#1c1c1c",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#070707",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#00000052",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#f1f1f1",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#9c9c9c",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#f1f1f1",
+      "size": 12,
+      "background": "#0e0e0e80",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#9c9c9c",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/light.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#f8f8f8",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#d5d5d5",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000001f",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#2b2b2b",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#474747",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#2b2b2b",
+      "size": 12,
+      "background": "#f1f1f1",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#717171",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/solarized-dark.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#073642",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#002b36",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000003d",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#eee8d5",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#93a1a1",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#eee8d5",
+      "size": 12,
+      "background": "#002b36",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#93a1a1",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/solarized-light.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#eee8d5",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#fdf6e3",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000001f",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#073642",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#586e75",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#073642",
+      "size": 12,
+      "background": "#fdf6e3",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#586e75",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/sulphurpool-dark.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#293256",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#202746",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000003d",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#dfe2f1",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#979db4",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#dfe2f1",
+      "size": 12,
+      "background": "#202746",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#979db4",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

assets/themes/sulphurpool-light.json 🔗

@@ -474,6 +474,21 @@
     "notification": {
       "margin": {
         "top": 10
+      },
+      "background": "#dfe2f1",
+      "corner_radius": 6,
+      "padding": 12,
+      "border": {
+        "color": "#f5f7ff",
+        "width": 1
+      },
+      "shadow": {
+        "blur": 16,
+        "color": "#0000001f",
+        "offset": [
+          0,
+          2
+        ]
       }
     },
     "notifications": {
@@ -481,8 +496,7 @@
       "margin": {
         "right": 10,
         "bottom": 10
-      },
-      "background": "#ff0000"
+      }
     }
   },
   "editor": {
@@ -1659,5 +1673,48 @@
     "padding": {
       "left": 6
     }
+  },
+  "incoming_request_notification": {
+    "header_avatar": {
+      "height": 12,
+      "width": 12,
+      "corner_radius": 6
+    },
+    "header_message": {
+      "family": "Zed Sans",
+      "color": "#293256",
+      "size": 12,
+      "margin": {
+        "left": 4
+      }
+    },
+    "header_height": 18,
+    "body_message": {
+      "family": "Zed Sans",
+      "color": "#5e6687",
+      "size": 12,
+      "margin": {
+        "top": 6,
+        "bottom": 6
+      }
+    },
+    "button": {
+      "family": "Zed Sans",
+      "color": "#293256",
+      "size": 12,
+      "background": "#f5f7ff",
+      "padding": 4,
+      "corner_radius": 6,
+      "margin": {
+        "left": 6
+      }
+    },
+    "dismiss_button": {
+      "color": "#5e6687",
+      "icon_width": 8,
+      "icon_height": 8,
+      "button_width": 8,
+      "button_height": 8
+    }
   }
 }

crates/client/src/user.rs 🔗

@@ -356,6 +356,24 @@ impl UserStore {
         )
     }
 
+    pub fn dismiss_contact_request(
+        &mut self,
+        requester_id: u64,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<()>> {
+        let client = self.client.upgrade();
+        cx.spawn_weak(|_, _| async move {
+            client
+                .ok_or_else(|| anyhow!("can't upgrade client reference"))?
+                .request(proto::RespondToContactRequest {
+                    requester_id,
+                    response: proto::ContactRequestResponse::Dismiss as i32,
+                })
+                .await?;
+            Ok(())
+        })
+    }
+
     fn perform_contact_request<T: RequestMessage>(
         &mut self,
         user_id: u64,

crates/collab/src/rpc.rs 🔗

@@ -1023,35 +1023,42 @@ impl Server {
             .await
             .user_id_for_connection(request.sender_id)?;
         let requester_id = UserId::from_proto(request.payload.requester_id);
-        let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32;
-        self.app_state
-            .db
-            .respond_to_contact_request(responder_id, requester_id, accept)
-            .await?;
-
-        let store = self.store().await;
-        // Update responder with new contact
-        let mut update = proto::UpdateContacts::default();
-        if accept {
-            update.contacts.push(store.contact_for_user(requester_id));
-        }
-        update
-            .remove_incoming_requests
-            .push(requester_id.to_proto());
-        for connection_id in store.connection_ids_for_user(responder_id) {
-            self.peer.send(connection_id, update.clone())?;
-        }
+        if request.payload.response == proto::ContactRequestResponse::Dismiss as i32 {
+            self.app_state
+                .db
+                .dismiss_contact_request(responder_id, requester_id)
+                .await?;
+        } else {
+            let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32;
+            self.app_state
+                .db
+                .respond_to_contact_request(responder_id, requester_id, accept)
+                .await?;
+
+            let store = self.store().await;
+            // Update responder with new contact
+            let mut update = proto::UpdateContacts::default();
+            if accept {
+                update.contacts.push(store.contact_for_user(requester_id));
+            }
+            update
+                .remove_incoming_requests
+                .push(requester_id.to_proto());
+            for connection_id in store.connection_ids_for_user(responder_id) {
+                self.peer.send(connection_id, update.clone())?;
+            }
 
-        // Update requester with new contact
-        let mut update = proto::UpdateContacts::default();
-        if accept {
-            update.contacts.push(store.contact_for_user(responder_id));
-        }
-        update
-            .remove_outgoing_requests
-            .push(responder_id.to_proto());
-        for connection_id in store.connection_ids_for_user(requester_id) {
-            self.peer.send(connection_id, update.clone())?;
+            // Update requester with new contact
+            let mut update = proto::UpdateContacts::default();
+            if accept {
+                update.contacts.push(store.contact_for_user(responder_id));
+            }
+            update
+                .remove_outgoing_requests
+                .push(responder_id.to_proto());
+            for connection_id in store.connection_ids_for_user(requester_id) {
+                self.peer.send(connection_id, update.clone())?;
+            }
         }
 
         response.send(proto::Ack {})?;

crates/contacts_panel/src/contact_notifications.rs 🔗

@@ -1,13 +1,26 @@
 use client::{User, UserStore};
-use gpui::{color::Color, elements::*, Entity, ModelHandle, View, ViewContext};
+use gpui::{
+    elements::*, impl_internal_actions, platform::CursorStyle, Entity, ModelHandle,
+    MutableAppContext, RenderContext, View, ViewContext,
+};
+use settings::Settings;
 use std::sync::Arc;
 use workspace::Notification;
 
+impl_internal_actions!(contact_notifications, [Dismiss]);
+
+pub fn init(cx: &mut MutableAppContext) {
+    cx.add_action(IncomingRequestNotification::dismiss);
+}
+
 pub struct IncomingRequestNotification {
     user: Arc<User>,
     user_store: ModelHandle<UserStore>,
 }
 
+#[derive(Clone)]
+struct Dismiss(u64);
+
 pub enum Event {
     Dismiss,
 }
@@ -21,12 +34,91 @@ impl View for IncomingRequestNotification {
         "IncomingRequestNotification"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
-        Empty::new()
-            .constrained()
-            .with_height(200.)
+    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+        enum Dismiss {}
+        enum Reject {}
+        enum Accept {}
+
+        let theme = cx.global::<Settings>().theme.clone();
+        let theme = &theme.incoming_request_notification;
+        let user_id = self.user.id;
+
+        Flex::column()
+            .with_child(
+                Flex::row()
+                    .with_children(self.user.avatar.clone().map(|avatar| {
+                        Image::new(avatar)
+                            .with_style(theme.header_avatar)
+                            .aligned()
+                            .left()
+                            .boxed()
+                    }))
+                    .with_child(
+                        Label::new(
+                            format!("{} added you", self.user.github_login),
+                            theme.header_message.text.clone(),
+                        )
+                        .contained()
+                        .with_style(theme.header_message.container)
+                        .aligned()
+                        .boxed(),
+                    )
+                    .with_child(
+                        MouseEventHandler::new::<Dismiss, _, _>(
+                            self.user.id as usize,
+                            cx,
+                            |_, _| {
+                                Svg::new("icons/reject.svg")
+                                    .with_color(theme.dismiss_button.color)
+                                    .constrained()
+                                    .with_width(theme.dismiss_button.icon_width)
+                                    .aligned()
+                                    .contained()
+                                    .with_style(theme.dismiss_button.container)
+                                    .constrained()
+                                    .with_width(theme.dismiss_button.button_width)
+                                    .with_height(theme.dismiss_button.button_width)
+                                    .aligned()
+                                    .boxed()
+                            },
+                        )
+                        .with_cursor_style(CursorStyle::PointingHand)
+                        .on_click(move |_, cx| cx.dispatch_action(Dismiss(user_id)))
+                        .flex_float()
+                        .boxed(),
+                    )
+                    .constrained()
+                    .with_height(theme.header_height)
+                    .boxed(),
+            )
+            .with_child(
+                Label::new(
+                    "They won't know if you decline.".to_string(),
+                    theme.body_message.text.clone(),
+                )
+                .contained()
+                .with_style(theme.body_message.container)
+                .boxed(),
+            )
+            .with_child(
+                Flex::row()
+                    .with_child(
+                        Label::new("Decline".to_string(), theme.button.text.clone())
+                            .contained()
+                            .with_style(theme.button.container)
+                            .boxed(),
+                    )
+                    .with_child(
+                        Label::new("Accept".to_string(), theme.button.text.clone())
+                            .contained()
+                            .with_style(theme.button.container)
+                            .boxed(),
+                    )
+                    .aligned()
+                    .right()
+                    .boxed(),
+            )
             .contained()
-            .with_background_color(Color::red())
             .boxed()
     }
 }
@@ -55,4 +147,13 @@ impl IncomingRequestNotification {
 
         Self { user, user_store }
     }
+
+    fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
+        self.user_store.update(cx, |store, cx| {
+            store
+                .dismiss_contact_request(self.user.id, cx)
+                .detach_and_log_err(cx);
+        });
+        cx.emit(Event::Dismiss);
+    }
 }

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -55,6 +55,7 @@ pub struct RespondToContactRequest {
 
 pub fn init(cx: &mut MutableAppContext) {
     contact_finder::init(cx);
+    contact_notifications::init(cx);
     cx.add_action(ContactsPanel::request_contact);
     cx.add_action(ContactsPanel::remove_contact);
     cx.add_action(ContactsPanel::respond_to_contact_request);

crates/rpc/proto/zed.proto 🔗

@@ -566,6 +566,7 @@ enum ContactRequestResponse {
     Accept = 0;
     Reject = 1;
     Block = 2;
+    Dismiss = 3;
 }
 
 message SendChannelMessage {

crates/theme/src/theme.rs 🔗

@@ -29,6 +29,7 @@ pub struct Theme {
     pub search: Search,
     pub project_diagnostics: ProjectDiagnostics,
     pub breadcrumbs: ContainedText,
+    pub incoming_request_notification: IncomingRequestNotification,
 }
 
 #[derive(Deserialize, Default)]
@@ -354,6 +355,16 @@ pub struct ProjectDiagnostics {
     pub tab_summary_spacing: f32,
 }
 
+#[derive(Deserialize, Default)]
+pub struct IncomingRequestNotification {
+    pub header_avatar: ImageStyle,
+    pub header_message: ContainedText,
+    pub header_height: f32,
+    pub body_message: ContainedText,
+    pub button: ContainedText,
+    pub dismiss_button: IconButton,
+}
+
 #[derive(Clone, Deserialize, Default)]
 pub struct Editor {
     pub text_color: Color,

styles/src/styleTree/app.ts 🔗

@@ -10,6 +10,7 @@ import search from "./search";
 import picker from "./picker";
 import workspace from "./workspace";
 import projectDiagnostics from "./projectDiagnostics";
+import incomingRequestNotification from "./incomingRequestNotification";
 
 export const panel = {
   padding: { top: 12, left: 12, bottom: 12, right: 12 },
@@ -32,6 +33,7 @@ export default function app(theme: Theme): Object {
       padding: {
         left: 6,
       },
-    }
+    },
+    incomingRequestNotification: incomingRequestNotification(theme),
   };
 }

styles/src/styleTree/incomingRequestNotification.ts 🔗

@@ -0,0 +1,35 @@
+import Theme from "../themes/theme";
+import { backgroundColor, iconColor, text } from "./components";
+
+export default function incomingRequestNotification(theme: Theme): Object {
+  return {
+    headerAvatar: {
+      height: 12,
+      width: 12,
+      cornerRadius: 6,
+    },
+    headerMessage: {
+      ...text(theme, "sans", "primary", { size: "xs" }),
+      margin: { left: 4 }
+    },
+    headerHeight: 18,
+    bodyMessage: {
+      ...text(theme, "sans", "secondary", { size: "xs" }),
+      margin: { top: 6, bottom: 6 },
+    },
+    button: {
+      ...text(theme, "sans", "primary", { size: "xs" }),
+      background: backgroundColor(theme, "on300"),
+      padding: 4,
+      cornerRadius: 6,
+      margin: { left: 6 },
+    },
+    dismissButton: {
+      color: iconColor(theme, "secondary"),
+      iconWidth: 8,
+      iconHeight: 8,
+      buttonWidth: 8,
+      buttonHeight: 8,
+    }
+  }
+}

styles/src/styleTree/workspace.ts 🔗

@@ -1,5 +1,5 @@
 import Theme from "../themes/theme";
-import { backgroundColor, border, iconColor, text } from "./components";
+import { backgroundColor, border, iconColor, shadow, text } from "./components";
 import statusBar from "./statusBar";
 
 export default function workspace(theme: Theme) {
@@ -148,11 +148,15 @@ export default function workspace(theme: Theme) {
     },
     notification: {
       margin: { top: 10 },
+      background: backgroundColor(theme, 300),
+      cornerRadius: 6,
+      padding: 12,
+      border: border(theme, "primary"),
+      shadow: shadow(theme),
     },
     notifications: {
       width: 256,
       margin: { right: 10, bottom: 10 },
-      background: "#ff0000",
     }
   };
 }