aura-gui 0.3.0

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Botón con las cuatro variantes de shadcn y el interruptor `switch`.

use crate::core::px;
use crate::view::interactivity::ClickHandler;
use crate::view::prelude::*;
use std::rc::Rc;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ButtonVariant {
    Primary,
    Ghost,
    Subtle,
    Danger,
}

pub struct Button {
    label: String,
    variant: ButtonVariant,
    style: Style,
    id: Option<String>,
    on_click: Option<ClickHandler>,
}

impl Button {
    pub fn new(label: impl Into<String>) -> Self {
        Self { label: label.into(), variant: ButtonVariant::Primary, style: Style::default(), id: None, on_click: None }
    }

    /// Atajo de `Button::new(..)`: la variante principal ya es la de por defecto.
    pub fn primary(label: impl Into<String>) -> Self {
        Self::new(label)
    }

    pub fn ghost(label: impl Into<String>) -> Self {
        Self::new(label).variant(ButtonVariant::Ghost)
    }

    pub fn subtle(label: impl Into<String>) -> Self {
        Self::new(label).variant(ButtonVariant::Subtle)
    }

    pub fn danger(label: impl Into<String>) -> Self {
        Self::new(label).variant(ButtonVariant::Danger)
    }

    pub fn variant(mut self, variant: ButtonVariant) -> Self {
        self.variant = variant;
        self
    }

    pub fn id(mut self, id: impl Into<String>) -> Self {
        self.id = Some(id.into());
        self
    }

    pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut EventContext) + 'static) -> Self {
        self.on_click = Some(Rc::new(handler));
        self
    }

    /// `(fondo, texto, fondo en hover, fondo en active)`.
    fn palette(&self) -> (Option<Token>, Token, Token, Token) {
        match self.variant {
            ButtonVariant::Primary => (Some(Token::Accent), Token::AccentText, Token::AccentHover, Token::Accent),
            ButtonVariant::Danger => (Some(Token::Danger), Token::DangerText, Token::DangerHover, Token::Danger),
            ButtonVariant::Subtle => (Some(Token::ElevatedSurface), Token::Text, Token::Hover, Token::Active),
            ButtonVariant::Ghost => (None, Token::Text, Token::Hover, Token::Active),
        }
    }
}

impl Styled for Button {
    fn style(&mut self) -> &mut Style {
        &mut self.style
    }
}

impl IntoElement for Button {
    fn into_element(self) -> AnyElement {
        let (background, foreground, hover, active) = self.palette();
        let is_subtle = self.variant == ButtonVariant::Subtle;
        let handler = self.on_click.clone();

        div()
            .role(Role::Button)
            .label(self.label.clone())
            .flex_row()
            .items_center()
            .justify_center()
            .h(px(32.0))
            .px_3()
            .rounded_md()
            .text_sm()
            .text_color(foreground)
            .when_some(background, |button, color| button.bg(color))
            .when(is_subtle, |button| button.border_1().border_color(Token::BorderSubtle))
            .hover(|style| style.bg(hover))
            .active(|style| style.bg(active))
            .when_some(self.id, |button, id| button.id(id))
            .when_some(handler, |button, handler| button.cursor_pointer().on_click(move |event, cx| handler(event, cx)))
            .refine_style(&self.style)
            .child(self.label)
            .into_element()
    }
}

/// Interruptor (on/off). `id` debe ser estable y único: lleva el foco y la accesibilidad.
pub fn switch(
    on: bool,
    id: impl Into<String>,
    on_click: impl Fn(&ClickEvent, &mut EventContext) + 'static,
) -> AnyElement {
    let knob = div().w(px(14.0)).h(px(14.0)).rounded_full().bg(if on { Token::Text } else { Token::TextMuted });
    div()
        .id(id)
        .role(Role::Switch)
        .flex_row()
        .items_center()
        .w(px(34.0))
        .h(px(20.0))
        .px(px(3.0))
        .rounded_full()
        .cursor_pointer()
        .bg(if on { Token::Accent } else { Token::Border })
        .when(on, |track| track.justify_end())
        .on_click(on_click)
        .child(knob)
        .into_element()
}