♻️ (bootsier): Reorganiza clases y tokens utilitarios

This commit is contained in:
Manuel Cillero 2026-08-01 07:15:13 +02:00
parent 1a3a295f18
commit fdd2dca280
15 changed files with 694 additions and 633 deletions

View file

@ -447,12 +447,12 @@ body {
} }
#intro-badges { #intro-badges {
min-height: 3rem; min-height: 3.2rem;
text-align: center; text-align: center;
visibility: hidden; visibility: hidden;
} }
#intro-badges img { #intro-badges img {
margin-bottom: 1.1rem; margin-bottom: 0.25rem;
} }
#intro-release:not([src]) { #intro-release:not([src]) {
display: none; display: none;

View file

@ -145,12 +145,12 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
// Botones de acción. // Botones de acción.
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop( .with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
PropsOp::add_classes(class::ButtonColor::solid( PropsOp::add_classes(class::ButtonColor::solid(
token::Color::Primary, ThemeColor::Primary,
)), )),
)) ))
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop( .with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
PropsOp::add_classes(class::ButtonColor::outline( PropsOp::add_classes(class::ButtonColor::outline(
token::Color::Secondary, ThemeColor::Secondary,
)), )),
)) ))
.with_child( .with_child(
@ -269,12 +269,12 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
// Botones de acción. // Botones de acción.
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop( .with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
PropsOp::add_classes(class::ButtonColor::solid( PropsOp::add_classes(class::ButtonColor::solid(
token::Color::Primary, ThemeColor::Primary,
)), )),
)) ))
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop( .with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
PropsOp::add_classes(class::ButtonColor::outline( PropsOp::add_classes(class::ButtonColor::outline(
token::Color::Secondary, ThemeColor::Secondary,
)), )),
)) ))
.with_child( .with_child(
@ -423,12 +423,12 @@ fn form_lists() -> Form {
// Botones de acción. // Botones de acción.
.with_child( .with_child(
Button::submit(L10n::t("btn_submit", &LOC)).with_prop(PropsOp::add_classes( 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( .with_child(
Button::reset(L10n::t("btn_reset", &LOC)).with_prop(PropsOp::add_classes( 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( .with_child(

View file

@ -17,7 +17,7 @@ impl Extension for SuperMenu {
async fn initialize(&self) { async fn initialize(&self) {
let navbar_menu = bs::Navbar::brand_left(bs::navbar::Brand::new()) 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( .with_item(bs::navbar::Item::nav(
bs::Nav::new() bs::Nav::new()
.with_item(bs::nav::Item::link(L10n::t("menus_item_link", &LOC), "/")) .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( .with_item(bs::navbar::Item::nav(
bs::Nav::new() bs::Nav::new()
.with_prop(PropsOp::add_classes(class::Margin::with( .with_prop(PropsOp::add_classes(class::Margin::with(
token::Side::Start, BoxSide::Start,
token::ScaleSize::Auto, ScaleSize::Auto,
))) )))
.with_item(bs::nav::Item::link( .with_item(bs::nav::Item::link(
L10n::t("menus_item_sign_up", &LOC), L10n::t("menus_item_sign_up", &LOC),

View file

@ -1,16 +1,29 @@
//! Define clases para aplicar en componentes del tema. //! 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; mod color;
pub use color::{Background, Text}; pub use color::{Bg, BgColor};
pub use color::{Text, TextColor};
mod button; mod button;
pub use button::{ButtonColor, ButtonSize}; pub use button::{ButtonColor, ButtonColorStyle, ButtonSize, ButtonSizeKind};
mod border; mod border;
pub use border::Border; pub use border::{Border, BorderColor};
mod rounded; mod rounded;
pub use rounded::Rounded; pub use rounded::{Rounded, RoundedRadius};
mod layout; mod layout;
pub use layout::{Margin, Padding}; pub use layout::{Margin, Padding};

View file

@ -1,6 +1,114 @@
use pagetop::prelude::*; 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<ThemeColor> 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<CowStr> for BorderColor {
/// Permite pasar [`BorderColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
fn into(self) -> CowStr {
self.to_class().into()
}
}
// **< Border >*************************************************************************************
/// Clases para definir **bordes**. /// 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()`). /// - Crear un borde con tamaño por defecto (`Border::new()`).
/// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL). /// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL).
/// - Asignar **un tamaño global** para todo el borde (`size`). /// - Asignar **un tamaño global** para todo el borde (`size`).
/// - Aplicar un **color** al borde (`ColorBorder`). /// - Aplicar un **color** al borde (`BorderColor`).
/// - Aplicar un nivel de **opacidad** (`Opacity`). /// - Aplicar un nivel de **opacidad** (`OpacityLevel`).
/// ///
/// # Comportamiento aditivo / sustractivo /// # Comportamiento aditivo / sustractivo
/// ///
/// - **Aditivo**: basta con crear un borde sin tamaño con `class::Border::default()` para ir /// - **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 /// - **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 /// `class::Border::new()` o `class::Border::with(ScaleSize::Two)` y eliminar los lados deseados
/// deseados con `token::ScaleSize::Zero`. /// 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 /// # Ejemplos
/// ///
@ -30,29 +138,29 @@ use crate::theme::token::{ColorBorder, Opacity, ScaleSize, Side};
/// use pagetop_bootsier::theme::*; /// use pagetop_bootsier::theme::*;
/// ///
/// // Borde global. /// // Borde global.
/// let b = class::Border::with(token::ScaleSize::Two); /// let b = class::Border::with(ScaleSize::Two);
/// assert_eq!(b.to_class(), "border-2"); /// assert_eq!(b.to_class(), "border-2");
/// ///
/// // Aditivo (sólo borde superior): /// // 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"); /// assert_eq!(b.to_class(), "border-top-1");
/// ///
/// // Sustractivo (borde global menos el superior): /// // 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"); /// assert_eq!(b.to_class(), "border border-top-0");
/// ///
/// // Ancho por lado (lado lógico inicial a 2 y final a 4): /// // Ancho por lado (lado lógico inicial a 2 y final a 4):
/// let b = class::Border::default() /// let b = class::Border::default()
/// .with_side(token::Side::Start, token::ScaleSize::Two) /// .with_side(BoxSide::Start, ScaleSize::Two)
/// .with_side(token::Side::End, token::ScaleSize::Four); /// .with_side(BoxSide::End, ScaleSize::Four);
/// assert_eq!(b.to_class(), "border-end-4 border-start-2"); /// assert_eq!(b.to_class(), "border-end-4 border-start-2");
/// ///
/// // Combinado (ejemplo completo): /// // Combinado (ejemplo completo):
/// let b = class::Border::new() // Borde por defecto. /// let b = class::Border::new() // Borde por defecto.
/// .with_side(token::Side::Top, token::ScaleSize::Zero) // Quita borde superior. /// .with_side(BoxSide::Top, ScaleSize::Zero) // Quita borde superior.
/// .with_side(token::Side::End, token::ScaleSize::Three) // Ancho 3 para lado lógico final. /// .with_side(BoxSide::End, ScaleSize::Three) // Ancho 3 para lado lógico final.
/// .with_color(token::Color::Primary) /// .with_color(ThemeColor::Primary)
/// .with_opacity(token::Opacity::Half); /// .with_opacity(OpacityLevel::Half);
/// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50"); /// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50");
/// ``` /// ```
#[rustfmt::skip] #[rustfmt::skip]
@ -63,8 +171,8 @@ pub struct Border {
end : ScaleSize, end : ScaleSize,
bottom : ScaleSize, bottom : ScaleSize,
start : ScaleSize, start : ScaleSize,
color : ColorBorder, color : BorderColor,
opacity: Opacity, opacity: OpacityLevel,
} }
impl Border { impl Border {
@ -75,24 +183,24 @@ impl Border {
/// Define un borde con un tamaño global (`size`) para todos los lados. /// Define un borde con un tamaño global (`size`) para todos los lados.
pub fn with(size: ScaleSize) -> Self { pub fn with(size: ScaleSize) -> Self {
Self::default().with_side(Side::All, size) Self::default().with_side(BoxSide::All, size)
} }
// **< Border BUILDER >************************************************************************* // **< Border BUILDER >*************************************************************************
/// Ajusta el tamaño del borde en el lado indicado (ver [`Side`](crate::theme::token::Side)). /// Ajusta el tamaño del borde en el lado indicado (ver [`BoxSide`](crate::theme::BoxSide)).
pub fn with_side(mut self, side: Side, size: ScaleSize) -> Self { pub fn with_side(mut self, side: BoxSide, size: ScaleSize) -> Self {
match side { match side {
Side::All => self.all = size, BoxSide::All => self.all = size,
Side::Top => self.top = size, BoxSide::Top => self.top = size,
Side::Bottom => self.bottom = size, BoxSide::Bottom => self.bottom = size,
Side::Start => self.start = size, BoxSide::Start => self.start = size,
Side::End => self.end = size, BoxSide::End => self.end = size,
Side::LeftAndRight => { BoxSide::LeftAndRight => {
self.start = size; self.start = size;
self.end = size; self.end = size;
} }
Side::TopAndBottom => { BoxSide::TopAndBottom => {
self.top = size; self.top = size;
self.bottom = size; self.bottom = size;
} }
@ -102,15 +210,15 @@ impl Border {
/// Establece el color del borde. /// Establece el color del borde.
/// ///
/// Acepta un tipo convertible en [`ColorBorder`]. Un [`Color`](crate::theme::token::Color) se /// Acepta un tipo convertible en [`BorderColor`]. Un [`ThemeColor`] se convierte
/// convierte automáticamente en [`ColorBorder::Theme`]. /// automáticamente en [`BorderColor::Solid`].
pub fn with_color(mut self, color: impl Into<ColorBorder>) -> Self { pub fn with_color(mut self, color: impl Into<BorderColor>) -> Self {
self.color = color.into(); self.color = color.into();
self self
} }
/// Establece la opacidad del borde. /// 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.opacity = opacity;
self self
} }
@ -150,11 +258,11 @@ impl From<ScaleSize> for Border {
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # use pagetop_bootsier::theme::*;
/// // Convertir explícitamente con `From::from`: /// // 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"); /// assert_eq!(b.to_class(), "border-2");
/// ///
/// // Convertir implícitamente con `into()`: /// // 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"); /// assert_eq!(b.to_class(), "border");
/// ``` /// ```
fn from(size: ScaleSize) -> Self { fn from(size: ScaleSize) -> Self {

View file

@ -1,15 +1,20 @@
use pagetop::prelude::*; use pagetop::prelude::*;
use crate::theme::token::Color; use crate::theme::ThemeColor;
// **< ButtonColor >******************************************************************************** // **< ButtonColor >********************************************************************************
/// Estilo visual aplicado al color de un botón ([`ButtonColor`]).
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
enum ButtonColorStyle { pub enum ButtonColorStyle {
/// Sin clase de color (estilo por defecto del tema).
#[default] #[default]
None, None,
/// Botón sólido: genera la clase `btn-{color}`.
Solid, Solid,
/// Botón con contorno: genera la clase `btn-outline-{color}`.
Outline, Outline,
/// Botón tipo enlace: genera la clase `btn-link`.
Link, Link,
} }
@ -23,11 +28,11 @@ enum ButtonColorStyle {
/// ///
/// // Botón sólido. /// // Botón sólido.
/// let save = bs::Button::submit(L10n::n("Save")) /// 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. /// // Botón con contorno.
/// let cancel = bs::Button::plain(L10n::n("Cancel")) /// 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. /// // Botón tipo enlace.
/// let back = bs::Button::plain(L10n::n("Back")) /// let back = bs::Button::plain(L10n::n("Back"))
@ -36,7 +41,7 @@ enum ButtonColorStyle {
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub struct ButtonColor { pub struct ButtonColor {
style: ButtonColorStyle, style: ButtonColorStyle,
color: Color, color: ThemeColor,
} }
impl ButtonColor { impl ButtonColor {
@ -46,7 +51,7 @@ impl ButtonColor {
} }
/// Botón sólido: genera la clase `btn-{color}`. /// Botón sólido: genera la clase `btn-{color}`.
pub fn solid(color: Color) -> Self { pub fn solid(color: ThemeColor) -> Self {
Self { Self {
style: ButtonColorStyle::Solid, style: ButtonColorStyle::Solid,
color, color,
@ -55,7 +60,7 @@ impl ButtonColor {
} }
/// Botón con contorno: genera la clase `btn-outline-{color}`. /// Botón con contorno: genera la clase `btn-outline-{color}`.
pub fn outline(color: Color) -> Self { pub fn outline(color: ThemeColor) -> Self {
Self { Self {
style: ButtonColorStyle::Outline, style: ButtonColorStyle::Outline,
color, color,
@ -74,11 +79,17 @@ impl ButtonColor {
// **< ButtonColor BUILDER >******************************************************************** // **< ButtonColor BUILDER >********************************************************************
/// Cambia el color aplicado al botón (`btn-*` o `btn-outline-*`). /// 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.color = color;
self 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 >******************************************************************** // **< ButtonColor HELPERS >********************************************************************
/// Añade la clase `btn-*` a la cadena de clases. /// Añade la clase `btn-*` a la cadena de clases.
@ -117,11 +128,15 @@ impl Into<CowStr> for ButtonColor {
// **< ButtonSize >********************************************************************************* // **< ButtonSize >*********************************************************************************
/// Tamaño aplicado a un botón ([`ButtonSize`]).
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
enum ButtonSizeVariant { pub enum ButtonSizeKind {
/// Sin clase de tamaño (tamaño por defecto del tema).
#[default] #[default]
None, None,
/// Botón compacto: genera la clase `btn-sm`.
Small, Small,
/// Botón grande: genera la clase `btn-lg`.
Large, Large,
} }
@ -141,7 +156,7 @@ enum ButtonSizeVariant {
/// ``` /// ```
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub struct ButtonSize { pub struct ButtonSize {
size: ButtonSizeVariant, size: ButtonSizeKind,
} }
impl ButtonSize { impl ButtonSize {
@ -153,26 +168,34 @@ impl ButtonSize {
/// Botón compacto: genera la clase `btn-sm`. /// Botón compacto: genera la clase `btn-sm`.
pub fn small() -> Self { pub fn small() -> Self {
Self { Self {
size: ButtonSizeVariant::Small, size: ButtonSizeKind::Small,
} }
} }
/// Botón grande: genera la clase `btn-lg`. /// Botón grande: genera la clase `btn-lg`.
pub fn large() -> Self { pub fn large() -> Self {
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 >********************************************************************* // **< ButtonSize HELPERS >*********************************************************************
/// Añade la clase `btn-sm` o `btn-lg` a la cadena de clases. /// Añade la clase `btn-sm` o `btn-lg` a la cadena de clases.
#[inline] #[inline]
pub fn push_to(self, classes: &mut String) { pub fn push_to(self, classes: &mut String) {
let class = match self.size { let class = match self.size {
ButtonSizeVariant::None => return, ButtonSizeKind::None => return,
ButtonSizeVariant::Small => "btn-sm", ButtonSizeKind::Small => "btn-sm",
ButtonSizeVariant::Large => "btn-lg", ButtonSizeKind::Large => "btn-lg",
}; };
if !classes.is_empty() { if !classes.is_empty() {
classes.push(' '); classes.push(' ');

View file

@ -1,8 +1,130 @@
use pagetop::prelude::*; 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<ThemeColor> 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<CowStr> 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**. /// Clases para establecer **color/opacidad del fondo**.
/// ///
@ -12,33 +134,32 @@ use crate::theme::token::{ColorBg, ColorText, Opacity};
/// use pagetop_bootsier::theme::*; /// use pagetop_bootsier::theme::*;
/// ///
/// // Sin clases. /// // Sin clases.
/// let s = class::Background::new(); /// let s = class::Bg::new();
/// assert_eq!(s.to_class(), ""); /// assert_eq!(s.to_class(), "");
/// ///
/// // Sólo color de fondo (forma corta con Color). /// // Sólo color de fondo (forma corta con ThemeColor).
/// let s = class::Background::with(token::Color::Primary); /// let s = class::Bg::with(ThemeColor::Primary);
/// assert_eq!(s.to_class(), "bg-primary"); /// assert_eq!(s.to_class(), "bg-primary");
/// ///
/// // Color más opacidad. /// // Color más opacidad.
/// let s = class::Background::with(token::ColorBg::BodySecondary) /// let s = class::Bg::with(class::BgColor::BodySecondary).with_opacity(OpacityLevel::Half);
/// .with_opacity(token::Opacity::Half);
/// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50"); /// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50");
/// ///
/// // Usando `From<ColorBg>`. /// // Usando `From<BgColor>`.
/// let s: class::Background = token::ColorBg::Black.into(); /// let s: class::Bg = class::BgColor::Black.into();
/// assert_eq!(s.to_class(), "bg-black"); /// assert_eq!(s.to_class(), "bg-black");
/// ///
/// // Usando `From<(ColorBg, Opacity)>`. /// // Usando `From<(BgColor, OpacityLevel)>`.
/// 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"); /// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
/// ``` /// ```
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub struct Background { pub struct Bg {
color: ColorBg, color: BgColor,
opacity: Opacity, opacity: OpacityLevel,
} }
impl Background { impl Bg {
/// Prepara un nuevo estilo para aplicar al fondo. /// Prepara un nuevo estilo para aplicar al fondo.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
@ -46,41 +167,41 @@ impl Background {
/// Crea un estilo fijando el color de fondo (`bg-*`). /// Crea un estilo fijando el color de fondo (`bg-*`).
/// ///
/// Acepta cualquier tipo convertible en [`ColorBg`]. Un [`Color`](crate::theme::token::Color) /// Acepta cualquier tipo convertible en [`BgColor`]. Un [`ThemeColor`] se convierte
/// se convierte automáticamente en [`ColorBg::Theme`]: /// automáticamente en [`BgColor::Solid`]:
/// ///
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # use pagetop_bootsier::theme::*;
/// // Forma corta con Color: /// // Forma corta con ThemeColor:
/// let s = class::Background::with(token::Color::Primary); /// let s = class::Bg::with(ThemeColor::Primary);
/// assert_eq!(s.to_class(), "bg-primary"); /// assert_eq!(s.to_class(), "bg-primary");
/// ///
/// // Forma explícita para variantes no temáticas: /// // 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"); /// assert_eq!(s.to_class(), "bg-body");
/// ``` /// ```
pub fn with(color: impl Into<ColorBg>) -> Self { pub fn with(color: impl Into<BgColor>) -> Self {
Self::default().with_color(color) Self::default().with_color(color)
} }
// **< Background BUILDER >********************************************************************* // **< Bg BUILDER >*****************************************************************************
/// Establece el color de fondo (`bg-*`). /// Establece el color de fondo (`bg-*`).
/// ///
/// Acepta cualquier tipo convertible en [`ColorBg`]. Un [`Color`](crate::theme::token::Color) /// Acepta cualquier tipo convertible en [`BgColor`]. Un [`ThemeColor`] se convierte
/// se convierte automáticamente en [`ColorBg::Theme`]. /// automáticamente en [`BgColor::Solid`].
pub fn with_color(mut self, color: impl Into<ColorBg>) -> Self { pub fn with_color(mut self, color: impl Into<BgColor>) -> Self {
self.color = color.into(); self.color = color.into();
self self
} }
/// Establece la opacidad del fondo (`bg-opacity-*`). /// 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.opacity = opacity;
self self
} }
// **< Background HELPERS >********************************************************************* // **< Bg HELPERS >*****************************************************************************
/// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo /// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo
/// los fragmentos vacíos. /// los fragmentos vacíos.
@ -101,40 +222,173 @@ impl Background {
} }
} }
impl From<(ColorBg, Opacity)> for Background { impl From<(BgColor, OpacityLevel)> for Bg {
/// Crea la clase para un [`Background`](crate::theme::class::Background) a partir del color de /// Crea la clase para un [`Bg`](crate::theme::class::Bg) a partir del color de fondo y la
/// fondo y la opacidad. /// opacidad.
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # 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"); /// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
/// ``` /// ```
fn from((color, opacity): (ColorBg, Opacity)) -> Self { fn from((color, opacity): (BgColor, OpacityLevel)) -> Self {
Background::with(color).with_opacity(opacity) Bg::with(color).with_opacity(opacity)
} }
} }
impl From<ColorBg> for Background { impl From<BgColor> for Bg {
/// Crea la clase para un [`Background`](crate::theme::class::Background) a partir del color de /// Crea la clase para un [`Bg`](crate::theme::class::Bg) a partir del color de fondo.
/// fondo.
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # 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"); /// assert_eq!(s.to_class(), "bg-black");
/// ``` /// ```
fn from(color: ColorBg) -> Self { fn from(color: BgColor) -> Self {
Background::with(color) Bg::with(color)
} }
} }
impl Into<CowStr> for Background { impl Into<CowStr> for Bg {
/// Permite pasar [`Background`] directamente a [`PropsOp`](pagetop::prelude::PropsOp). /// 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<ThemeColor> 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<CowStr> for TextColor {
/// Permite pasar [`TextColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
fn into(self) -> CowStr { fn into(self) -> CowStr {
self.to_class().into() self.to_class().into()
} }
@ -153,30 +407,30 @@ impl Into<CowStr> for Background {
/// let s = class::Text::new(); /// let s = class::Text::new();
/// assert_eq!(s.to_class(), ""); /// assert_eq!(s.to_class(), "");
/// ///
/// // Sólo color del texto (forma corta con Color). /// // Sólo color del texto (forma corta con ThemeColor).
/// let s = class::Text::with(token::Color::Primary); /// let s = class::Text::with(ThemeColor::Primary);
/// assert_eq!(s.to_class(), "text-primary"); /// assert_eq!(s.to_class(), "text-primary");
/// ///
/// // Color del texto y opacidad. /// // Color del texto y opacidad.
/// let s = class::Text::new().with_color(token::ColorText::White) /// let s = class::Text::new().with_color(class::TextColor::White)
/// .with_opacity(token::Opacity::SemiTransparent); /// .with_opacity(OpacityLevel::SemiTransparent);
/// assert_eq!(s.to_class(), "text-white text-opacity-25"); /// assert_eq!(s.to_class(), "text-white text-opacity-25");
/// ///
/// // Usando `From<ColorText>`. /// // Usando `From<TextColor>`.
/// let s: class::Text = token::ColorText::Black.into(); /// let s: class::Text = class::TextColor::Black.into();
/// assert_eq!(s.to_class(), "text-black"); /// assert_eq!(s.to_class(), "text-black");
/// ///
/// // Usando `From<(ColorText, Opacity)>`. /// // Usando `From<(TextColor, OpacityLevel)>`.
/// let s: class::Text = ( /// let s: class::Text = (
/// token::ColorText::Theme(token::Color::Danger), /// class::TextColor::Solid(ThemeColor::Danger),
/// token::Opacity::Opaque, /// OpacityLevel::Opaque,
/// ).into(); /// ).into();
/// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// assert_eq!(s.to_class(), "text-danger text-opacity-100");
/// ``` /// ```
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub struct Text { pub struct Text {
color: ColorText, color: TextColor,
opacity: Opacity, opacity: OpacityLevel,
} }
impl Text { impl Text {
@ -187,20 +441,20 @@ impl Text {
/// Crea un estilo fijando el color del texto (`text-*`). /// Crea un estilo fijando el color del texto (`text-*`).
/// ///
/// Acepta cualquier tipo convertible en [`ColorText`]. Un [`Color`](crate::theme::token::Color) /// Acepta cualquier tipo convertible en [`TextColor`]. Un [`ThemeColor`] se convierte
/// se convierte automáticamente en [`ColorText::Theme`]: /// automáticamente en [`TextColor::Solid`]:
/// ///
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # use pagetop_bootsier::theme::*;
/// // Forma corta con Color: /// // Forma corta con ThemeColor:
/// let s = class::Text::with(token::Color::Danger); /// let s = class::Text::with(ThemeColor::Danger);
/// assert_eq!(s.to_class(), "text-danger"); /// assert_eq!(s.to_class(), "text-danger");
/// ///
/// // Forma explícita para variantes no temáticas: /// // 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"); /// assert_eq!(s.to_class(), "text-body");
/// ``` /// ```
pub fn with(color: impl Into<ColorText>) -> Self { pub fn with(color: impl Into<TextColor>) -> Self {
Self::default().with_color(color) Self::default().with_color(color)
} }
@ -208,15 +462,15 @@ impl Text {
/// Establece el color del texto (`text-*`). /// Establece el color del texto (`text-*`).
/// ///
/// Acepta cualquier tipo convertible en [`ColorText`]. Un [`Color`](crate::theme::token::Color) /// Acepta cualquier tipo convertible en [`TextColor`]. Un [`ThemeColor`] se convierte
/// se convierte automáticamente en [`ColorText::Theme`]. /// automáticamente en [`TextColor::Solid`].
pub fn with_color(mut self, color: impl Into<ColorText>) -> Self { pub fn with_color(mut self, color: impl Into<TextColor>) -> Self {
self.color = color.into(); self.color = color.into();
self self
} }
/// Establece la opacidad del texto (`text-opacity-*`). /// 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.opacity = opacity;
self 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 /// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto y su
/// opacidad. /// opacidad.
/// ///
@ -250,27 +504,27 @@ impl From<(ColorText, Opacity)> for Text {
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # use pagetop_bootsier::theme::*;
/// let s: class::Text = ( /// let s: class::Text = (
/// token::ColorText::Theme(token::Color::Danger), /// class::TextColor::Solid(ThemeColor::Danger),
/// token::Opacity::Opaque, /// OpacityLevel::Opaque,
/// ).into(); /// ).into();
/// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// 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) Text::with(color).with_opacity(opacity)
} }
} }
impl From<ColorText> for Text { impl From<TextColor> for Text {
/// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto. /// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto.
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # 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"); /// assert_eq!(s.to_class(), "text-black");
/// ``` /// ```
fn from(color: ColorText) -> Self { fn from(color: TextColor) -> Self {
Text::with(color) Text::with(color)
} }
} }

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*; use pagetop::prelude::*;
use crate::theme::token::{BreakPoint, ScaleSize, Side}; use crate::theme::{BoxSide, BreakPoint, ScaleSize};
// **< Margin >************************************************************************************* // **< Margin >*************************************************************************************
@ -11,19 +11,19 @@ use crate::theme::token::{BreakPoint, ScaleSize, Side};
/// ```rust /// ```rust
/// use pagetop_bootsier::theme::*; /// 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"); /// assert_eq!(m.to_class(), "mt-3");
/// ///
/// let m = class::Margin::with(token::Side::Start, token::ScaleSize::Auto) /// let m = class::Margin::with(BoxSide::Start, ScaleSize::Auto)
/// .with_breakpoint(token::BreakPoint::LG); /// .with_breakpoint(BreakPoint::LG);
/// assert_eq!(m.to_class(), "ms-lg-auto"); /// 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(), ""); /// assert_eq!(m.to_class(), "");
/// ``` /// ```
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub struct Margin { pub struct Margin {
side: Side, side: BoxSide,
size: ScaleSize, size: ScaleSize,
breakpoint: BreakPoint, breakpoint: BreakPoint,
} }
@ -31,7 +31,7 @@ pub struct Margin {
impl Margin { impl Margin {
/// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de /// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
/// ruptura. /// ruptura.
pub fn with(side: Side, size: ScaleSize) -> Self { pub fn with(side: BoxSide, size: ScaleSize) -> Self {
Margin { Margin {
side, side,
size, size,
@ -54,13 +54,13 @@ impl Margin {
#[inline] #[inline]
const fn side_prefix(&self) -> &'static str { const fn side_prefix(&self) -> &'static str {
match self.side { match self.side {
Side::All => "m", BoxSide::All => "m",
Side::Top => "mt", BoxSide::Top => "mt",
Side::Bottom => "mb", BoxSide::Bottom => "mb",
Side::Start => "ms", BoxSide::Start => "ms",
Side::End => "me", BoxSide::End => "me",
Side::LeftAndRight => "mx", BoxSide::LeftAndRight => "mx",
Side::TopAndBottom => "my", BoxSide::TopAndBottom => "my",
} }
} }
@ -114,19 +114,19 @@ impl Into<CowStr> for Margin {
/// ```rust /// ```rust
/// use pagetop_bootsier::theme::*; /// 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"); /// assert_eq!(p.to_class(), "px-2");
/// ///
/// let p = class::Padding::with(token::Side::End, token::ScaleSize::Four) /// let p = class::Padding::with(BoxSide::End, ScaleSize::Four)
/// .with_breakpoint(token::BreakPoint::SM); /// .with_breakpoint(BreakPoint::SM);
/// assert_eq!(p.to_class(), "pe-sm-4"); /// 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. /// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding.
/// ``` /// ```
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub struct Padding { pub struct Padding {
side: Side, side: BoxSide,
size: ScaleSize, size: ScaleSize,
breakpoint: BreakPoint, breakpoint: BreakPoint,
} }
@ -134,7 +134,7 @@ pub struct Padding {
impl Padding { impl Padding {
/// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de /// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
/// ruptura. /// ruptura.
pub fn with(side: Side, size: ScaleSize) -> Self { pub fn with(side: BoxSide, size: ScaleSize) -> Self {
Padding { Padding {
side, side,
size, size,
@ -157,13 +157,13 @@ impl Padding {
#[inline] #[inline]
const fn side_prefix(&self) -> &'static str { const fn side_prefix(&self) -> &'static str {
match self.side { match self.side {
Side::All => "p", BoxSide::All => "p",
Side::Top => "pt", BoxSide::Top => "pt",
Side::Bottom => "pb", BoxSide::Bottom => "pb",
Side::Start => "ps", BoxSide::Start => "ps",
Side::End => "pe", BoxSide::End => "pe",
Side::LeftAndRight => "px", BoxSide::LeftAndRight => "px",
Side::TopAndBottom => "py", BoxSide::TopAndBottom => "py",
} }
} }

View file

@ -1,6 +1,98 @@
use pagetop::prelude::*; 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<CowStr> 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**. /// Clases para definir **esquinas redondeadas**.
/// ///
@ -18,7 +110,7 @@ use crate::theme::token::RoundedRadius;
/// use pagetop_bootsier::theme::*; /// use pagetop_bootsier::theme::*;
/// ///
/// // Radio global: /// // Radio global:
/// let r = class::Rounded::with(token::RoundedRadius::Default); /// let r = class::Rounded::with(class::RoundedRadius::Default);
/// assert_eq!(r.to_class(), "rounded"); /// assert_eq!(r.to_class(), "rounded");
/// ///
/// // Sin redondeo: /// // Sin redondeo:
@ -26,18 +118,18 @@ use crate::theme::token::RoundedRadius;
/// assert_eq!(r.to_class(), ""); /// assert_eq!(r.to_class(), "");
/// ///
/// // Radio en las esquinas de un lado lógico: /// // 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"); /// assert_eq!(r.to_class(), "rounded-end-2");
/// ///
/// // Radio en una esquina concreta: /// // 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"); /// assert_eq!(r.to_class(), "rounded-top-start-3");
/// ///
/// // Combinado (ejemplo completo): /// // Combinado (ejemplo completo):
/// let r = class::Rounded::new() /// let r = class::Rounded::new()
/// .with_top(token::RoundedRadius::Default) // Añade redondeo arriba. /// .with_top(class::RoundedRadius::Default) // Añade redondeo arriba.
/// .with_bottom_start(token::RoundedRadius::Scale4) // Añade una esquina redondeada concreta. /// .with_bottom_start(class::RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
/// .with_bottom_end(token::RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. /// .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"); /// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
/// ``` /// ```
#[rustfmt::skip] #[rustfmt::skip]

View file

@ -1,28 +1,10 @@
//! Tipos enumerados para construir clases del tema. //! 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; mod breakpoint;
pub use breakpoint::BreakPoint; pub use breakpoint::BreakPoint;
mod color; mod color;
pub use color::{Color, ColorBg, ColorText, Opacity}; pub use color::{OpacityLevel, ThemeColor};
mod border;
pub use border::ColorBorder;
mod rounded;
pub use rounded::RoundedRadius;
mod layout; mod layout;
pub use layout::{ScaleSize, Side}; pub use layout::{BoxSide, ScaleSize};

View file

@ -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<Color> 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)
}
}

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*; 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)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum BreakPoint { pub enum BreakPoint {
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.

View file

@ -1,15 +1,17 @@
use pagetop::prelude::*; use pagetop::prelude::*;
// **< Color >************************************************************************************** // **< ThemeColor >*********************************************************************************
/// Paleta de colores temáticos. /// Paleta de colores temáticos.
/// ///
/// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`, /// 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 /// etc.). Se utiliza para componer las clases de color de [`Bg`], [`Border`] o [`Text`].
/// ([`Background`](crate::theme::class::Background)), bordes ///
/// ([`Border`](crate::theme::class::Border)) o texto ([`Text`](crate::theme::class::Text)). /// [`Bg`]: crate::theme::class::Bg
/// [`Border`]: crate::theme::class::Border
/// [`Text`]: crate::theme::class::Text
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum Color { pub enum ThemeColor {
#[default] #[default]
Primary, Primary,
Secondary, Secondary,
@ -21,7 +23,7 @@ pub enum Color {
Dark, Dark,
} }
impl Color { impl ThemeColor {
/// Devuelve el nombre del color Bootstrap (`"primary"`, `"danger"`, etc.). /// Devuelve el nombre del color Bootstrap (`"primary"`, `"danger"`, etc.).
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
@ -39,16 +41,18 @@ impl Color {
} }
} }
// **< Opacity >************************************************************************************ // **< OpacityLevel >*******************************************************************************
/// Niveles de opacidad (`opacity-*`). /// Niveles de opacidad (`opacity-*`).
/// ///
/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*` /// Se utiliza para las clases que definen la transparencia de [`Bg`] (`bg-opacity-*`), [`Border`]
/// ([`Background`](crate::theme::class::Background)), de los bordes `border-opacity-*` /// (`border-opacity-*`) o [`Text`] (`text-opacity-*`).
/// ([`Border`](crate::theme::class::Border)) o del texto `text-opacity-*` ///
/// ([`Text`](crate::theme::class::Text)). /// [`Bg`]: crate::theme::class::Bg
/// [`Border`]: crate::theme::class::Border
/// [`Text`]: crate::theme::class::Text
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum Opacity { pub enum OpacityLevel {
/// No define ninguna clase. /// No define ninguna clase.
#[default] #[default]
Default, Default,
@ -66,7 +70,7 @@ pub enum Opacity {
Transparent, Transparent,
} }
impl Opacity { impl OpacityLevel {
// Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase. // Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase.
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
@ -106,9 +110,9 @@ impl Opacity {
/// ///
/// ```rust /// ```rust
/// # use pagetop_bootsier::theme::*; /// # use pagetop_bootsier::theme::*;
/// assert_eq!(token::Opacity::Opaque.to_class(), "opacity-100"); /// assert_eq!(OpacityLevel::Opaque.to_class(), "opacity-100");
/// assert_eq!(token::Opacity::Half.to_class(), "opacity-50"); /// assert_eq!(OpacityLevel::Half.to_class(), "opacity-50");
/// assert_eq!(token::Opacity::Default.to_class(), ""); /// assert_eq!(OpacityLevel::Default.to_class(), "");
/// ``` /// ```
#[inline] #[inline]
pub fn to_class(self) -> String { pub fn to_class(self) -> String {
@ -117,237 +121,3 @@ impl Opacity {
class 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<Color> 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<Color> 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)
}
}

View file

@ -2,11 +2,14 @@ use pagetop::prelude::*;
// **< ScaleSize >********************************************************************************** // **< 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), /// Se usa como parámetro de tamaño para las clases de [`Border`], [`Margin`] y [`Padding`]. La
/// [`Margin`](crate::theme::class::Margin) y [`Padding`](crate::theme::class::Padding). La
/// variante `Auto` no aplica en `Padding`. /// 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)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum ScaleSize { pub enum ScaleSize {
/// Sin tamaño (no define ninguna clase). /// 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). /// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL).
/// ///
/// Se usa como selector de lado en [`Border`](crate::theme::class::Border), /// Se usa como selector de lado para las clases de [`Border`], [`Margin`] y [`Padding`].
/// [`Margin`](crate::theme::class::Margin) y [`Padding`](crate::theme::class::Padding). ///
/// [`Border`]: crate::theme::class::Border
/// [`Margin`]: crate::theme::class::Margin
/// [`Padding`]: crate::theme::class::Padding
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum Side { pub enum BoxSide {
/// Todos los lados. /// Todos los lados.
#[default] #[default]
All, All,

View file

@ -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
}
}