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::<TagStory>(window, cx),
StoryContainer::panel::<TextareaStory>(window, cx),
StoryContainer::panel::<ToggleStory>(window, cx),
StoryContainer::panel::<TooltipStory>(window, cx),
StoryContainer::panel::<TreeStory>(window, cx),
StoryContainer::panel::<VirtualListStory>(window, cx),

View file

@ -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<Self>) -> 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, 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<bool>, _, cx| {
view.checked[0] = checkeds[0];
view.checked[1] = checkeds[1];
view.checked[2] = checkeds[2];

View file

@ -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<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
}
impl Toggle {
fn new() -> Self {
/// Create a new Toggle element.
pub fn new(id: impl Into<ElementId>) -> 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<SharedString>) -> Self {
Self::new().child(label.into())
/// Add a label to the toggle.
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 {
Self::new().child(icon.into())
}
pub fn id(self, id: impl Into<ElementId>) -> InteractiveToggle {
InteractiveToggle {
id: id.into(),
toggle: self,
on_change: None,
}
/// Add icon to the toggle.
pub fn icon(mut self, icon: impl Into<Icon>) -> 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<Item = AnyElement>) {
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<Item = AnyElement>) {
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<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| {
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<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 {
/// Create a new ToggleGroup element.
pub fn new(id: impl Into<ElementId>) -> 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<bool>` parameter represents the check state of each [`Toggle`] in the group.
pub fn on_change(
/// The `&Vec<bool>` parameter represents the new check state of each [`Toggle`] in the group.
pub fn on_click(
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.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);
}
})
})

View file

@ -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<Self>) -> 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<bool>` 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<Self>) -> 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<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
### 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();
}))