♻️ Migra #[builder_fn] a #[builder_impl]

Sustituye las ~400 anotaciones `#[builder_fn]` método a método por un
único `#[builder_impl]` por `impl`/`trait`, en 74 ficheros de pagetop y
sus extensiones (admin, bootsier, menu, user).
This commit is contained in:
Manuel Cillero 2026-08-29 19:25:58 +02:00
parent c34dc02357
commit 1be3d88888
74 changed files with 104 additions and 394 deletions

View file

@ -18,15 +18,15 @@ const EXTRA_COLOR: &str = "bootsier.badge.color";
///
/// let badge = bs::Badge::labeled(Lc::n("Beta")).with_color(BootsierColors::Dark);
/// ```
#[builder_impl]
pub trait BadgeBootsier {
/// Fuerza un color de la paleta de Bootsier, ignorando el que le correspondería a la `Intent`
/// del badge. `None` restablece el comportamiento por defecto (color derivado de la `Intent`).
#[builder_fn]
fn with_color(self, color: impl Into<Option<BootsierColors>>) -> Self;
}
#[builder_impl]
impl BadgeBootsier for Badge {
#[builder_fn]
fn with_color(mut self, color: impl Into<Option<BootsierColors>>) -> Self {
match color.into() {
Some(color) => self.alter_prop(PropsOp::set_extra(EXTRA_COLOR, color)),

View file

@ -33,36 +33,32 @@ const EXTRA_COLOR: &str = "bootsier.button.color";
/// .with_style(button::Style::Solid(Intent::Neutral))
/// .with_color(BootsierColors::Light);
/// ```
#[builder_impl]
pub trait ButtonBootsier {
/// Marca el botón como activo (`.active`, `aria-pressed="true"`).
#[builder_fn]
fn with_active(self, active: bool) -> Self;
/// Expande el botón al ancho completo de su contenedor (`w-100`).
#[builder_fn]
fn with_full_width(self, full_width: bool) -> Self;
/// Fuerza un color de la paleta de Bootsier, ignorando el que le correspondería a la `Intent`
/// del botón. `None` restablece el comportamiento por defecto (color derivado de la `Intent`).
/// Sin efecto si el estilo del botón es [`Style::Link`] o [`Style::None`].
#[builder_fn]
fn with_color(self, color: impl Into<Option<BootsierColors>>) -> Self;
}
#[builder_impl]
impl ButtonBootsier for Button {
#[builder_fn]
fn with_active(mut self, active: bool) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_ACTIVE, active));
self
}
#[builder_fn]
fn with_full_width(mut self, full_width: bool) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_FULL_WIDTH, full_width));
self
}
#[builder_fn]
fn with_color(mut self, color: impl Into<Option<BootsierColors>>) -> Self {
match color.into() {
Some(color) => self.alter_prop(PropsOp::set_extra(EXTRA_COLOR, color)),

View file

@ -32,18 +32,18 @@ const EXTRA_WIDTH: &str = "bootsier.container.width";
/// .with_prop(PropsOp::add_classes(class::Border::with(ScaleSize::One)))
/// .with_prop(PropsOp::add_classes(class::Rounded::new()));
/// ```
#[builder_impl]
pub trait ContainerBootsier {
/// Establece el comportamiento del ancho para el contenedor.
///
/// Determina si el contenedor aplica los anchos máximos predefinidos para cada punto de
/// ruptura, o si ocupa siempre el 100% del ancho disponible, o lo hace hasta un ancho máximo
/// explícito. Ver [`Width`] para las variantes disponibles.
#[builder_fn]
fn with_width(self, width: Width) -> Self;
}
#[builder_impl]
impl ContainerBootsier for Container {
#[builder_fn]
fn with_width(mut self, width: Width) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_WIDTH, width));
self

View file

@ -44,54 +44,46 @@ const EXTRA_MENU_POSITION: &str = "bootsier.dropdown.menu_position";
/// .with_item(bs::dropdown::Item::header(Lc::n("User session")))
/// .with_item(bs::dropdown::Item::button(Lc::n("Sign out")));
/// ```
#[builder_impl]
pub trait DropdownBootsier {
/// Indica si el botón del menú está integrado en un grupo de botones.
#[builder_fn]
fn with_button_grouped(self, grouped: bool) -> Self;
/// Establece la política de cierre automático del menú desplegable.
#[builder_fn]
fn with_auto_close(self, auto_close: AutoClose) -> Self;
/// Establece la dirección de despliegue del menú.
#[builder_fn]
fn with_direction(self, direction: Direction) -> Self;
/// Configura la alineación horizontal (con posible comportamiento *responsive* adicional).
#[builder_fn]
fn with_menu_align(self, align: MenuAlign) -> Self;
/// Configura la posición del menú.
#[builder_fn]
fn with_menu_position(self, position: MenuPosition) -> Self;
}
#[builder_impl]
impl DropdownBootsier for Dropdown {
#[builder_fn]
fn with_button_grouped(mut self, grouped: bool) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_BUTTON_GROUPED, grouped));
self
}
#[builder_fn]
fn with_auto_close(mut self, auto_close: AutoClose) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_AUTO_CLOSE, auto_close));
self
}
#[builder_fn]
fn with_direction(mut self, direction: Direction) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_DIRECTION, direction));
self
}
#[builder_fn]
fn with_menu_align(mut self, align: MenuAlign) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_MENU_ALIGN, align));
self
}
#[builder_fn]
fn with_menu_position(mut self, position: MenuPosition) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_MENU_POSITION, position));
self

