,
- delegate: D,
- picker: View
>,
+pub struct Dropdown {
+ id: ElementId,
+ focus_handle: FocusHandle,
+ picker: View>>,
open: bool,
/// The value of the selected item.
value: Option,
+ title: Option,
}
-impl Dropdown {
+impl Dropdown
+where
+ D: DropdownDelegate + 'static,
+{
pub fn new(id: impl Into, delegate: D, cx: &mut ViewContext) -> Self {
- let delegate = DropdownPickerDelegate {
- // delegate,
+ let picker_delegate = DropdownPickerDelegate {
+ delegate,
dropdown: cx.view().downgrade(),
selected_index: 0,
};
- let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
+ let picker = cx.new_view(|cx| Picker::uniform_list(picker_delegate, cx));
Self {
- delegate,
- base: div().id(id.into()),
+ id: id.into(),
+ focus_handle: cx.focus_handle(),
picker,
open: false,
+ title: None,
value: None,
}
}
-}
-impl RenderOnce for Dropdown {
- fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
- self.base
- .child(self.value.unwrap_or_else(|| "Select...".into()))
- .when(self.open, |this| this.child(self.picker))
+ pub fn set_value(&mut self, value: impl Into, cx: &mut ViewContext) {
+ self.value = Some(value.into());
+ cx.notify();
+ }
+}
+
+impl FocusableView for Dropdown
+where
+ D: DropdownDelegate + 'static,
+{
+ fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl Render for Dropdown
+where
+ D: DropdownDelegate + 'static,
+{
+ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
+ let title = self.title.clone().unwrap_or_else(|| "Select...".into());
+ let is_focused = self.focus_handle.is_focused(cx);
+
+ div()
+ .key_context("Dropdown")
+ .size_full()
+ .relative()
+ .child(
+ div()
+ .id(self.id.clone())
+ .relative()
+ .flex()
+ .w_full()
+ .items_center()
+ .justify_between()
+ .bg(cx.theme().background)
+ .border_1()
+ .border_color(cx.theme().input)
+ .rounded_sm()
+ .shadow_sm()
+ .px_3()
+ .py_2()
+ .when(is_focused, |this| this.border_color(cx.theme().ring))
+ .on_click(cx.listener(|this, _, cx| {
+ this.open = !this.open;
+ cx.notify();
+ }))
+ .child(
+ v_flex()
+ .items_center()
+ .justify_between()
+ .child(title)
+ .child(IconName::ChevronDown),
+ ),
+ )
+ .when(self.open, |this| {
+ this.child(
+ div()
+ .absolute()
+ // Top is the dropdown input height + border
+ .top(px(50.))
+ .left_0()
+ .bg(cx.theme().background)
+ .border_1()
+ .border_color(cx.theme().input)
+ .rounded_sm()
+ .shadow_md()
+ .track_focus(&self.picker.focus_handle(cx))
+ .child(self.picker.clone()),
+ )
+ })
}
}
diff --git a/crates/ui/src/icon.rs b/crates/ui/src/icon.rs
index b178dcc4..057031d2 100644
--- a/crates/ui/src/icon.rs
+++ b/crates/ui/src/icon.rs
@@ -8,6 +8,7 @@ pub enum IconName {
Maximize,
Minimize,
Close,
+ ChevronDown,
}
impl IconName {
@@ -19,6 +20,7 @@ impl IconName {
IconName::Maximize => "icons/maximize.svg",
IconName::Minimize => "icons/minimize.svg",
IconName::Close => "icons/close.svg",
+ IconName::ChevronDown => "icons/chevron-down.svg",
}
.into()
}
diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs
index 5be2f43d..eebe6bf4 100644
--- a/crates/ui/src/lib.rs
+++ b/crates/ui/src/lib.rs
@@ -16,7 +16,7 @@ pub mod theme;
pub mod title_bar;
pub use styled_ext::StyledExt;
pub mod divider;
-// pub mod dropdown;
+pub mod dropdown;
pub mod input;
pub mod list;
pub mod picker;
diff --git a/crates/ui/src/picker.rs b/crates/ui/src/picker.rs
index 0dd7bf2b..69ba5321 100644
--- a/crates/ui/src/picker.rs
+++ b/crates/ui/src/picker.rs
@@ -204,7 +204,6 @@ impl Picker {
pub fn uniform_list(delegate: D, cx: &mut ViewContext) -> Self {
let query_input = Self::new_query_input("Search...", cx);
-
Self::new(delegate, ContainerKind::UniformList, Some(query_input), cx)
}