♻️ [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 39033ef641
commit 623ef7e2c7
33 changed files with 1607 additions and 647 deletions

View file

@ -2,11 +2,9 @@
use crate::prelude::*;
use std::fmt;
// **< FontSize >***********************************************************************************
#[derive(AutoDefault)]
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum FontSize {
ExtraLarge,
XxLarge,
@ -24,7 +22,7 @@ pub enum FontSize {
#[rustfmt::skip]
impl FontSize {
#[inline]
pub const fn as_str(&self) -> &'static str {
pub const fn as_str(self) -> &'static str {
match self {
FontSize::ExtraLarge => "fs__x3l",
FontSize::XxLarge => "fs__x2l",
@ -40,12 +38,6 @@ impl FontSize {
}
}
impl fmt::Display for FontSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
// *************************************************************************************************
mod html;

View file

@ -87,9 +87,6 @@ use crate::{core, AutoDefault};
#[allow(type_alias_bounds)]
pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>;
mod join_classes;
pub use join_classes::JoinClasses;
mod unit;
pub use unit::UnitValue;

View file

@ -23,7 +23,7 @@ enum Source {
///
/// Permite especificar en qué contexto se aplica el CSS, adaptándose a diferentes dispositivos o
/// situaciones de impresión.
#[derive(AutoDefault)]
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum TargetMedia {
/// Se aplica en todos los casos (el atributo `media` se omite).
#[default]
@ -39,7 +39,7 @@ pub enum TargetMedia {
/// Devuelve el valor para el atributo `media` (`Some(...)`) o `None` para `Default`.
#[rustfmt::skip]
impl TargetMedia {
fn as_str_opt(&self) -> Option<&str> {
const fn as_str(self) -> Option<&'static str> {
match self {
TargetMedia::Default => None,
TargetMedia::Print => Some("print"),
@ -171,7 +171,7 @@ impl Asset for StyleSheet {
link
rel="stylesheet"
href=(join_pair!(path, "?v=", &self.version))
media=[self.media.as_str_opt()];
media=[self.media.as_str()];
},
Source::Inline(_, f) => html! {
style { (PreEscaped((f)(cx))) };

View file

@ -1,67 +0,0 @@
/// Añade a los *slices* de elementos [`AsRef<str>`] un método para unir clases CSS.
///
/// El método es [`join_classes()`](JoinClasses::join_classes), que une las cadenas **no vacías**
/// del *slice* usando un espacio como separador.
pub trait JoinClasses {
/// Une las cadenas **no vacías** de un *slice* usando un espacio como separador.
///
/// Son cadenas vacías únicamente los elementos del *slice* cuya longitud es `0` (p. ej., `""`);
/// no se realiza recorte ni normalización, por lo que elementos como `" "` no se consideran
/// vacíos.
///
/// Si todas las cadenas están vacías, devuelve una cadena vacía. Acepta elementos que
/// implementen [`AsRef<str>`] como `&str`, [`String`] o `Cow<'_, str>`.
///
/// # Ejemplos
///
/// ```rust
/// # use pagetop::prelude::*;
/// let classes = ["btn", "", "btn-primary"];
/// assert_eq!(classes.join_classes(), "btn btn-primary");
///
/// let empty: [&str; 3] = ["", "", ""];
/// assert_eq!(empty.join_classes(), "");
///
/// let border = String::from("border");
/// let border_top = String::from("border-top-0");
/// let v = vec![&border, "", "", "", &border_top];
/// assert_eq!(v.as_slice().join_classes(), "border border-top-0");
///
/// // Elementos con espacios afectan al resultado.
/// let spaced = ["btn", " ", "primary "];
/// assert_eq!(spaced.join_classes(), "btn primary ");
/// ```
fn join_classes(&self) -> String;
}
impl<T> JoinClasses for [T]
where
T: AsRef<str>,
{
#[inline]
fn join_classes(&self) -> String {
let mut count = 0usize;
let mut total = 0usize;
for s in self.iter().map(T::as_ref).filter(|s| !s.is_empty()) {
count += 1;
total += s.len();
}
if count == 0 {
return String::new();
}
let separator = " ";
let mut result = String::with_capacity(total + separator.len() * count.saturating_sub(1));
for (i, s) in self
.iter()
.map(T::as_ref)
.filter(|s| !s.is_empty())
.enumerate()
{
if i > 0 {
result.push_str(separator);
}
result.push_str(s);
}
result
}
}

View file

@ -27,7 +27,7 @@ use crate::AutoDefault;
/// };
/// ```
#[derive(AutoDefault)]
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum PageTopSvg {
/// Versión por defecto con el logotipo a color.
#[default]

View file

@ -33,8 +33,8 @@ pub use crate::trace;
// alias obsoletos se volverá a declarar como `pub use crate::html::*;`.
pub use crate::html::{
display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue,
ClassesOp, Escaper, Favicon, JavaScript, JoinClasses, Markup, PageTopSvg, PreEscaped,
PrepareMarkup, StyleSheet, TargetMedia, UnitValue, DOCTYPE,
ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup,
StyleSheet, TargetMedia, UnitValue, DOCTYPE,
};
pub use crate::locale::*;