View file

@ -22,18 +22,18 @@ const EXTRA_FLOATING_LABEL: &str = "bootsier.form.input.floating_label";
/// .with_placeholder(Lc::n("Enter your name"))
/// .with_floating_label(true);
/// ```
#[builder_impl]
pub trait InputBootsier {
/// Establece si la etiqueta se muestra flotante sobre el campo.
///
/// Cuando está activo, la etiqueta se superpone al campo y asciende al enfocarlo o cuando tiene
/// contenido. Requiere que el campo tenga un atributo `placeholder` definido; si no se
/// especifica, se fuerza `placeholder=""` antes del renderizado.
#[builder_fn]
fn with_floating_label(self, floating: bool) -> Self;
}
#[builder_impl]
impl InputBootsier for Field {
#[builder_fn]
fn with_floating_label(mut self, floating: bool) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating));
self

View file

@ -24,6 +24,7 @@ const EXTRA_FLOATING_LABEL: &str = "bootsier.form.select.floating_label";
/// .with_item(bs::form::select::Item::new("es", Lc::n("Spanish")))
/// .with_item(bs::form::select::Item::new("en", Lc::n("English")));
/// ```
#[builder_impl]
pub trait SelectBootsier {
/// Establece si la etiqueta se muestra flotante sobre el campo.
///
@ -33,12 +34,11 @@ pub trait SelectBootsier {
/// Si se usa la etiqueta flotante, se anulan los valores establecidos con
/// [`with_multiple()`](form::select::Field::with_multiple) y
/// [`with_rows()`](form::select::Field::with_rows) antes del renderizado.
#[builder_fn]
fn with_floating_label(self, floating: bool) -> Self;
}
#[builder_impl]
impl SelectBootsier for Field {
#[builder_fn]
fn with_floating_label(mut self, floating: bool) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating));
self

View file

@ -22,6 +22,7 @@ const EXTRA_FLOATING_LABEL: &str = "bootsier.form.textarea.floating_label";
/// .with_placeholder(Lc::n("Write here..."))
/// .with_floating_label(true);
/// ```
#[builder_impl]
pub trait TextareaBootsier {
/// Establece si la etiqueta se muestra flotante sobre el campo.
///
@ -31,12 +32,11 @@ pub trait TextareaBootsier {
///
/// Si se usa la etiqueta flotante, se anula el valor establecido con
/// [`with_rows()`](form::Textarea::with_rows) antes del renderizado.
#[builder_fn]
fn with_floating_label(self, floating: bool) -> Self;
}
#[builder_impl]
impl TextareaBootsier for Textarea {
#[builder_fn]
fn with_floating_label(mut self, floating: bool) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating));
self

View file

@ -78,6 +78,7 @@ impl Component for Icon {
}
}
#[builder_impl]
impl Icon {
pub fn font() -> Self {
Self::default().with_icon_kind(IconKind::Font(FontSize::default()))
@ -104,26 +105,22 @@ impl Icon {
// **< Icon BUILDER >***************************************************************************
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
#[builder_fn]
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
self.props.alter_id(id);
self
}
/// Modifica identificador, clases CSS, atributos HTML o valores extra del componente.
#[builder_fn]
pub fn with_prop(mut self, op: PropsOp) -> Self {
self.props.alter_prop(op);
self
}
#[builder_fn]
pub fn with_icon_kind(mut self, icon_kind: IconKind) -> Self {
self.icon_kind = icon_kind;
self
}
#[builder_fn]
pub fn with_aria_label(mut self, label: Lc) -> Self {
self.aria_label.alter_value(label);
self

View file

@ -46,14 +46,14 @@ pub(crate) const EXTRA_IN_NAVBAR: &str = "bootsier.nav.in_navbar";
/// ))
/// .with_item(bs::nav::Item::link_disabled(Lc::n("Disabled"), "#"));
/// ```
#[builder_impl]
pub trait NavBootsier {
/// Cambia el estilo del menú (*Tabs*, *Pills*, *Underline* o *Default*).
#[builder_fn]
fn with_kind(self, kind: Kind) -> Self;
}
#[builder_impl]
impl NavBootsier for Nav {
#[builder_fn]
fn with_kind(mut self, kind: Kind) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_KIND, kind));
self

View file

@ -140,6 +140,7 @@ const EXTRA_EXPAND: &str = "bootsier.navbar.expand";
/// .with_item(bs::nav::Item::link(Lc::n("Stock"), "/stock"))
/// ));
/// ```
#[builder_impl]
pub trait NavbarBootsier {
/// Crea una barra de navegación cuyo contenido se muestra en un **offcanvas**.
fn offcanvas(oc: bs::Offcanvas) -> Self;
@ -151,14 +152,13 @@ pub trait NavbarBootsier {
fn offcanvas_brand_right(brand: Brand, oc: bs::Offcanvas) -> Self;
/// Define a partir de qué punto de ruptura la barra de navegación deja de colapsar.
#[builder_fn]
fn with_expand(self, bp: BreakPoint) -> Self;
/// Define dónde se mostrará la barra de navegación dentro del documento.
#[builder_fn]
fn with_position(self, position: bs::navbar::Position) -> Self;
}
#[builder_impl]
impl NavbarBootsier for Navbar {
fn offcanvas(oc: bs::Offcanvas) -> Self {
let mut navbar = Self::new();
@ -187,13 +187,11 @@ impl NavbarBootsier for Navbar {
navbar
}
#[builder_fn]
fn with_expand(mut self, bp: BreakPoint) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_EXPAND, bp));
self
}
#[builder_fn]
fn with_position(mut self, position: bs::navbar::Position) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_POSITION, position));
self

