(pagetop): Añade Flex/FlexItem para usar Flexbox

Container y Navbar lo adoptan vía `with_flex()`/`PropsOp::flex_item()`.
Y se elimina `ButtonSet` porque su funcionalidad queda cubierta por este
mecanismo más general. Incluye el ejemplo `examples/intro-flex.rs` con
los patrones de uso.
This commit is contained in:
Manuel Cillero 2026-09-04 01:04:53 +02:00
parent 4e4fdf7b10
commit 17e16652e4
26 changed files with 2023 additions and 193 deletions

View file

@ -1,10 +1,7 @@
//! Definiciones para crear botones ([`Button`]) y conjuntos de botones ([`ButtonSet`]).
//! Definiciones para crear botones ([`Button`]).
mod props;
pub use props::{Kind, Size, Style};
mod component;
pub use component::Button;
mod set;
pub use set::ButtonSet;

View file

@ -1,71 +0,0 @@
use crate::prelude::*;
/// Componente para mostrar un **conjunto de botones**.
///
/// Envuelve los botones en un contenedor que cada tema estiliza para separarlos visualmente. Sólo
/// admite componentes [`Button`].
///
/// # Ejemplo
///
/// ```rust,no_run
/// use pagetop::prelude::*;
///
/// let actions = button::ButtonSet::new()
/// .with_button(Button::submit(Lc::n("Save")))
/// .with_button(Button::plain(Lc::n("Cancel")));
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct ButtonSet {
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
props: Props,
/// Devuelve los botones del conjunto.
buttons: Children,
}
#[async_trait]
impl Component for ButtonSet {
fn new() -> Self {
Self::default()
}
fn id(&self) -> Option<String> {
self.props.get_id()
}
fn setup(&mut self, _cx: &Context) {
self.alter_prop(PropsOp::prepend_classes("button-set"));
}
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let buttons = self.buttons().render(cx).await;
if buttons.is_empty() {
return Ok(html! {});
}
Ok(html! {
div (self.props()) { (buttons) }
})
}
}
#[builder_impl]
impl ButtonSet {
// **< ButtonSet BUILDER >*************************************************************************
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
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.
pub fn with_prop(mut self, op: PropsOp) -> Self {
self.props.alter_prop(op);
self
}
/// Añade un botón al conjunto, o modifica su lista de botones con una operación [`TypedOp`].
pub fn with_button(mut self, op: impl Into<TypedOp<Button>>) -> Self {
self.buttons.alter_child(op.into());
self
}
}

View file

@ -47,6 +47,9 @@ pub struct Container {
props: Props,
/// Devuelve el tipo semántico del contenedor.
kind: Kind,
/// Devuelve el posicionamiento Flexbox como contenedor, si tiene alguno.
#[getters(copy)]
flex: Option<Flex>,
/// Devuelve la lista de componentes (`children`) del contenedor.
children: Children,
}
@ -61,6 +64,12 @@ impl Component for Container {
self.props.get_id()
}
fn setup(&mut self, _cx: &Context) {
if let Some(flex) = self.flex() {
flex.apply_to(&mut self.props);
}
}
#[rustfmt::skip]
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
let output = self.children().render(cx).await;
@ -134,6 +143,12 @@ impl Container {
self
}
/// Establece el posicionamiento Flexbox como contenedor (usa `None` para quitarlo).
pub fn with_flex(mut self, flex: impl Into<Option<Flex>>) -> Self {
self.flex = flex.into();
self
}
/// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con
/// una operación [`ChildOp`].
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {

View file

@ -136,8 +136,7 @@ impl Dialog {
/// lista de componentes (`children`) del pie con una operación [`ChildOp`].
///
/// El pie ya se maqueta en fila y alineado a la derecha por su propia clase CSS
/// (`dialog-footer`); por lo que no requiere un [`ButtonSet`](super::ButtonSet) para alinear
/// los botones, aunque puede usarse si se desea.
/// (`dialog-footer`); no requiere ninguna configuración adicional para alinear los botones.
pub fn with_footer(mut self, op: impl Into<ChildOp>) -> Self {
self.footer.alter_child(op.into());
self

View file

@ -81,6 +81,9 @@ pub struct Navbar {
props: Props,
/// Devuelve la disposición configurada para la barra de navegación.
layout: navbar::Layout,
/// Devuelve el posicionamiento Flexbox como contenedor, si tiene alguno.
#[getters(copy)]
flex: Option<Flex>,
/// Devuelve la lista de contenidos.
items: Children,
}
@ -128,37 +131,44 @@ impl Component for Navbar {
let id = self.id().unwrap();
let id_content = util::join!(id, "-content");
// Posicionamiento Flexbox opcional (no del `<nav>`, cuya estructura la fija `layout()`).
let mut content_props = Props::default();
if let Some(flex) = self.flex() {
flex.apply_to(&mut content_props);
}
content_props.alter_prop(PropsOp::prepend_classes("navbar-content"));
Ok(html! {
nav (self.props()) {
@match self.layout() {
// Barra más sencilla: sólo contenido, siempre visible.
navbar::Layout::Simple => {
div class="navbar-content" { (items) }
div (content_props) { (items) }
},
// Barra sencilla que se puede contraer/expandir.
navbar::Layout::SimpleToggle => {
(button(cx, &id_content))
div id=(&id_content) class="navbar-content" { (items) }
div id=(&id_content) (content_props) { (items) }
},
// Barra con marca, siempre visible, sin botón.
navbar::Layout::SimpleBrandLeft(brand) => {
(brand.render(cx).await)
div class="navbar-content" { (items) }
div (content_props) { (items) }
},
// Barra con marca y botón, en ese orden.
navbar::Layout::BrandLeft(brand) => {
(brand.render(cx).await)
(button(cx, &id_content))
div id=(&id_content) class="navbar-content" { (items) }
div id=(&id_content) (content_props) { (items) }
},
// Barra con botón y marca, en ese orden.
navbar::Layout::BrandRight(brand) => {
(button(cx, &id_content))
div id=(&id_content) class="navbar-content" { (items) }
div id=(&id_content) (content_props) { (items) }
(brand.render(cx).await)
},
}
@ -216,6 +226,15 @@ impl Navbar {
self
}
/// Establece el posicionamiento Flexbox como contenedor (usa `None` para quitarlo).
///
/// No afecta a la posición de la marca ni del botón de despliegue, que quedan fijados con
/// [`with_layout()`](Self::with_layout).
pub fn with_flex(mut self, flex: impl Into<Option<Flex>>) -> Self {
self.flex = flex.into();
self
}
/// Añade un nuevo contenido a la barra de navegación o modifica la lista de contenidos de la
/// barra con una operación [`TypedOp`].
///