aura-gui 0.3.5

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.
//! Recetas de estilo: las combinaciones que toda app repite, con nombre propio.
//!
//! Son una capa fina encima de las utilidades de Tailwind: cada receta fija los mismos campos que escribirías
//! a mano, sin trabajo de layout adicional. Al ser un trait, valen para cualquier elemento que implemente
//! [`Styled`] (`Div`, `Button`, `Badge`, `Style`…).
//!
//! ```no_run
//! use aura_ui::prelude::*;
//! div().page().child(
//!     div().card().p_6().child("Hola"),
//! );
//! ```

use crate::core::px;
use crate::view::styled::Styled;
use crate::view::theme::Token;

/// Atajos semánticos encima de las utilidades atómicas.
pub trait Recipes: Styled + Sized {
    /// Lienzo de una pantalla: ocupa todo, en columna, con el fondo y el texto del tema.
    fn page(self) -> Self {
        self.size_full().flex_col().bg(Token::Canvas).text_color(Token::Text)
    }

    /// Tarjeta elevada: superficie, borde, esquinas, sombra suave y relleno.
    fn card(self) -> Self {
        self.flex_col().p_4().rounded_xl().border_1().border_color(Token::Border).bg(Token::ElevatedSurface).shadow_sm()
    }

    /// Panel plano para barras laterales y docks: superficie y borde tenue.
    fn panel(self) -> Self {
        self.flex_col().bg(Token::Surface).border_1().border_color(Token::BorderSubtle).rounded_lg()
    }

    /// Fila con los hijos centrados en vertical (icono + texto, barra de herramientas).
    fn hstack(self) -> Self {
        self.flex_row().items_center()
    }

    /// Columna, para agrupar sin repetir `flex_col`.
    fn vstack(self) -> Self {
        self.flex_col()
    }

    /// Centra en los dos ejes; se combina con `.flex_1()` para llenar el hueco.
    fn center(self) -> Self {
        self.items_center().justify_center()
    }

    /// Texto secundario.
    fn muted(self) -> Self {
        self.text_color(Token::TextMuted)
    }

    /// Texto pequeño y secundario (pistas, metadatos).
    fn caption(self) -> Self {
        self.text_xs().text_color(Token::TextMuted)
    }

    /// Título de pantalla o de sección.
    fn heading(self) -> Self {
        self.text_2xl().font_semibold().tracking_tight()
    }

    /// Título de una sección o de una fila destacada.
    fn subheading(self) -> Self {
        self.text_lg().font_semibold()
    }

    /// Contenedor con scroll vertical que puede encogerse dentro de una columna.
    fn scroll_y(self) -> Self {
        self.overflow_y_scroll().min_h(px(0.0))
    }

    /// Ancho disponible sin desbordar: el patrón de una columna flexible.
    fn flexible(self) -> Self {
        self.flex_1().min_w(px(0.0))
    }
}

impl<T: Styled> Recipes for T {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::view::style::Style;

    #[test]
    fn page_and_card_fijan_los_tokens_del_tema() {
        let page = Style::default().page();
        assert_eq!(page.width, Some(crate::view::style::Length::Percent(1.0)));
        assert_eq!(page.height, Some(crate::view::style::Length::Percent(1.0)));
        assert_eq!(page.background, Some(Token::Canvas.into()));
        assert_eq!(page.text_color, Some(Token::Text.into()));

        let card = Style::default().card();
        assert_eq!(card.background, Some(Token::ElevatedSurface.into()));
        assert_eq!(card.radius, Some(px(12.0)));
        assert_eq!(card.padding.top, Some(px(16.0)));
        assert!(card.shadows().is_some());
    }

    #[test]
    fn composiciones_no_repiten_campos() {
        let style = Style::default().hstack().muted().caption().heading();
        assert_eq!(style.direction, Some(crate::view::style::Direction::Row));
        assert_eq!(style.font_size, Some(px(24.0)), "heading manda sobre caption");
        assert_eq!(style.text_color, Some(Token::TextMuted.into()));
    }

    #[test]
    fn recetas_encadenan_con_utilidades_normales() {
        let style = Style::default().card().p_8().bg(Token::Surface).rounded_full();
        assert_eq!(style.padding.top, Some(px(32.0)), "la utilidad posterior gana");
        assert_eq!(style.background, Some(Token::Surface.into()));
        assert_eq!(style.radius, Some(px(9999.0)));
    }
}