✨ (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
|
|
@ -68,20 +68,20 @@ async fn navbar_expands_at_md_by_default_and_at_the_chosen_breakpoint() {
|
|||
let item =
|
||||
|| bs::navbar::Item::nav(bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")));
|
||||
|
||||
let mut navbar = bs::Navbar::simple().with_item(item());
|
||||
let mut navbar = bs::Navbar::simple_toggle().with_item(item());
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
assert!(html.contains("navbar-expand-md"));
|
||||
// The expansion class comes from the `Navbar` component only, so it is not duplicated.
|
||||
assert_eq!(html.matches("navbar-expand").count(), 1);
|
||||
|
||||
let mut navbar = bs::Navbar::simple()
|
||||
let mut navbar = bs::Navbar::simple_toggle()
|
||||
.with_expand(Breakpoint::Xxxl)
|
||||
.with_item(item());
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
assert!(html.contains("navbar-expand-xxl"));
|
||||
|
||||
// Bootstrap needs a plain `navbar-expand` to keep the bar always expanded.
|
||||
let mut navbar = bs::Navbar::simple()
|
||||
let mut navbar = bs::Navbar::simple_toggle()
|
||||
.with_expand(Breakpoint::Xs)
|
||||
.with_item(item());
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
|
|
@ -89,6 +89,37 @@ async fn navbar_expands_at_md_by_default_and_at_the_chosen_breakpoint() {
|
|||
assert!(!html.contains("navbar-expand-"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn navbar_without_toggle_ignores_the_breakpoint() {
|
||||
let item =
|
||||
|| bs::navbar::Item::nav(bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")));
|
||||
let brand = || Brand::new().with_title(Lc::n("PageTop"));
|
||||
|
||||
// With nothing to collapse, Bootstrap would still stack the menu below the breakpoint.
|
||||
let mut navbar = bs::Navbar::simple()
|
||||
.with_expand(Breakpoint::Lg)
|
||||
.with_item(item());
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
assert!(html.contains("navbar-expand"));
|
||||
assert!(!html.contains("navbar-expand-"));
|
||||
|
||||
let mut navbar = bs::Navbar::simple_brand_left(brand())
|
||||
.with_expand(Breakpoint::Lg)
|
||||
.with_item(item());
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
assert!(html.contains("navbar-expand"));
|
||||
assert!(!html.contains("navbar-expand-"));
|
||||
// The menu stays next to the brand instead of being pushed to the opposite end.
|
||||
assert!(html.contains(r#"<div class="navbar-collapse">"#));
|
||||
|
||||
// The layouts with a toggle keep their breakpoint.
|
||||
let mut navbar = bs::Navbar::brand_left(brand())
|
||||
.with_expand(Breakpoint::Lg)
|
||||
.with_item(item());
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
assert!(html.contains("navbar-expand-lg"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_uses_either_the_plain_or_the_breakpoint_class() {
|
||||
// Bootstrap's `.offcanvas` is always fixed, so it must not accompany `.offcanvas-{bp}`.
|
||||
|
|
|
|||
62
extensions/pagetop-bootsier/tests/navbar_offcanvas.rs
Normal file
62
extensions/pagetop-bootsier/tests/navbar_offcanvas.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Verifies that a `Navbar` with its content in an `Offcanvas` renders a working panel: the toggle
|
||||
// button must point to the `id` of the panel, and the panel must carry its own placement class.
|
||||
|
||||
use pagetop::prelude::*;
|
||||
use pagetop_bootsier::theme::*;
|
||||
|
||||
fn cx() -> Context {
|
||||
Context::default().with_theme(&pagetop_bootsier::Bootsier)
|
||||
}
|
||||
|
||||
fn panel(placement: bs::offcanvas::Placement) -> bs::Offcanvas {
|
||||
bs::Offcanvas::new().with_placement(placement)
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_button_points_to_the_panel_id() {
|
||||
let brand = || Brand::new().with_title(Lc::n("PageTop"));
|
||||
let navbars = [
|
||||
bs::Navbar::offcanvas(panel(bs::offcanvas::Placement::Start)),
|
||||
bs::Navbar::offcanvas_brand_left(brand(), panel(bs::offcanvas::Placement::Start)),
|
||||
bs::Navbar::offcanvas_brand_right(brand(), panel(bs::offcanvas::Placement::Start)),
|
||||
];
|
||||
|
||||
for mut navbar in navbars {
|
||||
navbar = navbar.with_item(bs::navbar::Item::nav(
|
||||
bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")),
|
||||
));
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
|
||||
// The panel is rendered by the navbar, so the navbar must set its `id` up.
|
||||
let target = html
|
||||
.split(r##"data-bs-target="#"##)
|
||||
.nth(1)
|
||||
.and_then(|rest| rest.split('"').next())
|
||||
.unwrap_or_default();
|
||||
assert!(!target.is_empty(), "the button has no target in {html}");
|
||||
assert!(html.contains(&format!(r#"aria-controls="{target}""#)));
|
||||
assert!(
|
||||
html.contains(&format!(r#"id="{target}""#)),
|
||||
"no panel with id `{target}` in {html}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn offcanvas_panel_gets_its_own_classes() {
|
||||
for (placement, class) in [
|
||||
(bs::offcanvas::Placement::Start, "offcanvas-start"),
|
||||
(bs::offcanvas::Placement::End, "offcanvas-end"),
|
||||
] {
|
||||
let mut navbar = bs::Navbar::offcanvas(panel(placement)).with_item(bs::navbar::Item::nav(
|
||||
bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/")),
|
||||
));
|
||||
let html = navbar.render(&mut cx()).await.into_string();
|
||||
|
||||
// Without them Bootstrap would show the panel inline instead of hiding and placing it.
|
||||
assert!(
|
||||
html.contains(&format!(r#"class="offcanvas {class}""#)),
|
||||
"missing `offcanvas {class}` in {html}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue