diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index b9251bff6e02c22893dbfc8b75ce7d1866025a09..7a12e1f44509ba9f116305c1b4710da3c7e47001 100644 --- a/crates/ui/src/components/toggle.rs +++ b/crates/ui/src/components/toggle.rs @@ -339,6 +339,68 @@ impl RenderOnce for CheckboxWithLabel { } } +/// Defines the color for a switch component. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum SwitchColor { + #[default] + Default, + Accent, + Error, + Warning, + Success, + Custom(Hsla), +} + +impl SwitchColor { + fn get_colors(&self, is_on: bool, cx: &App) -> (Hsla, Hsla) { + if !is_on { + return ( + cx.theme().colors().element_disabled, + cx.theme().colors().border, + ); + } + + match self { + SwitchColor::Default => { + let colors = cx.theme().colors(); + let base_color = colors.text; + let bg_color = colors.element_background.blend(base_color.opacity(0.08)); + (bg_color, colors.border_variant) + } + SwitchColor::Accent => { + let status = cx.theme().status(); + (status.info.opacity(0.4), status.info.opacity(0.2)) + } + SwitchColor::Error => { + let status = cx.theme().status(); + (status.error.opacity(0.4), status.error.opacity(0.2)) + } + SwitchColor::Warning => { + let status = cx.theme().status(); + (status.warning.opacity(0.4), status.warning.opacity(0.2)) + } + SwitchColor::Success => { + let status = cx.theme().status(); + (status.success.opacity(0.4), status.success.opacity(0.2)) + } + SwitchColor::Custom(color) => (*color, color.opacity(0.6)), + } + } +} + +impl From for Color { + fn from(color: SwitchColor) -> Self { + match color { + SwitchColor::Default => Color::Default, + SwitchColor::Accent => Color::Accent, + SwitchColor::Error => Color::Error, + SwitchColor::Warning => Color::Warning, + SwitchColor::Success => Color::Success, + SwitchColor::Custom(_) => Color::Default, + } + } +} + /// # Switch /// /// Switches are used to represent opposite states, such as enabled or disabled. @@ -350,6 +412,7 @@ pub struct Switch { on_click: Option>, label: Option, key_binding: Option, + color: SwitchColor, } impl Switch { @@ -362,9 +425,16 @@ impl Switch { on_click: None, label: None, key_binding: None, + color: SwitchColor::default(), } } + /// Sets the color of the switch using the specified [`SwitchColor`]. + pub fn color(mut self, color: SwitchColor) -> Self { + self.color = color; + self + } + /// Sets the disabled state of the [`Switch`]. pub fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; @@ -397,25 +467,17 @@ impl RenderOnce for Switch { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let is_on = self.toggle_state == ToggleState::Selected; let adjust_ratio = if is_light(cx) { 1.5 } else { 1.0 }; + let base_color = cx.theme().colors().text; + let thumb_color = base_color; + let (bg_color, border_color) = self.color.get_colors(is_on, cx); - let bg_color = if is_on { - cx.theme() - .colors() - .element_background - .blend(base_color.opacity(0.08)) + let bg_hover_color = if is_on { + bg_color.blend(base_color.opacity(0.16 * adjust_ratio)) } else { - cx.theme().colors().element_background - }; - let thumb_color = base_color.opacity(0.8); - let thumb_hover_color = base_color; - let border_color = cx.theme().colors().border_variant; - // Lighter themes need higher contrast borders - let border_hover_color = if is_on { - border_color.blend(base_color.opacity(0.16 * adjust_ratio)) - } else { - border_color.blend(base_color.opacity(0.05 * adjust_ratio)) + bg_color.blend(base_color.opacity(0.05 * adjust_ratio)) }; + let thumb_opacity = match (is_on, self.disabled) { (_, true) => 0.2, (true, false) => 1.0, @@ -432,24 +494,20 @@ impl RenderOnce for Switch { h_flex() .when(is_on, |on| on.justify_end()) .when(!is_on, |off| off.justify_start()) - .items_center() .size_full() .rounded_full() .px(DynamicSpacing::Base02.px(cx)) .bg(bg_color) - .border_1() - .border_color(border_color) .when(!self.disabled, |this| { - this.group_hover(group_id.clone(), |el| el.border_color(border_hover_color)) + this.group_hover(group_id.clone(), |el| el.bg(bg_hover_color)) }) + .border_1() + .border_color(border_color) .child( div() .size(DynamicSpacing::Base12.rems(cx)) .rounded_full() .bg(thumb_color) - .when(!self.disabled, |this| { - this.group_hover(group_id.clone(), |el| el.bg(thumb_hover_color)) - }) .opacity(thumb_opacity), ), ); @@ -482,6 +540,7 @@ pub struct SwitchWithLabel { toggle_state: ToggleState, on_click: Arc, disabled: bool, + color: SwitchColor, } impl SwitchWithLabel { @@ -498,6 +557,7 @@ impl SwitchWithLabel { toggle_state: toggle_state.into(), on_click: Arc::new(on_click), disabled: false, + color: SwitchColor::default(), } } @@ -506,6 +566,12 @@ impl SwitchWithLabel { self.disabled = disabled; self } + + /// Sets the color of the switch using the specified [`SwitchColor`]. + pub fn color(mut self, color: SwitchColor) -> Self { + self.color = color; + self + } } impl RenderOnce for SwitchWithLabel { @@ -516,6 +582,7 @@ impl RenderOnce for SwitchWithLabel { .child( Switch::new(self.id.clone(), self.toggle_state) .disabled(self.disabled) + .color(self.color) .on_click({ let on_click = self.on_click.clone(); move |checked, window, cx| { @@ -667,6 +734,53 @@ impl Component for Switch { ), ], ), + example_group_with_title( + "Colors", + vec![ + single_example( + "Default", + Switch::new("switch_default_style", ToggleState::Selected) + .color(SwitchColor::Default) + .on_click(|_, _, _cx| {}) + .into_any_element(), + ), + single_example( + "Accent", + Switch::new("switch_accent_style", ToggleState::Selected) + .color(SwitchColor::Accent) + .on_click(|_, _, _cx| {}) + .into_any_element(), + ), + single_example( + "Error", + Switch::new("switch_error_style", ToggleState::Selected) + .color(SwitchColor::Error) + .on_click(|_, _, _cx| {}) + .into_any_element(), + ), + single_example( + "Warning", + Switch::new("switch_warning_style", ToggleState::Selected) + .color(SwitchColor::Warning) + .on_click(|_, _, _cx| {}) + .into_any_element(), + ), + single_example( + "Success", + Switch::new("switch_success_style", ToggleState::Selected) + .color(SwitchColor::Success) + .on_click(|_, _, _cx| {}) + .into_any_element(), + ), + single_example( + "Custom", + Switch::new("switch_custom_style", ToggleState::Selected) + .color(SwitchColor::Custom(hsla(300.0 / 360.0, 0.6, 0.6, 1.0))) + .on_click(|_, _, _cx| {}) + .into_any_element(), + ), + ], + ), example_group_with_title( "Disabled", vec![