♻️ (bootsier): Refactoriza la gestión de clases

- Mejora la legibilidad del código.
- Simplifica las alteraciones de clases en los componentes `Container`,
  `Dropdown`, `Image`, `Nav`, `Navbar` y `Offcanvas` usando métodos
  dedicados para generar clases en función de sus propiedades.
- Mejora los enums añadiendo métodos que devuelven sus clases
  asociadas, reduciendo código repetitivo.
- Elimina el trait `JoinClasses` y su implementación, integrando la
  lógica de unión de clases directamente en los componentes.
This commit is contained in:
Manuel Cillero 2025-11-15 13:16:15 +01:00
parent 748bd81bf1
commit 2e39af0856
33 changed files with 1607 additions and 647 deletions

View file

@ -1,9 +1,7 @@
use pagetop::prelude::*;
use std::fmt;
/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
#[derive(AutoDefault)]
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum BreakPoint {
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.
#[default]
@ -21,71 +19,96 @@ pub enum BreakPoint {
}
impl BreakPoint {
// Devuelve la identificación del punto de ruptura.
#[rustfmt::skip]
#[inline]
const fn suffix(&self) -> Option<&'static str> {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::None => None,
Self::SM => Some("sm"),
Self::MD => Some("md"),
Self::LG => Some("lg"),
Self::XL => Some("xl"),
Self::XXL => Some("xxl"),
Self::None => "",
Self::SM => "sm",
Self::MD => "md",
Self::LG => "lg",
Self::XL => "xl",
Self::XXL => "xxl",
}
}
/// Genera un nombre de clase CSS basado en el punto de ruptura.
///
/// Si es un punto de ruptura efectivo concatena el prefijo, un guion (`-`) y el sufijo
/// asociado. Para `None` devuelve sólo el prefijo.
///
/// # Ejemplo
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let breakpoint = BreakPoint::MD;
/// assert_eq!(breakpoint.to_class("col"), "col-md");
///
/// let breakpoint = BreakPoint::None;
/// assert_eq!(breakpoint.to_class("offcanvas"), "offcanvas");
/// ```
// Añade el punto de ruptura con un prefijo y un sufijo (opcional) separados por un guion `-` a
// la cadena de clases.
//
// - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío).
// - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`.
#[inline]
pub fn to_class(&self, prefix: impl AsRef<str>) -> String {
join_pair!(prefix, "-", self.suffix().unwrap_or_default())
}
/// Intenta generar un nombre de clase CSS basado en el punto de ruptura.
///
/// Si es un punto de ruptura efectivo devuelve `Some(String)` concatenando el prefijo, un guion
/// (`-`) y el sufijo asociado. En otro caso devuelve `None`.
///
/// # Ejemplo
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// let breakpoint = BreakPoint::MD;
/// let class = breakpoint.try_class("col");
/// assert_eq!(class, Some("col-md".to_string()));
///
/// let breakpoint = BreakPoint::None;
/// let class = breakpoint.try_class("navbar-expand");
/// assert_eq!(class, None);
/// ```
#[inline]
pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> {
self.suffix().map(|suffix| join_pair!(prefix, "-", suffix))
}
}
impl fmt::Display for BreakPoint {
/// Implementa [`Display`](std::fmt::Display) para asociar `"sm"`, `"md"`, `"lg"`, `"xl"` o
/// `"xxl"` a los puntos de ruptura `BreakPoint::SM`, `MD`, `LG`, `XL` o `XXL`, respectivamente.
/// Y `""` (cadena vacía) a `BreakPoint::None`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(suffix) = self.suffix() {
f.write_str(suffix)
} else {
Ok(())
pub(crate) fn push_class(self, classes: &mut String, prefix: &str, suffix: &str) {
if prefix.is_empty() {
return;
}
if !classes.is_empty() {
classes.push(' ');
}
match self {
Self::None => classes.push_str(prefix),
_ => {
classes.push_str(prefix);
classes.push('-');
classes.push_str(self.as_str());
}
}
if !suffix.is_empty() {
classes.push('-');
classes.push_str(suffix);
}
}
// Devuelve la clase para el punto de ruptura, con un prefijo y un sufijo opcional, separados
// por un guion `-`.
//
// - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío).
// - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`.
// - Si `prefix` está vacío devuelve `""`.
//
// # Ejemplos
//
// ```rust
// # use pagetop_bootsier::prelude::*;
// let bp = BreakPoint::MD;
// assert_eq!(bp.class_with("col", ""), "col-md");
// assert_eq!(bp.class_with("col", "6"), "col-md-6");
//
// let bp = BreakPoint::None;
// assert_eq!(bp.class_with("offcanvas", ""), "offcanvas");
// assert_eq!(bp.class_with("col", "12"), "col-12");
//
// let bp = BreakPoint::LG;
// assert_eq!(bp.class_with("", "3"), "");
// ```
#[inline]
pub(crate) fn class_with(self, prefix: &str, suffix: &str) -> String {
if prefix.is_empty() {
return String::new();
}
let bp = self.as_str();
let has_bp = !bp.is_empty();
let has_suffix = !suffix.is_empty();
let mut len = prefix.len();
if has_bp {
len += 1 + bp.len();
}
if has_suffix {
len += 1 + suffix.len();
}
let mut class = String::with_capacity(len);
class.push_str(prefix);
if has_bp {
class.push('-');
class.push_str(bp);
}
if has_suffix {
class.push('-');
class.push_str(suffix);
}
class
}
}