feat: add combobox component
This commit is contained in:
@@ -11,6 +11,7 @@ use gpui_native_core::avatar::Avatar;
|
||||
use gpui_native_core::badge::{Badge, BadgeVariant};
|
||||
use gpui_native_core::button::{Button, ButtonVariant};
|
||||
use gpui_native_core::checkbox::Checkbox;
|
||||
use gpui_native_core::combobox::{Combobox, ComboboxOption, ComboboxState};
|
||||
use gpui_native_core::common::{Sizable as _, Size, h_flex, v_flex};
|
||||
use gpui_native_core::context_menu::ContextMenu;
|
||||
use gpui_native_core::dialog::Dialog;
|
||||
@@ -54,6 +55,7 @@ const CATALOG: &[(&str, &[(&str, &str)])] = &[
|
||||
("button", "Button"),
|
||||
("input", "Input"),
|
||||
("field", "Field"),
|
||||
("combobox", "Combobox"),
|
||||
("status-dot", "Status dot"),
|
||||
("checkbox", "Checkbox"),
|
||||
("radio", "Radio"),
|
||||
@@ -390,6 +392,12 @@ impl Gallery {
|
||||
PanelDescriptor::new("field", "Field", |_, cx| cx.new(FieldDemo::new).into()),
|
||||
cx,
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("combobox", "Combobox", |window, cx| {
|
||||
cx.new(|cx| ComboboxDemo::new(window, cx)).into()
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("status-dot", "Status dot", |_, cx| {
|
||||
cx.new(|_| StatusDotDemo).into()
|
||||
@@ -1020,6 +1028,128 @@ impl Render for StatusDotDemo {
|
||||
}
|
||||
}
|
||||
|
||||
fn combo_option(label: &'static str) -> ComboboxOption<SharedString> {
|
||||
(SharedString::from(label), SharedString::from(label))
|
||||
}
|
||||
|
||||
struct ComboboxDemo {
|
||||
branch: Entity<ComboboxState<SharedString>>,
|
||||
label: Entity<ComboboxState<SharedString>>,
|
||||
label_created: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl ComboboxDemo {
|
||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let view = cx.entity();
|
||||
let branch = cx.new(|cx| {
|
||||
ComboboxState::new(
|
||||
Combobox::new(
|
||||
"combo-demo-branch",
|
||||
"Branch",
|
||||
vec![
|
||||
combo_option("main"),
|
||||
combo_option("next"),
|
||||
combo_option("release/1.0"),
|
||||
combo_option("release/2.0"),
|
||||
],
|
||||
)
|
||||
.selected(SharedString::from("main"))
|
||||
.placeholder("Switch branch…")
|
||||
.keep_unmatched_text(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let label_builder = Combobox::new(
|
||||
"combo-demo-label",
|
||||
"Label",
|
||||
vec![
|
||||
combo_option("bug"),
|
||||
combo_option("docs"),
|
||||
combo_option("enhancement"),
|
||||
],
|
||||
)
|
||||
.placeholder("Pick or type a label…")
|
||||
.on_create(move |text: SharedString, window, cx| {
|
||||
// Push the typed text as a real option and choose it: the picker
|
||||
// never mutates its own list behind the caller's back, so the
|
||||
// demo does it here.
|
||||
view.update(cx, |demo, cx| {
|
||||
demo.label_created = Some(text.clone());
|
||||
demo.label.update(cx, |label, cx| {
|
||||
let mut options = label.options_snapshot();
|
||||
if !options.iter().any(|(value, _)| value == &text) {
|
||||
options.push((text.clone(), text.clone()));
|
||||
label.set_options(options, cx);
|
||||
}
|
||||
label.set_selected(Some(text), window, cx);
|
||||
});
|
||||
});
|
||||
});
|
||||
let label = cx.new(|cx| ComboboxState::new(label_builder, window, cx));
|
||||
Self {
|
||||
branch,
|
||||
label,
|
||||
label_created: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ComboboxDemo {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = cx.theme().colors;
|
||||
|
||||
let branch_value = self
|
||||
.branch
|
||||
.read(cx)
|
||||
.selected()
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
let label_value = self
|
||||
.label_created
|
||||
.clone()
|
||||
.unwrap_or_else(|| SharedString::from("none"));
|
||||
|
||||
ScrollArea::new("combobox-demo")
|
||||
.size_full()
|
||||
.p_4()
|
||||
.child(demo_section(
|
||||
"keep unmatched",
|
||||
colors,
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Field::new("combo-demo-branch-field", "Branch")
|
||||
.description("Type a name that is not in the list to keep it.")
|
||||
.child(self.branch.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child(format!("branch: {branch_value}")),
|
||||
),
|
||||
))
|
||||
.child(demo_section(
|
||||
"create on close",
|
||||
colors,
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Field::new("combo-demo-label-field", "Label")
|
||||
.description("Typing a new label creates it when the field blurs.")
|
||||
.child(self.label.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child(format!("label: {label_value}")),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckboxDemo {
|
||||
checked: bool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user