From 39033ef641a3ebf3391c8764d26b1adba4ce7fc5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 10 Nov 2025 07:45:05 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20clases=20de=20fondo,?= =?UTF-8?q?=20texto,=20bordes=20y=20esquinas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactoriza el componente contenedor `Container` para usar estas clases y aplicar los nuevos enums `Kind` y `Width` para mejorar el comportamiento semántico y *responsive*. - Actualiza los componentes `Dropdown`, `Image`, `Nav`, `Navbar` y `Offcanvas` para usar los nuevos métodos de unión de clases. - Elimina propiedades de estilo redundantes de los componentes `Navbar` e `Image`, simplificando sus interfaces. --- extensions/pagetop-bootsier/src/lib.rs | 1 - extensions/pagetop-bootsier/src/theme.rs | 14 +- extensions/pagetop-bootsier/src/theme/aux.rs | 11 +- .../pagetop-bootsier/src/theme/aux/border.rs | 182 ++-------- .../src/theme/aux/breakpoint.rs | 77 ++--- .../pagetop-bootsier/src/theme/aux/button.rs | 58 ++++ .../pagetop-bootsier/src/theme/aux/color.rs | 258 ++++---------- .../pagetop-bootsier/src/theme/aux/rounded.rs | 186 +--------- .../pagetop-bootsier/src/theme/aux/size.rs | 28 -- .../pagetop-bootsier/src/theme/classes.rs | 10 + .../src/theme/classes/border.rs | 155 +++++++++ .../src/theme/classes/color.rs | 207 ++++++++++++ .../src/theme/classes/rounded.rs | 163 +++++++++ .../pagetop-bootsier/src/theme/container.rs | 319 ++---------------- .../src/theme/container/component.rs | 197 +++++++++++ .../src/theme/container/props.rs | 44 +++ .../pagetop-bootsier/src/theme/dropdown.rs | 2 +- .../src/theme/dropdown/component.rs | 10 +- .../src/theme/image/component.rs | 52 +-- .../src/theme/nav/component.rs | 2 +- .../src/theme/navbar/component.rs | 55 +-- .../pagetop-bootsier/src/theme/navbar/item.rs | 2 +- .../src/theme/offcanvas/component.rs | 2 +- 23 files changed, 1041 insertions(+), 994 deletions(-) create mode 100644 extensions/pagetop-bootsier/src/theme/aux/button.rs delete mode 100644 extensions/pagetop-bootsier/src/theme/aux/size.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/border.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/color.rs create mode 100644 extensions/pagetop-bootsier/src/theme/classes/rounded.rs create mode 100644 extensions/pagetop-bootsier/src/theme/container/component.rs create mode 100644 extensions/pagetop-bootsier/src/theme/container/props.rs diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 6df5341..4cb35a2 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -98,7 +98,6 @@ pub mod theme; /// *Prelude* del tema. pub mod prelude { pub use crate::config::*; - pub use crate::theme::aux::*; pub use crate::theme::*; } diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index 4c547f6..2c6b575 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -1,10 +1,18 @@ //! Definiciones y componentes del tema. +//! +//! En esta página, el apartado **Modules** incluye las definiciones necesarias para los componentes +//! que se muestran en el apartado **Structs**, mientras que en **Enums** se listan los elementos +//! auxiliares del tema utilizados en clases y componentes. -pub mod aux; +mod aux; +pub use aux::*; + +pub mod classes; // Container. -mod container; -pub use container::{Container, ContainerType, ContainerWidth}; +pub mod container; +#[doc(inline)] +pub use container::Container; // Dropdown. pub mod dropdown; diff --git a/extensions/pagetop-bootsier/src/theme/aux.rs b/extensions/pagetop-bootsier/src/theme/aux.rs index a56b14e..528126d 100644 --- a/extensions/pagetop-bootsier/src/theme/aux.rs +++ b/extensions/pagetop-bootsier/src/theme/aux.rs @@ -5,14 +5,13 @@ pub use breakpoint::BreakPoint; mod color; pub use color::{Color, Opacity}; -pub use color::{ColorBg, ColorBorder, ColorButton, ColorText}; -pub use color::{StyleBg, StyleBorder, StyleText}; +pub use color::{ColorBg, ColorText}; mod border; -pub use border::{Border, BorderSize}; +pub use border::{BorderColor, BorderSize}; mod rounded; -pub use rounded::{Rounded, RoundedRadius}; +pub use rounded::RoundedRadius; -mod size; -pub use size::ButtonSize; +mod button; +pub use button::{ButtonColor, ButtonSize}; diff --git a/extensions/pagetop-bootsier/src/theme/aux/border.rs b/extensions/pagetop-bootsier/src/theme/aux/border.rs index de3df81..47547c3 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/border.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/border.rs @@ -1,16 +1,47 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::aux::Color; use std::fmt; +// **< BorderColor >******************************************************************************** + +/// Colores `border-*` para los bordes ([`classes::Border`](crate::theme::classes::Border)). +#[derive(AutoDefault)] +pub enum BorderColor { + /// No define ninguna clase. + #[default] + Default, + /// Genera internamente clases `border-{color}`. + Theme(Color), + /// Genera internamente clases `border-{color}-subtle` (un tono suavizado del color). + Subtle(Color), + /// Color negro. + Black, + /// Color blanco. + White, +} + +#[rustfmt::skip] +impl fmt::Display for BorderColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Theme(c) => write!(f, "border-{c}"), + Self::Subtle(c) => write!(f, "border-{c}-subtle"), + Self::Black => f.write_str("border-black"), + Self::White => f.write_str("border-white"), + } + } +} + // **< BorderSize >********************************************************************************* -/// Tamaño para el ancho de los bordes ([`Border`]). +/// Tamaño para el ancho de los bordes ([`classes::Border`](crate::theme::classes::Border)). /// /// Mapea a `border`, `border-0` y `border-{1..5}`: /// -/// - `None` no añade clase (devuelve `""`). +/// - `None` no añade ninguna clase. /// - `Default` genera `border` (borde por defecto del tema). /// - `Zero` genera `border-0` (sin borde). /// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente). @@ -29,7 +60,7 @@ pub enum BorderSize { impl BorderSize { #[rustfmt::skip] - fn to_class(&self, prefix: impl AsRef) -> String { + pub(crate) fn to_class(&self, prefix: impl AsRef) -> String { match self { Self::None => String::new(), Self::Default => String::from(prefix.as_ref()), @@ -48,146 +79,3 @@ impl fmt::Display for BorderSize { write!(f, "{}", self.to_class("border")) } } - -// **< Border >************************************************************************************* - -/// Agrupa propiedades para crear **bordes**. -/// -/// Permite: -/// -/// - Definir un tamaño **global** para todo el borde (`size`). -/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, -/// respetando LTR/RTL). -/// - Aplicar un **color** al borde (`ColorBorder`). -/// - Aplicar un nivel de **opacidad** (`BorderOpacity`). -/// -/// # Comportamiento aditivo / sustractivo -/// -/// - **Aditivo**: basta con crear un borde sin tamaño con `Border::new()` para ir añadiendo cada -/// lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`. -/// -/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando -/// `Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`. -/// -/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado. -/// -/// # Ejemplos -/// -/// **Borde global:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::with(BorderSize::Scale2); -/// assert_eq!(b.to_string(), "border-2"); -/// ``` -/// -/// **Aditivo (solo borde superior):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::new().with_top(BorderSize::Scale1); -/// assert_eq!(b.to_string(), "border-top-1"); -/// ``` -/// -/// **Sustractivo (borde global menos el superior):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::with(BorderSize::Default).with_top(BorderSize::Zero); -/// assert_eq!(b.to_string(), "border border-top-0"); -/// ``` -/// -/// **Ancho por lado (lado lógico inicial a 2 y final a 4):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4); -/// assert_eq!(b.to_string(), "border-end-4 border-start-2"); -/// ``` -/// -/// **Combinado (ejemplo completo):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let b = Border::with(BorderSize::Default) // Borde global por defecto. -/// .with_top(BorderSize::Zero) // Quita borde superior. -/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. -/// .with_style(StyleBorder::Both(ColorBorder::Theme(Color::Primary), Opacity::Half)); -/// -/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); -/// ``` -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Border { - size : BorderSize, - top : BorderSize, - end : BorderSize, - bottom: BorderSize, - start : BorderSize, - style : StyleBorder, -} - -impl Border { - /// Prepara un borde **sin tamaño global** de partida. - pub fn new() -> Self { - Self::default() - } - - /// Crea un borde **con tamaño global** (`size`). - pub fn with(size: BorderSize) -> Self { - Self::default().with_size(size) - } - - // **< Border BUILDER >************************************************************************* - - /// Establece el tamaño global del borde (`border*`). - pub fn with_size(mut self, size: BorderSize) -> Self { - self.size = size; - self - } - - /// Establece el tamaño del borde superior (`border-top-*`). - pub fn with_top(mut self, size: BorderSize) -> Self { - self.top = size; - self - } - - /// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL. - pub fn with_end(mut self, size: BorderSize) -> Self { - self.end = size; - self - } - - /// Establece el tamaño del borde inferior (`border-bottom-*`). - pub fn with_bottom(mut self, size: BorderSize) -> Self { - self.bottom = size; - self - } - - /// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL. - pub fn with_start(mut self, size: BorderSize) -> Self { - self.start = size; - self - } - - /// Establece el estilo de color/opacidad del borde. - pub fn with_style(mut self, style: StyleBorder) -> Self { - self.style = style; - self - } -} - -impl fmt::Display for Border { - /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start` y - /// *color*/*opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - join_opt!([ - self.size.to_string(), - self.top.to_class("border-top"), - self.end.to_class("border-end"), - self.bottom.to_class("border-bottom"), - self.start.to_class("border-start"), - self.style.to_string(), - ]; " ") - .unwrap_or_default() - ) - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs index 601aa54..27d7c29 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs @@ -3,25 +3,6 @@ use pagetop::prelude::*; use std::fmt; /// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. -/// -/// - `"sm"`, `"md"`, `"lg"`, `"xl"` o `"xxl"` para los puntos de ruptura `SM`, `MD`, `LG`, `XL` o -/// `XXL`, respectivamente. -/// - `""` (cadena vacía) para `None`. -/// -/// # Ejemplos -/// -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// assert_eq!(BreakPoint::MD.to_string(), "md"); -/// assert_eq!(BreakPoint::None.to_string(), ""); -/// -/// // Forma correcta para clases con prefijo: -/// assert_eq!(BreakPoint::MD.to_class("col"), "col-md"); -/// assert_eq!(BreakPoint::None.to_class("offcanvas"), "offcanvas"); -/// -/// assert_eq!(BreakPoint::XXL.try_class("col"), Some("col-xxl".to_string())); -/// assert_eq!(BreakPoint::None.try_class("offcanvas"), None); -/// ``` #[derive(AutoDefault)] pub enum BreakPoint { /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. @@ -40,46 +21,43 @@ pub enum BreakPoint { } impl BreakPoint { - /// Comprueba si es un punto de ruptura efectivo. - /// - /// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`; y `false` en otro caso. + #[rustfmt::skip] #[inline] - pub const fn is_breakpoint(&self) -> bool { - !matches!(self, Self::None) + const fn suffix(&self) -> Option<&'static str> { + match self { + Self::None => None, + Self::SM => Some("sm"), + Self::MD => Some("md"), + Self::LG => Some("lg"), + Self::XL => Some("xl"), + Self::XXL => Some("xxl"), + } } /// Genera un nombre de clase CSS basado en el punto de ruptura. /// - /// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), concatena - /// el prefijo, un guion (`-`) y el sufijo asociado. En otro caso devuelve únicamente el - /// prefijo. + /// Si es un punto de ruptura efectivo concatena el prefijo, un guion (`-`) y el sufijo + /// asociado. Para `None` devuelve sólo el prefijo. /// /// # Ejemplo /// /// ```rust /// # use pagetop_bootsier::prelude::*; /// let breakpoint = BreakPoint::MD; - /// let class = breakpoint.to_class("col"); - /// assert_eq!(class, "col-md".to_string()); + /// assert_eq!(breakpoint.to_class("col"), "col-md"); /// /// let breakpoint = BreakPoint::None; - /// let class = breakpoint.to_class("offcanvas"); - /// assert_eq!(class, "offcanvas".to_string()); + /// assert_eq!(breakpoint.to_class("offcanvas"), "offcanvas"); /// ``` #[inline] pub fn to_class(&self, prefix: impl AsRef) -> String { - if self.is_breakpoint() { - join!(prefix, "-", self.to_string()) - } else { - String::from(prefix.as_ref()) - } + join_pair!(prefix, "-", self.suffix().unwrap_or_default()) } /// Intenta generar un nombre de clase CSS basado en el punto de ruptura. /// - /// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), devuelve - /// `Some(String)` concatenando el prefijo, un guion (`-`) y el sufijo asociado. En otro caso - /// devuelve `None`. + /// Si es un punto de ruptura efectivo devuelve `Some(String)` concatenando el prefijo, un guion + /// (`-`) y el sufijo asociado. En otro caso devuelve `None`. /// /// # Ejemplo /// @@ -95,24 +73,19 @@ impl BreakPoint { /// ``` #[inline] pub fn try_class(&self, prefix: impl AsRef) -> Option { - if self.is_breakpoint() { - Some(join!(prefix, "-", self.to_string())) - } else { - None - } + self.suffix().map(|suffix| join_pair!(prefix, "-", suffix)) } } -#[rustfmt::skip] impl fmt::Display for BreakPoint { + /// Implementa [`Display`](std::fmt::Display) para asociar `"sm"`, `"md"`, `"lg"`, `"xl"` o + /// `"xxl"` a los puntos de ruptura `BreakPoint::SM`, `MD`, `LG`, `XL` o `XXL`, respectivamente. + /// Y `""` (cadena vacía) a `BreakPoint::None`. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::None => Ok(()), - Self::SM => f.write_str("sm"), - Self::MD => f.write_str("md"), - Self::LG => f.write_str("lg"), - Self::XL => f.write_str("xl"), - Self::XXL => f.write_str("xxl"), + if let Some(suffix) = self.suffix() { + f.write_str(suffix) + } else { + Ok(()) } } } diff --git a/extensions/pagetop-bootsier/src/theme/aux/button.rs b/extensions/pagetop-bootsier/src/theme/aux/button.rs new file mode 100644 index 0000000..a2f2efb --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/aux/button.rs @@ -0,0 +1,58 @@ +use pagetop::prelude::*; + +use crate::theme::aux::Color; + +use std::fmt; + +// **< ButtonColor >******************************************************************************** + +/// Variantes de color `btn-*` para botones. +#[derive(AutoDefault)] +pub enum ButtonColor { + /// No define ninguna clase. + #[default] + Default, + /// Genera internamente clases `btn-{color}` (botón relleno). + Background(Color), + /// Genera `btn-outline-{color}` (fondo transparente y contorno con borde). + Outline(Color), + /// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto. + Link, +} + +#[rustfmt::skip] +impl fmt::Display for ButtonColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Background(c) => write!(f, "btn-{c}"), + Self::Outline(c) => write!(f, "btn-outline-{c}"), + Self::Link => f.write_str("btn-link"), + } + } +} + +// **< ButtonSize >********************************************************************************* + +/// Tamaño visual de un botón. +#[derive(AutoDefault)] +pub enum ButtonSize { + /// Tamaño por defecto del tema (no añade clase). + #[default] + Default, + /// Botón compacto. + Small, + /// Botón destacado/grande. + Large, +} + +#[rustfmt::skip] +impl fmt::Display for ButtonSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => Ok(()), + Self::Small => f.write_str("btn-sm"), + Self::Large => f.write_str("btn-lg"), + } + } +} diff --git a/extensions/pagetop-bootsier/src/theme/aux/color.rs b/extensions/pagetop-bootsier/src/theme/aux/color.rs index 17d192d..d0f1da0 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/color.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/color.rs @@ -7,8 +7,10 @@ use std::fmt; /// Paleta de colores temáticos. /// /// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`, -/// etc.). Este tipo enumerado sirve de base para componer clases de color para el fondo -/// ([`ColorBg`]), bordes ([`ColorBorder`]) y texto ([`ColorText`]). +/// etc.). Este tipo enumerado sirve de base para componer las clases de color para fondo +/// ([`classes::Background`](crate::theme::classes::Background)), bordes +/// ([`classes::Border`](crate::theme::classes::Border)) y texto +/// ([`classes::Text`](crate::theme::classes::Text)). #[derive(AutoDefault)] pub enum Color { #[default] @@ -38,12 +40,69 @@ impl fmt::Display for Color { } } +// **< Opacity >************************************************************************************ + +/// Niveles de opacidad (`opacity-*`). +/// +/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` +/// ([`classes::Background`](crate::theme::classes::Background)), de los bordes `border-opacity-*` +/// ([`classes::Border`](crate::theme::classes::Border)) o del texto `text-opacity-*` +/// ([`classes::Text`](crate::theme::classes::Text)). +#[derive(AutoDefault)] +pub enum Opacity { + /// No define ninguna clase. + #[default] + Default, + /// Permite generar clases `*-opacity-100` (100% de opacidad). + Opaque, + /// Permite generar clases `*-opacity-75` (75%). + SemiOpaque, + /// Permite generar clases `*-opacity-50` (50%). + Half, + /// Permite generar clases `*-opacity-25` (25%). + SemiTransparent, + /// Permite generar clases `*-opacity-10` (10%). + AlmostTransparent, + /// Permite generar clases `*-opacity-0` (0%, totalmente transparente). + Transparent, +} + +impl Opacity { + #[rustfmt::skip] + #[inline] + const fn suffix(&self) -> &'static str { + match self { + Self::Default => "", + Self::Opaque => "opacity-100", + Self::SemiOpaque => "opacity-75", + Self::Half => "opacity-50", + Self::SemiTransparent => "opacity-25", + Self::AlmostTransparent => "opacity-10", + Self::Transparent => "opacity-0", + } + } + + #[inline] + pub(crate) fn to_class(&self, prefix: impl AsRef) -> String { + match self { + Self::Default => String::new(), + _ => join_pair!(prefix, "-", self.suffix()), + } + } +} + +impl fmt::Display for Opacity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.suffix()) + } +} + // **< ColorBg >************************************************************************************ /// Colores `bg-*` para el fondo. #[derive(AutoDefault)] pub enum ColorBg { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + /// No define ninguna clase. #[default] Default, /// Fondo predefinido del tema (`bg-body`). @@ -81,71 +140,12 @@ impl fmt::Display for ColorBg { } } -// **< ColorBorder >******************************************************************************** - -/// Colores `border-*` para los bordes ([`Border`](crate::theme::aux::Border)). -#[derive(AutoDefault)] -pub enum ColorBorder { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `border-{color}`. - Theme(Color), - /// Genera internamente clases `border-{color}-subtle` (un tono suavizado del color). - Subtle(Color), - /// Color negro. - Black, - /// Color blanco. - White, -} - -#[rustfmt::skip] -impl fmt::Display for ColorBorder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Theme(c) => write!(f, "border-{c}"), - Self::Subtle(c) => write!(f, "border-{c}-subtle"), - Self::Black => f.write_str("border-black"), - Self::White => f.write_str("border-white"), - } - } -} - -// **< ColorButton >******************************************************************************** - -/// Variantes de color `btn-*` para botones. -#[derive(AutoDefault)] -pub enum ColorButton { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `btn-{color}` (botón relleno). - Background(Color), - /// Genera `btn-outline-{color}` (fondo transparente y contorno con borde). - Outline(Color), - /// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto. - Link, -} - -#[rustfmt::skip] -impl fmt::Display for ColorButton { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Background(c) => write!(f, "btn-{c}"), - Self::Outline(c) => write!(f, "btn-outline-{c}"), - Self::Link => f.write_str("btn-link"), - } - } -} - // **< ColorText >********************************************************************************** /// Colores `text-*` para el texto. #[derive(AutoDefault)] pub enum ColorText { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). + /// No define ninguna clase. #[default] Default, /// Color predefinido del tema (`text-body`). @@ -182,135 +182,3 @@ impl fmt::Display for ColorText { } } } - -// **< Opacity >************************************************************************************ - -/// Niveles de opacidad (`opacity-*`). -/// -/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` -/// ([`StyleBg`]), de los bordes `border-opacity-*` ([`StyleBorder`]) o del texto `text-opacity-*` -/// ([`StyleText`]). -#[rustfmt::skip] -#[derive(AutoDefault)] -pub enum Opacity { - /// Genera internamente clases `opacity-100` (100% de opacidad). - #[default] - Opaque, - /// Genera internamente clases `opacity-75` (75%). - SemiOpaque, - /// Genera internamente clases `opacity-50` (50%). - Half, - /// Genera internamente clases `opacity-25` (25%). - SemiTransparent, - /// Genera internamente clases `opacity-10` (10%). - AlmostTransparent, - /// Genera internamente clases `opacity-0` (0%, totalmente transparente). - Transparent, -} - -#[rustfmt::skip] -impl fmt::Display for Opacity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Opaque => f.write_str("opacity-100"), - Self::SemiOpaque => f.write_str("opacity-75"), - Self::Half => f.write_str("opacity-50"), - Self::SemiTransparent => f.write_str("opacity-25"), - Self::AlmostTransparent => f.write_str("opacity-10"), - Self::Transparent => f.write_str("opacity-0"), - } - } -} - -// **< StyleBg >*********************************************************************************** - -/// Estilos de color/opacidad para el fondo. -#[derive(AutoDefault)] -pub enum StyleBg { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `bg-*`. - Color(ColorBg), - /// Genera internamente clases `bg-opacity-*`. - Opacity(Opacity), - /// Genera internamente clases `bg-* bg-opacity-*`. - Both(ColorBg, Opacity), -} - -#[rustfmt::skip] -impl fmt::Display for StyleBg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Color(c) => write!(f, "{c}"), - Self::Opacity(o) => write!(f, "bg-{o}"), - Self::Both(c, o) => write!(f, "{c} bg-{o}"), - } - } -} - -// **< StyleBorder >******************************************************************************* - -/// Estilos de color/opacidad para los bordes ([`Border`](crate::theme::aux::Border)). -#[derive(AutoDefault)] -pub enum StyleBorder { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `border-*`. - Color(ColorBorder), - /// Genera internamente clases `border-opacity-*`. - Opacity(Opacity), - /// Genera internamente clases `border-* border-opacity-*`. - Both(ColorBorder, Opacity), -} - -#[rustfmt::skip] -impl fmt::Display for StyleBorder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Color(c) => write!(f, "{c}"), - Self::Opacity(o) => write!(f, "border-{o}"), - Self::Both(c, o) => write!(f, "{c} border-{o}"), - } - } -} - -// **< StyleText >********************************************************************************* - -/// Estilos de color/opacidad para texto y fondo del texto. -#[derive(AutoDefault)] -pub enum StyleText { - /// No define ninguna clase (devuelve `""` para facilitar la composición de clases). - #[default] - Default, - /// Genera internamente clases `text-*`. - Color(ColorText), - /// Genera internamente clases `text-opacity-*`. - Opacity(Opacity), - /// Genera internamente clases `text-* text-opacity-*`. - Both(ColorText, Opacity), - /// Genera internamente clases `text-bg-*` (para el color de fondo del texto). - Bg(Color), - /// Genera internamente clases `text-bg-* text-*`. - BgAndColor(Color, ColorText), - /// Genera internamente clases `text-bg-* text-* text-opacity-*`. - All(Color, ColorText, Opacity), -} - -#[rustfmt::skip] -impl fmt::Display for StyleText { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Color(c) => write!(f, "{c}"), - Self::Opacity(o) => write!(f, "text-{o}"), - Self::Both(c, o) => write!(f, "{c} text-{o}"), - Self::Bg(bg) => write!(f, "text-bg-{bg}"), - Self::BgAndColor(bg, c) => write!(f, "text-bg-{bg} {c}"), - Self::All(bg, c, o) => write!(f, "text-bg-{bg} {c} text-{o}"), - } - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs index 470a48a..8eac7c5 100644 --- a/extensions/pagetop-bootsier/src/theme/aux/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/aux/rounded.rs @@ -2,13 +2,11 @@ use pagetop::prelude::*; use std::fmt; -// **< RoundedRadius >****************************************************************************** - -/// Radio para el redondeo de esquinas ([`Rounded`]). +/// Radio para el redondeo de esquinas ([`classes::Rounded`](crate::theme::classes::Rounded)). /// /// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`. /// -/// - `None` no añade clase (devuelve `""`). +/// - `None` no añade ninguna clase. /// - `Default` genera `rounded` (radio por defecto del tema). /// - `Zero` genera `rounded-0` (sin redondeo). /// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente). @@ -31,18 +29,18 @@ pub enum RoundedRadius { impl RoundedRadius { #[rustfmt::skip] - fn to_class(&self, base: impl AsRef) -> String { + pub(crate) fn to_class(&self, prefix: impl AsRef) -> String { match self { RoundedRadius::None => String::new(), - RoundedRadius::Default => String::from(base.as_ref()), - RoundedRadius::Zero => join!(base, "-0"), - RoundedRadius::Scale1 => join!(base, "-1"), - RoundedRadius::Scale2 => join!(base, "-2"), - RoundedRadius::Scale3 => join!(base, "-3"), - RoundedRadius::Scale4 => join!(base, "-4"), - RoundedRadius::Scale5 => join!(base, "-5"), - RoundedRadius::Circle => join!(base, "-circle"), - RoundedRadius::Pill => join!(base, "-pill"), + RoundedRadius::Default => String::from(prefix.as_ref()), + RoundedRadius::Zero => join!(prefix, "-0"), + RoundedRadius::Scale1 => join!(prefix, "-1"), + RoundedRadius::Scale2 => join!(prefix, "-2"), + RoundedRadius::Scale3 => join!(prefix, "-3"), + RoundedRadius::Scale4 => join!(prefix, "-4"), + RoundedRadius::Scale5 => join!(prefix, "-5"), + RoundedRadius::Circle => join!(prefix, "-circle"), + RoundedRadius::Pill => join!(prefix, "-pill"), } } } @@ -52,163 +50,3 @@ impl fmt::Display for RoundedRadius { write!(f, "{}", self.to_class("rounded")) } } - -// **< Rounded >************************************************************************************ - -/// Agrupa propiedades para crear **esquinas redondeadas**. -/// -/// Permite: -/// -/// - Definir un radio **global para todas las esquinas** (`radius`). -/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`, -/// `start`, **en este orden**, respetando LTR/RTL). -/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`, -/// `bottom-end`, **en este orden**, respetando LTR/RTL). -/// -/// # Ejemplos -/// -/// **Radio global:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::with(RoundedRadius::Default); -/// assert_eq!(r.to_string(), "rounded"); -/// ``` -/// -/// **Sin redondeo:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new(); -/// assert_eq!(r.to_string(), ""); -/// ``` -/// -/// **Radio en las esquinas de un lado lógico:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new().with_end(RoundedRadius::Scale2); -/// assert_eq!(r.to_string(), "rounded-end-2"); -/// ``` -/// -/// **Radio en una esquina concreta:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new().with_top_start(RoundedRadius::Scale3); -/// assert_eq!(r.to_string(), "rounded-top-start-3"); -/// ``` -/// -/// **Combinado (ejemplo completo):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; -/// let r = Rounded::new() -/// .with_top(RoundedRadius::Default) // Añade redondeo arriba. -/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. -/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. -/// -/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); -/// ``` -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Rounded { - radius : RoundedRadius, - top : RoundedRadius, - end : RoundedRadius, - bottom : RoundedRadius, - start : RoundedRadius, - top_start : RoundedRadius, - top_end : RoundedRadius, - bottom_start: RoundedRadius, - bottom_end : RoundedRadius, -} - -impl Rounded { - /// Prepara las esquinas **sin redondeo global** de partida. - pub fn new() -> Self { - Self::default() - } - - /// Crea las esquinas **con redondeo global** (`radius`). - pub fn with(radius: RoundedRadius) -> Self { - Self::default().with_radius(radius) - } - - // **< Rounded BUILDER >************************************************************************ - - /// Establece el radio global de las esquinas (`rounded*`). - pub fn with_radius(mut self, radius: RoundedRadius) -> Self { - self.radius = radius; - self - } - - /// Establece el radio en las esquinas del lado superior (`rounded-top-*`). - pub fn with_top(mut self, radius: RoundedRadius) -> Self { - self.top = radius; - self - } - - /// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL. - pub fn with_end(mut self, radius: RoundedRadius) -> Self { - self.end = radius; - self - } - - /// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`). - pub fn with_bottom(mut self, radius: RoundedRadius) -> Self { - self.bottom = radius; - self - } - - /// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta - /// LTR/RTL. - pub fn with_start(mut self, radius: RoundedRadius) -> Self { - self.start = radius; - self - } - - /// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL. - pub fn with_top_start(mut self, radius: RoundedRadius) -> Self { - self.top_start = radius; - self - } - - /// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL. - pub fn with_top_end(mut self, radius: RoundedRadius) -> Self { - self.top_end = radius; - self - } - - /// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta - /// LTR/RTL. - pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self { - self.bottom_start = radius; - self - } - - /// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL. - pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self { - self.bottom_end = radius; - self - } -} - -impl fmt::Display for Rounded { - /// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, - /// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las - /// definiciones vacías. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - join_opt!([ - self.radius.to_string(), - self.top.to_class("rounded-top"), - self.end.to_class("rounded-end"), - self.bottom.to_class("rounded-bottom"), - self.start.to_class("rounded-start"), - self.top_start.to_class("rounded-top-start"), - self.top_end.to_class("rounded-top-end"), - self.bottom_start.to_class("rounded-bottom-start"), - self.bottom_end.to_class("rounded-bottom-end"), - ]; " ") - .unwrap_or_default() - ) - } -} diff --git a/extensions/pagetop-bootsier/src/theme/aux/size.rs b/extensions/pagetop-bootsier/src/theme/aux/size.rs deleted file mode 100644 index d0abdb5..0000000 --- a/extensions/pagetop-bootsier/src/theme/aux/size.rs +++ /dev/null @@ -1,28 +0,0 @@ -use pagetop::prelude::*; - -use std::fmt; - -// **< ButtonSize >********************************************************************************* - -/// Tamaño visual de un botón. -#[derive(AutoDefault)] -pub enum ButtonSize { - /// Tamaño por defecto del tema (no añade clase). - #[default] - Default, - /// Botón compacto. - Small, - /// Botón destacado/grande. - Large, -} - -#[rustfmt::skip] -impl fmt::Display for ButtonSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Default => Ok(()), - Self::Small => f.write_str("btn-sm"), - Self::Large => f.write_str("btn-lg"), - } - } -} diff --git a/extensions/pagetop-bootsier/src/theme/classes.rs b/extensions/pagetop-bootsier/src/theme/classes.rs new file mode 100644 index 0000000..4e586e1 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes.rs @@ -0,0 +1,10 @@ +//! Conjunto de clases para aplicar en componentes del tema. + +mod color; +pub use color::{Background, Text}; + +mod border; +pub use border::Border; + +mod rounded; +pub use rounded::Rounded; diff --git a/extensions/pagetop-bootsier/src/theme/classes/border.rs b/extensions/pagetop-bootsier/src/theme/classes/border.rs new file mode 100644 index 0000000..f49c75c --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/border.rs @@ -0,0 +1,155 @@ +use pagetop::prelude::*; + +use crate::theme::aux::{BorderColor, BorderSize, Opacity}; + +use std::fmt; + +/// Clases para crear **bordes**. +/// +/// Permite: +/// +/// - Definir un tamaño **global** para todo el borde (`size`). +/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**, +/// respetando LTR/RTL). +/// - Aplicar un **color** al borde (`BorderColor`). +/// - Aplicar un nivel de **opacidad** (`Opacity`). +/// +/// # Comportamiento aditivo / sustractivo +/// +/// - **Aditivo**: basta con crear un borde sin tamaño con `classes::Border::new()` para ir +/// añadiendo cada lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`. +/// +/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando +/// `classes::Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`. +/// +/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado. +/// +/// # Ejemplos +/// +/// **Borde global:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::with(BorderSize::Scale2); +/// assert_eq!(b.to_string(), "border-2"); +/// ``` +/// +/// **Aditivo (solo borde superior):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::new().with_top(BorderSize::Scale1); +/// assert_eq!(b.to_string(), "border-top-1"); +/// ``` +/// +/// **Sustractivo (borde global menos el superior):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::with(BorderSize::Default).with_top(BorderSize::Zero); +/// assert_eq!(b.to_string(), "border border-top-0"); +/// ``` +/// +/// **Ancho por lado (lado lógico inicial a 2 y final a 4):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4); +/// assert_eq!(b.to_string(), "border-end-4 border-start-2"); +/// ``` +/// +/// **Combinado (ejemplo completo):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let b = classes::Border::with(BorderSize::Default) // Borde global por defecto. +/// .with_top(BorderSize::Zero) // Quita borde superior. +/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final. +/// .with_color(BorderColor::Theme(Color::Primary)) +/// .with_opacity(Opacity::Half); +/// +/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Border { + size : BorderSize, + top : BorderSize, + end : BorderSize, + bottom : BorderSize, + start : BorderSize, + color : BorderColor, + opacity: Opacity, +} + +impl Border { + /// Prepara un borde **sin tamaño global** de partida. + pub fn new() -> Self { + Self::default() + } + + /// Crea un borde **con tamaño global** (`size`). + pub fn with(size: BorderSize) -> Self { + Self::default().with_size(size) + } + + // **< Border BUILDER >************************************************************************* + + /// Establece el tamaño global del borde (`border*`). + pub fn with_size(mut self, size: BorderSize) -> Self { + self.size = size; + self + } + + /// Establece el tamaño del borde superior (`border-top-*`). + pub fn with_top(mut self, size: BorderSize) -> Self { + self.top = size; + self + } + + /// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL. + pub fn with_end(mut self, size: BorderSize) -> Self { + self.end = size; + self + } + + /// Establece el tamaño del borde inferior (`border-bottom-*`). + pub fn with_bottom(mut self, size: BorderSize) -> Self { + self.bottom = size; + self + } + + /// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL. + pub fn with_start(mut self, size: BorderSize) -> Self { + self.start = size; + self + } + + /// Establece el color del borde. + pub fn with_color(mut self, color: BorderColor) -> Self { + self.color = color; + self + } + + /// Establece la opacidad del borde. + pub fn with_opacity(mut self, opacity: Opacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Border { + /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, *color* + /// y *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + [ + self.size.to_string(), + self.top.to_class("border-top"), + self.end.to_class("border-end"), + self.bottom.to_class("border-bottom"), + self.start.to_class("border-start"), + self.color.to_string(), + self.opacity.to_class("border"), + ] + .join_classes() + ) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/classes/color.rs b/extensions/pagetop-bootsier/src/theme/classes/color.rs new file mode 100644 index 0000000..6579d21 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/color.rs @@ -0,0 +1,207 @@ +use pagetop::prelude::*; + +use crate::theme::aux::{ColorBg, ColorText, Opacity}; + +use std::fmt; + +// **< Bg >***************************************************************************************** + +/// Clases para establecer **color/opacidad del fondo**. +/// +/// # Ejemplos +/// +/// ``` +/// # use pagetop_bootsier::prelude::*; +/// // Sin clases. +/// let s = classes::Background::new(); +/// assert_eq!(s.to_string(), ""); +/// +/// // Sólo color de fondo. +/// let s = classes::Background::with(ColorBg::Theme(Color::Primary)); +/// assert_eq!(s.to_string(), "bg-primary"); +/// +/// // Color más opacidad. +/// let s = classes::Background::with(ColorBg::BodySecondary).with_opacity(Opacity::Half); +/// assert_eq!(s.to_string(), "bg-body-secondary bg-opacity-50"); +/// +/// // Usando `From`. +/// let s: classes::Background = ColorBg::Black.into(); +/// assert_eq!(s.to_string(), "bg-black"); +/// +/// // Usando `From<(ColorBg, Opacity)>`. +/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); +/// assert_eq!(s.to_string(), "bg-white bg-opacity-25"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Background { + color : ColorBg, + opacity: Opacity, +} + +impl Background { + /// Prepara un nuevo estilo para aplicar al fondo. + pub fn new() -> Self { + Self::default() + } + + /// Crea un estilo fijando el color de fondo (`bg-*`). + pub fn with(color: ColorBg) -> Self { + Self::default().with_color(color) + } + + // **< Bg BUILDER >***************************************************************************** + + /// Establece el color de fondo (`bg-*`). + pub fn with_color(mut self, color: ColorBg) -> Self { + self.color = color; + self + } + + /// Establece la opacidad del fondo (`bg-opacity-*`). + pub fn with_opacity(mut self, opacity: Opacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Background { + /// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo + /// las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let classes = [self.color.to_string(), self.opacity.to_class("bg")].join_classes(); + write!(f, "{classes}") + } +} + +impl From<(ColorBg, Opacity)> for Background { + /// Atajo para crear un [`classes::Background`](crate::theme::classes::Background) a partir del color de fondo y + /// la opacidad. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); + /// assert_eq!(s.to_string(), "bg-white bg-opacity-25"); + /// ``` + fn from((color, opacity): (ColorBg, Opacity)) -> Self { + Background::with(color).with_opacity(opacity) + } +} + +impl From for Background { + /// Atajo para crear un [`classes::Background`](crate::theme::classes::Background) a partir del color de fondo. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Background = ColorBg::Black.into(); + /// assert_eq!(s.to_string(), "bg-black"); + /// ``` + fn from(color: ColorBg) -> Self { + Background::with(color) + } +} + +// **< Text >*************************************************************************************** + +/// Clases para establecer **color/opacidad del texto**. +/// +/// # Ejemplos +/// +/// ``` +/// # use pagetop_bootsier::prelude::*; +/// // Sin clases. +/// let s = classes::Text::new(); +/// assert_eq!(s.to_string(), ""); +/// +/// // Sólo color del texto. +/// let s = classes::Text::with(ColorText::Theme(Color::Primary)); +/// assert_eq!(s.to_string(), "text-primary"); +/// +/// // Color del texto y opacidad. +/// let s = classes::Text::new().with_color(ColorText::White).with_opacity(Opacity::SemiTransparent); +/// assert_eq!(s.to_string(), "text-white text-opacity-25"); +/// +/// // Usando `From`. +/// let s: classes::Text = ColorText::Black.into(); +/// assert_eq!(s.to_string(), "text-black"); +/// +/// // Usando `From<(ColorText, Opacity)>`. +/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); +/// assert_eq!(s.to_string(), "text-danger text-opacity-100"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Text { + color : ColorText, + opacity: Opacity, +} + +impl Text { + /// Prepara un nuevo estilo para aplicar al texto. + pub fn new() -> Self { + Self::default() + } + + /// Crea un estilo fijando el color del texto (`text-*`). + pub fn with(color: ColorText) -> Self { + Self::default().with_color(color) + } + + // **< Text BUILDER >*************************************************************************** + + /// Establece el color del texto (`text-*`). + pub fn with_color(mut self, color: ColorText) -> Self { + self.color = color; + self + } + + /// Establece la opacidad del texto (`text-opacity-*`). + pub fn with_opacity(mut self, opacity: Opacity) -> Self { + self.opacity = opacity; + self + } +} + +impl fmt::Display for Text { + /// Concatena, en este orden, `text-*` y `text-opacity-*`, omitiendo las definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let classes = [self.color.to_string(), self.opacity.to_class("text")].join_classes(); + write!(f, "{classes}") + } +} + +impl From<(ColorText, Opacity)> for Text { + /// Atajo para crear un [`classes::Text`](crate::theme::classes::Text) a partir del color del + /// texto y su opacidad. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); + /// assert_eq!(s.to_string(), "text-danger text-opacity-100"); + /// ``` + fn from((color, opacity): (ColorText, Opacity)) -> Self { + Text::with(color).with_opacity(opacity) + } +} + +impl From for Text { + /// Atajo para crear un [`classes::Text`](crate::theme::classes::Text) a partir del color del + /// texto. + /// + /// # Ejemplo + /// + /// ``` + /// # use pagetop_bootsier::prelude::*; + /// let s: classes::Text = ColorText::Black.into(); + /// assert_eq!(s.to_string(), "text-black"); + /// ``` + fn from(color: ColorText) -> Self { + Text::with(color) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs new file mode 100644 index 0000000..b7510c1 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs @@ -0,0 +1,163 @@ +use pagetop::prelude::*; + +use crate::theme::aux::RoundedRadius; + +use std::fmt; + +/// Clases para definir **esquinas redondeadas**. +/// +/// Permite: +/// +/// - Definir un radio **global para todas las esquinas** (`radius`). +/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`, +/// `start`, **en este orden**, respetando LTR/RTL). +/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`, +/// `bottom-end`, **en este orden**, respetando LTR/RTL). +/// +/// # Ejemplos +/// +/// **Radio global:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::with(RoundedRadius::Default); +/// assert_eq!(r.to_string(), "rounded"); +/// ``` +/// +/// **Sin redondeo:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new(); +/// assert_eq!(r.to_string(), ""); +/// ``` +/// +/// **Radio en las esquinas de un lado lógico:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2); +/// assert_eq!(r.to_string(), "rounded-end-2"); +/// ``` +/// +/// **Radio en una esquina concreta:** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3); +/// assert_eq!(r.to_string(), "rounded-top-start-3"); +/// ``` +/// +/// **Combinado (ejemplo completo):** +/// ```rust +/// # use pagetop_bootsier::prelude::*; +/// let r = classes::Rounded::new() +/// .with_top(RoundedRadius::Default) // Añade redondeo arriba. +/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. +/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. +/// +/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); +/// ``` +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Rounded { + radius : RoundedRadius, + top : RoundedRadius, + end : RoundedRadius, + bottom : RoundedRadius, + start : RoundedRadius, + top_start : RoundedRadius, + top_end : RoundedRadius, + bottom_start: RoundedRadius, + bottom_end : RoundedRadius, +} + +impl Rounded { + /// Prepara las esquinas **sin redondeo global** de partida. + pub fn new() -> Self { + Self::default() + } + + /// Crea las esquinas **con redondeo global** (`radius`). + pub fn with(radius: RoundedRadius) -> Self { + Self::default().with_radius(radius) + } + + // **< Rounded BUILDER >************************************************************************ + + /// Establece el radio global de las esquinas (`rounded*`). + pub fn with_radius(mut self, radius: RoundedRadius) -> Self { + self.radius = radius; + self + } + + /// Establece el radio en las esquinas del lado superior (`rounded-top-*`). + pub fn with_top(mut self, radius: RoundedRadius) -> Self { + self.top = radius; + self + } + + /// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL. + pub fn with_end(mut self, radius: RoundedRadius) -> Self { + self.end = radius; + self + } + + /// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`). + pub fn with_bottom(mut self, radius: RoundedRadius) -> Self { + self.bottom = radius; + self + } + + /// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta + /// LTR/RTL. + pub fn with_start(mut self, radius: RoundedRadius) -> Self { + self.start = radius; + self + } + + /// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL. + pub fn with_top_start(mut self, radius: RoundedRadius) -> Self { + self.top_start = radius; + self + } + + /// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL. + pub fn with_top_end(mut self, radius: RoundedRadius) -> Self { + self.top_end = radius; + self + } + + /// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta + /// LTR/RTL. + pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self { + self.bottom_start = radius; + self + } + + /// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL. + pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self { + self.bottom_end = radius; + self + } +} + +impl fmt::Display for Rounded { + /// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, + /// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las + /// definiciones vacías. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + [ + self.radius.to_string(), + self.top.to_class("rounded-top"), + self.end.to_class("rounded-end"), + self.bottom.to_class("rounded-bottom"), + self.start.to_class("rounded-start"), + self.top_start.to_class("rounded-top-start"), + self.top_end.to_class("rounded-top-end"), + self.bottom_start.to_class("rounded-bottom-start"), + self.bottom_end.to_class("rounded-bottom-end"), + ] + .join_classes() + ) + } +} diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs index 411eb0a..a860110 100644 --- a/extensions/pagetop-bootsier/src/theme/container.rs +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -1,299 +1,24 @@ -use pagetop::prelude::*; +//! Definiciones para crear contenedores de componentes ([`Container`]). +//! +//! Cada contenedor envuelve contenido usando la etiqueta semántica indicada por +//! [`container::Kind`](crate::theme::container::Kind). +//! +//! Con [`container::Width`](crate::theme::container::Width) se puede definir el ancho y el +//! comportamiento *responsive* del contenedor. También permite aplicar utilidades de estilo para el +//! fondo, texto, borde o esquinas redondeadas. +//! +//! # Ejemplo +//! +//! ```rust +//! # use pagetop::prelude::*; +//! # use pagetop_bootsier::prelude::*; +//! let main = Container::main() +//! .with_id("main-page") +//! .with_width(container::Width::From(BreakPoint::LG)); +//! ``` -use crate::prelude::*; +mod props; +pub use props::{Kind, Width}; -// **< ContainerType >****************************************************************************** - -/// Tipo de contenedor ([`Container`]). -/// -/// Permite aplicar la etiqueta HTML apropiada (`
`, `
`, etc.) manteniendo una API -/// común a todos los contenedores. -#[derive(AutoDefault)] -pub enum ContainerType { - /// Contenedor genérico (`
`). - #[default] - Default, - /// Contenido principal de la página (`
`). - Main, - /// Encabezado de la página o de sección (`
`). - Header, - /// Pie de la página o de sección (`