Allow ListHeader to take a meta

This commit is contained in:
Nate Butler 2023-11-01 12:43:25 -04:00
parent 6ed60769ac
commit 8dafd5f1f3
9 changed files with 105 additions and 74 deletions

1
assets/icons/at-sign.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-at-sign"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/></svg>

After

Width:  |  Height:  |  Size: 300 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-off"><path d="M8.7 3A6 6 0 0 1 18 8a21.3 21.3 0 0 0 .6 5"/><path d="M17 17H3s3-2 3-9a4.67 4.67 0 0 1 .3-1.7"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="m2 2 20 20"/></svg>

After

Width:  |  Height:  |  Size: 387 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-ring"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/><path d="M4 2C2.8 3.7 2 5.7 2 8"/><path d="M22 8c0-2.3-.8-4.3-2-6"/></svg>

After

Width:  |  Height:  |  Size: 382 B

View file

@ -1,8 +1 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.60124 1.25086C8.60124 1.75459 8.26278 2.17927 7.80087 2.30989C10.1459 2.4647 12 4.41582 12 6.79999V10.25C12 11.0563 12.0329 11.7074 12.7236 12.0528C12.931 12.1565 13.0399 12.3892 12.9866 12.6149C12.9333 12.8406 12.7319 13 12.5 13H8.16144C8.36904 13.1832 8.49997 13.4513 8.49997 13.75C8.49997 14.3023 8.05226 14.75 7.49997 14.75C6.94769 14.75 6.49997 14.3023 6.49997 13.75C6.49997 13.4513 6.63091 13.1832 6.83851 13H2.49999C2.2681 13 2.06664 12.8406 2.01336 12.6149C1.96009 12.3892 2.06897 12.1565 2.27638 12.0528C2.96708 11.7074 2.99999 11.0563 2.99999 10.25V6.79999C2.99999 4.41537 4.85481 2.46396 7.20042 2.3098C6.73867 2.17908 6.40036 1.75448 6.40036 1.25086C6.40036 0.643104 6.89304 0.150421 7.5008 0.150421C8.10855 0.150421 8.60124 0.643104 8.60124 1.25086ZM7.49999 3.29999C5.56699 3.29999 3.99999 4.86699 3.99999 6.79999V10.25L4.00002 10.3009C4.0005 10.7463 4.00121 11.4084 3.69929 12H11.3007C10.9988 11.4084 10.9995 10.7463 11 10.3009L11 10.25V6.79999C11 4.86699 9.43299 3.29999 7.49999 3.29999Z"
fill="currentColor"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 309 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail-open"><path d="M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0l8 6Z"/><path d="m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10"/></svg>

After

Width:  |  Height:  |  Size: 390 B

View file

@ -15,12 +15,20 @@ pub enum ListItemVariant {
Inset,
}
pub enum ListHeaderMeta {
// TODO: These should be IconButtons
Tools(Vec<Icon>),
// TODO: This should be a button
Button(Label),
Text(Label),
}
#[derive(Component)]
pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
meta: Option<ListHeaderMeta>,
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<ListHeaderMeta>) -> 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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
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<V: 'static> List<V> {
};
v_stack()
.w_full()
.py_1()
.children(self.header.map(|header| header.toggleable(self.toggleable)))
.child(list_content)

View file

@ -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,
]))),
),
),
)
}

View file

@ -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",
}
}
}

View file

@ -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<PlayerWithCallStatus> {
pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> {
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<V: 'static>() -> Vec<ListItem<V>> {
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)