From 0524c51b3eeb0d4cdb7a12b6b8e07d170bd9d593 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 5 Nov 2025 10:47:55 +0800 Subject: [PATCH] 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(..) ``` --- crates/story/src/main.rs | 1 + crates/story/src/toggle_story.rs | 158 ++++++++++++++++-------- crates/ui/src/button/toggle.rs | 139 ++++++++------------- docs/docs/components/toggle.md | 200 +++++++++++-------------------- 4 files changed, 225 insertions(+), 273 deletions(-) diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index d74f337f..bae65ce8 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -77,6 +77,7 @@ impl Gallery { StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), + StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), diff --git a/crates/story/src/toggle_story.rs b/crates/story/src/toggle_story.rs index 237db210..9d39f07e 100644 --- a/crates/story/src/toggle_story.rs +++ b/crates/story/src/toggle_story.rs @@ -4,10 +4,13 @@ use gpui::{ }; use gpui_component::{ + IconName, Sizable, StyledExt, button::{Toggle, ToggleGroup, ToggleVariants}, - h_flex, v_flex, IconName, Sizable, + v_flex, }; +use crate::section; + pub struct ToggleStory { focus_handle: FocusHandle, single_toggle: usize, @@ -51,16 +54,16 @@ impl Focusable for ToggleStory { impl Render for ToggleStory { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() + .size_full() .gap_6() .child( - h_flex() - .gap_2() + section("Toggle") .child( - Toggle::label("Single Toggle Item 1") - .id("single-toggle-item-1") + Toggle::new("item1") + .label("Single Toggle Item 1") .large() .checked(self.single_toggle == 1) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { if *checked { view.single_toggle = 1; } @@ -68,11 +71,11 @@ impl Render for ToggleStory { })), ) .child( - Toggle::label("Single Toggle Item 2") - .id("single-toggle-item-2") + Toggle::new("item2") + .label("Single Toggle Item 2") .large() .checked(self.single_toggle == 2) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { if *checked { view.single_toggle = 2; } @@ -80,11 +83,11 @@ impl Render for ToggleStory { })), ) .child( - Toggle::icon(IconName::Eye) - .id("single-toggle-item-3") + Toggle::new("item3") + .icon(IconName::Eye) .large() .checked(self.single_toggle == 3) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { if *checked { view.single_toggle = 3; } @@ -93,32 +96,50 @@ impl Render for ToggleStory { ), ) .child( - h_flex() - .gap_5() + section("Toggle Group with Ghost Style") + .v_flex() + .gap_4() .child( ToggleGroup::new("toggle-button-group1") - .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) - .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) - .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) - .child(Toggle::icon(IconName::Check).checked(self.checked[3])) - .child(Toggle::label("Other").checked(self.checked[4])) - .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1])) + .child( + Toggle::new(2) + .icon(IconName::Inbox) + .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, _, cx| { view.checked[0] = checkeds[0]; view.checked[1] = checkeds[1]; view.checked[2] = checkeds[2]; view.checked[3] = checkeds[3]; + view.checked[4] = checkeds[4]; cx.notify(); })), ) .child( ToggleGroup::new("toggle-button-group1-sm") .small() - .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) - .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) - .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) - .child(Toggle::icon(IconName::Check).checked(self.checked[3])) - .child(Toggle::label("Other").checked(self.checked[4])) - .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1])) + .child( + Toggle::new(2) + .icon(IconName::Inbox) + .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, _, cx| { view.checked[0] = checkeds[0]; view.checked[1] = checkeds[1]; view.checked[2] = checkeds[2]; @@ -130,12 +151,20 @@ impl Render for ToggleStory { .child( ToggleGroup::new("toggle-button-group1-xs") .xsmall() - .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) - .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) - .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) - .child(Toggle::icon(IconName::Check).checked(self.checked[3])) - .child(Toggle::label("Other").checked(self.checked[4])) - .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1])) + .child( + Toggle::new(2) + .icon(IconName::Inbox) + .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, _, cx| { view.checked[0] = checkeds[0]; view.checked[1] = checkeds[1]; view.checked[2] = checkeds[2]; @@ -146,17 +175,26 @@ impl Render for ToggleStory { ), ) .child( - h_flex() - .gap_5() + section("Toggle Group with Outline Style") + .v_flex() + .gap_4() .child( ToggleGroup::new("toggle-button-group2") .outline() - .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) - .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) - .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) - .child(Toggle::icon(IconName::Check).checked(self.checked[3])) - .child(Toggle::label("Other").checked(self.checked[4])) - .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1])) + .child( + Toggle::new(2) + .icon(IconName::Inbox) + .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, _, cx| { view.checked[0] = checkeds[0]; view.checked[1] = checkeds[1]; view.checked[2] = checkeds[2]; @@ -169,12 +207,20 @@ impl Render for ToggleStory { ToggleGroup::new("toggle-button-group2-sm") .outline() .small() - .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) - .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) - .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) - .child(Toggle::icon(IconName::Check).checked(self.checked[3])) - .child(Toggle::label("Other").checked(self.checked[4])) - .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1])) + .child( + Toggle::new(2) + .icon(IconName::Inbox) + .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, _, cx| { view.checked[0] = checkeds[0]; view.checked[1] = checkeds[1]; view.checked[2] = checkeds[2]; @@ -187,12 +233,20 @@ impl Render for ToggleStory { ToggleGroup::new("toggle-button-group2-xs") .outline() .xsmall() - .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) - .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) - .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) - .child(Toggle::icon(IconName::Check).checked(self.checked[3])) - .child(Toggle::label("Other").checked(self.checked[4])) - .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.checked[1])) + .child( + Toggle::new(2) + .icon(IconName::Inbox) + .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, _, cx| { view.checked[0] = checkeds[0]; view.checked[1] = checkeds[1]; view.checked[2] = checkeds[2]; diff --git a/crates/ui/src/button/toggle.rs b/crates/ui/src/button/toggle.rs index f590863f..c6e3a78e 100644 --- a/crates/ui/src/button/toggle.rs +++ b/crates/ui/src/button/toggle.rs @@ -17,10 +17,13 @@ pub enum ToggleVariant { } pub trait ToggleVariants: Sized { + /// Set the variant of the toggle. fn with_variant(self, variant: ToggleVariant) -> Self; + /// Set the variant to ghost. fn ghost(self) -> Self { self.with_variant(ToggleVariant::Ghost) } + /// Set the variant to outline. fn outline(self) -> Self { self.with_variant(ToggleVariant::Outline) } @@ -28,53 +31,58 @@ pub trait ToggleVariants: Sized { #[derive(IntoElement)] pub struct Toggle { + id: ElementId, style: StyleRefinement, checked: bool, size: Size, variant: ToggleVariant, disabled: bool, children: SmallVec<[AnyElement; 1]>, -} - -#[derive(IntoElement)] -pub struct InteractiveToggle { - id: ElementId, - toggle: Toggle, - on_change: Option>, + on_click: Option>, } impl Toggle { - fn new() -> Self { + /// Create a new Toggle element. + pub fn new(id: impl Into) -> Self { Self { + id: id.into(), style: StyleRefinement::default(), checked: false, size: Size::default(), variant: ToggleVariant::default(), disabled: false, children: smallvec![], + on_click: None, } } - pub fn label(label: impl Into) -> Self { - Self::new().child(label.into()) + /// Add a label to the toggle. + pub fn label(mut self, label: impl Into) -> Self { + let label: SharedString = label.into(); + self.children.push(label.into_any_element()); + self } - pub fn icon(icon: impl Into) -> Self { - Self::new().child(icon.into()) - } - - pub fn id(self, id: impl Into) -> InteractiveToggle { - InteractiveToggle { - id: id.into(), - toggle: self, - on_change: None, - } + /// Add icon to the toggle. + pub fn icon(mut self, icon: impl Into) -> Self { + let icon: Icon = icon.into(); + self.children.push(icon.into()); + self } + /// Set the checked state of the toggle, default: false pub fn checked(mut self, checked: bool) -> Self { self.checked = checked; 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 { @@ -84,6 +92,12 @@ impl ToggleVariants for Toggle { } } +impl ParentElement for Toggle { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements); + } +} + impl Disableable for Toggle { fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; @@ -104,12 +118,6 @@ impl Styled for Toggle { } } -impl ParentElement for Toggle { - fn extend(&mut self, elements: impl IntoIterator) { - self.children.extend(elements); - } -} - impl RenderOnce for Toggle { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { let checked = self.checked; @@ -117,6 +125,7 @@ impl RenderOnce for Toggle { let hoverable = !disabled && !checked; div() + .id(self.id) .flex() .flex_row() .items_center() @@ -146,67 +155,15 @@ impl RenderOnce for Toggle { }) .refine_style(&self.style) .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) -> 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) { - 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| { - this.when_some(self.on_change, |this, on_change| { - this.on_click(move |_, window, cx| on_change(&!checked, window, cx)) + this.when_some(self.on_click, |this, on_click| { + this.on_click(move |_, window, cx| on_click(&!checked, window, cx)) }) }) } } +/// A group of toggles. #[derive(IntoElement)] pub struct ToggleGroup { id: ElementId, @@ -215,10 +172,11 @@ pub struct ToggleGroup { variant: ToggleVariant, disabled: bool, items: Vec, - on_change: Option, &mut Window, &mut App) + 'static>>, + on_click: Option, &mut Window, &mut App) + 'static>>, } impl ToggleGroup { + /// Create a new ToggleGroup element. pub fn new(id: impl Into) -> Self { Self { id: id.into(), @@ -227,7 +185,7 @@ impl ToggleGroup { variant: ToggleVariant::default(), disabled: false, 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. /// - /// The `&Vec` parameter represents the check state of each [`Toggle`] in the group. - pub fn on_change( + /// The `&Vec` parameter represents the new check state of each [`Toggle`] in the group. + pub fn on_click( mut self, - on_change: impl Fn(&Vec, &mut Window, &mut App) + 'static, + on_click: impl Fn(&Vec, &mut Window, &mut App) + 'static, ) -> Self { - self.on_change = Some(Rc::new(on_change)); + self.on_click = Some(Rc::new(on_click)); self } } @@ -300,21 +258,20 @@ impl RenderOnce for ToggleGroup { |(ix, item)| { let state = state.clone(); item.disabled(disabled) - .id(ix) .with_size(self.size) .with_variant(self.variant) - .on_change(move |_, _, _| { + .on_click(move |_, _, _| { state.set(Some(ix)); }) } })) .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| { if let Some(ix) = state.get() { let mut checks = checks.clone(); checks[ix] = !checks[ix]; - on_change(&checks, window, cx); + on_click(&checks, window, cx); } }) }) diff --git a/docs/docs/components/toggle.md b/docs/docs/components/toggle.md index 6d352a1e..bb305cf3 100644 --- a/docs/docs/components/toggle.md +++ b/docs/docs/components/toggle.md @@ -18,23 +18,25 @@ use gpui_component::button::{Toggle, ToggleGroup}; ### Basic Toggle ```rust -Toggle::label("Toggle me") - .id("basic-toggle") +Toggle::new("toggle1"). + .label("Toggle me") .checked(false) - .on_change(|checked, _, _| { + .on_click(|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 ```rust use gpui_component::IconName; -Toggle::icon(IconName::Eye) - .id("visibility-toggle") +Toggle::new("toggle2") + .icon(IconName::Eye) .checked(true) - .on_change(|checked, _, _| { + .on_click(|checked, _, _| { println!("Visibility: {}", if *checked { "shown" } else { "hidden" }); }) ``` @@ -48,10 +50,10 @@ struct MyView { impl Render for MyView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - Toggle::label("Active") - .id("active-toggle") + Toggle::new("active") + .label("Active") .checked(self.is_active) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { view.is_active = *checked; cx.notify(); })) @@ -63,36 +65,37 @@ impl Render for MyView { ```rust // Ghost toggle (default) -Toggle::label("Ghost") - .id("ghost-toggle") +Toggle::new("ghost-toggle") .ghost() + .label("Ghost") // Outline toggle -Toggle::label("Outline") - .id("outline-toggle") +Toggle::new("outline-toggle") .outline() + .label("Outline") ``` ### Different Sizes ```rust // Extra small -Toggle::icon(IconName::Star) - .id("xs-toggle") +Toggle::new("xs-toggle") + .icon(IconName::Star) .xsmall() // Small -Toggle::label("Small") - .id("small-toggle") +Toggle::new("small-toggle") + .label("Small") .small() // Medium (default) -Toggle::label("Medium") - .id("medium-toggle") +Toggle::new("medium-toggle") + .label("Medium") + // Large -Toggle::label("Large") - .id("large-toggle") +Toggle::new("large-toggle") + .label("Large") .large() ``` @@ -100,14 +103,14 @@ Toggle::label("Large") ```rust // Disabled unchecked -Toggle::label("Disabled") - .id("disabled-toggle") +Toggle::new("disabled-toggle") + .label("Disabled") .disabled(true) .checked(false) // Disabled checked -Toggle::label("Selected (Disabled)") - .id("disabled-checked-toggle") +Toggle::new("disabled-checked-toggle") + .label("Selected (Disabled)") .disabled(true) .checked(true) ``` @@ -144,15 +147,17 @@ Toggle buttons can be grouped together using `ToggleGroup` for related options: ```rust ToggleGroup::new("filter-group") - .child(Toggle::icon(IconName::Bell)) - .child(Toggle::icon(IconName::Bot)) - .child(Toggle::icon(IconName::Inbox)) - .child(Toggle::label("Other")) - .on_change(|checkeds, _, _| { + .child(Toggle::new(0).icon(IconName::Bell)) + .child(Toggle::new(1).icon(IconName::Bot)) + .child(Toggle::new(2).icon(IconName::Inbox)) + .child(Toggle::new(3).label("Other")) + .on_click(|checkeds, _, _| { println!("Selected toggles: {:?}", checkeds); }) ``` +The `on_click` callback receives a `Vec` representing the **new checked state** of each toggle in the group. + ### Toggle Group with Controlled State ```rust @@ -166,11 +171,11 @@ struct FilterView { impl Render for FilterView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { ToggleGroup::new("filters") - .child(Toggle::icon(IconName::Bell).checked(self.notifications)) - .child(Toggle::icon(IconName::Bot).checked(self.bots)) - .child(Toggle::icon(IconName::Inbox).checked(self.inbox)) - .child(Toggle::label("Other").checked(self.other)) - .on_change(cx.listener(|view, checkeds, _, cx| { + .child(Toggle::new(0).icon(IconName::Bell).checked(self.notifications)) + .child(Toggle::new(1).icon(IconName::Bot).checked(self.bots)) + .child(Toggle::new(2).icon(IconName::Inbox).checked(self.inbox)) + .child(Toggle::new(3).label("Other").checked(self.other)) + .on_click(cx.listener(|view, checkeds, _, cx| { view.notifications = checkeds[0]; view.bots = checkeds[1]; view.inbox = checkeds[2]; @@ -188,16 +193,16 @@ impl Render for FilterView { ToggleGroup::new("compact-filters") .outline() .small() - .child(Toggle::icon(IconName::Filter)) - .child(Toggle::icon(IconName::Sort)) - .child(Toggle::icon(IconName::Search)) + .child(Toggle::new(0).icon(IconName::Filter)) + .child(Toggle::new(1).icon(IconName::Sort)) + .child(Toggle::new(2).icon(IconName::Search)) // Ghost variant (default), extra small ToggleGroup::new("mini-toolbar") .xsmall() - .child(Toggle::icon(IconName::Bold)) - .child(Toggle::icon(IconName::Italic)) - .child(Toggle::icon(IconName::Underline)) + .child(Toggle::new(0).icon(IconName::Bold)) + .child(Toggle::new(1).icon(IconName::Italic)) + .child(Toggle::new(2).icon(IconName::Underline)) ``` ## Event Handling @@ -205,9 +210,9 @@ ToggleGroup::new("mini-toolbar") ### Individual Toggle Events ```rust -Toggle::label("Subscribe") - .id("subscribe-toggle") - .on_change(|checked, window, cx| { +Toggle::new("subscribe-toggle") + .label("Subscribe") + .on_click(|checked, window, cx| { if *checked { // Handle subscription logic println!("Subscribed!"); @@ -218,72 +223,6 @@ Toggle::label("Subscribe") }) ``` -### Toggle Group Events - -The `on_change` callback for `ToggleGroup` receives a `Vec` 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` | -| `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 ### Toolbar with Toggle Buttons @@ -305,11 +244,11 @@ h_flex() .child( ToggleGroup::new("formatting") .small() - .child(Toggle::icon(IconName::Bold).checked(self.bold)) - .child(Toggle::icon(IconName::Italic).checked(self.italic)) - .child(Toggle::icon(IconName::Underline).checked(self.underline)) - .child(Toggle::icon(IconName::Strikethrough).checked(self.strikethrough)) - .on_change(cx.listener(|view, states, _, cx| { + .child(Toggle::new(0).icon(IconName::Bold).checked(self.bold)) + .child(Toggle::new(1).icon(IconName::Italic).checked(self.italic)) + .child(Toggle::new(2).icon(IconName::Underline).checked(self.underline)) + .child(Toggle::new(3).icon(IconName::Strikethrough).checked(self.strikethrough)) + .on_click(cx.listener(|view, states, _, cx| { view.bold = states[0]; view.italic = states[1]; view.underline = states[2]; @@ -336,10 +275,10 @@ v_flex() .child( ToggleGroup::new("status-filters") .outline() - .child(Toggle::label("Completed").checked(self.show_completed)) - .child(Toggle::label("Pending").checked(self.show_pending)) - .child(Toggle::label("Cancelled").checked(self.show_cancelled)) - .on_change(cx.listener(|view, states, _, cx| { + .child(Toggle::new(0).label("Completed").checked(self.show_completed)) + .child(Toggle::new(1).label("Pending").checked(self.show_pending)) + .child(Toggle::new(2).label("Cancelled").checked(self.show_cancelled)) + .on_click(cx.listener(|view, states, _, cx| { view.show_completed = states[0]; view.show_pending = states[1]; view.show_cancelled = states[2]; @@ -347,10 +286,10 @@ v_flex() })) ) .child( - Toggle::label("Show urgent only") - .id("urgent-filter") + Toggle::new("urgent-filter") + .label("Show urgent only") .checked(self.show_urgent) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { view.show_urgent = *checked; cx.notify(); })) @@ -382,10 +321,10 @@ v_flex() ) ) .child( - Toggle::icon(IconName::Mail) - .id("email-notifications") + Toggle::new("email-notifications") + .icon(IconName::Mail) .checked(self.email_notifications) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { view.email_notifications = *checked; cx.notify(); })) @@ -397,10 +336,10 @@ v_flex() .justify_between() .child(Label::new("Push notifications")) .child( - Toggle::icon(IconName::Bell) - .id("push-notifications") + Toggle::new("push-notifications") + .icon(IconName::Bell) .checked(self.push_notifications) - .on_change(cx.listener(|view, checked, _, cx| { + .on_click(cx.listener(|view, checked, _, cx| { view.push_notifications = *checked; cx.notify(); })) @@ -431,11 +370,12 @@ v_flex() .into_iter() .enumerate() .map(|(i, category)| { - Toggle::label(category) + Toggle::new(i) + .label(category) .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(); cx.notify(); }))