From fdd2dca2803b132fb1f16943d5fab85129fc5737 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 1 Aug 2026 07:15:13 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(bootsier):=20Reorganiza?= =?UTF-8?q?=20clases=20y=20tokens=20utilitarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/intro.css | 4 +- examples/form-controls.rs | 12 +- examples/navbar-menus.rs | 6 +- .../pagetop-bootsier/src/theme/class.rs | 21 +- .../src/theme/class/border.rs | 178 ++++++-- .../src/theme/class/button.rs | 53 ++- .../pagetop-bootsier/src/theme/class/color.rs | 392 +++++++++++++++--- .../src/theme/class/layout.rs | 54 +-- .../src/theme/class/rounded.rs | 106 ++++- .../pagetop-bootsier/src/theme/token.rs | 22 +- .../src/theme/token/border.rs | 103 ----- .../src/theme/token/breakpoint.rs | 2 +- .../pagetop-bootsier/src/theme/token/color.rs | 270 +----------- .../src/theme/token/layout.rs | 20 +- .../src/theme/token/rounded.rs | 84 ---- 15 files changed, 694 insertions(+), 633 deletions(-) delete mode 100644 extensions/pagetop-bootsier/src/theme/token/border.rs delete mode 100644 extensions/pagetop-bootsier/src/theme/token/rounded.rs diff --git a/assets/css/intro.css b/assets/css/intro.css index 0f51eb51..bceacb68 100644 --- a/assets/css/intro.css +++ b/assets/css/intro.css @@ -447,12 +447,12 @@ body { } #intro-badges { - min-height: 3rem; + min-height: 3.2rem; text-align: center; visibility: hidden; } #intro-badges img { - margin-bottom: 1.1rem; + margin-bottom: 0.25rem; } #intro-release:not([src]) { display: none; diff --git a/examples/form-controls.rs b/examples/form-controls.rs index f8be00c7..29010de6 100644 --- a/examples/form-controls.rs +++ b/examples/form-controls.rs @@ -145,12 +145,12 @@ async fn form_controls(request: HttpRequest) -> Result { // Botones de acción. .with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop( PropsOp::add_classes(class::ButtonColor::solid( - token::Color::Primary, + ThemeColor::Primary, )), )) .with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop( PropsOp::add_classes(class::ButtonColor::outline( - token::Color::Secondary, + ThemeColor::Secondary, )), )) .with_child( @@ -269,12 +269,12 @@ async fn form_controls(request: HttpRequest) -> Result { // Botones de acción. .with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop( PropsOp::add_classes(class::ButtonColor::solid( - token::Color::Primary, + ThemeColor::Primary, )), )) .with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop( PropsOp::add_classes(class::ButtonColor::outline( - token::Color::Secondary, + ThemeColor::Secondary, )), )) .with_child( @@ -423,12 +423,12 @@ fn form_lists() -> Form { // Botones de acción. .with_child( Button::submit(L10n::t("btn_submit", &LOC)).with_prop(PropsOp::add_classes( - class::ButtonColor::solid(token::Color::Primary), + class::ButtonColor::solid(ThemeColor::Primary), )), ) .with_child( Button::reset(L10n::t("btn_reset", &LOC)).with_prop(PropsOp::add_classes( - class::ButtonColor::outline(token::Color::Secondary), + class::ButtonColor::outline(ThemeColor::Secondary), )), ) .with_child( diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index a4c182fa..fcc6e84c 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -17,7 +17,7 @@ impl Extension for SuperMenu { async fn initialize(&self) { let navbar_menu = bs::Navbar::brand_left(bs::navbar::Brand::new()) - .with_expand(token::BreakPoint::LG) + .with_expand(BreakPoint::LG) .with_item(bs::navbar::Item::nav( bs::Nav::new() .with_item(bs::nav::Item::link(L10n::t("menus_item_link", &LOC), "/")) @@ -89,8 +89,8 @@ impl Extension for SuperMenu { .with_item(bs::navbar::Item::nav( bs::Nav::new() .with_prop(PropsOp::add_classes(class::Margin::with( - token::Side::Start, - token::ScaleSize::Auto, + BoxSide::Start, + ScaleSize::Auto, ))) .with_item(bs::nav::Item::link( L10n::t("menus_item_sign_up", &LOC), diff --git a/extensions/pagetop-bootsier/src/theme/class.rs b/extensions/pagetop-bootsier/src/theme/class.rs index 51ab1ba0..93c5e16a 100644 --- a/extensions/pagetop-bootsier/src/theme/class.rs +++ b/extensions/pagetop-bootsier/src/theme/class.rs @@ -1,16 +1,29 @@ //! Define clases para aplicar en componentes del tema. +//! +//! Incluyen puntos de ruptura, colores y niveles de opacidad, escalas de tamaño y lados, necesarios +//! para crear determinadas clases del tema: +//! +//! ```rust,no_run +//! use pagetop_bootsier::theme::*; +//! +//! let bg = class::Bg::with(ThemeColor::Primary); +//! let border = class::Border::new() +//! .with_side(BoxSide::Top, ScaleSize::Zero) +//! .with_color(ThemeColor::Danger); +//! ``` mod color; -pub use color::{Background, Text}; +pub use color::{Bg, BgColor}; +pub use color::{Text, TextColor}; mod button; -pub use button::{ButtonColor, ButtonSize}; +pub use button::{ButtonColor, ButtonColorStyle, ButtonSize, ButtonSizeKind}; mod border; -pub use border::Border; +pub use border::{Border, BorderColor}; mod rounded; -pub use rounded::Rounded; +pub use rounded::{Rounded, RoundedRadius}; mod layout; pub use layout::{Margin, Padding}; diff --git a/extensions/pagetop-bootsier/src/theme/class/border.rs b/extensions/pagetop-bootsier/src/theme/class/border.rs index 1255fd21..b25780e9 100644 --- a/extensions/pagetop-bootsier/src/theme/class/border.rs +++ b/extensions/pagetop-bootsier/src/theme/class/border.rs @@ -1,6 +1,114 @@ use pagetop::prelude::*; -use crate::theme::token::{ColorBorder, Opacity, ScaleSize, Side}; +use crate::theme::{BoxSide, OpacityLevel, ScaleSize, ThemeColor}; + +// **< BorderColor >******************************************************************************** + +/// Esquema de color para los bordes ([`Border`]). +/// +/// - `Solid(ThemeColor)` y `Subtle(ThemeColor)` usan la paleta de colores temáticos +/// ([`ThemeColor`]). +/// - `Black` y `White` son colores fijos independientes del tema. +/// - `Default` no genera ninguna clase. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum BorderColor { + /// No define ninguna clase. + #[default] + Default, + /// Genera la clase `border-{color}`. + Solid(ThemeColor), + /// Genera la clase `border-{color}-subtle` (un tono suavizado del color). + Subtle(ThemeColor), + /// Color negro. + Black, + /// Color blanco. + White, +} + +impl BorderColor { + // Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { + match self { + Self::Default => None, + Self::Solid(_) => Some(""), + Self::Subtle(_) => Some("-subtle"), + Self::Black => Some("-black"), + Self::White => Some("-white"), + } + } + + /// Añade la clase `border-*` a la cadena de clases. + #[inline] + pub fn push_to(self, classes: &mut String) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Solid(c) | Self::Subtle(c) => { + classes.push_str("border-"); + classes.push_str(c.as_str()); + } + _ => classes.push_str("border"), + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `border-*` correspondiente al color de borde. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// let solid = class::BorderColor::Solid(ThemeColor::Primary).to_class(); + /// assert_eq!(solid, "border-primary"); + /// + /// let subtle = class::BorderColor::Subtle(ThemeColor::Warning).to_class(); + /// assert_eq!(subtle, "border-warning-subtle"); + /// + /// let black = class::BorderColor::Black.to_class(); + /// assert_eq!(black, "border-black"); + /// + /// let none = class::BorderColor::Default.to_class(); + /// assert_eq!(none, ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + let mut class = String::new(); + self.push_to(&mut class); + class + } +} + +impl From for BorderColor { + /// Convierte un [`ThemeColor`] en [`BorderColor::Solid`]. + /// + /// Es el atajo habitual para los colores temáticos. Para los demás esquemas (`Subtle`, `Black`, + /// `White`) sigue usando [`BorderColor`]. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// let border: class::BorderColor = ThemeColor::Success.into(); + /// assert_eq!(border.to_class(), "border-success"); + /// ``` + fn from(color: ThemeColor) -> Self { + Self::Solid(color) + } +} + +impl Into for BorderColor { + /// Permite pasar [`BorderColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). + fn into(self) -> CowStr { + self.to_class().into() + } +} + +// **< Border >************************************************************************************* /// Clases para definir **bordes**. /// @@ -10,19 +118,19 @@ use crate::theme::token::{ColorBorder, Opacity, ScaleSize, Side}; /// - Crear un borde con tamaño por defecto (`Border::new()`). /// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL). /// - Asignar **un tamaño global** para todo el borde (`size`). -/// - Aplicar un **color** al borde (`ColorBorder`). -/// - Aplicar un nivel de **opacidad** (`Opacity`). +/// - Aplicar un **color** al borde (`BorderColor`). +/// - Aplicar un nivel de **opacidad** (`OpacityLevel`). /// /// # Comportamiento aditivo / sustractivo /// /// - **Aditivo**: basta con crear un borde sin tamaño con `class::Border::default()` para ir -/// añadiendo cada lado lógico con el tamaño deseado usando `token::ScaleSize::{One..Five}`. +/// añadiendo cada lado lógico con el tamaño deseado usando `ScaleSize::{One..Five}`. /// /// - **Sustractivo**: se crea un borde con tamaño predefinido, por ejemplo usando -/// `class::Border::new()` o `class::Border::with(token::ScaleSize::Two)` y eliminar los lados -/// deseados con `token::ScaleSize::Zero`. +/// `class::Border::new()` o `class::Border::with(ScaleSize::Two)` y eliminar los lados deseados +/// con `ScaleSize::Zero`. /// -/// - **Anchos diferentes por lado**: usando `token::ScaleSize::{Zero..Five}` en cada lado deseado. +/// - **Anchos diferentes por lado**: usando `ScaleSize::{Zero..Five}` en cada lado deseado. /// /// # Ejemplos /// @@ -30,29 +138,29 @@ use crate::theme::token::{ColorBorder, Opacity, ScaleSize, Side}; /// use pagetop_bootsier::theme::*; /// /// // Borde global. -/// let b = class::Border::with(token::ScaleSize::Two); +/// let b = class::Border::with(ScaleSize::Two); /// assert_eq!(b.to_class(), "border-2"); /// /// // Aditivo (sólo borde superior): -/// let b = class::Border::default().with_side(token::Side::Top, token::ScaleSize::One); +/// let b = class::Border::default().with_side(BoxSide::Top, ScaleSize::One); /// assert_eq!(b.to_class(), "border-top-1"); /// /// // Sustractivo (borde global menos el superior): -/// let b = class::Border::new().with_side(token::Side::Top, token::ScaleSize::Zero); +/// let b = class::Border::new().with_side(BoxSide::Top, ScaleSize::Zero); /// assert_eq!(b.to_class(), "border border-top-0"); /// /// // Ancho por lado (lado lógico inicial a 2 y final a 4): /// let b = class::Border::default() -/// .with_side(token::Side::Start, token::ScaleSize::Two) -/// .with_side(token::Side::End, token::ScaleSize::Four); +/// .with_side(BoxSide::Start, ScaleSize::Two) +/// .with_side(BoxSide::End, ScaleSize::Four); /// assert_eq!(b.to_class(), "border-end-4 border-start-2"); /// /// // Combinado (ejemplo completo): -/// let b = class::Border::new() // Borde por defecto. -/// .with_side(token::Side::Top, token::ScaleSize::Zero) // Quita borde superior. -/// .with_side(token::Side::End, token::ScaleSize::Three) // Ancho 3 para lado lógico final. -/// .with_color(token::Color::Primary) -/// .with_opacity(token::Opacity::Half); +/// let b = class::Border::new() // Borde por defecto. +/// .with_side(BoxSide::Top, ScaleSize::Zero) // Quita borde superior. +/// .with_side(BoxSide::End, ScaleSize::Three) // Ancho 3 para lado lógico final. +/// .with_color(ThemeColor::Primary) +/// .with_opacity(OpacityLevel::Half); /// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50"); /// ``` #[rustfmt::skip] @@ -63,8 +171,8 @@ pub struct Border { end : ScaleSize, bottom : ScaleSize, start : ScaleSize, - color : ColorBorder, - opacity: Opacity, + color : BorderColor, + opacity: OpacityLevel, } impl Border { @@ -75,24 +183,24 @@ impl Border { /// Define un borde con un tamaño global (`size`) para todos los lados. pub fn with(size: ScaleSize) -> Self { - Self::default().with_side(Side::All, size) + Self::default().with_side(BoxSide::All, size) } // **< Border BUILDER >************************************************************************* - /// Ajusta el tamaño del borde en el lado indicado (ver [`Side`](crate::theme::token::Side)). - pub fn with_side(mut self, side: Side, size: ScaleSize) -> Self { + /// Ajusta el tamaño del borde en el lado indicado (ver [`BoxSide`](crate::theme::BoxSide)). + pub fn with_side(mut self, side: BoxSide, size: ScaleSize) -> Self { match side { - Side::All => self.all = size, - Side::Top => self.top = size, - Side::Bottom => self.bottom = size, - Side::Start => self.start = size, - Side::End => self.end = size, - Side::LeftAndRight => { + BoxSide::All => self.all = size, + BoxSide::Top => self.top = size, + BoxSide::Bottom => self.bottom = size, + BoxSide::Start => self.start = size, + BoxSide::End => self.end = size, + BoxSide::LeftAndRight => { self.start = size; self.end = size; } - Side::TopAndBottom => { + BoxSide::TopAndBottom => { self.top = size; self.bottom = size; } @@ -102,15 +210,15 @@ impl Border { /// Establece el color del borde. /// - /// Acepta un tipo convertible en [`ColorBorder`]. Un [`Color`](crate::theme::token::Color) se - /// convierte automáticamente en [`ColorBorder::Theme`]. - pub fn with_color(mut self, color: impl Into) -> Self { + /// Acepta un tipo convertible en [`BorderColor`]. Un [`ThemeColor`] se convierte + /// automáticamente en [`BorderColor::Solid`]. + pub fn with_color(mut self, color: impl Into) -> Self { self.color = color.into(); self } /// Establece la opacidad del borde. - pub fn with_opacity(mut self, opacity: Opacity) -> Self { + pub fn with_opacity(mut self, opacity: OpacityLevel) -> Self { self.opacity = opacity; self } @@ -150,11 +258,11 @@ impl From for Border { /// ```rust /// # use pagetop_bootsier::theme::*; /// // Convertir explícitamente con `From::from`: - /// let b = class::Border::from(token::ScaleSize::Two); + /// let b = class::Border::from(ScaleSize::Two); /// assert_eq!(b.to_class(), "border-2"); /// /// // Convertir implícitamente con `into()`: - /// let b: class::Border = token::ScaleSize::Auto.into(); + /// let b: class::Border = ScaleSize::Auto.into(); /// assert_eq!(b.to_class(), "border"); /// ``` fn from(size: ScaleSize) -> Self { diff --git a/extensions/pagetop-bootsier/src/theme/class/button.rs b/extensions/pagetop-bootsier/src/theme/class/button.rs index 28ab792f..733fd3d8 100644 --- a/extensions/pagetop-bootsier/src/theme/class/button.rs +++ b/extensions/pagetop-bootsier/src/theme/class/button.rs @@ -1,15 +1,20 @@ use pagetop::prelude::*; -use crate::theme::token::Color; +use crate::theme::ThemeColor; // **< ButtonColor >******************************************************************************** +/// Estilo visual aplicado al color de un botón ([`ButtonColor`]). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -enum ButtonColorStyle { +pub enum ButtonColorStyle { + /// Sin clase de color (estilo por defecto del tema). #[default] None, + /// Botón sólido: genera la clase `btn-{color}`. Solid, + /// Botón con contorno: genera la clase `btn-outline-{color}`. Outline, + /// Botón tipo enlace: genera la clase `btn-link`. Link, } @@ -23,11 +28,11 @@ enum ButtonColorStyle { /// /// // Botón sólido. /// let save = bs::Button::submit(L10n::n("Save")) -/// .with_prop(PropsOp::add_classes(class::ButtonColor::solid(token::Color::Primary))); +/// .with_prop(PropsOp::add_classes(class::ButtonColor::solid(ThemeColor::Primary))); /// /// // Botón con contorno. /// let cancel = bs::Button::plain(L10n::n("Cancel")) -/// .with_prop(PropsOp::add_classes(class::ButtonColor::outline(token::Color::Secondary))); +/// .with_prop(PropsOp::add_classes(class::ButtonColor::outline(ThemeColor::Secondary))); /// /// // Botón tipo enlace. /// let back = bs::Button::plain(L10n::n("Back")) @@ -36,7 +41,7 @@ enum ButtonColorStyle { #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct ButtonColor { style: ButtonColorStyle, - color: Color, + color: ThemeColor, } impl ButtonColor { @@ -46,7 +51,7 @@ impl ButtonColor { } /// Botón sólido: genera la clase `btn-{color}`. - pub fn solid(color: Color) -> Self { + pub fn solid(color: ThemeColor) -> Self { Self { style: ButtonColorStyle::Solid, color, @@ -55,7 +60,7 @@ impl ButtonColor { } /// Botón con contorno: genera la clase `btn-outline-{color}`. - pub fn outline(color: Color) -> Self { + pub fn outline(color: ThemeColor) -> Self { Self { style: ButtonColorStyle::Outline, color, @@ -74,11 +79,17 @@ impl ButtonColor { // **< ButtonColor BUILDER >******************************************************************** /// Cambia el color aplicado al botón (`btn-*` o `btn-outline-*`). - pub fn with_color(mut self, color: Color) -> Self { + pub fn with_color(mut self, color: ThemeColor) -> Self { self.color = color; self } + /// Cambia el estilo aplicado al botón (sólido, contorno o enlace). + pub fn with_style(mut self, style: ButtonColorStyle) -> Self { + self.style = style; + self + } + // **< ButtonColor HELPERS >******************************************************************** /// Añade la clase `btn-*` a la cadena de clases. @@ -117,11 +128,15 @@ impl Into for ButtonColor { // **< ButtonSize >********************************************************************************* +/// Tamaño aplicado a un botón ([`ButtonSize`]). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -enum ButtonSizeVariant { +pub enum ButtonSizeKind { + /// Sin clase de tamaño (tamaño por defecto del tema). #[default] None, + /// Botón compacto: genera la clase `btn-sm`. Small, + /// Botón grande: genera la clase `btn-lg`. Large, } @@ -141,7 +156,7 @@ enum ButtonSizeVariant { /// ``` #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct ButtonSize { - size: ButtonSizeVariant, + size: ButtonSizeKind, } impl ButtonSize { @@ -153,26 +168,34 @@ impl ButtonSize { /// Botón compacto: genera la clase `btn-sm`. pub fn small() -> Self { Self { - size: ButtonSizeVariant::Small, + size: ButtonSizeKind::Small, } } /// Botón grande: genera la clase `btn-lg`. pub fn large() -> Self { Self { - size: ButtonSizeVariant::Large, + size: ButtonSizeKind::Large, } } + // **< ButtonSize BUILDER >********************************************************************* + + /// Cambia el tamaño aplicado al botón. + pub fn with_size(mut self, size: ButtonSizeKind) -> Self { + self.size = size; + self + } + // **< ButtonSize HELPERS >********************************************************************* /// Añade la clase `btn-sm` o `btn-lg` a la cadena de clases. #[inline] pub fn push_to(self, classes: &mut String) { let class = match self.size { - ButtonSizeVariant::None => return, - ButtonSizeVariant::Small => "btn-sm", - ButtonSizeVariant::Large => "btn-lg", + ButtonSizeKind::None => return, + ButtonSizeKind::Small => "btn-sm", + ButtonSizeKind::Large => "btn-lg", }; if !classes.is_empty() { classes.push(' '); diff --git a/extensions/pagetop-bootsier/src/theme/class/color.rs b/extensions/pagetop-bootsier/src/theme/class/color.rs index 871567e2..b2378a32 100644 --- a/extensions/pagetop-bootsier/src/theme/class/color.rs +++ b/extensions/pagetop-bootsier/src/theme/class/color.rs @@ -1,8 +1,130 @@ use pagetop::prelude::*; -use crate::theme::token::{ColorBg, ColorText, Opacity}; +use crate::theme::{OpacityLevel, ThemeColor}; -// **< Background >********************************************************************************* +// **< BgColor >************************************************************************************ + +/// Esquema de color para el fondo ([`Bg`]). +/// +/// - `Body`, `BodySecondary` y `BodyTertiary` siguen el esquema del tema (claro/oscuro). +/// - `Solid(ThemeColor)` y `Subtle(ThemeColor)` usan la paleta de colores temáticos +/// ([`ThemeColor`]). +/// - `Black`, `White`, `Transparent` son colores fijos independientes del tema. +/// - `Default` no genera ninguna clase. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum BgColor { + /// No define ninguna clase. + #[default] + Default, + /// Fondo predefinido del tema (`bg-body`). + Body, + /// Fondo predefinido del tema (`bg-body-secondary`). + BodySecondary, + /// Fondo predefinido del tema (`bg-body-tertiary`). + BodyTertiary, + /// Genera la clase `bg-{color}` (p. ej., `bg-primary`). + Solid(ThemeColor), + /// Genera la clase `bg-{color}-subtle` (un tono suavizado del color). + Subtle(ThemeColor), + /// Color negro. + Black, + /// Color blanco. + White, + /// Fondo transparente (`bg-transparent`). + Transparent, +} + +impl BgColor { + // Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { + match self { + Self::Default => None, + Self::Body => Some("-body"), + Self::BodySecondary => Some("-body-secondary"), + Self::BodyTertiary => Some("-body-tertiary"), + Self::Solid(_) => Some(""), + Self::Subtle(_) => Some("-subtle"), + Self::Black => Some("-black"), + Self::White => Some("-white"), + Self::Transparent => Some("-transparent"), + } + } + + /// Añade la clase de fondo `bg-*` a la cadena de clases. + #[inline] + pub fn push_to(self, classes: &mut String) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Solid(c) | Self::Subtle(c) => { + classes.push_str("bg-"); + classes.push_str(c.as_str()); + } + _ => classes.push_str("bg"), + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `bg-*` correspondiente al fondo. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// let body = class::BgColor::Body.to_class(); + /// assert_eq!(body, "bg-body"); + /// + /// let solid = class::BgColor::Solid(ThemeColor::Primary).to_class(); + /// assert_eq!(solid, "bg-primary"); + /// + /// let subtle = class::BgColor::Subtle(ThemeColor::Warning).to_class(); + /// assert_eq!(subtle, "bg-warning-subtle"); + /// + /// let transparent = class::BgColor::Transparent.to_class(); + /// assert_eq!(transparent, "bg-transparent"); + /// + /// let none = class::BgColor::Default.to_class(); + /// assert_eq!(none, ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + let mut class = String::new(); + self.push_to(&mut class); + class + } +} + +impl From for BgColor { + /// Convierte un [`ThemeColor`] en [`BgColor::Solid`]. + /// + /// Es el atajo habitual para los colores temáticos. Para los demás esquemas (`Body`, `Subtle`, + /// `Black`, etc.) sigue usando [`BgColor`]. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// let bg: class::BgColor = ThemeColor::Primary.into(); + /// assert_eq!(bg.to_class(), "bg-primary"); + /// ``` + fn from(color: ThemeColor) -> Self { + Self::Solid(color) + } +} + +impl Into for BgColor { + /// Permite pasar [`BgColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). + fn into(self) -> CowStr { + self.to_class().into() + } +} + +// **< Bg >***************************************************************************************** /// Clases para establecer **color/opacidad del fondo**. /// @@ -12,33 +134,32 @@ use crate::theme::token::{ColorBg, ColorText, Opacity}; /// use pagetop_bootsier::theme::*; /// /// // Sin clases. -/// let s = class::Background::new(); +/// let s = class::Bg::new(); /// assert_eq!(s.to_class(), ""); /// -/// // Sólo color de fondo (forma corta con Color). -/// let s = class::Background::with(token::Color::Primary); +/// // Sólo color de fondo (forma corta con ThemeColor). +/// let s = class::Bg::with(ThemeColor::Primary); /// assert_eq!(s.to_class(), "bg-primary"); /// /// // Color más opacidad. -/// let s = class::Background::with(token::ColorBg::BodySecondary) -/// .with_opacity(token::Opacity::Half); +/// let s = class::Bg::with(class::BgColor::BodySecondary).with_opacity(OpacityLevel::Half); /// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50"); /// -/// // Usando `From`. -/// let s: class::Background = token::ColorBg::Black.into(); +/// // Usando `From`. +/// let s: class::Bg = class::BgColor::Black.into(); /// assert_eq!(s.to_class(), "bg-black"); /// -/// // Usando `From<(ColorBg, Opacity)>`. -/// let s: class::Background = (token::ColorBg::White, token::Opacity::SemiTransparent).into(); +/// // Usando `From<(BgColor, OpacityLevel)>`. +/// let s: class::Bg = (class::BgColor::White, OpacityLevel::SemiTransparent).into(); /// assert_eq!(s.to_class(), "bg-white bg-opacity-25"); /// ``` #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub struct Background { - color: ColorBg, - opacity: Opacity, +pub struct Bg { + color: BgColor, + opacity: OpacityLevel, } -impl Background { +impl Bg { /// Prepara un nuevo estilo para aplicar al fondo. pub fn new() -> Self { Self::default() @@ -46,41 +167,41 @@ impl Background { /// Crea un estilo fijando el color de fondo (`bg-*`). /// - /// Acepta cualquier tipo convertible en [`ColorBg`]. Un [`Color`](crate::theme::token::Color) - /// se convierte automáticamente en [`ColorBg::Theme`]: + /// Acepta cualquier tipo convertible en [`BgColor`]. Un [`ThemeColor`] se convierte + /// automáticamente en [`BgColor::Solid`]: /// /// ```rust /// # use pagetop_bootsier::theme::*; - /// // Forma corta con Color: - /// let s = class::Background::with(token::Color::Primary); + /// // Forma corta con ThemeColor: + /// let s = class::Bg::with(ThemeColor::Primary); /// assert_eq!(s.to_class(), "bg-primary"); /// /// // Forma explícita para variantes no temáticas: - /// let s = class::Background::with(token::ColorBg::Body); + /// let s = class::Bg::with(class::BgColor::Body); /// assert_eq!(s.to_class(), "bg-body"); /// ``` - pub fn with(color: impl Into) -> Self { + pub fn with(color: impl Into) -> Self { Self::default().with_color(color) } - // **< Background BUILDER >********************************************************************* + // **< Bg BUILDER >***************************************************************************** /// Establece el color de fondo (`bg-*`). /// - /// Acepta cualquier tipo convertible en [`ColorBg`]. Un [`Color`](crate::theme::token::Color) - /// se convierte automáticamente en [`ColorBg::Theme`]. - pub fn with_color(mut self, color: impl Into) -> Self { + /// Acepta cualquier tipo convertible en [`BgColor`]. Un [`ThemeColor`] se convierte + /// automáticamente en [`BgColor::Solid`]. + pub fn with_color(mut self, color: impl Into) -> Self { self.color = color.into(); self } /// Establece la opacidad del fondo (`bg-opacity-*`). - pub fn with_opacity(mut self, opacity: Opacity) -> Self { + pub fn with_opacity(mut self, opacity: OpacityLevel) -> Self { self.opacity = opacity; self } - // **< Background HELPERS >********************************************************************* + // **< Bg HELPERS >***************************************************************************** /// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo /// los fragmentos vacíos. @@ -101,40 +222,173 @@ impl Background { } } -impl From<(ColorBg, Opacity)> for Background { - /// Crea la clase para un [`Background`](crate::theme::class::Background) a partir del color de - /// fondo y la opacidad. +impl From<(BgColor, OpacityLevel)> for Bg { + /// Crea la clase para un [`Bg`](crate::theme::class::Bg) a partir del color de fondo y la + /// opacidad. /// /// # Ejemplo /// /// ```rust /// # use pagetop_bootsier::theme::*; - /// let s: class::Background = (token::ColorBg::White, token::Opacity::SemiTransparent).into(); + /// let s: class::Bg = (class::BgColor::White, OpacityLevel::SemiTransparent).into(); /// assert_eq!(s.to_class(), "bg-white bg-opacity-25"); /// ``` - fn from((color, opacity): (ColorBg, Opacity)) -> Self { - Background::with(color).with_opacity(opacity) + fn from((color, opacity): (BgColor, OpacityLevel)) -> Self { + Bg::with(color).with_opacity(opacity) } } -impl From for Background { - /// Crea la clase para un [`Background`](crate::theme::class::Background) a partir del color de - /// fondo. +impl From for Bg { + /// Crea la clase para un [`Bg`](crate::theme::class::Bg) a partir del color de fondo. /// /// # Ejemplo /// /// ```rust /// # use pagetop_bootsier::theme::*; - /// let s: class::Background = token::ColorBg::Black.into(); + /// let s: class::Bg = class::BgColor::Black.into(); /// assert_eq!(s.to_class(), "bg-black"); /// ``` - fn from(color: ColorBg) -> Self { - Background::with(color) + fn from(color: BgColor) -> Self { + Bg::with(color) } } -impl Into for Background { - /// Permite pasar [`Background`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). +impl Into for Bg { + /// Permite pasar [`Bg`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). + fn into(self) -> CowStr { + self.to_class().into() + } +} + +// **< TextColor >********************************************************************************** + +/// Esquema de color para el texto ([`Text`]). +/// +/// - `Body`, `BodyEmphasis`, `BodySecondary` y `BodyTertiary` siguen el tema (claro/oscuro). +/// - `Solid(ThemeColor)` y `Emphasis(ThemeColor)` usan la paleta de colores temáticos +/// ([`ThemeColor`]). +/// - `Bg(ThemeColor)` genera la utilidad combinada `text-bg-{color}` (fondo más un color de texto +/// de contraste garantizado; no es una utilidad puramente de texto). +/// - `Black` y `White` son colores fijos independientes del tema. +/// - `Default` no genera ninguna clase. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum TextColor { + /// No define ninguna clase. + #[default] + Default, + /// Color predefinido del tema (`text-body`). + Body, + /// Color de mayor contraste según el tema (`text-body-emphasis`). + BodyEmphasis, + /// Color predefinido del tema (`text-body-secondary`). + BodySecondary, + /// Color predefinido del tema (`text-body-tertiary`). + BodyTertiary, + /// Genera la clase `text-{color}`. + Solid(ThemeColor), + /// Genera la clase `text-{color}-emphasis` (mayor contraste acorde al tema). + Emphasis(ThemeColor), + /// Genera la clase `text-bg-{color}` (fondo con color de texto de contraste garantizado). + Bg(ThemeColor), + /// Color negro. + Black, + /// Color blanco. + White, +} + +impl TextColor { + // Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { + match self { + Self::Default => None, + Self::Body => Some("-body"), + Self::BodyEmphasis => Some("-body-emphasis"), + Self::BodySecondary => Some("-body-secondary"), + Self::BodyTertiary => Some("-body-tertiary"), + Self::Solid(_) => Some(""), + Self::Emphasis(_) => Some("-emphasis"), + Self::Bg(_) => Some(""), + Self::Black => Some("-black"), + Self::White => Some("-white"), + } + } + + /// Añade la clase de texto `text-*` a la cadena de clases. + #[inline] + pub fn push_to(self, classes: &mut String) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Solid(c) | Self::Emphasis(c) => { + classes.push_str("text-"); + classes.push_str(c.as_str()); + } + Self::Bg(c) => { + classes.push_str("text-bg-"); + classes.push_str(c.as_str()); + } + _ => classes.push_str("text"), + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `text-*` correspondiente al color del texto. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// let body = class::TextColor::Body.to_class(); + /// assert_eq!(body, "text-body"); + /// + /// let solid = class::TextColor::Solid(ThemeColor::Primary).to_class(); + /// assert_eq!(solid, "text-primary"); + /// + /// let emphasis = class::TextColor::Emphasis(ThemeColor::Danger).to_class(); + /// assert_eq!(emphasis, "text-danger-emphasis"); + /// + /// let bg = class::TextColor::Bg(ThemeColor::Secondary).to_class(); + /// assert_eq!(bg, "text-bg-secondary"); + /// + /// let black = class::TextColor::Black.to_class(); + /// assert_eq!(black, "text-black"); + /// + /// let none = class::TextColor::Default.to_class(); + /// assert_eq!(none, ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + let mut class = String::new(); + self.push_to(&mut class); + class + } +} + +impl From for TextColor { + /// Convierte un [`ThemeColor`] en [`TextColor::Solid`]. + /// + /// Es el atajo habitual para los colores temáticos. Para los demás esquemas (`Body`, + /// `Emphasis`, `Black`, etc.) sigue usando [`TextColor`]. + /// + /// # Ejemplo + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// let text: class::TextColor = ThemeColor::Danger.into(); + /// assert_eq!(text.to_class(), "text-danger"); + /// ``` + fn from(color: ThemeColor) -> Self { + Self::Solid(color) + } +} + +impl Into for TextColor { + /// Permite pasar [`TextColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). fn into(self) -> CowStr { self.to_class().into() } @@ -153,30 +407,30 @@ impl Into for Background { /// let s = class::Text::new(); /// assert_eq!(s.to_class(), ""); /// -/// // Sólo color del texto (forma corta con Color). -/// let s = class::Text::with(token::Color::Primary); +/// // Sólo color del texto (forma corta con ThemeColor). +/// let s = class::Text::with(ThemeColor::Primary); /// assert_eq!(s.to_class(), "text-primary"); /// /// // Color del texto y opacidad. -/// let s = class::Text::new().with_color(token::ColorText::White) -/// .with_opacity(token::Opacity::SemiTransparent); +/// let s = class::Text::new().with_color(class::TextColor::White) +/// .with_opacity(OpacityLevel::SemiTransparent); /// assert_eq!(s.to_class(), "text-white text-opacity-25"); /// -/// // Usando `From`. -/// let s: class::Text = token::ColorText::Black.into(); +/// // Usando `From`. +/// let s: class::Text = class::TextColor::Black.into(); /// assert_eq!(s.to_class(), "text-black"); /// -/// // Usando `From<(ColorText, Opacity)>`. +/// // Usando `From<(TextColor, OpacityLevel)>`. /// let s: class::Text = ( -/// token::ColorText::Theme(token::Color::Danger), -/// token::Opacity::Opaque, +/// class::TextColor::Solid(ThemeColor::Danger), +/// OpacityLevel::Opaque, /// ).into(); /// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// ``` #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Text { - color: ColorText, - opacity: Opacity, + color: TextColor, + opacity: OpacityLevel, } impl Text { @@ -187,20 +441,20 @@ impl Text { /// Crea un estilo fijando el color del texto (`text-*`). /// - /// Acepta cualquier tipo convertible en [`ColorText`]. Un [`Color`](crate::theme::token::Color) - /// se convierte automáticamente en [`ColorText::Theme`]: + /// Acepta cualquier tipo convertible en [`TextColor`]. Un [`ThemeColor`] se convierte + /// automáticamente en [`TextColor::Solid`]: /// /// ```rust /// # use pagetop_bootsier::theme::*; - /// // Forma corta con Color: - /// let s = class::Text::with(token::Color::Danger); + /// // Forma corta con ThemeColor: + /// let s = class::Text::with(ThemeColor::Danger); /// assert_eq!(s.to_class(), "text-danger"); /// /// // Forma explícita para variantes no temáticas: - /// let s = class::Text::with(token::ColorText::Body); + /// let s = class::Text::with(class::TextColor::Body); /// assert_eq!(s.to_class(), "text-body"); /// ``` - pub fn with(color: impl Into) -> Self { + pub fn with(color: impl Into) -> Self { Self::default().with_color(color) } @@ -208,15 +462,15 @@ impl Text { /// Establece el color del texto (`text-*`). /// - /// Acepta cualquier tipo convertible en [`ColorText`]. Un [`Color`](crate::theme::token::Color) - /// se convierte automáticamente en [`ColorText::Theme`]. - pub fn with_color(mut self, color: impl Into) -> Self { + /// Acepta cualquier tipo convertible en [`TextColor`]. Un [`ThemeColor`] se convierte + /// automáticamente en [`TextColor::Solid`]. + pub fn with_color(mut self, color: impl Into) -> Self { self.color = color.into(); self } /// Establece la opacidad del texto (`text-opacity-*`). - pub fn with_opacity(mut self, opacity: Opacity) -> Self { + pub fn with_opacity(mut self, opacity: OpacityLevel) -> Self { self.opacity = opacity; self } @@ -241,7 +495,7 @@ impl Text { } } -impl From<(ColorText, Opacity)> for Text { +impl From<(TextColor, OpacityLevel)> for Text { /// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto y su /// opacidad. /// @@ -250,27 +504,27 @@ impl From<(ColorText, Opacity)> for Text { /// ```rust /// # use pagetop_bootsier::theme::*; /// let s: class::Text = ( - /// token::ColorText::Theme(token::Color::Danger), - /// token::Opacity::Opaque, + /// class::TextColor::Solid(ThemeColor::Danger), + /// OpacityLevel::Opaque, /// ).into(); /// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// ``` - fn from((color, opacity): (ColorText, Opacity)) -> Self { + fn from((color, opacity): (TextColor, OpacityLevel)) -> Self { Text::with(color).with_opacity(opacity) } } -impl From for Text { +impl From for Text { /// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto. /// /// # Ejemplo /// /// ```rust /// # use pagetop_bootsier::theme::*; - /// let s: class::Text = token::ColorText::Black.into(); + /// let s: class::Text = class::TextColor::Black.into(); /// assert_eq!(s.to_class(), "text-black"); /// ``` - fn from(color: ColorText) -> Self { + fn from(color: TextColor) -> Self { Text::with(color) } } diff --git a/extensions/pagetop-bootsier/src/theme/class/layout.rs b/extensions/pagetop-bootsier/src/theme/class/layout.rs index f00ffaba..92b1906a 100644 --- a/extensions/pagetop-bootsier/src/theme/class/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/class/layout.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use crate::theme::token::{BreakPoint, ScaleSize, Side}; +use crate::theme::{BoxSide, BreakPoint, ScaleSize}; // **< Margin >************************************************************************************* @@ -11,19 +11,19 @@ use crate::theme::token::{BreakPoint, ScaleSize, Side}; /// ```rust /// use pagetop_bootsier::theme::*; /// -/// let m = class::Margin::with(token::Side::Top, token::ScaleSize::Three); +/// let m = class::Margin::with(BoxSide::Top, ScaleSize::Three); /// assert_eq!(m.to_class(), "mt-3"); /// -/// let m = class::Margin::with(token::Side::Start, token::ScaleSize::Auto) -/// .with_breakpoint(token::BreakPoint::LG); +/// let m = class::Margin::with(BoxSide::Start, ScaleSize::Auto) +/// .with_breakpoint(BreakPoint::LG); /// assert_eq!(m.to_class(), "ms-lg-auto"); /// -/// let m = class::Margin::with(token::Side::All, token::ScaleSize::None); +/// let m = class::Margin::with(BoxSide::All, ScaleSize::None); /// assert_eq!(m.to_class(), ""); /// ``` #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Margin { - side: Side, + side: BoxSide, size: ScaleSize, breakpoint: BreakPoint, } @@ -31,7 +31,7 @@ pub struct Margin { impl Margin { /// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de /// ruptura. - pub fn with(side: Side, size: ScaleSize) -> Self { + pub fn with(side: BoxSide, size: ScaleSize) -> Self { Margin { side, size, @@ -54,13 +54,13 @@ impl Margin { #[inline] const fn side_prefix(&self) -> &'static str { match self.side { - Side::All => "m", - Side::Top => "mt", - Side::Bottom => "mb", - Side::Start => "ms", - Side::End => "me", - Side::LeftAndRight => "mx", - Side::TopAndBottom => "my", + BoxSide::All => "m", + BoxSide::Top => "mt", + BoxSide::Bottom => "mb", + BoxSide::Start => "ms", + BoxSide::End => "me", + BoxSide::LeftAndRight => "mx", + BoxSide::TopAndBottom => "my", } } @@ -114,19 +114,19 @@ impl Into for Margin { /// ```rust /// use pagetop_bootsier::theme::*; /// -/// let p = class::Padding::with(token::Side::LeftAndRight, token::ScaleSize::Two); +/// let p = class::Padding::with(BoxSide::LeftAndRight, ScaleSize::Two); /// assert_eq!(p.to_class(), "px-2"); /// -/// let p = class::Padding::with(token::Side::End, token::ScaleSize::Four) -/// .with_breakpoint(token::BreakPoint::SM); +/// let p = class::Padding::with(BoxSide::End, ScaleSize::Four) +/// .with_breakpoint(BreakPoint::SM); /// assert_eq!(p.to_class(), "pe-sm-4"); /// -/// let p = class::Padding::with(token::Side::All, token::ScaleSize::Auto); +/// let p = class::Padding::with(BoxSide::All, ScaleSize::Auto); /// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding. /// ``` #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub struct Padding { - side: Side, + side: BoxSide, size: ScaleSize, breakpoint: BreakPoint, } @@ -134,7 +134,7 @@ pub struct Padding { impl Padding { /// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de /// ruptura. - pub fn with(side: Side, size: ScaleSize) -> Self { + pub fn with(side: BoxSide, size: ScaleSize) -> Self { Padding { side, size, @@ -157,13 +157,13 @@ impl Padding { #[inline] const fn side_prefix(&self) -> &'static str { match self.side { - Side::All => "p", - Side::Top => "pt", - Side::Bottom => "pb", - Side::Start => "ps", - Side::End => "pe", - Side::LeftAndRight => "px", - Side::TopAndBottom => "py", + BoxSide::All => "p", + BoxSide::Top => "pt", + BoxSide::Bottom => "pb", + BoxSide::Start => "ps", + BoxSide::End => "pe", + BoxSide::LeftAndRight => "px", + BoxSide::TopAndBottom => "py", } } diff --git a/extensions/pagetop-bootsier/src/theme/class/rounded.rs b/extensions/pagetop-bootsier/src/theme/class/rounded.rs index 188aaf97..4bd497eb 100644 --- a/extensions/pagetop-bootsier/src/theme/class/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/class/rounded.rs @@ -1,6 +1,98 @@ use pagetop::prelude::*; -use crate::theme::token::RoundedRadius; +// **< RoundedRadius >****************************************************************************** + +/// Radio para el redondeo de esquinas ([`Rounded`]). +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum RoundedRadius { + /// No define ninguna clase. + #[default] + None, + /// Genera `rounded` (radio por defecto del tema). + Default, + /// Genera `rounded-0` (sin redondeo). + Zero, + /// Genera `rounded-1`. + Scale1, + /// Genera `rounded-2`. + Scale2, + /// Genera `rounded-3`. + Scale3, + /// Genera `rounded-4`. + Scale4, + /// Genera `rounded-5`. + Scale5, + /// Genera `rounded-circle`. + Circle, + /// Genera `rounded-pill`. + Pill, +} + +impl RoundedRadius { + // Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el + // redondeo por defecto. + #[rustfmt::skip] + #[inline] + const fn suffix(self) -> Option<&'static str> { + match self { + Self::None => None, + Self::Default => Some(""), + Self::Zero => Some("-0"), + Self::Scale1 => Some("-1"), + Self::Scale2 => Some("-2"), + Self::Scale3 => Some("-3"), + Self::Scale4 => Some("-4"), + Self::Scale5 => Some("-5"), + Self::Circle => Some("-circle"), + Self::Pill => Some("-pill"), + } + } + + /// Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`, + /// `rounded-bottom-start`, o vacío para `rounded-*`). + #[inline] + pub fn push_to(self, classes: &mut String, prefix: &str) { + if let Some(suffix) = self.suffix() { + if !classes.is_empty() { + classes.push(' '); + } + if prefix.is_empty() { + classes.push_str("rounded"); + } else { + classes.push_str(prefix); + } + classes.push_str(suffix); + } + } + + /// Devuelve la clase `rounded-*` para el redondeo de esquinas. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// assert_eq!(class::RoundedRadius::Default.to_class(), "rounded"); + /// assert_eq!(class::RoundedRadius::Zero.to_class(), "rounded-0"); + /// assert_eq!(class::RoundedRadius::Scale3.to_class(), "rounded-3"); + /// assert_eq!(class::RoundedRadius::Circle.to_class(), "rounded-circle"); + /// assert_eq!(class::RoundedRadius::None.to_class(), ""); + /// ``` + #[inline] + pub fn to_class(self) -> String { + let mut class = String::new(); + self.push_to(&mut class, ""); + class + } +} + +impl Into for RoundedRadius { + /// Permite pasar [`RoundedRadius`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). + fn into(self) -> CowStr { + self.to_class().into() + } +} + +// **< Rounded >************************************************************************************ /// Clases para definir **esquinas redondeadas**. /// @@ -18,7 +110,7 @@ use crate::theme::token::RoundedRadius; /// use pagetop_bootsier::theme::*; /// /// // Radio global: -/// let r = class::Rounded::with(token::RoundedRadius::Default); +/// let r = class::Rounded::with(class::RoundedRadius::Default); /// assert_eq!(r.to_class(), "rounded"); /// /// // Sin redondeo: @@ -26,18 +118,18 @@ use crate::theme::token::RoundedRadius; /// assert_eq!(r.to_class(), ""); /// /// // Radio en las esquinas de un lado lógico: -/// let r = class::Rounded::new().with_end(token::RoundedRadius::Scale2); +/// let r = class::Rounded::new().with_end(class::RoundedRadius::Scale2); /// assert_eq!(r.to_class(), "rounded-end-2"); /// /// // Radio en una esquina concreta: -/// let r = class::Rounded::new().with_top_start(token::RoundedRadius::Scale3); +/// let r = class::Rounded::new().with_top_start(class::RoundedRadius::Scale3); /// assert_eq!(r.to_class(), "rounded-top-start-3"); /// /// // Combinado (ejemplo completo): /// let r = class::Rounded::new() -/// .with_top(token::RoundedRadius::Default) // Añade redondeo arriba. -/// .with_bottom_start(token::RoundedRadius::Scale4) // Añade una esquina redondeada concreta. -/// .with_bottom_end(token::RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. +/// .with_top(class::RoundedRadius::Default) // Añade redondeo arriba. +/// .with_bottom_start(class::RoundedRadius::Scale4) // Añade una esquina redondeada concreta. +/// .with_bottom_end(class::RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. /// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); /// ``` #[rustfmt::skip] diff --git a/extensions/pagetop-bootsier/src/theme/token.rs b/extensions/pagetop-bootsier/src/theme/token.rs index 9cb7c386..6fdaf93e 100644 --- a/extensions/pagetop-bootsier/src/theme/token.rs +++ b/extensions/pagetop-bootsier/src/theme/token.rs @@ -1,28 +1,10 @@ //! Tipos enumerados para construir clases del tema. -//! -//! Incluyen puntos de ruptura, colores, escalas de tamaño, lados, etc. Se pueden importar -//! globalmente junto al resto del tema: -//! -//! ```rust,no_run -//! use pagetop_bootsier::theme::*; -//! -//! let bg = class::Background::with(token::Color::Primary); -//! let border = class::Border::new() -//! .with_side(token::Side::Top, token::ScaleSize::Zero) -//! .with_color(token::Color::Danger); -//! ``` mod breakpoint; pub use breakpoint::BreakPoint; mod color; -pub use color::{Color, ColorBg, ColorText, Opacity}; - -mod border; -pub use border::ColorBorder; - -mod rounded; -pub use rounded::RoundedRadius; +pub use color::{OpacityLevel, ThemeColor}; mod layout; -pub use layout::{ScaleSize, Side}; +pub use layout::{BoxSide, ScaleSize}; diff --git a/extensions/pagetop-bootsier/src/theme/token/border.rs b/extensions/pagetop-bootsier/src/theme/token/border.rs deleted file mode 100644 index c1c6d0d2..00000000 --- a/extensions/pagetop-bootsier/src/theme/token/border.rs +++ /dev/null @@ -1,103 +0,0 @@ -use pagetop::prelude::*; - -use crate::theme::token::Color; - -/// Esquema de color para los bordes ([`Border`](crate::theme::class::Border)). -/// -/// - `Theme(Color)` y `Subtle(Color)` usan la paleta de colores temáticos ([`Color`]). -/// - `Black` y `White` son colores fijos independientes del tema. -/// - `Default` no genera ninguna clase. -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum ColorBorder { - /// No define ninguna clase. - #[default] - Default, - /// Genera la clase `border-{color}`. - Theme(Color), - /// Genera la clase `border-{color}-subtle` (un tono suavizado del color). - Subtle(Color), - /// Color negro. - Black, - /// Color blanco. - White, -} - -impl ColorBorder { - // Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase. - #[rustfmt::skip] - #[inline] - const fn suffix(self) -> Option<&'static str> { - match self { - Self::Default => None, - Self::Theme(_) => Some(""), - Self::Subtle(_) => Some("-subtle"), - Self::Black => Some("-black"), - Self::White => Some("-white"), - } - } - - // Añade la clase `border-*` a la cadena de clases. - #[inline] - pub(crate) fn push_to(self, classes: &mut String) { - if let Some(suffix) = self.suffix() { - if !classes.is_empty() { - classes.push(' '); - } - match self { - Self::Theme(c) | Self::Subtle(c) => { - classes.push_str("border-"); - classes.push_str(c.as_str()); - } - _ => classes.push_str("border"), - } - classes.push_str(suffix); - } - } - - /// Devuelve la clase `border-*` correspondiente al color de borde. - /// - /// # Ejemplos - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// let theme = token::ColorBorder::Theme(token::Color::Primary).to_class(); - /// assert_eq!(theme, "border-primary"); - /// - /// let subtle = token::ColorBorder::Subtle(token::Color::Warning).to_class(); - /// assert_eq!(subtle, "border-warning-subtle"); - /// - /// let black = token::ColorBorder::Black.to_class(); - /// assert_eq!(black, "border-black"); - /// - /// let none = token::ColorBorder::Default.to_class(); - /// assert_eq!(none, ""); - /// ``` - #[inline] - pub fn to_class(self) -> String { - let mut class = String::new(); - self.push_to(&mut class); - class - } -} - -impl From for ColorBorder { - /// Convierte un [`Color`] en [`ColorBorder::Theme`]. - /// - /// Permite pasar un [`Color`] directamente donde se espera un [`ColorBorder`], sin necesidad - /// de envolver el valor en [`ColorBorder::Theme`]. Es el atajo habitual para los colores - /// temáticos. - /// - /// Para los demás esquemas (`Subtle`, `Black`, `White`) se sigue usando [`ColorBorder`] - /// directamente. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// let border: token::ColorBorder = token::Color::Success.into(); - /// assert_eq!(border.to_class(), "border-success"); - /// ``` - fn from(color: Color) -> Self { - Self::Theme(color) - } -} diff --git a/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs index 15ecb6f1..31310760 100644 --- a/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. +/// Puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum BreakPoint { /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. diff --git a/extensions/pagetop-bootsier/src/theme/token/color.rs b/extensions/pagetop-bootsier/src/theme/token/color.rs index cf90288a..e47e8219 100644 --- a/extensions/pagetop-bootsier/src/theme/token/color.rs +++ b/extensions/pagetop-bootsier/src/theme/token/color.rs @@ -1,15 +1,17 @@ use pagetop::prelude::*; -// **< Color >************************************************************************************** +// **< ThemeColor >********************************************************************************* /// Paleta de colores temáticos. /// /// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`, -/// etc.). Este tipo enumerado sirve de referencia para componer las clases de color para el fondo -/// ([`Background`](crate::theme::class::Background)), bordes -/// ([`Border`](crate::theme::class::Border)) o texto ([`Text`](crate::theme::class::Text)). +/// etc.). Se utiliza para componer las clases de color de [`Bg`], [`Border`] o [`Text`]. +/// +/// [`Bg`]: crate::theme::class::Bg +/// [`Border`]: crate::theme::class::Border +/// [`Text`]: crate::theme::class::Text #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum Color { +pub enum ThemeColor { #[default] Primary, Secondary, @@ -21,7 +23,7 @@ pub enum Color { Dark, } -impl Color { +impl ThemeColor { /// Devuelve el nombre del color Bootstrap (`"primary"`, `"danger"`, etc.). #[rustfmt::skip] #[inline] @@ -39,16 +41,18 @@ impl Color { } } -// **< Opacity >************************************************************************************ +// **< OpacityLevel >******************************************************************************* /// Niveles de opacidad (`opacity-*`). /// -/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` -/// ([`Background`](crate::theme::class::Background)), de los bordes `border-opacity-*` -/// ([`Border`](crate::theme::class::Border)) o del texto `text-opacity-*` -/// ([`Text`](crate::theme::class::Text)). +/// Se utiliza para las clases que definen la transparencia de [`Bg`] (`bg-opacity-*`), [`Border`] +/// (`border-opacity-*`) o [`Text`] (`text-opacity-*`). +/// +/// [`Bg`]: crate::theme::class::Bg +/// [`Border`]: crate::theme::class::Border +/// [`Text`]: crate::theme::class::Text #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum Opacity { +pub enum OpacityLevel { /// No define ninguna clase. #[default] Default, @@ -66,7 +70,7 @@ pub enum Opacity { Transparent, } -impl Opacity { +impl OpacityLevel { // Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase. #[rustfmt::skip] #[inline] @@ -106,9 +110,9 @@ impl Opacity { /// /// ```rust /// # use pagetop_bootsier::theme::*; - /// assert_eq!(token::Opacity::Opaque.to_class(), "opacity-100"); - /// assert_eq!(token::Opacity::Half.to_class(), "opacity-50"); - /// assert_eq!(token::Opacity::Default.to_class(), ""); + /// assert_eq!(OpacityLevel::Opaque.to_class(), "opacity-100"); + /// assert_eq!(OpacityLevel::Half.to_class(), "opacity-50"); + /// assert_eq!(OpacityLevel::Default.to_class(), ""); /// ``` #[inline] pub fn to_class(self) -> String { @@ -117,237 +121,3 @@ impl Opacity { class } } - -// **< ColorBg >************************************************************************************ - -/// Esquema de color para el fondo ([`Background`](crate::theme::class::Background)). -/// -/// - `Body`, `BodySecondary` y `BodyTertiary` siguen el esquema del tema (claro/oscuro). -/// - `Theme(Color)` y `Subtle(Color)` usan la paleta de colores temáticos ([`Color`]). -/// - `Black`, `White`, `Transparent` son colores fijos independientes del tema. -/// - `Default` no genera ninguna clase. -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum ColorBg { - /// No define ninguna clase. - #[default] - Default, - /// Fondo predefinido del tema (`bg-body`). - Body, - /// Fondo predefinido del tema (`bg-body-secondary`). - BodySecondary, - /// Fondo predefinido del tema (`bg-body-tertiary`). - BodyTertiary, - /// Genera la clase `bg-{color}` (p. ej., `bg-primary`). - Theme(Color), - /// Genera la clase `bg-{color}-subtle` (un tono suavizado del color). - Subtle(Color), - /// Color negro. - Black, - /// Color blanco. - White, - /// Fondo transparente (`bg-transparent`). - Transparent, -} - -impl ColorBg { - // Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase. - #[rustfmt::skip] - #[inline] - const fn suffix(self) -> Option<&'static str> { - match self { - Self::Default => None, - Self::Body => Some("-body"), - Self::BodySecondary => Some("-body-secondary"), - Self::BodyTertiary => Some("-body-tertiary"), - Self::Theme(_) => Some(""), - Self::Subtle(_) => Some("-subtle"), - Self::Black => Some("-black"), - Self::White => Some("-white"), - Self::Transparent => Some("-transparent"), - } - } - - // Añade la clase de fondo `bg-*` a la cadena de clases. - #[inline] - pub(crate) fn push_to(self, classes: &mut String) { - if let Some(suffix) = self.suffix() { - if !classes.is_empty() { - classes.push(' '); - } - match self { - Self::Theme(c) | Self::Subtle(c) => { - classes.push_str("bg-"); - classes.push_str(c.as_str()); - } - _ => classes.push_str("bg"), - } - classes.push_str(suffix); - } - } - - /// Devuelve la clase `bg-*` correspondiente al fondo. - /// - /// # Ejemplos - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// let body = token::ColorBg::Body.to_class(); - /// assert_eq!(body, "bg-body"); - /// - /// let theme = token::ColorBg::Theme(token::Color::Primary).to_class(); - /// assert_eq!(theme, "bg-primary"); - /// - /// let subtle = token::ColorBg::Subtle(token::Color::Warning).to_class(); - /// assert_eq!(subtle, "bg-warning-subtle"); - /// - /// let transparent = token::ColorBg::Transparent.to_class(); - /// assert_eq!(transparent, "bg-transparent"); - /// - /// let none = token::ColorBg::Default.to_class(); - /// assert_eq!(none, ""); - /// ``` - #[inline] - pub fn to_class(self) -> String { - let mut class = String::new(); - self.push_to(&mut class); - class - } -} - -impl From for ColorBg { - /// Convierte un [`Color`] en [`ColorBg::Theme`]. - /// - /// Permite pasar un [`Color`] directamente donde se espera un [`ColorBg`], sin necesidad de - /// envolver el valor en [`ColorBg::Theme`]. Es el atajo habitual para los colores temáticos. - /// - /// Para los demás esquemas (`Body`, `Subtle`, `Black`, etc.) se sigue usando [`ColorBg`] - /// directamente. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// let bg: token::ColorBg = token::Color::Primary.into(); - /// assert_eq!(bg.to_class(), "bg-primary"); - /// ``` - fn from(color: Color) -> Self { - Self::Theme(color) - } -} - -// **< ColorText >********************************************************************************** - -/// Esquema de color para el texto ([`Text`](crate::theme::class::Text)). -/// -/// - `Body`, `BodyEmphasis`, `BodySecondary` y `BodyTertiary` siguen el tema (claro/oscuro). -/// - `Theme(Color)` y `Emphasis(Color)` usan la paleta de colores temáticos ([`Color`]). -/// - `Black` y `White` son colores fijos independientes del tema. -/// - `Default` no genera ninguna clase. -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum ColorText { - /// No define ninguna clase. - #[default] - Default, - /// Color predefinido del tema (`text-body`). - Body, - /// Color de mayor contraste según el tema (`text-body-emphasis`). - BodyEmphasis, - /// Color predefinido del tema (`text-body-secondary`). - BodySecondary, - /// Color predefinido del tema (`text-body-tertiary`). - BodyTertiary, - /// Genera la clase `text-{color}`. - Theme(Color), - /// Genera la clase `text-{color}-emphasis` (mayor contraste acorde al tema). - Emphasis(Color), - /// Color negro. - Black, - /// Color blanco. - White, -} - -impl ColorText { - // Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase. - #[rustfmt::skip] - #[inline] - const fn suffix(self) -> Option<&'static str> { - match self { - Self::Default => None, - Self::Body => Some("-body"), - Self::BodyEmphasis => Some("-body-emphasis"), - Self::BodySecondary => Some("-body-secondary"), - Self::BodyTertiary => Some("-body-tertiary"), - Self::Theme(_) => Some(""), - Self::Emphasis(_) => Some("-emphasis"), - Self::Black => Some("-black"), - Self::White => Some("-white"), - } - } - - // Añade la clase de texto `text-*` a la cadena de clases. - #[inline] - pub(crate) fn push_to(self, classes: &mut String) { - if let Some(suffix) = self.suffix() { - if !classes.is_empty() { - classes.push(' '); - } - match self { - Self::Theme(c) | Self::Emphasis(c) => { - classes.push_str("text-"); - classes.push_str(c.as_str()); - } - _ => classes.push_str("text"), - } - classes.push_str(suffix); - } - } - - /// Devuelve la clase `text-*` correspondiente al color del texto. - /// - /// # Ejemplos - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// let body = token::ColorText::Body.to_class(); - /// assert_eq!(body, "text-body"); - /// - /// let theme = token::ColorText::Theme(token::Color::Primary).to_class(); - /// assert_eq!(theme, "text-primary"); - /// - /// let emphasis = token::ColorText::Emphasis(token::Color::Danger).to_class(); - /// assert_eq!(emphasis, "text-danger-emphasis"); - /// - /// let black = token::ColorText::Black.to_class(); - /// assert_eq!(black, "text-black"); - /// - /// let none = token::ColorText::Default.to_class(); - /// assert_eq!(none, ""); - /// ``` - #[inline] - pub fn to_class(self) -> String { - let mut class = String::new(); - self.push_to(&mut class); - class - } -} - -impl From for ColorText { - /// Convierte un [`Color`] en [`ColorText::Theme`]. - /// - /// Permite pasar un [`Color`] directamente donde se espera un [`ColorText`], sin necesidad de - /// envolver el valor en [`ColorText::Theme`]. Es el atajo habitual para los colores temáticos. - /// - /// Para los demás esquemas (`Body`, `Emphasis`, `Black`, etc.) se sigue usando [`ColorText`] - /// directamente. - /// - /// # Ejemplo - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// let text: token::ColorText = token::Color::Danger.into(); - /// assert_eq!(text.to_class(), "text-danger"); - /// ``` - fn from(color: Color) -> Self { - Self::Theme(color) - } -} diff --git a/extensions/pagetop-bootsier/src/theme/token/layout.rs b/extensions/pagetop-bootsier/src/theme/token/layout.rs index 2e4f8bd6..a5df6b7c 100644 --- a/extensions/pagetop-bootsier/src/theme/token/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/token/layout.rs @@ -2,11 +2,14 @@ use pagetop::prelude::*; // **< ScaleSize >********************************************************************************** -/// Escala discreta de tamaños para clases utilitarias de Bootstrap. +/// Escala discreta de tamaños para clases utilitarias. /// -/// Se usa como parámetro de tamaño en [`Border`](crate::theme::class::Border), -/// [`Margin`](crate::theme::class::Margin) y [`Padding`](crate::theme::class::Padding). La +/// Se usa como parámetro de tamaño para las clases de [`Border`], [`Margin`] y [`Padding`]. La /// variante `Auto` no aplica en `Padding`. +/// +/// [`Border`]: crate::theme::class::Border +/// [`Margin`]: crate::theme::class::Margin +/// [`Padding`]: crate::theme::class::Padding #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum ScaleSize { /// Sin tamaño (no define ninguna clase). @@ -61,14 +64,17 @@ impl ScaleSize { } } -// **< Side >*************************************************************************************** +// **< BoxSide >************************************************************************************ /// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL). /// -/// Se usa como selector de lado en [`Border`](crate::theme::class::Border), -/// [`Margin`](crate::theme::class::Margin) y [`Padding`](crate::theme::class::Padding). +/// Se usa como selector de lado para las clases de [`Border`], [`Margin`] y [`Padding`]. +/// +/// [`Border`]: crate::theme::class::Border +/// [`Margin`]: crate::theme::class::Margin +/// [`Padding`]: crate::theme::class::Padding #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum Side { +pub enum BoxSide { /// Todos los lados. #[default] All, diff --git a/extensions/pagetop-bootsier/src/theme/token/rounded.rs b/extensions/pagetop-bootsier/src/theme/token/rounded.rs deleted file mode 100644 index c1621b09..00000000 --- a/extensions/pagetop-bootsier/src/theme/token/rounded.rs +++ /dev/null @@ -1,84 +0,0 @@ -use pagetop::prelude::*; - -/// Radio para el redondeo de esquinas ([`Rounded`](crate::theme::class::Rounded)). -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum RoundedRadius { - /// No define ninguna clase. - #[default] - None, - /// Genera `rounded` (radio por defecto del tema). - Default, - /// Genera `rounded-0` (sin redondeo). - Zero, - /// Genera `rounded-1`. - Scale1, - /// Genera `rounded-2`. - Scale2, - /// Genera `rounded-3`. - Scale3, - /// Genera `rounded-4`. - Scale4, - /// Genera `rounded-5`. - Scale5, - /// Genera `rounded-circle`. - Circle, - /// Genera `rounded-pill`. - Pill, -} - -impl RoundedRadius { - // Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el - // redondeo por defecto. - #[rustfmt::skip] - #[inline] - const fn suffix(self) -> Option<&'static str> { - match self { - Self::None => None, - Self::Default => Some(""), - Self::Zero => Some("-0"), - Self::Scale1 => Some("-1"), - Self::Scale2 => Some("-2"), - Self::Scale3 => Some("-3"), - Self::Scale4 => Some("-4"), - Self::Scale5 => Some("-5"), - Self::Circle => Some("-circle"), - Self::Pill => Some("-pill"), - } - } - - // Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`, - // `rounded-bottom-start`, o vacío para `rounded-*`). - #[inline] - pub(crate) fn push_to(self, classes: &mut String, prefix: &str) { - if let Some(suffix) = self.suffix() { - if !classes.is_empty() { - classes.push(' '); - } - if prefix.is_empty() { - classes.push_str("rounded"); - } else { - classes.push_str(prefix); - } - classes.push_str(suffix); - } - } - - /// Devuelve la clase `rounded-*` para el redondeo de esquinas. - /// - /// # Ejemplos - /// - /// ```rust - /// # use pagetop_bootsier::theme::*; - /// assert_eq!(token::RoundedRadius::Default.to_class(), "rounded"); - /// assert_eq!(token::RoundedRadius::Zero.to_class(), "rounded-0"); - /// assert_eq!(token::RoundedRadius::Scale3.to_class(), "rounded-3"); - /// assert_eq!(token::RoundedRadius::Circle.to_class(), "rounded-circle"); - /// assert_eq!(token::RoundedRadius::None.to_class(), ""); - /// ``` - #[inline] - pub fn to_class(self) -> String { - let mut class = String::new(); - self.push_to(&mut class, ""); - class - } -}