T06b forms selection: checkbox, radio, cards, switch, select, combobox, multi select, cascader, transfer list, list box, chips, rating, sliders, knob, stepper

This commit is contained in:
ZacharyZhang-NY committed 2026-09-25 06:41:35 -04:00
1 parent ec5713c10b
commit e39d623b58
36 files changed
+5170 -198

No files matched your search

+121
View File
@@ -0,0 +1,121 @@
use std::rc::Rc;
use gpui::{
App, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce,
SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::*,
};
use super::{Choice, listbox::chosen, options::OnValues};
use crate::{
primitives::{FocusRing, Icon, IconName},
theme::{ActiveTheme, ControlSize, IconSize, TextSize},
};
/// Pills for a short set of choices. One is chosen, or several with `multiple`.
#[derive(IntoElement)]
pub struct ChoiceChips {
id: ElementId,
choices: Vec<Choice>,
selected: Vec<SharedString>,
multiple: bool,
on_change: Option<OnValues>,
}
impl ChoiceChips {
pub fn new(id: impl Into<ElementId>, choices: impl IntoIterator<Item = Choice>) -> Self {
Self {
id: id.into(),
choices: choices.into_iter().collect(),
selected: Vec::new(),
multiple: false,
on_change: None,
}
}
pub fn selected(mut self, values: impl IntoIterator<Item = impl Into<SharedString>>) -> Self {
self.selected = values.into_iter().map(Into::into).collect();
self
}
/// Filter chips: each toggles, and chosen ones show a check.
pub fn multiple(mut self) -> Self {
self.multiple = true;
self
}
pub fn on_change(
mut self,
handler: impl Fn(&[SharedString], &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ChoiceChips {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let colors = &theme.colors;
let (choices, selected) = (Rc::new(self.choices), Rc::new(self.selected));
let chips: Vec<_> = choices
.iter()
.enumerate()
.map(|(ix, choice)| {
let on = selected.contains(&choice.value);
let (fg, bg, border) = if on {
(colors.on_accent, colors.accent, colors.accent)
} else {
(colors.fg_muted, colors.surface, colors.border_strong)
};
let (id, choices, selected, multiple, on_change) = (
self.id.clone(),
choices.clone(),
selected.clone(),
self.multiple,
self.on_change.clone(),
);
div()
.id(("chip", ix))
.flex()
.items_center()
.gap_1p5()
.h(theme.control_height(ControlSize::Sm))
.px_3()
.rounded_full()
.border_1()
.border_color(border)
.bg(bg)
.text_size(theme.text_size(TextSize::Sm))
.text_color(if choice.disabled {
colors.fg_disabled
} else {
fg
})
.when(!choice.disabled, |chip| {
chip.tab_index(0)
.focus_ring(cx)
.cursor_pointer()
.when(!on, |chip| chip.hover(|style| style.bg(colors.hover)))
.on_mouse_down(MouseButton::Left, |_, window, _| {
window.prevent_default()
})
.on_click(move |_, window, cx| {
let next = chosen(&choices, &selected, ix, multiple);
log::info!("choice chips {id:?}: {next:?}");
if let Some(on_change) = &on_change {
on_change(&next, window, cx);
}
})
})
.when(on && self.multiple, |chip| {
chip.child(Icon::new(IconName::Check).size(IconSize::Xs).color(fg))
})
.when_some(choice.icon, |chip, icon| {
chip.child(Icon::new(icon).size(IconSize::Xs).color(fg))
})
.child(choice.label.clone())
})
.collect();
div().id(self.id).flex().flex_wrap().gap_2().children(chips)
}
}