View file

@ -90,25 +90,23 @@ impl Component for Offcanvas {
}
}
#[builder_impl]
impl Offcanvas {
// **< Offcanvas BUILDER >**********************************************************************
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
#[builder_fn]
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
self.props.alter_id(id);
self
}
/// Modifica identificador, clases CSS, atributos HTML o valores extra del componente.
#[builder_fn]
pub fn with_prop(mut self, op: PropsOp) -> Self {
self.props.alter_prop(op);
self
}
/// Establece el título del encabezado.
#[builder_fn]
pub fn with_title(mut self, title: Lc) -> Self {
self.title = title;
self
@ -123,7 +121,6 @@ impl Offcanvas {
/// Por ejemplo, con `BreakPoint::Lg`, será *offcanvas* en móviles y tabletas, y visible
/// directamente en pantallas grandes. Por defecto usa `BreakPoint::None` para que sea
/// *offcanvas* siempre.
#[builder_fn]
pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self {
self.breakpoint = bp;
self
@ -131,28 +128,24 @@ impl Offcanvas {
/// Ajusta la capa de fondo del panel para definir su comportamiento al hacer clic fuera del
/// panel.
#[builder_fn]
pub fn with_backdrop(mut self, backdrop: bs::offcanvas::Backdrop) -> Self {
self.backdrop = backdrop;
self
}
/// Permite o bloquea el desplazamiento de la página principal mientras el panel está abierto.
#[builder_fn]
pub fn with_body_scroll(mut self, scrolling: bs::offcanvas::BodyScroll) -> Self {
self.body_scroll = scrolling;
self
}
/// Indica desde qué borde de la ventana entra y se ancla el panel.
#[builder_fn]
pub fn with_placement(mut self, placement: bs::offcanvas::Placement) -> Self {
self.placement = placement;
self
}
/// Fija el estado inicial del panel (oculto o visible al cargar).
#[builder_fn]
pub fn with_visibility(mut self, visibility: bs::offcanvas::Visibility) -> Self {
self.visibility = visibility;
self
@ -160,7 +153,6 @@ impl Offcanvas {
/// Añade un nuevo componente al panel o modifica la lista de componentes (`children`) con una
/// operación [`ChildOp`].
#[builder_fn]
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
self.children.alter_child(op.into());
self

View file

@ -65,6 +65,7 @@ impl Component for Item {
}
}
#[builder_impl]
impl Item {
/// Crea un ítem de navegación con etiqueta, ruta e icono.
///
@ -82,21 +83,18 @@ impl Item {
// **< Item BUILDER >***************************************************************************
/// Establece el texto localizable del ítem.
#[builder_fn]
pub fn with_label(mut self, label: Lc) -> Self {
self.label = label;
self
}
/// Establece la ruta de destino del ítem.
#[builder_fn]
pub fn with_route(mut self, route: impl Into<Option<Route>>) -> Self {
self.route = route.into();
self
}
/// Establece el nombre del icono de Bootstrap Icons (sin el prefijo `bi-`).
#[builder_fn]
pub fn with_icon(mut self, icon: impl Into<CowStr>) -> Self {
self.icon = icon.into();
self

View file

@ -40,6 +40,7 @@ impl Component for Section {
}
}
#[builder_impl]
impl Section {
/// Crea un encabezado de sección con el título indicado.
pub fn titled(title: Lc) -> Self {
@ -49,7 +50,6 @@ impl Section {
// **< Section BUILDER >************************************************************************
/// Establece el título localizable de la sección.
#[builder_fn]
pub fn with_title(mut self, title: Lc) -> Self {
self.title = title;
self