ui: Add the SwitchField component (#34713)

This will be useful for both the current agent panel and some other
onboarding stuff we're working on. Also ended up removing the
`SwitchWithLabel` as it was unused.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-07-18 15:03:14 -03:00 committed by GitHub
parent 87555d3f0b
commit 64ce696aae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -543,28 +543,48 @@ impl RenderOnce for Switch {
} }
} }
/// A [`Switch`] that has a [`Label`]. /// # SwitchField
#[derive(IntoElement)] ///
pub struct SwitchWithLabel { /// A field component that combines a label, description, and switch into one reusable component.
///
/// # Examples
///
/// ```
/// use ui::prelude::*;
///
/// SwitchField::new(
/// "feature-toggle",
/// "Enable feature",
/// "This feature adds new functionality to the app.",
/// ToggleState::Unselected,
/// |state, window, cx| {
/// // Logic here
/// }
/// );
/// ```
#[derive(IntoElement, RegisterComponent)]
pub struct SwitchField {
id: ElementId, id: ElementId,
label: Label, label: SharedString,
description: SharedString,
toggle_state: ToggleState, toggle_state: ToggleState,
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>, on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
disabled: bool, disabled: bool,
color: SwitchColor, color: SwitchColor,
} }
impl SwitchWithLabel { impl SwitchField {
/// Creates a switch with an attached label.
pub fn new( pub fn new(
id: impl Into<ElementId>, id: impl Into<ElementId>,
label: Label, label: impl Into<SharedString>,
description: impl Into<SharedString>,
toggle_state: impl Into<ToggleState>, toggle_state: impl Into<ToggleState>,
on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static, on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
) -> Self { ) -> Self {
Self { Self {
id: id.into(), id: id.into(),
label, label: label.into(),
description: description.into(),
toggle_state: toggle_state.into(), toggle_state: toggle_state.into(),
on_click: Arc::new(on_click), on_click: Arc::new(on_click),
disabled: false, disabled: false,
@ -572,43 +592,141 @@ impl SwitchWithLabel {
} }
} }
/// Sets the disabled state of the [`SwitchWithLabel`].
pub fn disabled(mut self, disabled: bool) -> Self { pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled; self.disabled = disabled;
self self
} }
/// Sets the color of the switch using the specified [`SwitchColor`]. /// Sets the color of the switch using the specified [`SwitchColor`].
/// This changes the color scheme of the switch when it's in the "on" state.
pub fn color(mut self, color: SwitchColor) -> Self { pub fn color(mut self, color: SwitchColor) -> Self {
self.color = color; self.color = color;
self self
} }
} }
impl RenderOnce for SwitchWithLabel { impl RenderOnce for SwitchField {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
h_flex() h_flex()
.id(SharedString::from(format!("{}-container", self.id))) .id(SharedString::from(format!("{}-container", self.id)))
.gap(DynamicSpacing::Base08.rems(cx)) .w_full()
.gap_4()
.justify_between()
.flex_wrap()
.child( .child(
Switch::new(self.id.clone(), self.toggle_state) v_flex()
.disabled(self.disabled) .gap_0p5()
.color(self.color) .max_w_5_6()
.on_click({ .child(Label::new(self.label))
let on_click = self.on_click.clone(); .child(Label::new(self.description).color(Color::Muted)),
move |checked, window, cx| {
(on_click)(checked, window, cx);
}
}),
) )
.child( .child(
div() Switch::new(
.id(SharedString::from(format!("{}-label", self.id))) SharedString::from(format!("{}-switch", self.id)),
.child(self.label), self.toggle_state,
)
.color(self.color)
.disabled(self.disabled)
.on_click({
let on_click = self.on_click.clone();
move |state, window, cx| {
(on_click)(state, window, cx);
}
}),
) )
} }
} }
impl Component for SwitchField {
fn scope() -> ComponentScope {
ComponentScope::Input
}
fn description() -> Option<&'static str> {
Some("A field component that combines a label, description, and switch")
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.gap_6()
.children(vec![
example_group_with_title(
"States",
vec![
single_example(
"Unselected",
SwitchField::new(
"switch_field_unselected",
"Enable notifications",
"Receive notifications when new messages arrive.",
ToggleState::Unselected,
|_, _, _| {},
)
.into_any_element(),
),
single_example(
"Selected",
SwitchField::new(
"switch_field_selected",
"Enable notifications",
"Receive notifications when new messages arrive.",
ToggleState::Selected,
|_, _, _| {},
)
.into_any_element(),
),
],
),
example_group_with_title(
"Colors",
vec![
single_example(
"Default",
SwitchField::new(
"switch_field_default",
"Default color",
"This uses the default switch color.",
ToggleState::Selected,
|_, _, _| {},
)
.into_any_element(),
),
single_example(
"Accent",
SwitchField::new(
"switch_field_accent",
"Accent color",
"This uses the accent color scheme.",
ToggleState::Selected,
|_, _, _| {},
)
.color(SwitchColor::Accent)
.into_any_element(),
),
],
),
example_group_with_title(
"Disabled",
vec![single_example(
"Disabled",
SwitchField::new(
"switch_field_disabled",
"Disabled field",
"This field is disabled and cannot be toggled.",
ToggleState::Selected,
|_, _, _| {},
)
.disabled(true)
.into_any_element(),
)],
),
])
.into_any_element(),
)
}
}
impl Component for Checkbox { impl Component for Checkbox {
fn scope() -> ComponentScope { fn scope() -> ComponentScope {
ComponentScope::Input ComponentScope::Input