diff --git a/assets/icons/at-sign.svg b/assets/icons/at-sign.svg new file mode 100644 index 0000000000..5adac38f62 --- /dev/null +++ b/assets/icons/at-sign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bell-off.svg b/assets/icons/bell-off.svg new file mode 100644 index 0000000000..db1021f2d3 --- /dev/null +++ b/assets/icons/bell-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bell-ring.svg b/assets/icons/bell-ring.svg new file mode 100644 index 0000000000..da51fdc5be --- /dev/null +++ b/assets/icons/bell-ring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/bell.svg b/assets/icons/bell.svg index ea1c6dd42e..4c7d5472db 100644 --- a/assets/icons/bell.svg +++ b/assets/icons/bell.svg @@ -1,8 +1 @@ - - - + \ No newline at end of file diff --git a/assets/icons/mail-open.svg b/assets/icons/mail-open.svg new file mode 100644 index 0000000000..b63915bd73 --- /dev/null +++ b/assets/icons/mail-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index ebc442afdc..ad7ec2214f 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -15,12 +15,20 @@ pub enum ListItemVariant { Inset, } +pub enum ListHeaderMeta { + // TODO: These should be IconButtons + Tools(Vec), + // TODO: This should be a button + Button(Label), + Text(Label), +} + #[derive(Component)] pub struct ListHeader { label: SharedString, left_icon: Option, + meta: Option, variant: ListItemVariant, - state: InteractionState, toggleable: Toggleable, } @@ -29,8 +37,8 @@ impl ListHeader { Self { label: label.into(), left_icon: None, + meta: None, variant: ListItemVariant::default(), - state: InteractionState::default(), toggleable: Toggleable::Toggleable(ToggleState::Toggled), } } @@ -50,8 +58,8 @@ impl ListHeader { self } - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; + pub fn meta(mut self, meta: Option) -> Self { + self.meta = meta; self } @@ -74,34 +82,37 @@ impl ListHeader { } } - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let is_toggleable = self.toggleable != Toggleable::NotToggleable; let is_toggled = self.toggleable.is_toggled(); let disclosure_control = self.disclosure_control(); + let meta = match self.meta { + Some(ListHeaderMeta::Tools(icons)) => div().child( + h_stack() + .gap_2() + .items_center() + .children(icons.into_iter().map(|i| { + IconElement::new(i) + .color(IconColor::Muted) + .size(IconSize::Small) + })), + ), + Some(ListHeaderMeta::Button(label)) => div().child(label), + Some(ListHeaderMeta::Text(label)) => div().child(label), + None => div(), + }; + h_stack() .flex_1() .w_full() .bg(cx.theme().colors().surface) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(cx.theme().colors().border_focused) - }) + // TODO: Add focus state + // .when(self.state == InteractionState::Focused, |this| { + // this.border() + // .border_color(cx.theme().colors().border_focused) + // }) .relative() .child( div() @@ -109,22 +120,28 @@ impl ListHeader { .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .flex() .flex_1() + .items_center() + .justify_between() .w_full() .gap_1() - .items_center() .child( - div() - .flex() + h_stack() .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child(Label::new(self.label.clone()).color(LabelColor::Muted)), + .child( + div() + .flex() + .gap_1() + .items_center() + .children(self.left_icon.map(|i| { + IconElement::new(i) + .color(IconColor::Muted) + .size(IconSize::Small) + })) + .child(Label::new(self.label.clone()).color(LabelColor::Muted)), + ) + .child(disclosure_control), ) - .child(disclosure_control), + .child(meta), ) } } @@ -593,6 +610,7 @@ impl List { }; v_stack() + .w_full() .py_1() .children(self.header.map(|header| header.toggleable(self.toggleable))) .child(list_content) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 10b0e07af6..c102a2cf57 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, static_new_notification_items, static_read_notification_items}; +use crate::{prelude::*, static_new_notification_items, Icon, ListHeaderMeta}; use crate::{List, ListHeader}; #[derive(Component)] @@ -28,14 +28,16 @@ impl NotificationsPanel { .overflow_y_scroll() .child( List::new(static_new_notification_items()) - .header(ListHeader::new("NEW").toggle(ToggleState::Toggled)) - .toggle(ToggleState::Toggled), - ) - .child( - List::new(static_read_notification_items()) - .header(ListHeader::new("EARLIER").toggle(ToggleState::Toggled)) - .empty_message("No new notifications") - .toggle(ToggleState::Toggled), + .toggle(ToggleState::Toggled) + .header( + ListHeader::new("Notifications") + .toggle(ToggleState::Toggled) + .meta(Some(ListHeaderMeta::Tools(vec![ + Icon::AtSign, + Icon::BellOff, + Icon::MailOpen, + ]))), + ), ), ) } diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index f3d612562f..eef80cb5ad 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -40,7 +40,7 @@ impl IconColor { } } -#[derive(Debug, Default, PartialEq, Copy, Clone, EnumIter)] +#[derive(Debug, PartialEq, Copy, Clone, EnumIter)] pub enum Icon { Ai, ArrowLeft, @@ -67,7 +67,6 @@ pub enum Icon { Folder, FolderOpen, FolderX, - #[default] Hash, InlayHint, MagicWand, @@ -89,6 +88,11 @@ pub enum Icon { XCircle, Copilot, Envelope, + Bell, + BellOff, + BellRing, + MailOpen, + AtSign, } impl Icon { @@ -140,6 +144,11 @@ impl Icon { Icon::XCircle => "icons/error.svg", Icon::Copilot => "icons/copilot.svg", Icon::Envelope => "icons/feedback.svg", + Icon::Bell => "icons/bell.svg", + Icon::BellOff => "icons/bell-off.svg", + Icon::BellRing => "icons/bell-ring.svg", + Icon::MailOpen => "icons/mail-open.svg", + Icon::AtSign => "icons/at-sign.svg", } } } diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 7062c81954..ebbc89832c 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -7,9 +7,10 @@ use theme2::ActiveTheme; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, - HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, - Livestream, MicStatus, ModifierKeys, PaletteItem, Player, PlayerCallStatus, - PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, + HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListHeaderMeta, + ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, + PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, + VideoStatus, }; use crate::{HighlightedText, ListDetailsEntry}; @@ -327,25 +328,29 @@ pub fn static_players_with_call_status() -> Vec { pub fn static_new_notification_items() -> Vec> { vec![ - ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.") - .meta("4 people in stream."), - ListDetailsEntry::new("nathansobo accepted your contact request."), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_read_notification_items() -> Vec> { - vec![ - ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ - Button::new("Decline"), - Button::new("Accept").variant(crate::ButtonVariant::Filled), - ]), - ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") - .seen(true) - .meta("This stream has ended."), - ListDetailsEntry::new("as-cii accepted your contact request."), + ListItem::Header(ListSubHeader::new("New")), + ListItem::Details( + ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.") + .meta("4 people in stream."), + ), + ListItem::Details(ListDetailsEntry::new( + "nathansobo accepted your contact request.", + )), + ListItem::Header(ListSubHeader::new("Earlier")), + ListItem::Details( + ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![ + Button::new("Decline"), + Button::new("Accept").variant(crate::ButtonVariant::Filled), + ]), + ), + ListItem::Details( + ListDetailsEntry::new("maxdeviant invited you to a stream in #design.") + .seen(true) + .meta("This stream has ended."), + ), + ListItem::Details(ListDetailsEntry::new( + "as-cii accepted your contact request.", + )), ] .into_iter() .map(From::from)