diff --git a/crates/story/src/drawer_story.rs b/crates/story/src/drawer_story.rs index 4b17320e..b22c72aa 100644 --- a/crates/story/src/drawer_story.rs +++ b/crates/story/src/drawer_story.rs @@ -243,7 +243,7 @@ impl DrawerStory { matches: items.clone(), }; let list = cx.new(|cx| { - let mut list = ListState::new(delegate, window, cx); + let mut list = ListState::new(delegate, window, cx).searchable(true); list.focus(window, cx); list }); @@ -304,7 +304,6 @@ impl DrawerStory { ) .child( List::new(&list) - .searchable(true) .border_1() .border_color(cx.theme().border) .rounded(cx.theme().radius) diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index fa3ab046..b6556e98 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -367,7 +367,7 @@ impl ListStory { }; delegate.extend_more(100); - let company_list = cx.new(|cx| ListState::new(delegate, window, cx)); + let company_list = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true)); let _subscriptions = vec![ @@ -536,7 +536,6 @@ impl Render for ListStory { ) .child( List::new(&self.company_list) - .searchable(true) .p(px(8.)) .flex_1() .w_full() diff --git a/crates/story/src/select_story.rs b/crates/story/src/select_story.rs index 182dc5c7..4c06fccd 100644 --- a/crates/story/src/select_story.rs +++ b/crates/story/src/select_story.rs @@ -85,6 +85,7 @@ impl SelectStory { window, cx, ) + .searchable(true) }); let appearance_select = cx.new(|cx| { SelectState::new( @@ -111,7 +112,7 @@ impl SelectStory { "Watermelon & This is a long long long long long long long long long title", "Avocado", ]); - let fruit_select = cx.new(|cx| SelectState::new(fruits, None, window, cx)); + let fruit_select = cx.new(|cx| SelectState::new(fruits, None, window, cx).searchable(true)); cx.new(|cx| { cx.subscribe_in(&country_select, window, Self::on_select_event) @@ -191,7 +192,6 @@ impl Render for SelectStory { .child( section("Select").max_w_128().child( Select::new(&self.country_select) - .searchable(true) .search_placeholder("Search country by name or code") .cleanable() .disabled(self.disabled), @@ -200,7 +200,6 @@ impl Render for SelectStory { .child( section("Searchable").max_w_128().child( Select::new(&self.fruit_select) - .searchable(true) .disabled(self.disabled) .icon(IconName::Search) .w(px(320.)) diff --git a/crates/ui/src/input/popovers/code_action_menu.rs b/crates/ui/src/input/popovers/code_action_menu.rs index 45e3dd72..77a960f3 100644 --- a/crates/ui/src/input/popovers/code_action_menu.rs +++ b/crates/ui/src/input/popovers/code_action_menu.rs @@ -327,11 +327,7 @@ impl Render for CodeActionMenu { .top(pos.y) .max_w(max_width) .min_w(px(120.)) - .child( - List::new(&self.list) - .searchable(false) - .max_h(MAX_MENU_HEIGHT), - ) + .child(List::new(&self.list).max_h(MAX_MENU_HEIGHT)) .child( canvas( move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds), diff --git a/crates/ui/src/input/popovers/completion_menu.rs b/crates/ui/src/input/popovers/completion_menu.rs index b05597ea..8d6dc6d4 100644 --- a/crates/ui/src/input/popovers/completion_menu.rs +++ b/crates/ui/src/input/popovers/completion_menu.rs @@ -428,11 +428,7 @@ impl Render for CompletionMenu { editor_popover("completion-menu", cx) .max_w(max_width) .min_w(px(120.)) - .child( - List::new(&self.list) - .searchable(false) - .max_h(MAX_MENU_HEIGHT), - ) + .child(List::new(&self.list).max_h(MAX_MENU_HEIGHT)) .child( canvas( move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds), diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 9a876896..6ea6c5f9 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -47,8 +47,6 @@ pub enum ListEvent { struct ListOptions { size: Size, scrollbar_visible: bool, - selectable: bool, - searchable: bool, search_placeholder: Option, max_height: Option, paddings: EdgesRefinement, @@ -60,8 +58,6 @@ impl Default for ListOptions { size: Size::default(), scrollbar_visible: true, max_height: None, - selectable: true, - searchable: false, search_placeholder: None, paddings: EdgesRefinement::default(), } @@ -83,6 +79,8 @@ pub struct ListState { deferred_scroll_to_index: Option<(IndexPath, ScrollStrategy)>, mouse_right_clicked_index: Option, reset_on_cancel: bool, + searchable: bool, + selectable: bool, _search_task: Task<()>, _load_more_task: Task<()>, _query_input_subscription: Subscription, @@ -107,6 +105,8 @@ where query_input, last_query: None, selected_index: None, + selectable: true, + searchable: false, item_to_measure_index: IndexPath::default(), deferred_scroll_to_index: None, mouse_right_clicked_index: None, @@ -119,6 +119,25 @@ where } } + /// Sets whether the list is searchable, default is `false`. + /// + /// When `true`, there will be a search input at the top of the list. + pub fn searchable(mut self, searchable: bool) -> Self { + self.searchable = searchable; + self + } + + pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context) { + self.searchable = searchable; + cx.notify(); + } + + /// Sets whether the list is selectable, default is true. + pub fn selectable(mut self, selectable: bool) -> Self { + self.selectable = selectable; + self + } + pub fn delegate(&self) -> &D { &self.delegate } @@ -402,7 +421,7 @@ where window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let selectable = self.options.selectable; + let selectable = self.selectable; let selected = self.selected_index.map(|s| s.eq_row(ix)).unwrap_or(false); let mouse_right_clicked = self .mouse_right_clicked_index @@ -526,7 +545,7 @@ where D: ListDelegate, { fn focus_handle(&self, cx: &App) -> FocusHandle { - if self.options.searchable { + if self.searchable { self.query_input.focus_handle(cx) } else { self.focus_handle.clone() @@ -549,7 +568,7 @@ where } let loading = self.delegate().loading(cx); - let query_input = if self.options.searchable { + let query_input = if self.searchable { // sync placeholder if let Some(placeholder) = &self.options.search_placeholder { self.query_input.update(cx, |input, cx| { @@ -659,20 +678,6 @@ where self } - /// Sets whether the list is selectable, default is true. - pub fn selectable(mut self, selectable: bool) -> Self { - self.options.selectable = selectable; - self - } - - /// Sets whether the list is searchable, default is `false`. - /// - /// When `true`, there will be a search input at the top of the list. - pub fn searchable(mut self, searchable: bool) -> Self { - self.options.searchable = searchable; - self - } - /// Sets the placeholder text for the search input. pub fn search_placeholder(mut self, placeholder: impl Into) -> Self { self.options.search_placeholder = Some(placeholder.into()); diff --git a/crates/ui/src/select.rs b/crates/ui/src/select.rs index 82e218bb..c272abd2 100644 --- a/crates/ui/src/select.rs +++ b/crates/ui/src/select.rs @@ -303,7 +303,6 @@ struct SelectOptions { cleanable: bool, placeholder: Option, title_prefix: Option, - searchable: bool, search_placeholder: Option, empty: Option, menu_width: Length, @@ -324,7 +323,6 @@ impl Default for SelectOptions { menu_width: Length::Auto, disabled: false, appearance: true, - searchable: false, search_placeholder: None, } } @@ -334,6 +332,7 @@ impl Default for SelectOptions { pub struct SelectState { focus_handle: FocusHandle, options: SelectOptions, + searchable: bool, list: Entity>>, empty: Option AnyElement>>, /// Store the bounds of the input @@ -571,6 +570,7 @@ where let mut this = Self { focus_handle, options: SelectOptions::default(), + searchable: false, list, selected_value: None, open: false, @@ -583,6 +583,14 @@ where this } + /// Sets whether the dropdown menu is searchable, default is `false`. + /// + /// When `true`, there will be a search input at the top of the dropdown menu. + pub fn searchable(mut self, searchable: bool) -> Self { + self.searchable = searchable; + self + } + /// Set the selected index for the select. pub fn set_selected_index( &mut self, @@ -772,6 +780,7 @@ where D: SelectDelegate + 'static, { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let searchable = self.searchable; let is_focused = self.focus_handle.is_focused(window); let show_clean = self.options.cleanable && self.selected_index(cx).is_some(); let bounds = self.bounds; @@ -779,6 +788,9 @@ where let outline_visible = self.open || is_focused && !self.options.disabled; let popup_radius = cx.theme().radius.min(px(8.)); + self.list + .update(cx, |list, cx| list.set_searchable(searchable, cx)); + div() .size_full() .relative() @@ -888,7 +900,6 @@ where .shadow_md() .child( List::new(&self.list) - .searchable(self.options.searchable) .when_some( self.options.search_placeholder.clone(), |this, placeholder| { @@ -957,14 +968,6 @@ where self } - /// Sets whether the dropdown menu is searchable, default is `false`. - /// - /// When `true`, there will be a search input at the top of the dropdown menu. - pub fn searchable(mut self, searchable: bool) -> Self { - self.options.searchable = searchable; - self - } - /// Sets the placeholder text for the search input. pub fn search_placeholder(mut self, placeholder: impl Into) -> Self { self.options.search_placeholder = Some(placeholder.into()); diff --git a/docs/docs/components/list.md b/docs/docs/components/list.md index 3d5a1c94..c704ccc5 100644 --- a/docs/docs/components/list.md +++ b/docs/docs/components/list.md @@ -171,7 +171,7 @@ fn render_item( The list automatically includes a search input by default. Implement `perform_search` to handle queries: -And you should use `searchable(true)` when creating the list to show search input. +And you should use `searchable(true)` when creating the `ListState` to show search input. ```rust impl ListDelegate for MyListDelegate { @@ -192,8 +192,8 @@ impl ListDelegate for MyListDelegate { } } -let state = cx.new(|cx| ListState::new(delegate, window, cx)); -List::new(&state).searchable(true) +let state = cx.new(|cx| ListState::new(delegate, window, cx).searchable(true)); +List::new(&state) ``` ### List with Loading State diff --git a/docs/docs/components/select.md b/docs/docs/components/select.md index a47f1da8..b5c7713c 100644 --- a/docs/docs/components/select.md +++ b/docs/docs/components/select.md @@ -73,11 +73,10 @@ let fruits = SearchableVec::new(vec![ ]); let state = cx.new(|cx| { - SelectState::new(fruits, None, window, cx) + SelectState::new(fruits, None, window, cx).searchable(true) }); Select::new(&state) - .searchable(true) .icon(IconName::Search) // Shows search icon ```