Imporve dropdown (#73)
* Remove min-height * Provide `render_empty` for custom empty view * Fix crash when delegate is empty
This commit is contained in:
parent
81ecb1e11b
commit
d278d85058
3 changed files with 67 additions and 31 deletions
|
|
@ -43,6 +43,7 @@ pub struct DropdownStory {
|
|||
fruit_dropdown: View<Dropdown<Vec<SharedString>>>,
|
||||
simple_dropdown1: View<Dropdown<Vec<SharedString>>>,
|
||||
simple_dropdown2: View<Dropdown<Vec<SharedString>>>,
|
||||
simple_dropdown3: View<Dropdown<Vec<SharedString>>>,
|
||||
}
|
||||
|
||||
impl DropdownStory {
|
||||
|
|
@ -106,6 +107,18 @@ impl DropdownStory {
|
|||
.placeholder("Language")
|
||||
.title_prefix("Language: ")
|
||||
}),
|
||||
simple_dropdown3: cx.new_view(|cx| {
|
||||
Dropdown::string_list("string-list3", Vec::<SharedString>::new(), None, cx)
|
||||
.size(ui::Size::Small)
|
||||
.render_empty(|cx| {
|
||||
h_flex()
|
||||
.h_24()
|
||||
.justify_center()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("No Data")
|
||||
.into_any_element()
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -175,7 +188,8 @@ impl Render for DropdownStory {
|
|||
.w_128()
|
||||
.gap_2()
|
||||
.child(self.simple_dropdown1.clone())
|
||||
.child(self.simple_dropdown2.clone()),
|
||||
.child(self.simple_dropdown2.clone())
|
||||
.child(self.simple_dropdown3.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
actions, deferred, div, prelude::FluentBuilder as _, px, rems, AnyElement, AppContext,
|
||||
ClickEvent, DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement as _, Render,
|
||||
SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext,
|
||||
VisualContext as _, WeakView, WindowContext,
|
||||
actions, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, AppContext, ClickEvent,
|
||||
DismissEvent, Div, Element, ElementId, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
button::{Button, ButtonStyle},
|
||||
h_flex,
|
||||
list::{self, List, ListDelegate, ListItem},
|
||||
styled_ext::StyleSized,
|
||||
theme::ActiveTheme,
|
||||
Clickable, Icon, IconName, Size, StyledExt,
|
||||
};
|
||||
|
||||
actions!(dropdown, [Up, Down, Enter, Escape]);
|
||||
|
|
@ -20,15 +28,6 @@ pub fn init(cx: &mut AppContext) {
|
|||
])
|
||||
}
|
||||
|
||||
use crate::{
|
||||
button::{Button, ButtonStyle},
|
||||
h_flex,
|
||||
list::{self, List, ListDelegate, ListItem},
|
||||
styled_ext::StyleSized,
|
||||
theme::ActiveTheme as _,
|
||||
Clickable as _, Icon, IconName, Size, StyledExt,
|
||||
};
|
||||
|
||||
/// A trait for items that can be displayed in a dropdown.
|
||||
pub trait DropdownItem {
|
||||
type Value: Clone;
|
||||
|
|
@ -185,6 +184,7 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
|
|||
placeholder: SharedString,
|
||||
title_prefix: Option<SharedString>,
|
||||
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
||||
render_empty: Option<Rc<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
|
||||
}
|
||||
|
||||
impl<D> Dropdown<D>
|
||||
|
|
@ -214,6 +214,7 @@ where
|
|||
open: false,
|
||||
cleanable: false,
|
||||
title_prefix: None,
|
||||
render_empty: None,
|
||||
};
|
||||
this.set_selected_index(selected_index, cx);
|
||||
this
|
||||
|
|
@ -246,6 +247,14 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
pub fn render_empty<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&WindowContext) -> AnyElement + 'static,
|
||||
{
|
||||
self.render_empty = Some(Rc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_selected_index(
|
||||
&mut self,
|
||||
selected_index: Option<usize>,
|
||||
|
|
@ -326,19 +335,24 @@ where
|
|||
}
|
||||
|
||||
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
||||
let is_empty = self.list.read(cx).delegate().delegate.is_empty();
|
||||
|
||||
div()
|
||||
.absolute()
|
||||
.mt_1p5()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded(px(cx.theme().radius))
|
||||
.shadow_md()
|
||||
.track_focus(&self.list.focus_handle(cx))
|
||||
.child(self.list.clone())
|
||||
.on_mouse_down_out(|_, cx| {
|
||||
cx.dispatch_action(Box::new(Escape));
|
||||
})
|
||||
.map(|this| {
|
||||
if is_empty {
|
||||
if let Some(render_empty) = &self.render_empty {
|
||||
with_style(this, cx).child(render_empty(cx))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
} else {
|
||||
with_style(this, cx).child(self.list.clone())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
|
||||
|
|
@ -350,7 +364,7 @@ where
|
|||
.delegate
|
||||
.get(*selected_index)
|
||||
.map(|item| item.title().to_string())
|
||||
.unwrap();
|
||||
.unwrap_or_default();
|
||||
|
||||
h_flex()
|
||||
.children(self.title_prefix.clone().map(|prefix| {
|
||||
|
|
@ -367,6 +381,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn with_style(d: Focusable<Div>, cx: &WindowContext) -> Focusable<Div> {
|
||||
d.absolute()
|
||||
.mt_1p5()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().input)
|
||||
.rounded(px(cx.theme().radius))
|
||||
.shadow_md()
|
||||
}
|
||||
|
||||
impl Dropdown<Vec<SharedString>> {
|
||||
pub fn string_list(
|
||||
id: impl Into<ElementId>,
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@ use std::{cell::Cell, rc::Rc};
|
|||
|
||||
use crate::input::{InputEvent, TextInput};
|
||||
use crate::scroll::ScrollbarState;
|
||||
use crate::theme::{ActiveTheme, Colorize as _};
|
||||
use crate::theme::{ActiveTheme, Colorize};
|
||||
use crate::IconName;
|
||||
use crate::{scroll::Scrollbar, v_flex};
|
||||
use gpui::SharedString;
|
||||
use gpui::{
|
||||
actions, div, prelude::FluentBuilder as _, px, uniform_list, AppContext, FocusHandle,
|
||||
FocusableView, InteractiveElement as _, IntoElement, KeyBinding, Length, ListSizingBehavior,
|
||||
MouseButton, ParentElement as _, Render, Styled as _, Task, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext as _,
|
||||
actions, div, prelude::FluentBuilder, uniform_list, AppContext, FocusHandle, FocusableView,
|
||||
InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton,
|
||||
ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
|
||||
};
|
||||
use smol::Timer;
|
||||
|
||||
|
|
@ -326,7 +325,6 @@ where
|
|||
v_flex()
|
||||
.flex_grow()
|
||||
.relative()
|
||||
.min_h(px(100.))
|
||||
.when_some(self.max_height, |this, h| this.max_h(h))
|
||||
.overflow_hidden()
|
||||
.when(items_count == 0, |this| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue