✨ [bootsier] Añade más componentes y repasa código
- Se incorpora nuevo componente Dropdown. - Se crea un componente Navbar con soporte para marca, elementos de navegación. - Se implementa el componente Offcanvas con opciones de posición, visibilidad y fondo personalizables. - Mejora el manejo de imágenes con un nuevo componente de Image. - Se reorganizan los componentes del tema para una mejor estructura y usabilidad.
This commit is contained in:
parent
cf40af13bb
commit
82837c622e
29 changed files with 2608 additions and 9 deletions
202
extensions/pagetop-bootsier/src/theme/aux/border.rs
Normal file
202
extensions/pagetop-bootsier/src/theme/aux/border.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< BorderSize >*********************************************************************************
|
||||
|
||||
/// Tamaño (**ancho**) para los bordes ([`Border`]).
|
||||
///
|
||||
/// Mapea a `border`, `border-0` y `border-{1..5}`:
|
||||
///
|
||||
/// - `None` no añade clase (devuelve `""`).
|
||||
/// - `Default` genera `border` (borde por defecto del tema).
|
||||
/// - `Zero` genera `border-0` (sin borde).
|
||||
/// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BorderSize {
|
||||
#[default]
|
||||
None,
|
||||
Default,
|
||||
Zero,
|
||||
Scale1,
|
||||
Scale2,
|
||||
Scale3,
|
||||
Scale4,
|
||||
Scale5,
|
||||
}
|
||||
|
||||
impl BorderSize {
|
||||
#[rustfmt::skip]
|
||||
fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
||||
match self {
|
||||
Self::None => String::new(),
|
||||
Self::Default => String::from(prefix.as_ref()),
|
||||
Self::Zero => join!(prefix, "-0"),
|
||||
Self::Scale1 => join!(prefix, "-1"),
|
||||
Self::Scale2 => join!(prefix, "-2"),
|
||||
Self::Scale3 => join!(prefix, "-3"),
|
||||
Self::Scale4 => join!(prefix, "-4"),
|
||||
Self::Scale5 => join!(prefix, "-5"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BorderSize {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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 (`BorderColor`).
|
||||
/// - 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_color(BorderColor::Theme(Color::Primary))
|
||||
/// .with_opacity(BorderOpacity::Theme(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: BorderOpacity,
|
||||
}
|
||||
|
||||
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: BorderOpacity) -> Self {
|
||||
self.opacity = opacity;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Border {
|
||||
/// Concatena cada definición en el orden: *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,
|
||||
"{}",
|
||||
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.color.to_string(),
|
||||
self.opacity.to_string(),
|
||||
]; " ")
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
}
|
||||
127
extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs
Normal file
127
extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
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`.
|
||||
/// - `"fluid"` para las variantes `Fluid` y `FluidMax(_)`, útil para modelar `container-fluid` en
|
||||
/// [`Container`](crate::theme::Container). Se debe tener en cuenta que `"fluid"` **no** es un
|
||||
/// sufijo de *breakpoint*. Para construir clases válidas con prefijo (p. ej., `"col-md"`), se
|
||||
/// recomienda usar `to_class()` (Self::to_class) o `try_class()` (Self::try_class).
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// assert_eq!(BreakPoint::MD.to_string(), "md");
|
||||
/// assert_eq!(BreakPoint::None.to_string(), "");
|
||||
/// assert_eq!(BreakPoint::Fluid.to_string(), "fluid");
|
||||
///
|
||||
/// // Forma correcta para clases con prefijo:
|
||||
/// //assert_eq!(BreakPoint::MD.to_class("col"), "col-md");
|
||||
/// //assert_eq!(BreakPoint::Fluid.to_class("offcanvas"), "offcanvas");
|
||||
/// ```
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BreakPoint {
|
||||
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.
|
||||
#[default]
|
||||
None,
|
||||
/// **576px o más** - Dispositivos pequeños: teléfonos en modo horizontal.
|
||||
SM,
|
||||
/// **768px o más** - Dispositivos medianos: tabletas.
|
||||
MD,
|
||||
/// **992px o más** - Dispositivos grandes: puestos de escritorio.
|
||||
LG,
|
||||
/// **1200px o más** - Dispositivos muy grandes: puestos de escritorio grandes.
|
||||
XL,
|
||||
/// **1400px o más** - Dispositivos extragrandes: puestos de escritorio más grandes.
|
||||
XXL,
|
||||
/// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo.
|
||||
Fluid,
|
||||
/// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo hasta
|
||||
/// un ancho máximo indicado.
|
||||
FluidMax(UnitValue)
|
||||
}
|
||||
|
||||
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.
|
||||
#[inline]
|
||||
pub const fn is_breakpoint(&self) -> bool {
|
||||
matches!(self, Self::SM | Self::MD | Self::LG | Self::XL | Self::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.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// let breakpoint = BreakPoint::MD;
|
||||
/// let class = breakpoint.to_class("col");
|
||||
/// assert_eq!(class, "col-md".to_string());
|
||||
///
|
||||
/// let breakpoint = BreakPoint::Fluid;
|
||||
/// let class = breakpoint.to_class("offcanvas");
|
||||
/// assert_eq!(class, "offcanvas".to_string());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
||||
if self.is_breakpoint() {
|
||||
join!(prefix, "-", self.to_string())
|
||||
} else {
|
||||
String::from(prefix.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// let breakpoint = BreakPoint::MD;
|
||||
/// let class = breakpoint.try_class("col");
|
||||
/// assert_eq!(class, Some("col-md".to_string()));
|
||||
///
|
||||
/// let breakpoint = BreakPoint::Fluid;
|
||||
/// let class = breakpoint.try_class("navbar-expanded");
|
||||
/// assert_eq!(class, None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> {
|
||||
if self.is_breakpoint() {
|
||||
Some(join!(prefix, "-", self.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for BreakPoint {
|
||||
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"),
|
||||
// Devuelven "fluid" (para modelar `container-fluid`).
|
||||
Self::Fluid => f.write_str("fluid"),
|
||||
Self::FluidMax(_) => f.write_str("fluid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
155
extensions/pagetop-bootsier/src/theme/aux/color.rs
Normal file
155
extensions/pagetop-bootsier/src/theme/aux/color.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< Color >**************************************************************************************
|
||||
|
||||
/// Paleta de colores **temáticos**.
|
||||
///
|
||||
/// Equivalente a los nombres estándar de Bootstrap (`primary`, `secondary`, `success`, etc.). Sirve
|
||||
/// como base para componer clases de fondo ([`BgColor`]), borde ([`BorderColor`]) y texto
|
||||
/// ([`TextColor`]).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Color {
|
||||
#[default]
|
||||
Primary,
|
||||
Secondary,
|
||||
Success,
|
||||
Info,
|
||||
Warning,
|
||||
Danger,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Primary => f.write_str("primary"),
|
||||
Self::Secondary => f.write_str("secondary"),
|
||||
Self::Success => f.write_str("success"),
|
||||
Self::Info => f.write_str("info"),
|
||||
Self::Warning => f.write_str("warning"),
|
||||
Self::Danger => f.write_str("danger"),
|
||||
Self::Light => f.write_str("light"),
|
||||
Self::Dark => f.write_str("dark"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BgColor >************************************************************************************
|
||||
|
||||
/// Colores de fondo (`bg-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Body*` usa fondos predefinidos del tema (`bg-body`, `bg-body-secondary`, `bg-body-tertiary`).
|
||||
/// - `Theme(Color)` genera `bg-{color}` (p. ej., `bg-primary`).
|
||||
/// - `Subtle(Color)` genera `bg-{color}-subtle` (tono suave).
|
||||
/// - `Black` y `White` son colores explícitos.
|
||||
/// - `Transparent` no aplica color de fondo (`bg-transparent`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BgColor {
|
||||
#[default]
|
||||
Default,
|
||||
Body,
|
||||
BodySecondary,
|
||||
BodyTertiary,
|
||||
Theme(Color),
|
||||
Subtle(Color),
|
||||
Black,
|
||||
White,
|
||||
Transparent,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for BgColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Body => f.write_str("bg-body"),
|
||||
Self::BodySecondary => f.write_str("bg-body-secondary"),
|
||||
Self::BodyTertiary => f.write_str("bg-body-tertiary"),
|
||||
Self::Theme(c) => write!(f, "bg-{c}"),
|
||||
Self::Subtle(c) => write!(f, "bg-{c}-subtle"),
|
||||
Self::Black => f.write_str("bg-black"),
|
||||
Self::White => f.write_str("bg-white"),
|
||||
Self::Transparent => f.write_str("bg-transparent"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BorderColor >********************************************************************************
|
||||
|
||||
/// Colores (`border-*`) para los bordes ([`Border`](crate::theme::aux::Border)).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Color)` genera `border-{color}`.
|
||||
/// - `Subtle(Color)` genera `border-{color}-subtle` (versión suavizada).
|
||||
/// - `Black` y `White` son colores explícitos.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BorderColor {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Color),
|
||||
Subtle(Color),
|
||||
Black,
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< TextColor >**********************************************************************************
|
||||
|
||||
/// Colores de texto y fondos de texto (`text-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Body*` aplica colores predefinidos del tema (`text-body`, `text-body-emphasis`,
|
||||
/// `text-body-secondary`, `text-body-tertiary`).
|
||||
/// - `Theme(Color)` genera `text-{color}`.
|
||||
/// - `Emphasis(Color)` genera `text-{color}-emphasis` (contraste mayor acorde al tema).
|
||||
/// - `Background(Color)` genera `text-bg-{color}` (para color de fondo del texto).
|
||||
/// - `Black` y `White` son colores explícitos.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum TextColor {
|
||||
#[default]
|
||||
Default,
|
||||
Body,
|
||||
BodyEmphasis,
|
||||
BodySecondary,
|
||||
BodyTertiary,
|
||||
Theme(Color),
|
||||
Emphasis(Color),
|
||||
Background(Color),
|
||||
Black,
|
||||
White,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for TextColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Body => f.write_str("text-body"),
|
||||
Self::BodyEmphasis => f.write_str("text-body-emphasis"),
|
||||
Self::BodySecondary => f.write_str("text-body-secondary"),
|
||||
Self::BodyTertiary => f.write_str("text-body-tertiary"),
|
||||
Self::Theme(c) => write!(f, "text-{c}"),
|
||||
Self::Emphasis(c) => write!(f, "text-{c}-emphasis"),
|
||||
Self::Background(c) => write!(f, "text-bg-{c}"),
|
||||
Self::Black => f.write_str("text-black"),
|
||||
Self::White => f.write_str("text-white"),
|
||||
}
|
||||
}
|
||||
}
|
||||
109
extensions/pagetop-bootsier/src/theme/aux/opacity.rs
Normal file
109
extensions/pagetop-bootsier/src/theme/aux/opacity.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< Opacity >************************************************************************************
|
||||
|
||||
/// Niveles de **opacidad** (`opacity-*`).
|
||||
///
|
||||
/// Se usa para modular la transparencia del color de fondo `bg-opacity-*` ([`BgOpacity`]), borde
|
||||
/// `border-opacity-*` ([`BorderOpacity`]) o texto `text-opacity-*` ([`TextOpacity`]), según las
|
||||
/// siguientes equivalencias:
|
||||
///
|
||||
/// - `Opaque` => `opacity-100` (100% de opacidad).
|
||||
/// - `SemiOpaque` => `opacity-75` (75%).
|
||||
/// - `Half` => `opacity-50` (50%).
|
||||
/// - `SemiTransparent` => `opacity-25` (25%).
|
||||
/// - `AlmostTransparent` => `opacity-10` (10%).
|
||||
/// - `Transparent` => `opacity-0` (0%, totalmente transparente).
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Opacity {
|
||||
#[default]
|
||||
Opaque, // 100%
|
||||
SemiOpaque, // 75%
|
||||
Half, // 50%
|
||||
SemiTransparent, // 25%
|
||||
AlmostTransparent, // 10%
|
||||
Transparent, // 0%
|
||||
}
|
||||
|
||||
#[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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BgOpacity >**********************************************************************************
|
||||
|
||||
/// Opacidad para el fondo (`bg-opacity-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Opacity)` genera `bg-{opacity}` (p. ej., `bg-opacity-50`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BgOpacity {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Opacity),
|
||||
}
|
||||
|
||||
impl fmt::Display for BgOpacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(o) => write!(f, "bg-{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BorderOpacity >******************************************************************************
|
||||
|
||||
/// Opacidad (`border-opacity-*`) para los bordes ([`Border`](crate::theme::aux::Border)).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Opacity)` genera `border-{opacity}` (p. ej., `border-opacity-25`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BorderOpacity {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Opacity),
|
||||
}
|
||||
|
||||
impl fmt::Display for BorderOpacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(o) => write!(f, "border-{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< TextOpacity >********************************************************************************
|
||||
|
||||
/// Opacidad para el texto (`text-opacity-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Opacity)` genera `text-{opacity}` (p. ej., `text-opacity-100`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum TextOpacity {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Opacity),
|
||||
}
|
||||
|
||||
impl fmt::Display for TextOpacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(o) => write!(f, "text-{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
214
extensions/pagetop-bootsier/src/theme/aux/rounded.rs
Normal file
214
extensions/pagetop-bootsier/src/theme/aux/rounded.rs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< RoundedRadius >******************************************************************************
|
||||
|
||||
/// Radio (**redondeo**) para las esquinas ([`Rounded`]).
|
||||
///
|
||||
/// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`.
|
||||
///
|
||||
/// - `None` no añade clase (devuelve `""`).
|
||||
/// - `Default` genera `rounded` (radio por defecto del tema).
|
||||
/// - `Zero` genera `rounded-0` (sin redondeo).
|
||||
/// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente).
|
||||
/// - `Circle` genera `rounded-circle`.
|
||||
/// - `Pill` genera `rounded-pill`.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum RoundedRadius {
|
||||
#[default]
|
||||
None,
|
||||
Default,
|
||||
Zero,
|
||||
Scale1,
|
||||
Scale2,
|
||||
Scale3,
|
||||
Scale4,
|
||||
Scale5,
|
||||
Circle,
|
||||
Pill,
|
||||
}
|
||||
|
||||
impl RoundedRadius {
|
||||
#[rustfmt::skip]
|
||||
fn to_class(&self, base: impl AsRef<str>) -> 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RoundedRadius {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue