use crate::prelude::*; /// Componente para crear una **barra de navegación**. /// /// Permite mostrar enlaces, menús desplegables ([`nav::Item::dropdown()`]) y una marca de /// identidad, en distintas disposiciones controladas por [`navbar::Layout`]. /// /// Si no contiene elementos, el componente **no se renderiza**. /// /// # Ejemplos /// /// Barra **simple**, sólo con un menú horizontal: /// /// ```rust,no_run /// use pagetop::prelude::*; /// /// let navbar = Navbar::simple() /// .with_item(navbar::Item::nav( /// Nav::new() /// .with_item(nav::Item::link(Lc::n("Home"), "/")) /// .with_item(nav::Item::link(Lc::n("About"), "/about")) /// .with_item(nav::Item::link(Lc::n("Contact"), "/contact")), /// )); /// ``` /// /// Barra **colapsable**, con botón de despliegue: /// /// ```rust,no_run /// # use pagetop::prelude::*; /// let navbar = Navbar::simple_toggle() /// .with_item(navbar::Item::nav( /// Nav::new() /// .with_item(nav::Item::link(Lc::n("Home"), "/")) /// .with_item(nav::Item::link_blank(Lc::n("Doc"), "https://docs.rs")) /// .with_item(nav::Item::link(Lc::n("Support"), "/support")), /// )); /// ``` /// /// Barra con **marca de identidad** y menú, con menús desplegables: /// /// ```rust,no_run /// # use pagetop::prelude::*; /// let brand = Brand::new() /// .with_title(Lc::n("PageTop")) /// .with_route(Route::from("/")); /// /// let navbar = Navbar::brand_left(brand) /// .with_item(navbar::Item::nav( /// Nav::new() /// .with_item(nav::Item::link(Lc::n("Home"), "/")) /// .with_item(nav::Item::dropdown( /// Dropdown::new() /// .with_title(Lc::n("Tools")) /// .with_item(dropdown::Item::link(Lc::n("Generator"), "/tools/gen")) /// .with_item(dropdown::Item::link(Lc::n("Reports"), "/tools/reports")), /// )) /// .with_item(nav::Item::link_disabled(Lc::n("Disabled"), "#")), /// )); /// ``` /// /// Barra con **botón de despliegue** y **marca de identidad**, en ese orden: /// /// ```rust,no_run /// # use pagetop::prelude::*; /// let brand = Brand::new() /// .with_title(Lc::n("Intranet")) /// .with_route(Route::from("/")); /// /// let navbar = Navbar::brand_right(brand).with_item(navbar::Item::nav( /// Nav::new() /// .with_item(nav::Item::link(Lc::n("Dashboard"), "/dashboard")) /// .with_item(nav::Item::link(Lc::n("Users"), "/users")), /// )); /// ``` /// /// [`nav::Item::dropdown()`]: super::super::nav::Item::dropdown /// [`navbar::Layout`]: super::Layout #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Navbar { /// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente. 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: Flex, /// Devuelve la lista de contenidos. items: Children, } #[async_trait] impl Component for Navbar { fn new() -> Self { Self::default() } fn id(&self) -> Option { self.props.get_id() } fn setup(&mut self, cx: &mut Context) { // Asegura que la barra de navegación tiene un identificador único: lo necesita el botón de // despliegue para referenciar el contenido colapsable con `aria-controls`. self.alter_prop(PropsOp::ensure_id(cx.build_id::(1))); self.alter_prop(PropsOp::prepend_classes("navbar")); } async fn prepare(&self, cx: &mut Context) -> Result { // Si no hay contenidos, no tiene sentido mostrar una barra vacía. let items = self.items().render(cx).await; if items.is_empty() { return Ok(html! {}); } // `setup()` garantiza que habrá un `id` antes de renderizar. let id = self.id().unwrap(); let id_content = util::join!(id, "-content"); // Botón de despliegue para el contenido colapsable de la barra. let button = html! { button type="button" class="navbar-toggle" aria-expanded="false" aria-controls=(&id_content) aria-label=[Lc::l("navbar_toggle").lookup(cx)] { span class="navbar-toggle-icon" {} } }; // Posicionamiento Flexbox opcional (no del `