toggle: Refactor Toggle, ToggleGroup API. (#1515)

## Break Changes

- Refactor `Toggle` new API to require a `id`, and renamed `on_change`
to `on_click.
- Renamed `on_change` method to `on_click` for `ToggleGroup`.

```diff
- Toggle::label("Hello").id("hello").on_change(..)
+ Toggle::new("hello).label("Hello").on_click(..)

- ToggleGroup::new("group1).on_change(..)
+ ToggleGroup::new("group1).on_click(..)
```
This commit is contained in:
Jason Lee 2025-11-05 10:47:55 +08:00 committed by GitHub
parent a4e93132f1
commit 0524c51b3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 225 additions and 273 deletions

View file

@ -77,6 +77,7 @@ impl Gallery {
StoryContainer::panel::<TabsStory>(window, cx), StoryContainer::panel::<TabsStory>(window, cx),
StoryContainer::panel::<TagStory>(window, cx), StoryContainer::panel::<TagStory>(window, cx),
StoryContainer::panel::<TextareaStory>(window, cx), StoryContainer::panel::<TextareaStory>(window, cx),
StoryContainer::panel::<ToggleStory>(window, cx),
StoryContainer::panel::<TooltipStory>(window, cx), StoryContainer::panel::<TooltipStory>(window, cx),
StoryContainer::panel::<TreeStory>(window, cx), StoryContainer::panel::<TreeStory>(window, cx),
StoryContainer::panel::<VirtualListStory>(window, cx), StoryContainer::panel::<VirtualListStory>(window, cx),

View file

@ -4,10 +4,13 @@ use gpui::{
}; };
use gpui_component::{ use gpui_component::{
IconName, Sizable, StyledExt,
button::{Toggle, ToggleGroup, ToggleVariants}, button::{Toggle, ToggleGroup, ToggleVariants},
h_flex, v_flex, IconName, Sizable, v_flex,
}; };
use crate::section;
pub struct ToggleStory { pub struct ToggleStory {
focus_handle: FocusHandle, focus_handle: FocusHandle,
single_toggle: usize, single_toggle: usize,
@ -51,16 +54,16 @@ impl Focusable for ToggleStory {
impl Render for ToggleStory { impl Render for ToggleStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.size_full()
.gap_6() .gap_6()
.child( .child(
h_flex() section("Toggle")
.gap_2()
.child( .child(
Toggle::label("Single Toggle Item 1") Toggle::new("item1")
.id("single-toggle-item-1") .label("Single Toggle Item 1")
.large() .large()
.checked(self.single_toggle == 1) .checked(self.single_toggle == 1)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
if *checked { if *checked {
view.single_toggle = 1; view.single_toggle = 1;
} }
@ -68,11 +71,11 @@ impl Render for ToggleStory {
})), })),
) )
.child( .child(
Toggle::label("Single Toggle Item 2") Toggle::new("item2")
.id("single-toggle-item-2") .label("Single Toggle Item 2")
.large() .large()
.checked(self.single_toggle == 2) .checked(self.single_toggle == 2)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
if *checked { if *checked {
view.single_toggle = 2; view.single_toggle = 2;
} }
@ -80,11 +83,11 @@ impl Render for ToggleStory {
})), })),
) )
.child( .child(
Toggle::icon(IconName::Eye) Toggle::new("item3")
.id("single-toggle-item-3") .icon(IconName::Eye)
.large() .large()
.checked(self.single_toggle == 3) .checked(self.single_toggle == 3)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
if *checked { if *checked {
view.single_toggle = 3; view.single_toggle = 3;
} }
@ -93,32 +96,50 @@ impl Render for ToggleStory {
), ),
) )
.child( .child(
h_flex() section("Toggle Group with Ghost Style")
.gap_5() .v_flex()
.gap_4()
.child( .child(
ToggleGroup::new("toggle-button-group1") ToggleGroup::new("toggle-button-group1")
.child(Toggle::icon(IconName::Bell).checked(self.checked[0])) .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))
.child(Toggle::icon(IconName::Bot).checked(self.checked[1])) .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) .child(
.child(Toggle::icon(IconName::Check).checked(self.checked[3])) Toggle::new(2)
.child(Toggle::label("Other").checked(self.checked[4])) .icon(IconName::Inbox)
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| { .checked(self.checked[2]),
)
.child(
Toggle::new(3)
.icon(IconName::Check)
.checked(self.checked[3]),
)
.child(Toggle::new(4).label("Other").checked(self.checked[4]))
.on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
view.checked[0] = checkeds[0]; view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1]; view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2]; view.checked[2] = checkeds[2];
view.checked[3] = checkeds[3]; view.checked[3] = checkeds[3];
view.checked[4] = checkeds[4];
cx.notify(); cx.notify();
})), })),
) )
.child( .child(
ToggleGroup::new("toggle-button-group1-sm") ToggleGroup::new("toggle-button-group1-sm")
.small() .small()
.child(Toggle::icon(IconName::Bell).checked(self.checked[0])) .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))
.child(Toggle::icon(IconName::Bot).checked(self.checked[1])) .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) .child(
.child(Toggle::icon(IconName::Check).checked(self.checked[3])) Toggle::new(2)
.child(Toggle::label("Other").checked(self.checked[4])) .icon(IconName::Inbox)
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| { .checked(self.checked[2]),
)
.child(
Toggle::new(3)
.icon(IconName::Check)
.checked(self.checked[3]),
)
.child(Toggle::new(4).label("Other").checked(self.checked[4]))
.on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
view.checked[0] = checkeds[0]; view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1]; view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2]; view.checked[2] = checkeds[2];
@ -130,12 +151,20 @@ impl Render for ToggleStory {
.child( .child(
ToggleGroup::new("toggle-button-group1-xs") ToggleGroup::new("toggle-button-group1-xs")
.xsmall() .xsmall()
.child(Toggle::icon(IconName::Bell).checked(self.checked[0])) .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))
.child(Toggle::icon(IconName::Bot).checked(self.checked[1])) .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) .child(
.child(Toggle::icon(IconName::Check).checked(self.checked[3])) Toggle::new(2)
.child(Toggle::label("Other").checked(self.checked[4])) .icon(IconName::Inbox)
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| { .checked(self.checked[2]),
)
.child(
Toggle::new(3)
.icon(IconName::Check)
.checked(self.checked[3]),
)
.child(Toggle::new(4).label("Other").checked(self.checked[4]))
.on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
view.checked[0] = checkeds[0]; view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1]; view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2]; view.checked[2] = checkeds[2];
@ -146,17 +175,26 @@ impl Render for ToggleStory {
), ),
) )
.child( .child(
h_flex() section("Toggle Group with Outline Style")
.gap_5() .v_flex()
.gap_4()
.child( .child(
ToggleGroup::new("toggle-button-group2") ToggleGroup::new("toggle-button-group2")
.outline() .outline()
.child(Toggle::icon(IconName::Bell).checked(self.checked[0])) .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))
.child(Toggle::icon(IconName::Bot).checked(self.checked[1])) .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) .child(
.child(Toggle::icon(IconName::Check).checked(self.checked[3])) Toggle::new(2)
.child(Toggle::label("Other").checked(self.checked[4])) .icon(IconName::Inbox)
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| { .checked(self.checked[2]),
)
.child(
Toggle::new(3)
.icon(IconName::Check)
.checked(self.checked[3]),
)
.child(Toggle::new(4).label("Other").checked(self.checked[4]))
.on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
view.checked[0] = checkeds[0]; view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1]; view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2]; view.checked[2] = checkeds[2];
@ -169,12 +207,20 @@ impl Render for ToggleStory {
ToggleGroup::new("toggle-button-group2-sm") ToggleGroup::new("toggle-button-group2-sm")
.outline() .outline()
.small() .small()
.child(Toggle::icon(IconName::Bell).checked(self.checked[0])) .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))
.child(Toggle::icon(IconName::Bot).checked(self.checked[1])) .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) .child(
.child(Toggle::icon(IconName::Check).checked(self.checked[3])) Toggle::new(2)
.child(Toggle::label("Other").checked(self.checked[4])) .icon(IconName::Inbox)
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| { .checked(self.checked[2]),
)
.child(
Toggle::new(3)
.icon(IconName::Check)
.checked(self.checked[3]),
)
.child(Toggle::new(4).label("Other").checked(self.checked[4]))
.on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
view.checked[0] = checkeds[0]; view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1]; view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2]; view.checked[2] = checkeds[2];
@ -187,12 +233,20 @@ impl Render for ToggleStory {
ToggleGroup::new("toggle-button-group2-xs") ToggleGroup::new("toggle-button-group2-xs")
.outline() .outline()
.xsmall() .xsmall()
.child(Toggle::icon(IconName::Bell).checked(self.checked[0])) .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0]))
.child(Toggle::icon(IconName::Bot).checked(self.checked[1])) .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1]))
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) .child(
.child(Toggle::icon(IconName::Check).checked(self.checked[3])) Toggle::new(2)
.child(Toggle::label("Other").checked(self.checked[4])) .icon(IconName::Inbox)
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| { .checked(self.checked[2]),
)
.child(
Toggle::new(3)
.icon(IconName::Check)
.checked(self.checked[3]),
)
.child(Toggle::new(4).label("Other").checked(self.checked[4]))
.on_click(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
view.checked[0] = checkeds[0]; view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1]; view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2]; view.checked[2] = checkeds[2];

View file

@ -17,10 +17,13 @@ pub enum ToggleVariant {
} }
pub trait ToggleVariants: Sized { pub trait ToggleVariants: Sized {
/// Set the variant of the toggle.
fn with_variant(self, variant: ToggleVariant) -> Self; fn with_variant(self, variant: ToggleVariant) -> Self;
/// Set the variant to ghost.
fn ghost(self) -> Self { fn ghost(self) -> Self {
self.with_variant(ToggleVariant::Ghost) self.with_variant(ToggleVariant::Ghost)
} }
/// Set the variant to outline.
fn outline(self) -> Self { fn outline(self) -> Self {
self.with_variant(ToggleVariant::Outline) self.with_variant(ToggleVariant::Outline)
} }
@ -28,53 +31,58 @@ pub trait ToggleVariants: Sized {
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Toggle { pub struct Toggle {
id: ElementId,
style: StyleRefinement, style: StyleRefinement,
checked: bool, checked: bool,
size: Size, size: Size,
variant: ToggleVariant, variant: ToggleVariant,
disabled: bool, disabled: bool,
children: SmallVec<[AnyElement; 1]>, children: SmallVec<[AnyElement; 1]>,
} on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
#[derive(IntoElement)]
pub struct InteractiveToggle {
id: ElementId,
toggle: Toggle,
on_change: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
} }
impl Toggle { impl Toggle {
fn new() -> Self { /// Create a new Toggle element.
pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
id: id.into(),
style: StyleRefinement::default(), style: StyleRefinement::default(),
checked: false, checked: false,
size: Size::default(), size: Size::default(),
variant: ToggleVariant::default(), variant: ToggleVariant::default(),
disabled: false, disabled: false,
children: smallvec![], children: smallvec![],
on_click: None,
} }
} }
pub fn label(label: impl Into<SharedString>) -> Self { /// Add a label to the toggle.
Self::new().child(label.into()) pub fn label(mut self, label: impl Into<SharedString>) -> Self {
let label: SharedString = label.into();
self.children.push(label.into_any_element());
self
} }
pub fn icon(icon: impl Into<Icon>) -> Self { /// Add icon to the toggle.
Self::new().child(icon.into()) pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
} let icon: Icon = icon.into();
self.children.push(icon.into());
pub fn id(self, id: impl Into<ElementId>) -> InteractiveToggle { self
InteractiveToggle {
id: id.into(),
toggle: self,
on_change: None,
}
} }
/// Set the checked state of the toggle, default: false
pub fn checked(mut self, checked: bool) -> Self { pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked; self.checked = checked;
self self
} }
/// Set the callback to be called when the toggle is clicked.
///
/// The `&bool` parameter represents the new checked state of the toggle.
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
} }
impl ToggleVariants for Toggle { impl ToggleVariants for Toggle {
@ -84,6 +92,12 @@ impl ToggleVariants for Toggle {
} }
} }
impl ParentElement for Toggle {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl Disableable for Toggle { impl Disableable for Toggle {
fn disabled(mut self, disabled: bool) -> Self { fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled; self.disabled = disabled;
@ -104,12 +118,6 @@ impl Styled for Toggle {
} }
} }
impl ParentElement for Toggle {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for Toggle { impl RenderOnce for Toggle {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let checked = self.checked; let checked = self.checked;
@ -117,6 +125,7 @@ impl RenderOnce for Toggle {
let hoverable = !disabled && !checked; let hoverable = !disabled && !checked;
div() div()
.id(self.id)
.flex() .flex()
.flex_row() .flex_row()
.items_center() .items_center()
@ -146,67 +155,15 @@ impl RenderOnce for Toggle {
}) })
.refine_style(&self.style) .refine_style(&self.style)
.children(self.children) .children(self.children)
}
}
impl InteractiveToggle {
/// Sets the callback to be invoked when the toggle is clicked.
///
/// The first argument is a boolean indicating whether the toggle is checked.
pub fn on_change(mut self, on_change: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Box::new(on_change));
self
}
pub fn checked(mut self, checked: bool) -> Self {
self.toggle.checked = checked;
self
}
}
impl Sizable for InteractiveToggle {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.toggle = self.toggle.with_size(size);
self
}
}
impl ToggleVariants for InteractiveToggle {
fn with_variant(mut self, variant: ToggleVariant) -> Self {
self.toggle.variant = variant;
self
}
}
impl Disableable for InteractiveToggle {
fn disabled(mut self, disabled: bool) -> Self {
self.toggle.disabled = disabled;
self
}
}
impl ParentElement for InteractiveToggle {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.toggle.extend(elements);
}
}
impl RenderOnce for InteractiveToggle {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let checked = self.toggle.checked;
let disabled = self.toggle.disabled;
div()
.id(self.id)
.child(self.toggle)
.when(!disabled, |this| { .when(!disabled, |this| {
this.when_some(self.on_change, |this, on_change| { this.when_some(self.on_click, |this, on_click| {
this.on_click(move |_, window, cx| on_change(&!checked, window, cx)) this.on_click(move |_, window, cx| on_click(&!checked, window, cx))
}) })
}) })
} }
} }
/// A group of toggles.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ToggleGroup { pub struct ToggleGroup {
id: ElementId, id: ElementId,
@ -215,10 +172,11 @@ pub struct ToggleGroup {
variant: ToggleVariant, variant: ToggleVariant,
disabled: bool, disabled: bool,
items: Vec<Toggle>, items: Vec<Toggle>,
on_change: Option<Rc<dyn Fn(&Vec<bool>, &mut Window, &mut App) + 'static>>, on_click: Option<Rc<dyn Fn(&Vec<bool>, &mut Window, &mut App) + 'static>>,
} }
impl ToggleGroup { impl ToggleGroup {
/// Create a new ToggleGroup element.
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
id: id.into(), id: id.into(),
@ -227,7 +185,7 @@ impl ToggleGroup {
variant: ToggleVariant::default(), variant: ToggleVariant::default(),
disabled: false, disabled: false,
items: Vec::new(), items: Vec::new(),
on_change: None, on_click: None,
} }
} }
@ -245,12 +203,12 @@ impl ToggleGroup {
/// Set the callback to be called when the toggle group changes. /// Set the callback to be called when the toggle group changes.
/// ///
/// The `&Vec<bool>` parameter represents the check state of each [`Toggle`] in the group. /// The `&Vec<bool>` parameter represents the new check state of each [`Toggle`] in the group.
pub fn on_change( pub fn on_click(
mut self, mut self,
on_change: impl Fn(&Vec<bool>, &mut Window, &mut App) + 'static, on_click: impl Fn(&Vec<bool>, &mut Window, &mut App) + 'static,
) -> Self { ) -> Self {
self.on_change = Some(Rc::new(on_change)); self.on_click = Some(Rc::new(on_click));
self self
} }
} }
@ -300,21 +258,20 @@ impl RenderOnce for ToggleGroup {
|(ix, item)| { |(ix, item)| {
let state = state.clone(); let state = state.clone();
item.disabled(disabled) item.disabled(disabled)
.id(ix)
.with_size(self.size) .with_size(self.size)
.with_variant(self.variant) .with_variant(self.variant)
.on_change(move |_, _, _| { .on_click(move |_, _, _| {
state.set(Some(ix)); state.set(Some(ix));
}) })
} }
})) }))
.when(!disabled, |this| { .when(!disabled, |this| {
this.when_some(self.on_change, |this, on_change| { this.when_some(self.on_click, |this, on_click| {
this.on_click(move |_, window, cx| { this.on_click(move |_, window, cx| {
if let Some(ix) = state.get() { if let Some(ix) = state.get() {
let mut checks = checks.clone(); let mut checks = checks.clone();
checks[ix] = !checks[ix]; checks[ix] = !checks[ix];
on_change(&checks, window, cx); on_click(&checks, window, cx);
} }
}) })
}) })

View file

@ -18,23 +18,25 @@ use gpui_component::button::{Toggle, ToggleGroup};
### Basic Toggle ### Basic Toggle
```rust ```rust
Toggle::label("Toggle me") Toggle::new("toggle1").
.id("basic-toggle") .label("Toggle me")
.checked(false) .checked(false)
.on_change(|checked, _, _| { .on_click(|checked, _, _| {
println!("Toggle is now: {}", checked); println!("Toggle is now: {}", checked);
}) })
``` ```
Here, we can use `on_click` to handle toggle state changes. The callback receives the **new checked state** as a `bool`.
### Icon Toggle ### Icon Toggle
```rust ```rust
use gpui_component::IconName; use gpui_component::IconName;
Toggle::icon(IconName::Eye) Toggle::new("toggle2")
.id("visibility-toggle") .icon(IconName::Eye)
.checked(true) .checked(true)
.on_change(|checked, _, _| { .on_click(|checked, _, _| {
println!("Visibility: {}", if *checked { "shown" } else { "hidden" }); println!("Visibility: {}", if *checked { "shown" } else { "hidden" });
}) })
``` ```
@ -48,10 +50,10 @@ struct MyView {
impl Render for MyView { impl Render for MyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Toggle::label("Active") Toggle::new("active")
.id("active-toggle") .label("Active")
.checked(self.is_active) .checked(self.is_active)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
view.is_active = *checked; view.is_active = *checked;
cx.notify(); cx.notify();
})) }))
@ -63,36 +65,37 @@ impl Render for MyView {
```rust ```rust
// Ghost toggle (default) // Ghost toggle (default)
Toggle::label("Ghost") Toggle::new("ghost-toggle")
.id("ghost-toggle")
.ghost() .ghost()
.label("Ghost")
// Outline toggle // Outline toggle
Toggle::label("Outline") Toggle::new("outline-toggle")
.id("outline-toggle")
.outline() .outline()
.label("Outline")
``` ```
### Different Sizes ### Different Sizes
```rust ```rust
// Extra small // Extra small
Toggle::icon(IconName::Star) Toggle::new("xs-toggle")
.id("xs-toggle") .icon(IconName::Star)
.xsmall() .xsmall()
// Small // Small
Toggle::label("Small") Toggle::new("small-toggle")
.id("small-toggle") .label("Small")
.small() .small()
// Medium (default) // Medium (default)
Toggle::label("Medium") Toggle::new("medium-toggle")
.id("medium-toggle") .label("Medium")
// Large // Large
Toggle::label("Large") Toggle::new("large-toggle")
.id("large-toggle") .label("Large")
.large() .large()
``` ```
@ -100,14 +103,14 @@ Toggle::label("Large")
```rust ```rust
// Disabled unchecked // Disabled unchecked
Toggle::label("Disabled") Toggle::new("disabled-toggle")
.id("disabled-toggle") .label("Disabled")
.disabled(true) .disabled(true)
.checked(false) .checked(false)
// Disabled checked // Disabled checked
Toggle::label("Selected (Disabled)") Toggle::new("disabled-checked-toggle")
.id("disabled-checked-toggle") .label("Selected (Disabled)")
.disabled(true) .disabled(true)
.checked(true) .checked(true)
``` ```
@ -144,15 +147,17 @@ Toggle buttons can be grouped together using `ToggleGroup` for related options:
```rust ```rust
ToggleGroup::new("filter-group") ToggleGroup::new("filter-group")
.child(Toggle::icon(IconName::Bell)) .child(Toggle::new(0).icon(IconName::Bell))
.child(Toggle::icon(IconName::Bot)) .child(Toggle::new(1).icon(IconName::Bot))
.child(Toggle::icon(IconName::Inbox)) .child(Toggle::new(2).icon(IconName::Inbox))
.child(Toggle::label("Other")) .child(Toggle::new(3).label("Other"))
.on_change(|checkeds, _, _| { .on_click(|checkeds, _, _| {
println!("Selected toggles: {:?}", checkeds); println!("Selected toggles: {:?}", checkeds);
}) })
``` ```
The `on_click` callback receives a `Vec<bool>` representing the **new checked state** of each toggle in the group.
### Toggle Group with Controlled State ### Toggle Group with Controlled State
```rust ```rust
@ -166,11 +171,11 @@ struct FilterView {
impl Render for FilterView { impl Render for FilterView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
ToggleGroup::new("filters") ToggleGroup::new("filters")
.child(Toggle::icon(IconName::Bell).checked(self.notifications)) .child(Toggle::new(0).icon(IconName::Bell).checked(self.notifications))
.child(Toggle::icon(IconName::Bot).checked(self.bots)) .child(Toggle::new(1).icon(IconName::Bot).checked(self.bots))
.child(Toggle::icon(IconName::Inbox).checked(self.inbox)) .child(Toggle::new(2).icon(IconName::Inbox).checked(self.inbox))
.child(Toggle::label("Other").checked(self.other)) .child(Toggle::new(3).label("Other").checked(self.other))
.on_change(cx.listener(|view, checkeds, _, cx| { .on_click(cx.listener(|view, checkeds, _, cx| {
view.notifications = checkeds[0]; view.notifications = checkeds[0];
view.bots = checkeds[1]; view.bots = checkeds[1];
view.inbox = checkeds[2]; view.inbox = checkeds[2];
@ -188,16 +193,16 @@ impl Render for FilterView {
ToggleGroup::new("compact-filters") ToggleGroup::new("compact-filters")
.outline() .outline()
.small() .small()
.child(Toggle::icon(IconName::Filter)) .child(Toggle::new(0).icon(IconName::Filter))
.child(Toggle::icon(IconName::Sort)) .child(Toggle::new(1).icon(IconName::Sort))
.child(Toggle::icon(IconName::Search)) .child(Toggle::new(2).icon(IconName::Search))
// Ghost variant (default), extra small // Ghost variant (default), extra small
ToggleGroup::new("mini-toolbar") ToggleGroup::new("mini-toolbar")
.xsmall() .xsmall()
.child(Toggle::icon(IconName::Bold)) .child(Toggle::new(0).icon(IconName::Bold))
.child(Toggle::icon(IconName::Italic)) .child(Toggle::new(1).icon(IconName::Italic))
.child(Toggle::icon(IconName::Underline)) .child(Toggle::new(2).icon(IconName::Underline))
``` ```
## Event Handling ## Event Handling
@ -205,9 +210,9 @@ ToggleGroup::new("mini-toolbar")
### Individual Toggle Events ### Individual Toggle Events
```rust ```rust
Toggle::label("Subscribe") Toggle::new("subscribe-toggle")
.id("subscribe-toggle") .label("Subscribe")
.on_change(|checked, window, cx| { .on_click(|checked, window, cx| {
if *checked { if *checked {
// Handle subscription logic // Handle subscription logic
println!("Subscribed!"); println!("Subscribed!");
@ -218,72 +223,6 @@ Toggle::label("Subscribe")
}) })
``` ```
### Toggle Group Events
The `on_change` callback for `ToggleGroup` receives a `Vec<bool>` representing the state of each toggle:
```rust
ToggleGroup::new("options")
.child(Toggle::label("Option 1"))
.child(Toggle::label("Option 2"))
.child(Toggle::label("Option 3"))
.on_change(|states, _, _| {
for (i, checked) in states.iter().enumerate() {
if *checked {
println!("Option {} is selected", i + 1);
}
}
})
```
## API Reference
### Toggle
| Method | Description |
| ---------------- | ----------------------------------------------------------- |
| `label(str)` | Create toggle with text label |
| `icon(icon)` | Create toggle with icon |
| `id(id)` | Set element ID and make interactive |
| `checked(bool)` | Set checked/selected state |
| `disabled(bool)` | Set disabled state |
| `on_change(fn)` | Callback when clicked, receives `&bool` (new checked state) |
### Toggle Variants
| Method | Description |
| ----------- | ------------------------------------------------ |
| `ghost()` | Ghost variant (default) - transparent background |
| `outline()` | Outline variant - visible border |
### Toggle Sizing
Implements `Sizable` trait:
- `xsmall()` - Extra small toggle (20x20px)
- `small()` - Small toggle (24x24px)
- `medium()` - Medium toggle (32x32px, default)
- `large()` - Large toggle (36x36px)
- `with_size(size)` - Set explicit size
### ToggleGroup
| Method | Description |
| ---------------- | ------------------------------------------------------- |
| `new(id)` | Create a new toggle group with ID |
| `child(toggle)` | Add a toggle to the group |
| `children(iter)` | Add multiple toggles to the group |
| `on_change(fn)` | Callback when any toggle changes, receives `&Vec<bool>` |
| `disabled(bool)` | Disable all toggles in the group |
### ToggleGroup Styling
Implements `Sizable`, `ToggleVariants`, and `Disableable` traits:
- Size methods apply to all child toggles
- Variant methods apply to all child toggles
- `disabled(bool)` affects the entire group
## Examples ## Examples
### Toolbar with Toggle Buttons ### Toolbar with Toggle Buttons
@ -305,11 +244,11 @@ h_flex()
.child( .child(
ToggleGroup::new("formatting") ToggleGroup::new("formatting")
.small() .small()
.child(Toggle::icon(IconName::Bold).checked(self.bold)) .child(Toggle::new(0).icon(IconName::Bold).checked(self.bold))
.child(Toggle::icon(IconName::Italic).checked(self.italic)) .child(Toggle::new(1).icon(IconName::Italic).checked(self.italic))
.child(Toggle::icon(IconName::Underline).checked(self.underline)) .child(Toggle::new(2).icon(IconName::Underline).checked(self.underline))
.child(Toggle::icon(IconName::Strikethrough).checked(self.strikethrough)) .child(Toggle::new(3).icon(IconName::Strikethrough).checked(self.strikethrough))
.on_change(cx.listener(|view, states, _, cx| { .on_click(cx.listener(|view, states, _, cx| {
view.bold = states[0]; view.bold = states[0];
view.italic = states[1]; view.italic = states[1];
view.underline = states[2]; view.underline = states[2];
@ -336,10 +275,10 @@ v_flex()
.child( .child(
ToggleGroup::new("status-filters") ToggleGroup::new("status-filters")
.outline() .outline()
.child(Toggle::label("Completed").checked(self.show_completed)) .child(Toggle::new(0).label("Completed").checked(self.show_completed))
.child(Toggle::label("Pending").checked(self.show_pending)) .child(Toggle::new(1).label("Pending").checked(self.show_pending))
.child(Toggle::label("Cancelled").checked(self.show_cancelled)) .child(Toggle::new(2).label("Cancelled").checked(self.show_cancelled))
.on_change(cx.listener(|view, states, _, cx| { .on_click(cx.listener(|view, states, _, cx| {
view.show_completed = states[0]; view.show_completed = states[0];
view.show_pending = states[1]; view.show_pending = states[1];
view.show_cancelled = states[2]; view.show_cancelled = states[2];
@ -347,10 +286,10 @@ v_flex()
})) }))
) )
.child( .child(
Toggle::label("Show urgent only") Toggle::new("urgent-filter")
.id("urgent-filter") .label("Show urgent only")
.checked(self.show_urgent) .checked(self.show_urgent)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
view.show_urgent = *checked; view.show_urgent = *checked;
cx.notify(); cx.notify();
})) }))
@ -382,10 +321,10 @@ v_flex()
) )
) )
.child( .child(
Toggle::icon(IconName::Mail) Toggle::new("email-notifications")
.id("email-notifications") .icon(IconName::Mail)
.checked(self.email_notifications) .checked(self.email_notifications)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
view.email_notifications = *checked; view.email_notifications = *checked;
cx.notify(); cx.notify();
})) }))
@ -397,10 +336,10 @@ v_flex()
.justify_between() .justify_between()
.child(Label::new("Push notifications")) .child(Label::new("Push notifications"))
.child( .child(
Toggle::icon(IconName::Bell) Toggle::new("push-notifications")
.id("push-notifications") .icon(IconName::Bell)
.checked(self.push_notifications) .checked(self.push_notifications)
.on_change(cx.listener(|view, checked, _, cx| { .on_click(cx.listener(|view, checked, _, cx| {
view.push_notifications = *checked; view.push_notifications = *checked;
cx.notify(); cx.notify();
})) }))
@ -431,11 +370,12 @@ v_flex()
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(i, category)| { .map(|(i, category)| {
Toggle::label(category) Toggle::new(i)
.label(category)
.checked(self.selected_categories.get(i).copied().unwrap_or(false)) .checked(self.selected_categories.get(i).copied().unwrap_or(false))
}) })
) )
.on_change(cx.listener(|view, states, _, cx| { .on_click(cx.listener(|view, states, _, cx| {
view.selected_categories = states.clone(); view.selected_categories = states.clone();
cx.notify(); cx.notify();
})) }))