✨ (base): Implementa Navbar con funcionalidad base
Añade `Navbar::with_position()` y `navbar::Position` al núcleo, con las clases de Basic para barras fijas y pegajosas, y `navbar::Item::form()` para mostrar un formulario dentro de la barra. Las barras sin botón de despliegue (`Simple` y `SimpleBrandLeft`) ya no colapsan: el punto de corte no les afecta y su menú se mantiene en línea, con salto de línea si no cabe. En Bootsier usan `navbar-expand` y `SimpleBrandLeft` agrupa los contenidos junto a la marca.
This commit is contained in:
parent
f8f023b513
commit
c47724bb68
14 changed files with 1298 additions and 209 deletions
|
|
@ -8,12 +8,12 @@
|
|||
//! que identifique la compañía, producto o nombre del proyecto asociado a la solución web.
|
||||
|
||||
mod props;
|
||||
pub use props::{Layout, Position};
|
||||
pub use props::Layout;
|
||||
|
||||
pub use super::Brand;
|
||||
|
||||
pub use pagetop::base::component::Navbar;
|
||||
pub use pagetop::base::component::navbar::Item;
|
||||
pub use pagetop::base::component::navbar::{Item, Position};
|
||||
|
||||
mod component;
|
||||
pub use component::NavbarBootsier;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ const TOGGLE_COLLAPSE: &str = "collapse";
|
|||
const TOGGLE_OFFCANVAS: &str = "offcanvas";
|
||||
|
||||
const EXTRA_LAYOUT: &str = "bootsier.navbar.layout";
|
||||
const EXTRA_POSITION: &str = "bootsier.navbar.position";
|
||||
|
||||
/// Extensión de Bootsier para [`Navbar`].
|
||||
///
|
||||
|
|
@ -151,9 +150,6 @@ pub trait NavbarBootsier {
|
|||
|
||||
/// Crea una barra de navegación con **marca de identidad** y contenido en **offcanvas**.
|
||||
fn offcanvas_brand_right(brand: Brand, oc: bs::Offcanvas) -> Self;
|
||||
|
||||
/// Define dónde se mostrará la barra de navegación dentro del documento.
|
||||
fn with_position(self, position: bs::navbar::Position) -> Self;
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
|
|
@ -184,23 +180,33 @@ impl NavbarBootsier for Navbar {
|
|||
));
|
||||
navbar
|
||||
}
|
||||
|
||||
fn with_position(mut self, position: bs::navbar::Position) -> Self {
|
||||
self.alter_prop(PropsOp::set_extra(EXTRA_POSITION, position));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// **< Navbar SETUP >*******************************************************************************
|
||||
|
||||
pub(crate) fn setup(navbar: &mut Navbar) {
|
||||
let position = navbar
|
||||
// Sin botón de despliegue no hay nada que colapsar, así que el punto de corte no debe afectar a
|
||||
// la barra: Bootstrap apilaría igualmente el menú por debajo de él. `navbar-expand` a secas es
|
||||
// su «expandida siempre». Las disposiciones de Bootsier (`EXTRA_LAYOUT`, con `Offcanvas`)
|
||||
// siempre llevan botón; de las de base sólo `Simple` y `SimpleBrandLeft` no lo llevan.
|
||||
let has_toggle = navbar
|
||||
.props()
|
||||
.extra_or(EXTRA_POSITION, bs::navbar::Position::default());
|
||||
let mut classes = String::new();
|
||||
position.push_to(&mut classes);
|
||||
if !classes.is_empty() {
|
||||
navbar.alter_prop(PropsOp::add_classes(classes));
|
||||
.extra::<bs::navbar::Layout>(EXTRA_LAYOUT)
|
||||
.is_ok()
|
||||
|| !matches!(
|
||||
navbar.layout(),
|
||||
navbar::Layout::Simple | navbar::Layout::SimpleBrandLeft(_)
|
||||
);
|
||||
if !has_toggle {
|
||||
let expand = navbar.props().get_classes().and_then(|classes| {
|
||||
classes
|
||||
.split_whitespace()
|
||||
.find(|class| class.starts_with("navbar-expand-"))
|
||||
.map(String::from)
|
||||
});
|
||||
if let Some(class) = expand {
|
||||
navbar.alter_prop(PropsOp::replace_classes(class, "navbar-expand"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,10 +274,13 @@ pub(crate) async fn render(navbar: &Navbar, cx: &mut Context) -> Result<Markup,
|
|||
}
|
||||
},
|
||||
|
||||
// Barra con marca a la izquierda, siempre visible.
|
||||
// Barra con marca a la izquierda, siempre visible. El contenedor deja los
|
||||
// contenidos junto a la marca: sin él, el `space-between` de `container-fluid`
|
||||
// los llevaría al extremo opuesto. Sin `collapse`, `setup()` garantiza
|
||||
// `navbar-expand` y Bootstrap lo muestra siempre.
|
||||
bs::navbar::Layout::SimpleBrandLeft(brand) => {
|
||||
(brand.render(cx).await)
|
||||
(items)
|
||||
div class="navbar-collapse" { (items) }
|
||||
},
|
||||
|
||||
// Barra con marca a la izquierda y botón a la derecha.
|
||||
|
|
@ -298,32 +307,41 @@ pub(crate) async fn render(navbar: &Navbar, cx: &mut Context) -> Result<Markup,
|
|||
|
||||
// Barra cuyo contenido se muestra en un offcanvas, sin marca.
|
||||
bs::navbar::Layout::Offcanvas(offcanvas) => {
|
||||
@let id_content = offcanvas.id().unwrap_or_default();
|
||||
@let offcanvas = setup_offcanvas(&offcanvas, cx);
|
||||
@let id_content = offcanvas
|
||||
.as_ref()
|
||||
.and_then(|oc| oc.id()).unwrap_or_default();
|
||||
|
||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
||||
@if let Some(oc) = offcanvas.get() {
|
||||
@if let Some(oc) = &offcanvas {
|
||||
(oc.render_offcanvas(cx, Some(navbar.items())).await)
|
||||
}
|
||||
},
|
||||
|
||||
// Barra con marca a la izquierda y contenido en offcanvas.
|
||||
bs::navbar::Layout::OffcanvasBrandLeft(brand, offcanvas) => {
|
||||
@let id_content = offcanvas.id().unwrap_or_default();
|
||||
@let offcanvas = setup_offcanvas(&offcanvas, cx);
|
||||
@let id_content = offcanvas
|
||||
.as_ref()
|
||||
.and_then(|oc| oc.id()).unwrap_or_default();
|
||||
|
||||
(brand.render(cx).await)
|
||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
||||
@if let Some(oc) = offcanvas.get() {
|
||||
@if let Some(oc) = &offcanvas {
|
||||
(oc.render_offcanvas(cx, Some(navbar.items())).await)
|
||||
}
|
||||
},
|
||||
|
||||
// Barra con contenido en offcanvas y marca a la derecha.
|
||||
bs::navbar::Layout::OffcanvasBrandRight(brand, offcanvas) => {
|
||||
@let id_content = offcanvas.id().unwrap_or_default();
|
||||
@let offcanvas = setup_offcanvas(&offcanvas, cx);
|
||||
@let id_content = offcanvas
|
||||
.as_ref()
|
||||
.and_then(|oc| oc.id()).unwrap_or_default();
|
||||
|
||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
||||
(brand.render(cx).await)
|
||||
@if let Some(oc) = offcanvas.get() {
|
||||
@if let Some(oc) = &offcanvas {
|
||||
(oc.render_offcanvas(cx, Some(navbar.items())).await)
|
||||
}
|
||||
},
|
||||
|
|
@ -333,6 +351,16 @@ pub(crate) async fn render(navbar: &Navbar, cx: &mut Context) -> Result<Markup,
|
|||
})
|
||||
}
|
||||
|
||||
// El panel se renderiza con `render_offcanvas()` para inyectarle los contenidos de la barra, así
|
||||
// que no pasa por el ciclo de vida normal. Se clona y se le aplica aquí su `setup()`; sin él no
|
||||
// tendría `id` (el botón lo necesita para referenciarlo) ni las clases `offcanvas*` que lo ocultan
|
||||
// y lo colocan.
|
||||
fn setup_offcanvas(offcanvas: &Embed<bs::Offcanvas>, cx: &mut Context) -> Option<bs::Offcanvas> {
|
||||
let mut oc = offcanvas.get()?.clone();
|
||||
oc.setup(cx);
|
||||
Some(oc)
|
||||
}
|
||||
|
||||
// Traduce el `navbar::Layout` semántico de base (sin `Offcanvas`, sin `Position`/`expand`) a la
|
||||
// variante equivalente de `bs::navbar::Layout`, para las barras construidas con los constructores
|
||||
// heredados de `Navbar` (`simple()`, `simple_toggle()`, `simple_brand_left()`, `brand_left()`,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ use pagetop::prelude::*;
|
|||
|
||||
use crate::theme::*;
|
||||
|
||||
// **< Layout >*************************************************************************************
|
||||
|
||||
/// Representa los diferentes tipos de presentación de una barra de navegación
|
||||
/// [`Navbar`](crate::theme::bs::Navbar).
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
|
|
@ -40,62 +38,3 @@ pub enum Layout {
|
|||
/// izquierda y marca de identidad a la derecha.
|
||||
OffcanvasBrandRight(Embed<Brand>, Embed<bs::Offcanvas>),
|
||||
}
|
||||
|
||||
// **< Position >***********************************************************************************
|
||||
|
||||
/// Posición global de una barra de navegación [`Navbar`](crate::theme::bs::Navbar) en el documento.
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Position {
|
||||
/// Barra normal, fluye con el documento.
|
||||
#[default]
|
||||
Static,
|
||||
/// Barra fijada en la parte superior, siempre visible.
|
||||
///
|
||||
/// Puede ser necesario reservar espacio en la parte superior del contenido que fluye debajo
|
||||
/// para evitar que quede oculto por la barra.
|
||||
FixedTop,
|
||||
/// Barra fijada en la parte inferior, siempre visible.
|
||||
///
|
||||
/// Puede ser necesario reservar espacio en la parte inferior del contenido que fluye debajo
|
||||
/// para evitar que quede oculto por la barra.
|
||||
FixedBottom,
|
||||
/// La barra de navegación se fija en la parte superior al hacer *scroll*.
|
||||
StickyTop,
|
||||
/// La barra de navegación se fija en la parte inferior al hacer *scroll*.
|
||||
StickyBottom,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
/// Devuelve la clase base asociada a la posición de la barra de navegación.
|
||||
#[rustfmt::skip]
|
||||
#[inline]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Static => "",
|
||||
Self::FixedTop => "fixed-top",
|
||||
Self::FixedBottom => "fixed-bottom",
|
||||
Self::StickyTop => "sticky-top",
|
||||
Self::StickyBottom => "sticky-bottom",
|
||||
}
|
||||
}
|
||||
|
||||
/// Añade la clase asociada a la posición de la barra de navegación a la cadena de clases.
|
||||
#[inline]
|
||||
pub fn push_to(self, classes: &mut String) {
|
||||
let class = self.as_str();
|
||||
if class.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !classes.is_empty() {
|
||||
classes.push(' ');
|
||||
}
|
||||
classes.push_str(class);
|
||||
}
|
||||
|
||||
/// Devuelve la clase asociada a la posición de la barra de navegación.
|
||||
pub fn to_class(self) -> String {
|
||||
let mut class = String::new();
|
||||
self.push_to(&mut class);
|
||||
class
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue