Improve Dropdown to Add search support and custom icon (#106)
- Add SearchableVec to let Dropdown support search filter. - Fix dropdown title overflow. - Add to support custom dropdown right icon. https://github.com/user-attachments/assets/b9e28fce-3e0f-44f7-a0f4-3e01f6db31d1
This commit is contained in:
parent
12b5d1b501
commit
553b6409c0
3 changed files with 220 additions and 76 deletions
|
|
@ -4,10 +4,10 @@ use gpui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
dropdown::{Dropdown, DropdownEvent, DropdownItem},
|
dropdown::{Dropdown, DropdownEvent, DropdownItem, SearchableVec},
|
||||||
h_flex,
|
h_flex,
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
v_flex, Selection,
|
v_flex, IconName, Selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Country {
|
struct Country {
|
||||||
|
|
@ -38,7 +38,7 @@ impl DropdownItem for Country {
|
||||||
|
|
||||||
pub struct DropdownStory {
|
pub struct DropdownStory {
|
||||||
country_dropdown: View<Dropdown<Vec<Country>>>,
|
country_dropdown: View<Dropdown<Vec<Country>>>,
|
||||||
fruit_dropdown: View<Dropdown<Vec<SharedString>>>,
|
fruit_dropdown: View<Dropdown<SearchableVec<SharedString>>>,
|
||||||
simple_dropdown1: View<Dropdown<Vec<SharedString>>>,
|
simple_dropdown1: View<Dropdown<Vec<SharedString>>>,
|
||||||
simple_dropdown2: View<Dropdown<Vec<SharedString>>>,
|
simple_dropdown2: View<Dropdown<Vec<SharedString>>>,
|
||||||
simple_dropdown3: View<Dropdown<Vec<SharedString>>>,
|
simple_dropdown3: View<Dropdown<Vec<SharedString>>>,
|
||||||
|
|
@ -64,17 +64,21 @@ impl DropdownStory {
|
||||||
Dropdown::new("dropdown-country", countries, Some(6), cx).cleanable(true)
|
Dropdown::new("dropdown-country", countries, Some(6), cx).cleanable(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
let fruits = vec![
|
let fruits = SearchableVec::new(vec![
|
||||||
"Apple",
|
"Apple".into(),
|
||||||
"Orange",
|
"Orange".into(),
|
||||||
"Banana",
|
"Banana".into(),
|
||||||
"Grape",
|
"Grape".into(),
|
||||||
"Pineapple",
|
"Pineapple".into(),
|
||||||
"Watermelon & This is a longlonglonglonglonglonglonglonglong title",
|
"Watermelon & This is a longlonglonglonglonglonglonglonglong title".into(),
|
||||||
"Avocado",
|
"Avocado".into(),
|
||||||
];
|
]);
|
||||||
let fruit_dropdown =
|
let fruit_dropdown = cx.new_view(|cx| {
|
||||||
cx.new_view(|cx| Dropdown::string_list("dropdown-fruits", fruits, None, cx));
|
Dropdown::new("dropdown-fruits", fruits, None, cx)
|
||||||
|
.icon(IconName::Search)
|
||||||
|
.width(px(200.))
|
||||||
|
.menu_width(px(320.))
|
||||||
|
});
|
||||||
|
|
||||||
cx.new_view(|cx| {
|
cx.new_view(|cx| {
|
||||||
cx.subscribe(&country_dropdown, Self::on_dropdown_event)
|
cx.subscribe(&country_dropdown, Self::on_dropdown_event)
|
||||||
|
|
@ -84,9 +88,9 @@ impl DropdownStory {
|
||||||
country_dropdown,
|
country_dropdown,
|
||||||
fruit_dropdown,
|
fruit_dropdown,
|
||||||
simple_dropdown1: cx.new_view(|cx| {
|
simple_dropdown1: cx.new_view(|cx| {
|
||||||
Dropdown::string_list(
|
Dropdown::new(
|
||||||
"string-list1",
|
"string-list1",
|
||||||
vec!["QPUI", "Iced", "QT", "Cocoa"],
|
vec!["QPUI".into(), "Iced".into(), "QT".into(), "Cocoa".into()],
|
||||||
Some(0),
|
Some(0),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
@ -95,9 +99,14 @@ impl DropdownStory {
|
||||||
.title_prefix("UI: ")
|
.title_prefix("UI: ")
|
||||||
}),
|
}),
|
||||||
simple_dropdown2: cx.new_view(|cx| {
|
simple_dropdown2: cx.new_view(|cx| {
|
||||||
Dropdown::string_list(
|
Dropdown::new(
|
||||||
"string-list2",
|
"string-list2",
|
||||||
vec!["Rust", "Go", "C++", "JavaScript"],
|
vec![
|
||||||
|
"Rust".into(),
|
||||||
|
"Go".into(),
|
||||||
|
"C++".into(),
|
||||||
|
"JavaScript".into(),
|
||||||
|
],
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
@ -106,7 +115,7 @@ impl DropdownStory {
|
||||||
.title_prefix("Language: ")
|
.title_prefix("Language: ")
|
||||||
}),
|
}),
|
||||||
simple_dropdown3: cx.new_view(|cx| {
|
simple_dropdown3: cx.new_view(|cx| {
|
||||||
Dropdown::string_list("string-list3", Vec::<SharedString>::new(), None, cx)
|
Dropdown::new("string-list3", Vec::<SharedString>::new(), None, cx)
|
||||||
.size(ui::Size::Small)
|
.size(ui::Size::Small)
|
||||||
.empty(|cx| {
|
.empty(|cx| {
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, AppContext, ClickEvent,
|
actions, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, AppContext, ClickEvent,
|
||||||
DismissEvent, Div, Element, ElementId, EventEmitter, FocusHandle, Focusable, FocusableView,
|
DismissEvent, Div, Element, ElementId, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||||
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement, Render, SharedString,
|
InteractiveElement, IntoElement, KeyBinding, LayoutId, Length, ParentElement, Render,
|
||||||
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WeakView, WindowContext,
|
SharedString, StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext,
|
||||||
|
WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -57,7 +58,7 @@ impl DropdownItem for SharedString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DropdownDelegate {
|
pub trait DropdownDelegate: Sized {
|
||||||
type Item: DropdownItem;
|
type Item: DropdownItem;
|
||||||
|
|
||||||
fn len(&self) -> usize;
|
fn len(&self) -> usize;
|
||||||
|
|
@ -75,6 +76,14 @@ pub trait DropdownDelegate {
|
||||||
{
|
{
|
||||||
(0..self.len()).find(|&i| self.get(i).map_or(false, |item| item.value() == value))
|
(0..self.len()).find(|&i| self.get(i).map_or(false, |item| item.value() == value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_search(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_search(&mut self, _query: &str, _cx: &mut ViewContext<Dropdown<Self>>) -> Task<()> {
|
||||||
|
Task::Ready(Some(()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DropdownItem> DropdownDelegate for Vec<T> {
|
impl<T: DropdownItem> DropdownDelegate for Vec<T> {
|
||||||
|
|
@ -129,15 +138,11 @@ where
|
||||||
if let Some(item) = self.delegate.get(ix) {
|
if let Some(item) = self.delegate.get(ix) {
|
||||||
let list_item = ListItem::new(("list-item", ix))
|
let list_item = ListItem::new(("list-item", ix))
|
||||||
.check_icon(IconName::Check)
|
.check_icon(IconName::Check)
|
||||||
|
.cursor_pointer()
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.input_text_size(size)
|
.input_text_size(size)
|
||||||
.list_size(size)
|
.list_size(size)
|
||||||
.child(
|
.child(div().whitespace_nowrap().child(item.title().to_string()));
|
||||||
div()
|
|
||||||
.whitespace_nowrap()
|
|
||||||
.overflow_hidden()
|
|
||||||
.child(item.title().to_string()),
|
|
||||||
);
|
|
||||||
Some(list_item)
|
Some(list_item)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -170,9 +175,34 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) -> Task<()> {
|
||||||
|
self.dropdown
|
||||||
|
.upgrade()
|
||||||
|
.map_or(Task::Ready(None), |dropdown| {
|
||||||
|
dropdown.update(cx, |_, cx| self.delegate.perform_search(query, cx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn set_selected_index(&mut self, ix: Option<usize>, _: &mut ViewContext<List<Self>>) {
|
fn set_selected_index(&mut self, ix: Option<usize>, _: &mut ViewContext<List<Self>>) {
|
||||||
self.selected_index = ix;
|
self.selected_index = ix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_empty(&self, cx: &mut ViewContext<List<Self>>) -> impl IntoElement {
|
||||||
|
if let Some(empty) = self
|
||||||
|
.dropdown
|
||||||
|
.upgrade()
|
||||||
|
.and_then(|dropdown| dropdown.read(cx).empty.as_ref())
|
||||||
|
{
|
||||||
|
empty(cx).into_any_element()
|
||||||
|
} else {
|
||||||
|
h_flex()
|
||||||
|
.justify_center()
|
||||||
|
.py_6()
|
||||||
|
.text_color(cx.theme().muted)
|
||||||
|
.child(Icon::new(IconName::Inbox).size(px(28.)))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum DropdownEvent<D: DropdownDelegate + 'static> {
|
pub enum DropdownEvent<D: DropdownDelegate + 'static> {
|
||||||
|
|
@ -184,12 +214,80 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
list: View<List<DropdownListDelegate<D>>>,
|
list: View<List<DropdownListDelegate<D>>>,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
icon: Option<IconName>,
|
||||||
open: bool,
|
open: bool,
|
||||||
cleanable: bool,
|
cleanable: bool,
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
title_prefix: Option<SharedString>,
|
title_prefix: Option<SharedString>,
|
||||||
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
||||||
empty: Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
|
empty: Option<Box<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
|
||||||
|
width: Length,
|
||||||
|
menu_width: Length,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SearchableVec<T> {
|
||||||
|
items: Vec<T>,
|
||||||
|
matched_items: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: DropdownItem + Clone> SearchableVec<T> {
|
||||||
|
pub fn new(items: impl Into<Vec<T>>) -> Self {
|
||||||
|
let items = items.into();
|
||||||
|
Self {
|
||||||
|
items: items.clone(),
|
||||||
|
matched_items: items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: DropdownItem + Clone> DropdownDelegate for SearchableVec<T> {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.matched_items.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, ix: usize) -> Option<&Self::Item> {
|
||||||
|
self.matched_items.get(ix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position<V>(&self, value: &V) -> Option<usize>
|
||||||
|
where
|
||||||
|
Self::Item: DropdownItem<Value = V>,
|
||||||
|
V: PartialEq,
|
||||||
|
{
|
||||||
|
for (ix, item) in self.matched_items.iter().enumerate() {
|
||||||
|
if item.value() == value {
|
||||||
|
return Some(ix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_search(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_search(&mut self, query: &str, _cx: &mut ViewContext<Dropdown<Self>>) -> Task<()> {
|
||||||
|
self.matched_items = self
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.title().to_lowercase().contains(&query.to_lowercase()))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Task::Ready(Some(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<SharedString>> for SearchableVec<SharedString> {
|
||||||
|
fn from(items: Vec<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
items: items.clone(),
|
||||||
|
matched_items: items,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dropdown<D>
|
impl<D> Dropdown<D>
|
||||||
|
|
@ -208,18 +306,29 @@ where
|
||||||
selected_index,
|
selected_index,
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = cx.new_view(|cx| List::new(delegate, cx).no_query().max_h(rems(20.)));
|
let searchable = delegate.delegate.can_search();
|
||||||
|
|
||||||
|
let list = cx.new_view(|cx| {
|
||||||
|
let mut list = List::new(delegate, cx).max_h(rems(20.));
|
||||||
|
if !searchable {
|
||||||
|
list = list.no_query();
|
||||||
|
}
|
||||||
|
list
|
||||||
|
});
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
placeholder: "Select...".into(),
|
placeholder: "Select...".into(),
|
||||||
list,
|
list,
|
||||||
size: Size::Medium,
|
size: Size::Medium,
|
||||||
|
icon: None,
|
||||||
selected_value: None,
|
selected_value: None,
|
||||||
open: false,
|
open: false,
|
||||||
cleanable: false,
|
cleanable: false,
|
||||||
title_prefix: None,
|
title_prefix: None,
|
||||||
empty: None,
|
empty: None,
|
||||||
|
width: Length::Auto,
|
||||||
|
menu_width: Length::Auto,
|
||||||
};
|
};
|
||||||
this.set_selected_index(selected_index, cx);
|
this.set_selected_index(selected_index, cx);
|
||||||
this
|
this
|
||||||
|
|
@ -230,12 +339,30 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the width of the dropdown input, default: Length::Auto
|
||||||
|
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||||
|
self.width = width.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the width of the dropdown menu, default: Length::Auto
|
||||||
|
pub fn menu_width(mut self, width: impl Into<Length>) -> Self {
|
||||||
|
self.menu_width = width.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the placeholder for display when dropdown value is empty.
|
/// Set the placeholder for display when dropdown value is empty.
|
||||||
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
|
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
|
||||||
self.placeholder = placeholder.into();
|
self.placeholder = placeholder.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the right icon for the dropdown input, instead of the default arrow icon.
|
||||||
|
pub fn icon(mut self, icon: impl Into<IconName>) -> Self {
|
||||||
|
self.icon = Some(icon.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set title prefix for the dropdown.
|
/// Set title prefix for the dropdown.
|
||||||
///
|
///
|
||||||
/// e.g.: Country: United States
|
/// e.g.: Country: United States
|
||||||
|
|
@ -332,6 +459,9 @@ where
|
||||||
|
|
||||||
fn toggle_menu(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
fn toggle_menu(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
self.open = !self.open;
|
self.open = !self.open;
|
||||||
|
if self.open {
|
||||||
|
self.list.focus_handle(cx).focus(cx);
|
||||||
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,24 +475,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
||||||
let is_empty = self.list.read(cx).delegate().delegate.is_empty();
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.list.focus_handle(cx))
|
.track_focus(&self.list.focus_handle(cx))
|
||||||
.on_mouse_down_out(|_, cx| {
|
.on_mouse_down_out(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(Escape));
|
cx.dispatch_action(Box::new(Escape));
|
||||||
})
|
})
|
||||||
.map(|this| {
|
.map(|this| with_style(this, cx).child(self.list.clone()))
|
||||||
if is_empty {
|
|
||||||
if let Some(render_empty) = &self.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 {
|
fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
|
||||||
|
|
@ -401,18 +519,6 @@ fn with_style(d: Focusable<Div>, cx: &WindowContext) -> Focusable<Div> {
|
||||||
.shadow_md()
|
.shadow_md()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dropdown<Vec<SharedString>> {
|
|
||||||
pub fn string_list(
|
|
||||||
id: impl Into<ElementId>,
|
|
||||||
items: Vec<impl Into<SharedString>>,
|
|
||||||
selected_index: Option<usize>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let items = items.into_iter().map(Into::into).collect();
|
|
||||||
Self::new(id, items, selected_index, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> EventEmitter<DropdownEvent<D>> for Dropdown<D> where D: DropdownDelegate + 'static {}
|
impl<D> EventEmitter<DropdownEvent<D>> for Dropdown<D> where D: DropdownDelegate + 'static {}
|
||||||
impl<D> EventEmitter<DismissEvent> for Dropdown<D> where D: DropdownDelegate + 'static {}
|
impl<D> EventEmitter<DismissEvent> for Dropdown<D> where D: DropdownDelegate + 'static {}
|
||||||
impl<D> FocusableView for Dropdown<D>
|
impl<D> FocusableView for Dropdown<D>
|
||||||
|
|
@ -448,7 +554,6 @@ where
|
||||||
.id("dropdown-input")
|
.id("dropdown-input")
|
||||||
.relative()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
.w_full()
|
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.bg(cx.theme().background)
|
.bg(cx.theme().background)
|
||||||
|
|
@ -456,6 +561,13 @@ where
|
||||||
.border_color(cx.theme().input)
|
.border_color(cx.theme().input)
|
||||||
.rounded(px(cx.theme().radius))
|
.rounded(px(cx.theme().radius))
|
||||||
.shadow_sm()
|
.shadow_sm()
|
||||||
|
.cursor_pointer()
|
||||||
|
.overflow_hidden()
|
||||||
|
.input_text_size(self.size)
|
||||||
|
.map(|this| match self.width {
|
||||||
|
Length::Definite(l) => this.flex_none().w(l),
|
||||||
|
Length::Auto => this.w_full(),
|
||||||
|
})
|
||||||
.when(is_focused, |this| this.outline(cx))
|
.when(is_focused, |this| this.outline(cx))
|
||||||
.input_size(self.size)
|
.input_size(self.size)
|
||||||
.when(!self.open, |this| {
|
.when(!self.open, |this| {
|
||||||
|
|
@ -466,11 +578,17 @@ where
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(div().flex_1().child(self.display_title(cx)))
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(self.display_title(cx)),
|
||||||
|
)
|
||||||
.when(show_clean, |this| {
|
.when(show_clean, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("clean", cx)
|
Button::new("clean", cx)
|
||||||
.icon(IconName::Close)
|
.icon(IconName::CircleX)
|
||||||
.style(ButtonStyle::Ghost)
|
.style(ButtonStyle::Ghost)
|
||||||
.size(px(14.))
|
.size(px(14.))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
|
|
@ -478,17 +596,34 @@ where
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(!show_clean, |this| {
|
.when(!show_clean, |this| {
|
||||||
this.child(
|
let icon = match self.icon.clone() {
|
||||||
Icon::new(IconName::ChevronDown)
|
Some(icon) => icon,
|
||||||
.text_color(cx.theme().muted_foreground),
|
None => {
|
||||||
)
|
if self.open {
|
||||||
|
IconName::ChevronUp
|
||||||
|
} else {
|
||||||
|
IconName::ChevronDown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.child(Icon::new(icon).text_color(cx.theme().muted_foreground))
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(DropdownMenuElement {
|
.child(
|
||||||
id: "dropdown-menu".into(),
|
div()
|
||||||
dropdown: cx.view().clone(),
|
.absolute()
|
||||||
})
|
.overflow_hidden()
|
||||||
|
.map(|this| match self.menu_width {
|
||||||
|
Length::Auto => this,
|
||||||
|
Length::Definite(l) => this.w(l),
|
||||||
|
})
|
||||||
|
.child(DropdownMenuElement {
|
||||||
|
id: "dropdown-menu".into(),
|
||||||
|
dropdown: cx.view().clone(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable};
|
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable, Size};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
|
|
@ -130,17 +130,17 @@ impl RenderOnce for ListItem {
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.gap_x_1()
|
.gap_x_1()
|
||||||
.child(div().w_full().children(self.children))
|
.child(div().w_full().overflow_hidden().children(self.children))
|
||||||
.when_some(self.check_icon, |this, icon| {
|
.when_some(self.check_icon, |this, icon| {
|
||||||
this.child(
|
this.child(div().w_5().items_center().justify_center().when(
|
||||||
div()
|
self.selected,
|
||||||
.w_5()
|
|this| {
|
||||||
.items_center()
|
this.child(
|
||||||
.justify_center()
|
icon.size(Size::Small)
|
||||||
.when(self.selected, |this| {
|
.text_color(cx.theme().muted_foreground),
|
||||||
this.child(icon.text_color(cx.theme().muted_foreground))
|
)
|
||||||
}),
|
},
|
||||||
)
|
))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when_some(self.suffix, |this, suffix| this.child(suffix(cx)))
|
.when_some(self.suffix, |this, suffix| this.child(suffix(cx)))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue