mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-16 07:11:18 +00:00
Rework ListHeader
to be more open (#3467)
This PR reworks the `ListHeader` component to be more open. The `meta` method can now be used to append meta items of any element to the `ListHeader`, and they will be rendered with the appropriate spacing between them. Release Notes: - N/A
This commit is contained in:
parent
bd6fa66a7c
commit
e5a5b1e84c
9 changed files with 112 additions and 99 deletions
|
@ -2511,7 +2511,7 @@ impl CollabPanel {
|
|||
} else {
|
||||
el.child(
|
||||
ListHeader::new(text)
|
||||
.when_some(button, |el, button| el.right_button(button))
|
||||
.when_some(button, |el, button| el.meta(button))
|
||||
.selected(is_selected),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ pub enum ComponentStory {
|
|||
Keybinding,
|
||||
Label,
|
||||
List,
|
||||
ListHeader,
|
||||
ListItem,
|
||||
Scroll,
|
||||
Text,
|
||||
|
@ -44,6 +45,7 @@ impl ComponentStory {
|
|||
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
|
||||
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
|
||||
Self::List => cx.build_view(|_| ui::ListStory).into(),
|
||||
Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
|
||||
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
|
||||
Self::Scroll => ScrollStory::view(cx).into(),
|
||||
Self::Text => TextStory::view(cx).into(),
|
||||
|
|
|
@ -1,73 +1,11 @@
|
|||
mod list;
|
||||
mod list_header;
|
||||
mod list_item;
|
||||
mod list_separator;
|
||||
mod list_sub_header;
|
||||
|
||||
use gpui::{AnyElement, Div};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{v_stack, Label};
|
||||
|
||||
pub use list::*;
|
||||
pub use list_header::*;
|
||||
pub use list_item::*;
|
||||
pub use list_separator::*;
|
||||
pub use list_sub_header::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct List {
|
||||
/// Message to display when the list is empty
|
||||
/// Defaults to "No items"
|
||||
empty_message: SharedString,
|
||||
header: Option<ListHeader>,
|
||||
toggle: Option<bool>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl List {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
empty_message: "No items".into(),
|
||||
header: None,
|
||||
toggle: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
|
||||
self.empty_message = empty_message.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, header: ListHeader) -> Self {
|
||||
self.header = Some(header);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
|
||||
self.toggle = toggle.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for List {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for List {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
v_stack()
|
||||
.w_full()
|
||||
.py_1()
|
||||
.children(self.header.map(|header| header))
|
||||
.map(|this| match (self.children.is_empty(), self.toggle) {
|
||||
(false, _) => this.children(self.children),
|
||||
(true, Some(false)) => this,
|
||||
(true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
60
crates/ui2/src/components/list/list.rs
Normal file
60
crates/ui2/src/components/list/list.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use gpui::{AnyElement, Div};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{prelude::*, v_stack, Label, ListHeader};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct List {
|
||||
/// Message to display when the list is empty
|
||||
/// Defaults to "No items"
|
||||
empty_message: SharedString,
|
||||
header: Option<ListHeader>,
|
||||
toggle: Option<bool>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl List {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
empty_message: "No items".into(),
|
||||
header: None,
|
||||
toggle: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
|
||||
self.empty_message = empty_message.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
|
||||
self.header = header.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
|
||||
self.toggle = toggle.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for List {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for List {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
v_stack().w_full().py_1().children(self.header).map(|this| {
|
||||
match (self.children.is_empty(), self.toggle) {
|
||||
(false, _) => this.children(self.children),
|
||||
(true, Some(false)) => this,
|
||||
(true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,22 +1,16 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use gpui::{ClickEvent, Div};
|
||||
use gpui::{AnyElement, ClickEvent, Div};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label};
|
||||
|
||||
pub enum ListHeaderMeta {
|
||||
Tools(Vec<IconButton>),
|
||||
// TODO: This should be a button
|
||||
Button(Label),
|
||||
Text(Label),
|
||||
}
|
||||
use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ListHeader {
|
||||
label: SharedString,
|
||||
left_icon: Option<Icon>,
|
||||
meta: Option<ListHeaderMeta>,
|
||||
meta: SmallVec<[AnyElement; 2]>,
|
||||
toggle: Option<bool>,
|
||||
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
inset: bool,
|
||||
|
@ -28,7 +22,7 @@ impl ListHeader {
|
|||
Self {
|
||||
label: label.into(),
|
||||
left_icon: None,
|
||||
meta: None,
|
||||
meta: SmallVec::new(),
|
||||
inset: false,
|
||||
toggle: None,
|
||||
on_toggle: None,
|
||||
|
@ -49,17 +43,13 @@ impl ListHeader {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||
self.left_icon = left_icon;
|
||||
pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
|
||||
self.left_icon = left_icon.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_button(self, button: IconButton) -> Self {
|
||||
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
|
||||
self.meta = meta;
|
||||
pub fn meta(mut self, meta: impl IntoElement) -> Self {
|
||||
self.meta.push(meta.into_any_element());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -75,18 +65,6 @@ impl RenderOnce for ListHeader {
|
|||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let meta = match self.meta {
|
||||
Some(ListHeaderMeta::Tools(icons)) => div().child(
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.children(icons.into_iter().map(|i| i.icon_color(Color::Muted))),
|
||||
),
|
||||
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||
None => div(),
|
||||
};
|
||||
|
||||
h_stack().w_full().relative().child(
|
||||
div()
|
||||
.h_5()
|
||||
|
@ -120,7 +98,7 @@ impl RenderOnce for ListHeader {
|
|||
.map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
|
||||
),
|
||||
)
|
||||
.child(meta),
|
||||
.child(h_stack().gap_2().items_center().children(self.meta)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ mod icon_button;
|
|||
mod keybinding;
|
||||
mod label;
|
||||
mod list;
|
||||
mod list_header;
|
||||
mod list_item;
|
||||
|
||||
pub use avatar::*;
|
||||
|
@ -20,4 +21,5 @@ pub use icon_button::*;
|
|||
pub use keybinding::*;
|
||||
pub use label::*;
|
||||
pub use list::*;
|
||||
pub use list_header::*;
|
||||
pub use list_item::*;
|
||||
|
|
|
@ -22,12 +22,12 @@ impl Render for ListStory {
|
|||
.child(Story::label("With sections"))
|
||||
.child(
|
||||
List::new()
|
||||
.child(ListHeader::new("Fruits"))
|
||||
.header(ListHeader::new("Produce"))
|
||||
.child(ListSubHeader::new("Fruits"))
|
||||
.child(ListItem::new("apple").child("Apple"))
|
||||
.child(ListItem::new("banana").child("Banana"))
|
||||
.child(ListItem::new("cherry").child("Cherry"))
|
||||
.child(ListSeparator)
|
||||
.child(ListHeader::new("Vegetables"))
|
||||
.child(ListSubHeader::new("Root Vegetables"))
|
||||
.child(ListItem::new("carrot").child("Carrot"))
|
||||
.child(ListItem::new("potato").child("Potato"))
|
||||
|
|
33
crates/ui2/src/components/stories/list_header.rs
Normal file
33
crates/ui2/src/components/stories/list_header.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use gpui::{Div, Render};
|
||||
use story::Story;
|
||||
|
||||
use crate::{prelude::*, IconButton};
|
||||
use crate::{Icon, ListHeader};
|
||||
|
||||
pub struct ListHeaderStory;
|
||||
|
||||
impl Render for ListHeaderStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<ListHeader>())
|
||||
.child(Story::label("Default"))
|
||||
.child(ListHeader::new("Section 1"))
|
||||
.child(Story::label("With left icon"))
|
||||
.child(ListHeader::new("Section 2").left_icon(Icon::Bell))
|
||||
.child(Story::label("With left icon and meta"))
|
||||
.child(
|
||||
ListHeader::new("Section 3")
|
||||
.left_icon(Icon::BellOff)
|
||||
.meta(IconButton::new("action_1", Icon::Bolt)),
|
||||
)
|
||||
.child(Story::label("With multiple meta"))
|
||||
.child(
|
||||
ListHeader::new("Section 4")
|
||||
.meta(IconButton::new("action_1", Icon::Bolt))
|
||||
.meta(IconButton::new("action_2", Icon::ExclamationTriangle))
|
||||
.meta(IconButton::new("action_3", Icon::Plus)),
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue