Add Dropdown.
This commit is contained in:
parent
b3a32bc02c
commit
910f27a66a
7 changed files with 221 additions and 131 deletions
3
assets/icons/chevron-down.svg
Normal file
3
assets/icons/chevron-down.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down">
|
||||||
|
<path d="m6 9 6 6 6-6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 250 B |
|
|
@ -1,13 +1,13 @@
|
||||||
use std::{rc::Rc, vec};
|
use std::{rc::Rc, vec};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, ClickEvent, IntoElement, ParentElement, Render, RenderOnce, SharedString, Styled,
|
div, px, ClickEvent, IntoElement, ParentElement, Render, RenderOnce, SharedString, Styled,
|
||||||
ViewContext, WindowContext,
|
View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
checkbox::Checkbox,
|
checkbox::Checkbox,
|
||||||
dropdown::{Dropdown, DropdownItem},
|
dropdown::{Dropdown, DropdownDelegate, DropdownItem},
|
||||||
h_flex, v_flex, Disableable as _, Selection,
|
h_flex, v_flex, Disableable as _, Selection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,25 +25,54 @@ impl Country {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DropdownItem for Country {
|
impl DropdownItem for Country {
|
||||||
fn title(&self) -> SharedString {
|
fn title(&self) -> &str {
|
||||||
self.name.into()
|
self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value(&self) -> SharedString {
|
fn value(&self) -> &str {
|
||||||
self.code.into()
|
self.code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
struct CounterDelegate(Vec<Country>);
|
||||||
|
struct FuritDelegate(Vec<String>);
|
||||||
|
|
||||||
pub struct DropdownStory {
|
pub struct DropdownStory {
|
||||||
dropdown1: Dropdown,
|
country_dropdown: View<Dropdown<CounterDelegate>>,
|
||||||
dropdown2: Dropdown,
|
furit_dropdown: View<Dropdown<FuritDelegate>>,
|
||||||
countries: Vec<Country>,
|
}
|
||||||
|
|
||||||
|
impl DropdownDelegate for CounterDelegate {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, ix: usize) -> Option<&dyn DropdownItem> {
|
||||||
|
if let Some(item) = self.0.get(ix) {
|
||||||
|
Some(item)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DropdownDelegate for FuritDelegate {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, ix: usize) -> Option<&dyn DropdownItem> {
|
||||||
|
if let Some(item) = self.0.get(ix) {
|
||||||
|
Some(item)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DropdownStory {
|
impl DropdownStory {
|
||||||
pub(crate) fn new(cx: &mut WindowContext) -> Self {
|
pub(crate) fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
let countries = vec![
|
let countries = CounterDelegate(vec![
|
||||||
Country::new("United States", "US"),
|
Country::new("United States", "US"),
|
||||||
Country::new("Canada", "CA"),
|
Country::new("Canada", "CA"),
|
||||||
Country::new("Mexico", "MX"),
|
Country::new("Mexico", "MX"),
|
||||||
|
|
@ -55,22 +84,29 @@ impl DropdownStory {
|
||||||
Country::new("Colombia", "CO"),
|
Country::new("Colombia", "CO"),
|
||||||
Country::new("Venezuela", "VE"),
|
Country::new("Venezuela", "VE"),
|
||||||
Country::new("Ecuador", "EC"),
|
Country::new("Ecuador", "EC"),
|
||||||
];
|
]);
|
||||||
|
|
||||||
let items2 = vec![
|
let country_dropdown = cx.new_view(|cx| Dropdown::new("dropdown-country", countries, cx));
|
||||||
"Apple",
|
|
||||||
"Orange",
|
let furits = FuritDelegate(
|
||||||
"Banana",
|
[
|
||||||
"Grape",
|
"Apple",
|
||||||
"Pineapple",
|
"Orange",
|
||||||
"Watermelon",
|
"Banana",
|
||||||
"Avocado",
|
"Grape",
|
||||||
];
|
"Pineapple",
|
||||||
|
"Watermelon",
|
||||||
|
"Avocado",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
let furit_dropdown = cx.new_view(|cx| Dropdown::new("dropdown-furits", furits, cx));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
countries,
|
country_dropdown,
|
||||||
dropdown1: Dropdown::new("dropdown-country", Rc::new(countries), cx),
|
furit_dropdown,
|
||||||
dropdown2: Dropdown::new("dropdown-fruit", Rc::new(items2), cx),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,73 +116,34 @@ impl DropdownStory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for CheckboxStory {
|
impl Render for DropdownStory {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
story_case(
|
story_case(
|
||||||
"Checkbox",
|
"Dropdown",
|
||||||
"A control that allows the user to toggle between checked and not checked.",
|
"Displays a list of options for the user to pick from—triggered by a button.",
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex().items_start().justify_start().gap_4().child(
|
v_flex()
|
||||||
h_flex()
|
.size_full()
|
||||||
.items_center()
|
.items_start()
|
||||||
.gap_4()
|
.justify_start()
|
||||||
.child(self.check1)
|
|
||||||
.child(self.check1_1)
|
|
||||||
.child(
|
|
||||||
Checkbox::new("check1_2", cx)
|
|
||||||
.checked(Selection::Selected)
|
|
||||||
.on_click(Self::on_click),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child(
|
.child(
|
||||||
Checkbox::new("check2", cx)
|
h_flex()
|
||||||
.checked(Selection::Unselected)
|
.w_full()
|
||||||
.label("With label (Unchecked)")
|
.max_w(px(640.))
|
||||||
.on_click(Self::on_click),
|
.items_center()
|
||||||
|
.gap_4()
|
||||||
|
.child(self.country_dropdown.clone())
|
||||||
|
.child(self.furit_dropdown.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Checkbox::new("check2_1", cx)
|
h_flex()
|
||||||
.label("With Label (Indeterminate)")
|
.w_full()
|
||||||
.checked(Selection::Indeterminate)
|
.items_center()
|
||||||
.on_click(Self::on_click),
|
.gap_4()
|
||||||
)
|
.child("This is other text."),
|
||||||
.child(
|
|
||||||
Checkbox::new("check2_2", cx)
|
|
||||||
.label("With Label (Checked)")
|
|
||||||
.checked(Selection::Selected)
|
|
||||||
.on_click(Self::on_click),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
h_flex().items_center().gap_4().child(
|
|
||||||
h_flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_4()
|
|
||||||
.child(
|
|
||||||
Checkbox::new("check3", cx)
|
|
||||||
.label("Disabled Checked")
|
|
||||||
.checked(Selection::Selected)
|
|
||||||
.disabled(true),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Checkbox::new("check3_1", cx)
|
|
||||||
.label("Disabled Unchecked")
|
|
||||||
.checked(Selection::Unselected)
|
|
||||||
.disabled(true),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Checkbox::new("check3_2", cx)
|
|
||||||
.label("Disabled Indeterminate")
|
|
||||||
.checked(Selection::Indeterminate)
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use gpui::{
|
||||||
|
|
||||||
mod button_story;
|
mod button_story;
|
||||||
mod checkbox_story;
|
mod checkbox_story;
|
||||||
// mod dropdown_story;
|
mod dropdown_story;
|
||||||
mod input_story;
|
mod input_story;
|
||||||
mod picker_story;
|
mod picker_story;
|
||||||
mod switch_story;
|
mod switch_story;
|
||||||
|
|
@ -21,6 +21,7 @@ use ui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use button_story::ButtonStory;
|
use button_story::ButtonStory;
|
||||||
|
use dropdown_story::DropdownStory;
|
||||||
use input_story::InputStory;
|
use input_story::InputStory;
|
||||||
use picker_story::PickerStory;
|
use picker_story::PickerStory;
|
||||||
use switch_story::SwitchStory;
|
use switch_story::SwitchStory;
|
||||||
|
|
@ -77,6 +78,7 @@ enum StoryType {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Switch,
|
Switch,
|
||||||
Picker,
|
Picker,
|
||||||
|
Dropdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for StoryType {
|
impl Display for StoryType {
|
||||||
|
|
@ -87,6 +89,7 @@ impl Display for StoryType {
|
||||||
Self::Checkbox => write!(f, "Checkbox"),
|
Self::Checkbox => write!(f, "Checkbox"),
|
||||||
Self::Switch => write!(f, "Switch"),
|
Self::Switch => write!(f, "Switch"),
|
||||||
Self::Picker => write!(f, "Picker"),
|
Self::Picker => write!(f, "Picker"),
|
||||||
|
Self::Dropdown => write!(f, "Dropdown"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +101,7 @@ pub struct Stories {
|
||||||
input_story: View<InputStory>,
|
input_story: View<InputStory>,
|
||||||
switch_story: View<SwitchStory>,
|
switch_story: View<SwitchStory>,
|
||||||
picker_story: View<PickerStory>,
|
picker_story: View<PickerStory>,
|
||||||
|
dropdown_story: View<DropdownStory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stories {
|
impl Stories {
|
||||||
|
|
@ -108,6 +112,7 @@ impl Stories {
|
||||||
input_story: cx.new_view(|cx| InputStory::new(cx)),
|
input_story: cx.new_view(|cx| InputStory::new(cx)),
|
||||||
switch_story: cx.new_view(|cx| SwitchStory::new(cx)),
|
switch_story: cx.new_view(|cx| SwitchStory::new(cx)),
|
||||||
picker_story: cx.new_view(|cx| PickerStory::new(cx)),
|
picker_story: cx.new_view(|cx| PickerStory::new(cx)),
|
||||||
|
dropdown_story: cx.new_view(|cx| DropdownStory::new(cx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,27 +125,23 @@ impl Stories {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_story_buttons(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn tabs(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(TabBar::new("story-tabs").children(vec![
|
.child(TabBar::new("story-tabs").children(vec![
|
||||||
self.swith_button("story-button", StoryType::Button, cx),
|
self.tab("story-button", StoryType::Button, cx),
|
||||||
self.swith_button("story-input", StoryType::Input, cx),
|
self.tab("story-input", StoryType::Input, cx),
|
||||||
self.swith_button("story-checkbox", StoryType::Checkbox, cx),
|
self.tab("story-checkbox", StoryType::Checkbox, cx),
|
||||||
self.swith_button("story-switch", StoryType::Switch, cx),
|
self.tab("story-switch", StoryType::Switch, cx),
|
||||||
self.swith_button("story-picker", StoryType::Picker, cx),
|
self.tab("story-picker", StoryType::Picker, cx),
|
||||||
|
self.tab("story-dropdown", StoryType::Dropdown, cx),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swith_button(
|
fn tab(&self, id: &str, ty: StoryType, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
&self,
|
|
||||||
id: &str,
|
|
||||||
ty: StoryType,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> impl IntoElement {
|
|
||||||
let name = format!("{}", ty);
|
let name = format!("{}", ty);
|
||||||
let is_active = ty == self.active;
|
let is_active = ty == self.active;
|
||||||
|
|
||||||
|
|
@ -159,13 +160,14 @@ impl Render for Stories {
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.child(self.render_story_buttons(cx))
|
.child(self.tabs(cx))
|
||||||
.map(|this| match self.active {
|
.map(|this| match self.active {
|
||||||
StoryType::Button => this.child(self.button_story.clone()),
|
StoryType::Button => this.child(self.button_story.clone()),
|
||||||
StoryType::Input => this.child(self.input_story.clone()),
|
StoryType::Input => this.child(self.input_story.clone()),
|
||||||
StoryType::Checkbox => this.child(CheckboxStory::new(cx).into_any_element()),
|
StoryType::Checkbox => this.child(CheckboxStory::new(cx).into_any_element()),
|
||||||
StoryType::Switch => this.child(self.switch_story.clone()),
|
StoryType::Switch => this.child(self.switch_story.clone()),
|
||||||
StoryType::Picker => this.child(self.picker_story.clone()),
|
StoryType::Picker => this.child(self.picker_story.clone()),
|
||||||
|
StoryType::Dropdown => this.child(self.dropdown_story.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, Div, ElementId, InteractiveElement, IntoElement,
|
div, prelude::FluentBuilder as _, px, AppContext, Div, ElementId, FocusHandle, FocusableView,
|
||||||
ParentElement as _, Render, RenderOnce, SharedString, Stateful, Styled as _, View, ViewContext,
|
InteractiveElement, IntoElement, ParentElement as _, Render, RenderOnce, SharedString,
|
||||||
VisualContext as _, WeakView,
|
Stateful, StatefulInteractiveElement as _, Styled as _, View, ViewContext, VisualContext as _,
|
||||||
|
WeakView,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
h_flex,
|
||||||
list::ListItem,
|
list::ListItem,
|
||||||
picker::{Picker, PickerDelegate},
|
picker::{Picker, PickerDelegate},
|
||||||
IconName,
|
theme::ActiveTheme,
|
||||||
|
v_flex, IconName, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait for items that can be displayed in a dropdown.
|
/// A trait for items that can be displayed in a dropdown.
|
||||||
|
|
@ -18,19 +21,34 @@ pub trait DropdownItem {
|
||||||
fn value(&self) -> &str;
|
fn value(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DropdownDelegate {
|
impl DropdownItem for String {
|
||||||
type Item: DropdownItem;
|
fn title(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize;
|
fn value(&self) -> &str {
|
||||||
fn get(&self, index: usize) -> Option<&Self::Item>;
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DropdownPickerDelegate<D: DropdownDelegate> {
|
pub trait DropdownDelegate {
|
||||||
|
fn len(&self) -> usize;
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
fn get(&self, ix: usize) -> Option<&dyn DropdownItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DropdownPickerDelegate<D: DropdownDelegate + 'static> {
|
||||||
|
delegate: D,
|
||||||
dropdown: WeakView<Dropdown<D>>,
|
dropdown: WeakView<Dropdown<D>>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> PickerDelegate for DropdownPickerDelegate<D: PickerDelegate> {
|
impl<D> PickerDelegate for DropdownPickerDelegate<D>
|
||||||
|
where
|
||||||
|
D: DropdownDelegate + 'static,
|
||||||
|
{
|
||||||
type ListItem = ListItem;
|
type ListItem = ListItem;
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
|
|
@ -57,7 +75,7 @@ impl<D> PickerDelegate for DropdownPickerDelegate<D: PickerDelegate> {
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.py_1()
|
.py_1()
|
||||||
.px_3()
|
.px_3()
|
||||||
.child(item.title());
|
.child(item.title().to_string());
|
||||||
Some(list_item)
|
Some(list_item)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -68,7 +86,6 @@ impl<D> PickerDelegate for DropdownPickerDelegate<D: PickerDelegate> {
|
||||||
if let Some(view) = self.dropdown.upgrade() {
|
if let Some(view) = self.dropdown.upgrade() {
|
||||||
cx.update_view(&view, |view, cx| {
|
cx.update_view(&view, |view, cx| {
|
||||||
view.open = false;
|
view.open = false;
|
||||||
cx.notify();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,47 +94,117 @@ impl<D> PickerDelegate for DropdownPickerDelegate<D: PickerDelegate> {
|
||||||
if let Some(view) = self.dropdown.upgrade() {
|
if let Some(view) = self.dropdown.upgrade() {
|
||||||
cx.update_view(&view, |view, cx| {
|
cx.update_view(&view, |view, cx| {
|
||||||
if let Some(item) = self.delegate.get(self.selected_index) {
|
if let Some(item) = self.delegate.get(self.selected_index) {
|
||||||
view.value = Some(item.value());
|
view.title = Some(item.title().to_string().into());
|
||||||
|
view.value = Some(item.value().to_string().into());
|
||||||
}
|
}
|
||||||
view.open = false;
|
view.open = false;
|
||||||
cx.notify();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Dropdown<D: DropdownDelegate> {
|
pub struct Dropdown<D: DropdownDelegate + 'static> {
|
||||||
base: Stateful<Div>,
|
id: ElementId,
|
||||||
delegate: D,
|
focus_handle: FocusHandle,
|
||||||
picker: View<Picker<DropdownPickerDelegate>>,
|
picker: View<Picker<DropdownPickerDelegate<D>>>,
|
||||||
open: bool,
|
open: bool,
|
||||||
/// The value of the selected item.
|
/// The value of the selected item.
|
||||||
value: Option<SharedString>,
|
value: Option<SharedString>,
|
||||||
|
title: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: DropdownDelegate> Dropdown<D> {
|
impl<D> Dropdown<D>
|
||||||
|
where
|
||||||
|
D: DropdownDelegate + 'static,
|
||||||
|
{
|
||||||
pub fn new(id: impl Into<ElementId>, delegate: D, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(id: impl Into<ElementId>, delegate: D, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let delegate = DropdownPickerDelegate {
|
let picker_delegate = DropdownPickerDelegate {
|
||||||
// delegate,
|
delegate,
|
||||||
dropdown: cx.view().downgrade(),
|
dropdown: cx.view().downgrade(),
|
||||||
selected_index: 0,
|
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 {
|
Self {
|
||||||
delegate,
|
id: id.into(),
|
||||||
base: div().id(id.into()),
|
focus_handle: cx.focus_handle(),
|
||||||
picker,
|
picker,
|
||||||
open: false,
|
open: false,
|
||||||
|
title: None,
|
||||||
value: None,
|
value: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for Dropdown {
|
pub fn set_value(&mut self, value: impl Into<SharedString>, cx: &mut ViewContext<Self>) {
|
||||||
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
|
self.value = Some(value.into());
|
||||||
self.base
|
cx.notify();
|
||||||
.child(self.value.unwrap_or_else(|| "Select...".into()))
|
}
|
||||||
.when(self.open, |this| this.child(self.picker))
|
}
|
||||||
|
|
||||||
|
impl<D> FocusableView for Dropdown<D>
|
||||||
|
where
|
||||||
|
D: DropdownDelegate + 'static,
|
||||||
|
{
|
||||||
|
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Render for Dropdown<D>
|
||||||
|
where
|
||||||
|
D: DropdownDelegate + 'static,
|
||||||
|
{
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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()),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub enum IconName {
|
||||||
Maximize,
|
Maximize,
|
||||||
Minimize,
|
Minimize,
|
||||||
Close,
|
Close,
|
||||||
|
ChevronDown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconName {
|
impl IconName {
|
||||||
|
|
@ -19,6 +20,7 @@ impl IconName {
|
||||||
IconName::Maximize => "icons/maximize.svg",
|
IconName::Maximize => "icons/maximize.svg",
|
||||||
IconName::Minimize => "icons/minimize.svg",
|
IconName::Minimize => "icons/minimize.svg",
|
||||||
IconName::Close => "icons/close.svg",
|
IconName::Close => "icons/close.svg",
|
||||||
|
IconName::ChevronDown => "icons/chevron-down.svg",
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub mod theme;
|
||||||
pub mod title_bar;
|
pub mod title_bar;
|
||||||
pub use styled_ext::StyledExt;
|
pub use styled_ext::StyledExt;
|
||||||
pub mod divider;
|
pub mod divider;
|
||||||
// pub mod dropdown;
|
pub mod dropdown;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod picker;
|
pub mod picker;
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,6 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
|
|
||||||
pub fn uniform_list(delegate: D, cx: &mut ViewContext<Self>) -> Self {
|
pub fn uniform_list(delegate: D, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let query_input = Self::new_query_input("Search...", cx);
|
let query_input = Self::new_query_input("Search...", cx);
|
||||||
|
|
||||||
Self::new(delegate, ContainerKind::UniformList, Some(query_input), cx)
|
Self::new(delegate, ContainerKind::UniformList, Some(query_input), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue