✨ (pagetop): Añade Grid y extrae Flex de Container

This commit is contained in:
Manuel Cillero 2026-09-18 16:49:09 +02:00
parent dfff12881f
commit 307ca6f62c
44 changed files with 2554 additions and 735 deletions

View file

@ -9,8 +9,8 @@ use crate::registry;
/// Renderiza breadcrumb, encabezado (título + acciones locales), tareas locales (pestañas) y el
/// contenido de la página. No incluye ningún menú de navegación: mostrar las secciones del panel
/// (el "sidebar" o el "menú superior", según el tema) es responsabilidad del tema que intercepte
/// [`CoreTemplates::Admin`](pagetop::core::theme::CoreTemplates::Admin) -- por ejemplo
/// `pagetop-bootsier` -- leyendo directamente [`crate::registry::global()`]. El tema básico de
/// [`CoreTemplates::Admin`](pagetop::core::theme::CoreTemplates::Admin) (por ejemplo
/// `pagetop-bootsier`) leyendo directamente [`crate::registry::global()`]. El tema básico de
/// PageTop no lo hace, y una página de administración sigue siendo completamente navegable sin él,
/// a través del *dashboard* (`/admin`), las páginas de sección y este mismo breadcrumb.
///

View file

@ -162,7 +162,7 @@ impl Extension for Admin {
registry::build();
// Región neutra del core: cualquier tema puede decidir renderizarla (o no) sin que
// `pagetop-admin` dependa de ninguno en concreto -- ver `CoreRegions::Aside`.
// `pagetop-admin` dependa de ninguno en concreto (ver `CoreRegions::Aside`).
InRegion::Global(&CoreRegions::Aside).add(component::AdminMenu::new());
}

View file

@ -9,8 +9,8 @@ const EXTRA_COLOR: &str = "bootsier.badge.color";
/// Extensión de Bootsier para [`Badge`].
///
/// Permite forzar un color de la paleta de Bootsier ([`BootsierColors`]) en vez del que le
/// correspondería por defecto a la [`Intent`] del badge -- por ejemplo, para usar `Light`/`Dark`,
/// que `Intent` no tiene.
/// correspondería por defecto a la [`Intent`] del badge (por ejemplo, para usar `Light`/`Dark`,
/// que `Intent` no tiene).
///
/// ```rust,no_run
/// use pagetop::prelude::*;
@ -39,7 +39,7 @@ impl BadgeBootsier for Badge {
// **< Badge SETUP >********************************************************************************
pub(crate) fn setup(badge: &mut Badge) {
// `Badge::setup()` (core) ya ha traducido la intención con `Theme::intent_color()` -- la clase
// `Badge::setup()` (core) ya ha traducido la intención con `Theme::intent_color()`. La clase
// `badge-*` que hay que localizar es siempre la derivada de la `Intent`, con independencia de
// que `BadgeBootsier::with_color()` fuerce un color distinto para el destino `text-bg-*`.
let intent_color = BootsierColors::from(badge.intent()).as_str();

View file

@ -75,10 +75,9 @@ impl ButtonBootsier for Button {
pub(crate) fn setup(button: &mut Button) {
button.alter_prop(PropsOp::replace_classes("button", "btn"));
// `Button::setup()` (core) ya ha traducido la intención con `Theme::intent_color()` -- aquí
// sólo queda cambiar el prefijo `button-`/`button-outline-` por el equivalente
// `btn-`/`btn-outline-` de Bootstrap, conservando el mismo nombre de color salvo que
// `with_color()` lo sobrescriba.
// `Button::setup()` (core) ya ha traducido la intención con `Theme::intent_color()`. Aquí sólo
// queda cambiar el prefijo `button-`/`button-outline-` por el equivalente `btn-`/`btn-outline-`
// de Bootstrap, conservando el mismo nombre de color salvo que `with_color()` lo sobrescriba.
let override_color = button
.props()
.extra::<BootsierColors>(EXTRA_COLOR)

View file

@ -97,8 +97,8 @@ impl RegionName for BootsierRegions {
// **< Region RENDER >******************************************************************************
// Regiones de Bootsier: se renderizan sin el `<div role="region">` envolvente que aplica
// `layout::Region::prepare()` por defecto -- sus elementos van directamente dentro del contenedor
// que los gestiona (sidebar-menu o navbar-nav). Devuelve `None` si `component` no envuelve una
// `layout::Region::prepare()` por defecto. Sus elementos van directamente dentro del contenedor que
// los gestiona (sidebar-menu o navbar-nav). Devuelve `None` si `component` no envuelve una
// `BootsierRegions`, dejando que el resto de la cadena de temas (o el propio componente) resuelva
// el renderizado por defecto.
pub(crate) async fn render(

View file

@ -5,10 +5,10 @@ use crate::theme::{ContainerBootsier, bs};
use crate::{ADMINLTE_VERSION, LOCALES_BOOTSIER};
// Regiones de Bootsier: se renderiza sin el `<div role="region">` envolvente que aplica
// `layout::Template::prepare()` por defecto -- delega en `render_standard()`/`render_admin()`
// según la variante de `CoreTemplates` que envuelva el componente. Devuelve `None` si
// `component` no envuelve una `CoreTemplates`, dejando que el resto de la cadena de temas (o
// el propio componente) resuelva el renderizado por defecto.
// `layout::Template::prepare()` por defecto; delega en `render_standard()`/`render_admin()` según
// la variante de `CoreTemplates` que envuelva el componente. Devuelve `None` si `component` no
// envuelve una `CoreTemplates`, dejando que el resto de la cadena de temas (o el propio componente)
// resuelva el renderizado por defecto.
pub(crate) async fn render(
component: &layout::Template,
cx: &mut Context,

View file

@ -4,8 +4,8 @@ use crate::tree::{MenuKey, MenuNode, TreeOptions, build_tree, try_resolve_menu_u
/// Renderiza un menú completo como bloque de navegación HTML.
///
/// Se construye componiendo [`Nav`] y [`nav::Item`] -- con [`Dropdown`]/[`dropdown::Item`] para
/// los nodos con hijos -- a partir del árbol del menú, así que produce el mismo marcado accesible
/// Se construye componiendo [`Nav`] y [`nav::Item`] (con [`Dropdown`]/[`dropdown::Item`] para
/// los nodos con hijos) a partir del árbol del menú, así que produce el mismo marcado accesible
/// que cualquier `Nav`/`Dropdown` y se beneficia igual del CSS/JavaScript que aporta el tema
/// activo:
///
@ -25,17 +25,17 @@ use crate::tree::{MenuKey, MenuNode, TreeOptions, build_tree, try_resolve_menu_u
///
/// # Limitaciones conocidas
///
/// - **Profundidad máxima de 2 niveles.** [`Dropdown`] no admite submenús anidados (como
/// Bootstrap, del que toma su marcado): un nodo de tercer nivel o más profundo nunca se
/// construye -- [`TreeOptions::max_depth`] se acota internamente a `2` con independencia de lo
/// que indique [`with_max_depth()`](Self::with_max_depth), así que no hay pérdida silenciosa de
/// datos, sencillamente no se piden a la base de datos.
/// - **Profundidad máxima de 2 niveles.** [`Dropdown`] no admite submenús anidados (como Bootstrap,
/// del que toma su marcado): un nodo de tercer nivel o más profundo nunca se construye.
/// [`TreeOptions::max_depth`] se acota internamente a `2` con independencia de lo que indique
/// [`with_max_depth()`](Self::with_max_depth), así que no hay pérdida silenciosa de datos,
/// sencillamente no se piden a la base de datos.
/// - **Sin colapso responsive propio.** A diferencia del antiguo `Menu::collapsible`, `Nav` es una
/// lista plana sin botón ni JavaScript de colapso; una aplicación que necesite ese
/// comportamiento debe envolver `MenuBlock` en su propia chrome hasta que exista un componente
/// `Navbar` en el core.
/// lista plana sin botón ni JavaScript de colapso; una aplicación que necesite ese comportamiento
/// debe envolver `MenuBlock` en su propia chrome hasta que exista un componente `Navbar` en el
/// core.
/// - `MenuNode::in_active_trail` y `MenuNode::expanded` (ver [`crate::tree::MenuNode`]) todavía no
/// se reflejan en el marcado -- ni [`nav::Item`] ni [`dropdown::Item`] tienen hoy una forma
/// se reflejan en el marcado. Ni [`nav::Item`] ni [`dropdown::Item`] tienen hoy una forma
/// verificada de pre-abrirse en el servidor sin que la mejora progresiva del tema
/// (`accessible-menu` en `Basic`) lo sobrescriba al inicializarse. Sólo se traduce el estado
/// `is_active`/`enabled` de cada nodo.
@ -149,7 +149,7 @@ impl MenuBlock {
// Convierte un `MenuNode` de nivel 1 (raíz) en un `nav::Item`: sin hijos, enlace (o etiqueta sin
// ruta propia); con hijos, activador de un `Dropdown` con sus hijos como `dropdown::Item`. Los
// hijos de un `MenuNode` de nivel 1 nunca tienen a su vez hijos propios -- `TreeOptions::max_depth`
// hijos de un `MenuNode` de nivel 1 nunca tienen a su vez hijos propios. `TreeOptions::max_depth`
// se acota a 2 en `MenuBlock::prepare()`, así que no hay un tercer nivel que representar.
fn node_to_item(node: &MenuNode, cx: &Context) -> nav::Item {
if !node.children.is_empty() {

View file

@ -7,7 +7,7 @@ use crate::LOCALES_USER;
/// Campos de contraseña y confirmación, compuestos a partir de
/// [`form::input::Field::password()`] del core. No valida en ningún momento que ambos valores
/// coincidan -- eso ocurre en el servidor, tras el envío del formulario -- sólo renderiza los dos
/// coincidan (eso ocurre en el servidor, tras el envío del formulario), sólo renderiza los dos
/// campos, uno junto al otro, sin ningún contenedor propio.
#[derive(AutoDefault, Clone, Debug, Getters)]
pub(crate) struct PasswordConfirm {

View file

@ -330,7 +330,7 @@ fn edit_actions(
can_toggle_admin: bool,
waypoint: &Waypoint,
cx: &mut Context,
) -> Container {
) -> Flex {
let (next_status, label_key) = match status {
UserStatus::Blocked => ("active", "btn-activate"),
_ => ("blocked", "btn-block"),
@ -361,13 +361,10 @@ fn edit_actions(
status_form = status_form.with_prop(PropsOp::set(hx::CONFIRM, confirm));
}
let mut container = Container::new()
.with_flex(
Flex::new()
.with_wrap(flex::Behavior::Wrap)
.with_align(flex::Align::Center)
.with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))),
)
let mut container = Flex::new()
.with_wrap(flex::Behavior::Wrap)
.with_align(align::Items::Center)
.with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))
.with_child(
Button::submit(Lc::t("btn-save", &LOCALES_USER))
.with_style(button::Style::Solid(Intent::Primary))