Mail · Boxes and lists: mailboxes, folders and labels, the mail list, inbox zero
MailboxList sets mailboxes, folders and labels in sections: a press, an arrow or Enter opens one, the open one lit; a box with mail unread stands out with its count, and a label wears one of the theme's chart hues. MailList holds messages in a multiple-select list: the sender strong beside a dot while unread, the subject and first words under it, the time today, the weekday this week, the date this year or with its year, a paperclip for files, and a star whose press stays off the row. InboxZeroState points to feedback::EmptyState. Sections of selectable lists now have one home, lists::Sections, which ChannelList and MemberList use too; ListItem gains a third line.
This commit is contained in:
1 parent
7b38f99dcf
commit
7d20567921
17 files changed
+1244
-153
No files matched your search
@@ -20,6 +20,7 @@ pub struct ListItem {
|
||||
id: ElementId,
|
||||
title: SharedString,
|
||||
description: Option<SharedString>,
|
||||
detail: Option<SharedString>,
|
||||
leading: Option<AnyElement>,
|
||||
trailing: Option<AnyElement>,
|
||||
selected: bool,
|
||||
@@ -36,6 +37,7 @@ impl ListItem {
|
||||
id: id.into(),
|
||||
title: title.into(),
|
||||
description: None,
|
||||
detail: None,
|
||||
leading: None,
|
||||
trailing: None,
|
||||
selected: false,
|
||||
@@ -65,6 +67,12 @@ impl ListItem {
|
||||
self
|
||||
}
|
||||
|
||||
/// A third line, quieter still, such as a message's first words.
|
||||
pub fn detail(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.detail = Some(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// An icon or an avatar before the text.
|
||||
pub fn leading(mut self, element: impl IntoElement) -> Self {
|
||||
self.leading = Some(element.into_any_element());
|
||||
@@ -164,6 +172,11 @@ impl RenderOnce for ListItem {
|
||||
line(text)
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.text_color(colors.fg_muted)
|
||||
}))
|
||||
.children(self.detail.map(|text| {
|
||||
line(text)
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.text_color(colors.fg_subtle)
|
||||
})),
|
||||
)
|
||||
.children(self.trailing.map(|trailing| {
|
||||
|
||||
@@ -4,6 +4,7 @@ mod grouped;
|
||||
mod item;
|
||||
mod long;
|
||||
mod outline;
|
||||
mod sections;
|
||||
mod select;
|
||||
mod sortable;
|
||||
mod swipe;
|
||||
@@ -19,6 +20,7 @@ pub use grouped::GroupedList;
|
||||
pub use item::{List, ListItem};
|
||||
pub use long::{InfiniteList, VirtualList};
|
||||
pub use outline::Outline;
|
||||
pub(crate) use sections::Sections;
|
||||
pub use select::SelectableList;
|
||||
pub(crate) use select::{Pick, picked};
|
||||
pub use sortable::SortableList;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
use gpui::{
|
||||
App, ElementId, FontWeight, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
||||
Window, div,
|
||||
};
|
||||
|
||||
use super::{ListItem, SelectableList};
|
||||
use crate::{
|
||||
forms::OnValue,
|
||||
theme::{ActiveTheme, TextSize},
|
||||
};
|
||||
|
||||
/// Lists under titles down a column, each a `SelectableList` keyed by its title under the id: Tab moves between them, and the chosen key is lit wherever it sits. A press or an arrow selects, Enter or a double press activates. Owners check their titles and keys.
|
||||
#[derive(IntoElement)]
|
||||
pub(crate) struct Sections {
|
||||
id: ElementId,
|
||||
sections: Vec<(SharedString, Vec<(SharedString, ListItem)>)>,
|
||||
counted: bool,
|
||||
selected: Option<SharedString>,
|
||||
on_select: Option<OnValue>,
|
||||
on_activate: Option<OnValue>,
|
||||
}
|
||||
|
||||
impl Sections {
|
||||
pub(crate) fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
sections: Vec::new(),
|
||||
counted: false,
|
||||
selected: None,
|
||||
on_select: None,
|
||||
on_activate: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn section(
|
||||
mut self,
|
||||
title: SharedString,
|
||||
rows: Vec<(SharedString, ListItem)>,
|
||||
) -> Self {
|
||||
self.sections.push((title, rows));
|
||||
self
|
||||
}
|
||||
|
||||
/// Shows how many rows each section holds beside its title.
|
||||
pub(crate) fn counted(mut self) -> Self {
|
||||
self.counted = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn selected(mut self, key: Option<SharedString>) -> Self {
|
||||
self.selected = key;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn on_select(mut self, handler: OnValue) -> Self {
|
||||
self.on_select = Some(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn on_activate(mut self, handler: OnValue) -> Self {
|
||||
self.on_activate = Some(handler);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Sections {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let colors = &theme.colors;
|
||||
let sections =
|
||||
self.sections.into_iter().map(|(title, rows)| {
|
||||
let count = rows.len();
|
||||
let lit = self
|
||||
.selected
|
||||
.clone()
|
||||
.filter(|key| rows.iter().any(|(each, _)| each == key));
|
||||
let list = rows.into_iter().fold(
|
||||
SelectableList::new((self.id.clone(), format!("section-{title}"))),
|
||||
|list, (key, row)| list.row(key, row),
|
||||
);
|
||||
let (select, activate) = (self.on_select.clone(), self.on_activate.clone());
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.flex()
|
||||
.items_baseline()
|
||||
.gap_2()
|
||||
.text_size(theme.text_size(TextSize::Xs))
|
||||
.child(
|
||||
div()
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(colors.fg_muted)
|
||||
.child(title),
|
||||
)
|
||||
.children(self.counted.then(|| {
|
||||
div().text_color(colors.fg_subtle).child(count.to_string())
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
list.selected(lit)
|
||||
.on_change(move |keys, window, cx| {
|
||||
let key = keys.first().expect("a pick names a row");
|
||||
if let Some(select) = &select {
|
||||
select(key, window, cx);
|
||||
}
|
||||
})
|
||||
.on_activate(move |key, window, cx| {
|
||||
if let Some(activate) = &activate {
|
||||
activate(key, window, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
div().w_full().flex().flex_col().gap_4().children(sections)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user