Compare commits
No commits in common. "93804721e161e5e1d52800df9147b55cdd02eeb8" and "2f41f166f315fd39eeb10a1fb722ee853d72d429" have entirely different histories.
93804721e1
...
2f41f166f3
22 changed files with 495 additions and 739 deletions
|
|
@ -13,8 +13,7 @@ fn main() -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bootstrap_js_files(path: &Path) -> bool {
|
fn bootstrap_js_files(path: &Path) -> bool {
|
||||||
let bootstrap_js = "bootstrap.bundle.min.js";
|
|
||||||
// No filtra durante el desarrollo, solo en la compilación "release".
|
// No filtra durante el desarrollo, solo en la compilación "release".
|
||||||
env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release"
|
env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release"
|
||||||
|| path.file_name().is_some_and(|f| f == bootstrap_js)
|
|| path.file_name().is_some_and(|n| n == "bootstrap.min.js")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ impl Theme for Bootsier {
|
||||||
.with_weight(-90),
|
.with_weight(-90),
|
||||||
))
|
))
|
||||||
.alter_assets(ContextOp::AddJavaScript(
|
.alter_assets(ContextOp::AddJavaScript(
|
||||||
JavaScript::defer("/bootsier/js/bootstrap.bundle.min.js")
|
JavaScript::defer("/bootsier/js/bootstrap.min.js")
|
||||||
.with_version(BOOTSTRAP_VERSION)
|
.with_version(BOOTSTRAP_VERSION)
|
||||||
.with_weight(-90),
|
.with_weight(-90),
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub use nav::Nav;
|
||||||
// Navbar.
|
// Navbar.
|
||||||
pub mod navbar;
|
pub mod navbar;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use navbar::Navbar;
|
pub use navbar::{Navbar, NavbarToggler};
|
||||||
|
|
||||||
// Offcanvas.
|
// Offcanvas.
|
||||||
pub mod offcanvas;
|
pub mod offcanvas;
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,6 @@
|
||||||
//!
|
//!
|
||||||
//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y
|
//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y
|
||||||
//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n).
|
//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n).
|
||||||
//!
|
|
||||||
//! # Ejemplo
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let dd = Dropdown::new()
|
|
||||||
//! .with_title(L10n::n("Menu"))
|
|
||||||
//! .with_button_color(ButtonColor::Background(Color::Secondary))
|
|
||||||
//! .with_auto_close(dropdown::AutoClose::ClickableInside)
|
|
||||||
//! .with_direction(dropdown::Direction::Dropend)
|
|
||||||
//! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/"))
|
|
||||||
//! .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://www.google.es"))
|
|
||||||
//! .add_item(dropdown::Item::divider())
|
|
||||||
//! .add_item(dropdown::Item::header(L10n::n("User session")))
|
|
||||||
//! .add_item(dropdown::Item::button(L10n::n("Sign out")));
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::{AutoClose, Direction, MenuAlign, MenuPosition};
|
pub use props::{AutoClose, Direction, MenuAlign, MenuPosition};
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,22 @@ use crate::LOCALES_BOOTSIER;
|
||||||
/// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el
|
/// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el
|
||||||
/// resto de propiedades no afectarán a su representación en [`Nav`].
|
/// resto de propiedades no afectarán a su representación en [`Nav`].
|
||||||
///
|
///
|
||||||
/// Ver ejemplo en el módulo [`dropdown`].
|
/// # Ejemplo
|
||||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// let dd = Dropdown::new()
|
||||||
|
/// .with_title(L10n::n("Menu"))
|
||||||
|
/// .with_button_color(ButtonColor::Background(Color::Secondary))
|
||||||
|
/// .with_auto_close(dropdown::AutoClose::ClickableInside)
|
||||||
|
/// .with_direction(dropdown::Direction::Dropend)
|
||||||
|
/// .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/"))
|
||||||
|
/// .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://www.google.es"))
|
||||||
|
/// .add_item(dropdown::Item::divider())
|
||||||
|
/// .add_item(dropdown::Item::header(L10n::n("User session")))
|
||||||
|
/// .add_item(dropdown::Item::button(L10n::n("Sign out")));
|
||||||
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub struct Dropdown {
|
pub struct Dropdown {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl Component for Item {
|
||||||
} => {
|
} => {
|
||||||
let path = path(cx);
|
let path = path(cx);
|
||||||
let current_path = cx.request().map(|request| request.path());
|
let current_path = cx.request().map(|request| request.path());
|
||||||
let is_current = !*disabled && (current_path == Some(path));
|
let is_current = !*disabled && current_path.map_or(false, |p| p == path);
|
||||||
|
|
||||||
let mut classes = "dropdown-item".to_string();
|
let mut classes = "dropdown-item".to_string();
|
||||||
if is_current {
|
if is_current {
|
||||||
|
|
@ -274,7 +274,7 @@ impl Item {
|
||||||
&self.classes
|
&self.classes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el tipo de elemento representado.
|
/// Devuelve el tipo de elemento representado por este elemento.
|
||||||
pub fn item_kind(&self) -> &ItemKind {
|
pub fn item_kind(&self) -> &ItemKind {
|
||||||
&self.item_kind
|
&self.item_kind
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,6 @@
|
||||||
//!
|
//!
|
||||||
//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y
|
//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y
|
||||||
//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n).
|
//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n).
|
||||||
//!
|
|
||||||
//! # Ejemplo
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let nav = Nav::tabs()
|
|
||||||
//! .with_layout(nav::Layout::End)
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/"))
|
|
||||||
//! .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://www.google.es"))
|
|
||||||
//! .add_item(nav::Item::dropdown(
|
|
||||||
//! Dropdown::new()
|
|
||||||
//! .with_title(L10n::n("Options"))
|
|
||||||
//! .with_items(TypedOp::AddMany(vec![
|
|
||||||
//! Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action")),
|
|
||||||
//! Typed::with(dropdown::Item::link(L10n::n("Another action"), |_| "/another")),
|
|
||||||
//! ])),
|
|
||||||
//! ))
|
|
||||||
//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#"));
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::{Kind, Layout};
|
pub use props::{Kind, Layout};
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,25 @@ use crate::prelude::*;
|
||||||
/// como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). También permite
|
/// como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). También permite
|
||||||
/// controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)).
|
/// controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)).
|
||||||
///
|
///
|
||||||
/// Ver ejemplo en el módulo [`nav`].
|
/// # Ejemplo
|
||||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// let nav = Nav::tabs()
|
||||||
|
/// .with_layout(nav::Layout::End)
|
||||||
|
/// .add_item(nav::Item::link(L10n::n("Home"), |_| "/"))
|
||||||
|
/// .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://www.google.es"))
|
||||||
|
/// .add_item(nav::Item::dropdown(
|
||||||
|
/// Dropdown::new()
|
||||||
|
/// .with_title(L10n::n("Options"))
|
||||||
|
/// .with_items(TypedOp::AddMany(vec![
|
||||||
|
/// Typed::with(dropdown::Item::link(L10n::n("Action"), |_| "/action")),
|
||||||
|
/// Typed::with(dropdown::Item::link(L10n::n("Another action"), |_| "/another")),
|
||||||
|
/// ])),
|
||||||
|
/// ))
|
||||||
|
/// .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#"));
|
||||||
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub struct Nav {
|
pub struct Nav {
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl Component for Item {
|
||||||
|
|
||||||
ItemKind::Label(label) => PrepareMarkup::With(html! {
|
ItemKind::Label(label) => PrepareMarkup::With(html! {
|
||||||
li id=[self.id()] class=[self.classes().get()] {
|
li id=[self.id()] class=[self.classes().get()] {
|
||||||
span class="nav-link disabled" aria-disabled="true" {
|
span {
|
||||||
(label.using(cx))
|
(label.using(cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ impl Component for Item {
|
||||||
} => {
|
} => {
|
||||||
let path = path(cx);
|
let path = path(cx);
|
||||||
let current_path = cx.request().map(|request| request.path());
|
let current_path = cx.request().map(|request| request.path());
|
||||||
let is_current = !*disabled && (current_path == Some(path));
|
let is_current = !*disabled && current_path.map_or(false, |p| p == path);
|
||||||
|
|
||||||
let mut classes = "nav-link".to_string();
|
let mut classes = "nav-link".to_string();
|
||||||
if is_current {
|
if is_current {
|
||||||
|
|
@ -250,7 +250,7 @@ impl Item {
|
||||||
&self.classes
|
&self.classes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el tipo de elemento representado.
|
/// Devuelve el tipo de elemento representado por este elemento.
|
||||||
pub fn item_kind(&self) -> &ItemKind {
|
pub fn item_kind(&self) -> &ItemKind {
|
||||||
&self.item_kind
|
&self.item_kind
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,136 +1,11 @@
|
||||||
//! Definiciones para crear barras de navegación [`Navbar`].
|
mod component;
|
||||||
//!
|
pub use component::{Navbar, NavbarToggler, NavbarType};
|
||||||
//! Cada [`navbar::Item`](crate::theme::navbar::Item) representa un elemento individual de la barra
|
|
||||||
//! de navegación [`Navbar`], con distintos comportamientos según su finalidad, como menús
|
|
||||||
//! [`Nav`](crate::theme::Nav) o textos localizados usando [`L10n`](pagetop::locale::L10n).
|
|
||||||
//!
|
|
||||||
//! También puede mostrar una marca de identidad ([`navbar::Brand`](crate::theme::navbar::Brand))
|
|
||||||
//! que identifique la compañía, producto o nombre del proyecto asociado a la solución web.
|
|
||||||
//!
|
|
||||||
//! # Ejemplos
|
|
||||||
//!
|
|
||||||
//! Barra **simple**, sólo con un menú horizontal:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let navbar = Navbar::simple()
|
|
||||||
//! .add_item(navbar::Item::nav(
|
|
||||||
//! Nav::new()
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("About"), |_| "/about"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Contact"), |_| "/contact"))
|
|
||||||
//! ));
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let navbar = Navbar::simple_toggle()
|
|
||||||
//! .with_expand(BreakPoint::MD)
|
|
||||||
//! .add_item(navbar::Item::nav(
|
|
||||||
//! Nav::new()
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/"))
|
|
||||||
//! .add_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.example.com"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Support"), |_| "/support"))
|
|
||||||
//! ));
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let brand = navbar::Brand::new()
|
|
||||||
//! .with_title(L10n::n("PageTop"))
|
|
||||||
//! .with_path(Some(|_| "/"));
|
|
||||||
//!
|
|
||||||
//! let navbar = Navbar::brand_left(brand)
|
|
||||||
//! .add_item(navbar::Item::nav(
|
|
||||||
//! Nav::new()
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/"))
|
|
||||||
//! .add_item(nav::Item::dropdown(
|
|
||||||
//! Dropdown::new()
|
|
||||||
//! .with_title(L10n::n("Tools"))
|
|
||||||
//! .add_item(dropdown::Item::link(L10n::n("Generator"), |_| "/tools/gen"))
|
|
||||||
//! .add_item(dropdown::Item::link(L10n::n("Reports"), |_| "/tools/reports"))
|
|
||||||
//! ))
|
|
||||||
//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#"))
|
|
||||||
//! ));
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let brand = navbar::Brand::new()
|
|
||||||
//! .with_title(L10n::n("Intranet"))
|
|
||||||
//! .with_path(Some(|_| "/"));
|
|
||||||
//!
|
|
||||||
//! let navbar = Navbar::brand_right(brand)
|
|
||||||
//! .with_expand(BreakPoint::LG)
|
|
||||||
//! .add_item(navbar::Item::nav(
|
|
||||||
//! Nav::pills()
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users"))
|
|
||||||
//! ));
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let oc = Offcanvas::new()
|
|
||||||
//! .with_id("main_offcanvas")
|
|
||||||
//! .with_title(L10n::n("Main menu"))
|
|
||||||
//! .with_placement(offcanvas::Placement::Start)
|
|
||||||
//! .with_backdrop(offcanvas::Backdrop::Enabled);
|
|
||||||
//!
|
|
||||||
//! let navbar = Navbar::offcanvas(oc)
|
|
||||||
//! .add_item(navbar::Item::nav(
|
|
||||||
//! Nav::new()
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile"))
|
|
||||||
//! .add_item(nav::Item::dropdown(
|
|
||||||
//! Dropdown::new()
|
|
||||||
//! .with_title(L10n::n("More"))
|
|
||||||
//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings"))
|
|
||||||
//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help"))
|
|
||||||
//! ))
|
|
||||||
//! ));
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Barra **fija arriba**:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let brand = navbar::Brand::new()
|
|
||||||
//! .with_title(L10n::n("Main App"))
|
|
||||||
//! .with_path(Some(|_| "/"));
|
|
||||||
//!
|
|
||||||
//! let navbar = Navbar::brand_left(brand)
|
|
||||||
//! .with_position(navbar::Position::FixedTop)
|
|
||||||
//! .add_item(navbar::Item::nav(
|
|
||||||
//! Nav::new()
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Donors"), |_| "/donors"))
|
|
||||||
//! .add_item(nav::Item::link(L10n::n("Stock"), |_| "/stock"))
|
|
||||||
//! ));
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
mod props;
|
mod button_toggler;
|
||||||
pub use props::{Layout, Position};
|
pub use button_toggler::ButtonToggler;
|
||||||
|
|
||||||
|
mod content;
|
||||||
|
pub use content::{Content, ContentType};
|
||||||
|
|
||||||
mod brand;
|
mod brand;
|
||||||
pub use brand::Brand;
|
pub use brand::Brand;
|
||||||
|
|
||||||
mod component;
|
|
||||||
pub use component::Navbar;
|
|
||||||
|
|
||||||
mod item;
|
|
||||||
pub use item::Item;
|
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,16 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Marca de identidad para mostrar en una barra de navegación [`Navbar`].
|
|
||||||
///
|
|
||||||
/// Representa la identidad del sitio con una imagen, título y eslogan:
|
|
||||||
///
|
|
||||||
/// - Si hay URL ([`with_path()`](Self::with_path)), el bloque completo actúa como enlace. Por
|
|
||||||
/// defecto enlaza a la raíz del sitio (`/`).
|
|
||||||
/// - Si no hay imagen ([`with_image()`](Self::with_image)) ni título
|
|
||||||
/// ([`with_title()`](Self::with_title)), la marca de identidad no se renderiza.
|
|
||||||
/// - El eslogan ([`with_slogan()`](Self::with_slogan)) es opcional; por defecto no tiene contenido.
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub struct Brand {
|
pub struct Brand {
|
||||||
id : AttrId,
|
id : AttrId,
|
||||||
image : Typed<Image>,
|
#[default(_code = "global::SETTINGS.app.name.to_owned()")]
|
||||||
#[default(_code = "L10n::n(&global::SETTINGS.app.name)")]
|
app_name : String,
|
||||||
title : L10n,
|
slogan : AttrL10n,
|
||||||
slogan: L10n,
|
logo : Typed<Image>,
|
||||||
#[default(_code = "Some(|_| \"/\")")]
|
#[default(_code = "|_| \"/\"")]
|
||||||
path : Option<FnPathByContext>,
|
home : FnPathByContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Brand {
|
impl Component for Brand {
|
||||||
|
|
@ -33,79 +24,81 @@ impl Component for Brand {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
let image = self.image().render(cx);
|
let logo = self.logo().render(cx);
|
||||||
let title = self.title().using(cx);
|
let home = self.home()(cx);
|
||||||
if title.is_empty() && image.is_empty() {
|
let title = &L10n::l("site_home").lookup(cx);
|
||||||
return PrepareMarkup::None;
|
|
||||||
}
|
|
||||||
let slogan = self.slogan().using(cx);
|
|
||||||
PrepareMarkup::With(html! {
|
PrepareMarkup::With(html! {
|
||||||
@if let Some(path) = self.path() {
|
div id=[self.id()] class="branding__container" {
|
||||||
a class="navbar-brand" href=(path(cx)) { (image) (title) (slogan) }
|
div class="branding__content" {
|
||||||
} @else {
|
@if !logo.is_empty() {
|
||||||
span class="navbar-brand" { (image) (title) (slogan) }
|
a class="branding__logo" href=(home) title=[title] rel="home" {
|
||||||
|
(logo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div class="branding__text" {
|
||||||
|
a class="branding__name" href=(home) title=[title] rel="home" {
|
||||||
|
(self.app_name())
|
||||||
|
}
|
||||||
|
@if let Some(slogan) = self.slogan().lookup(cx) {
|
||||||
|
div class="branding__slogan" {
|
||||||
|
(slogan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Brand {
|
impl Brand {
|
||||||
// **< Brand BUILDER >**************************************************************************
|
// Brand BUILDER.
|
||||||
|
|
||||||
/// Establece el identificador único (`id`) de la marca.
|
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
self.id.alter_value(id);
|
self.id.alter_value(id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asigna o quita la imagen de marca. Si se pasa `None`, no se mostrará.
|
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_image(mut self, image: Option<Image>) -> Self {
|
pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self {
|
||||||
self.image.alter_component(image);
|
self.app_name = app_name.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Establece el título de la identidad de marca.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_title(mut self, title: L10n) -> Self {
|
|
||||||
self.title = title;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define el eslogan de la marca.
|
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_slogan(mut self, slogan: L10n) -> Self {
|
pub fn with_slogan(mut self, slogan: L10n) -> Self {
|
||||||
self.slogan = slogan;
|
self.slogan.alter_value(slogan);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define la URL de destino. Si es `None`, la marca no será un enlace.
|
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_path(mut self, path: Option<FnPathByContext>) -> Self {
|
pub fn with_logo(mut self, logo: Option<Image>) -> Self {
|
||||||
self.path = path;
|
self.logo.alter_component(logo);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Brand GETTERS >**************************************************************************
|
#[builder_fn]
|
||||||
|
pub fn with_home(mut self, home: FnPathByContext) -> Self {
|
||||||
/// Devuelve la imagen de marca (si la hay).
|
self.home = home;
|
||||||
pub fn image(&self) -> &Typed<Image> {
|
self
|
||||||
&self.image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el título de la identidad de marca.
|
// Brand GETTERS.
|
||||||
pub fn title(&self) -> &L10n {
|
|
||||||
&self.title
|
pub fn app_name(&self) -> &String {
|
||||||
|
&self.app_name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el eslogan de la marca.
|
pub fn slogan(&self) -> &AttrL10n {
|
||||||
pub fn slogan(&self) -> &L10n {
|
|
||||||
&self.slogan
|
&self.slogan
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve la función que resuelve la URL asociada a la marca (si existe).
|
pub fn logo(&self) -> &Typed<Image> {
|
||||||
pub fn path(&self) -> &Option<FnPathByContext> {
|
&self.logo
|
||||||
&self.path
|
}
|
||||||
|
|
||||||
|
pub fn home(&self) -> &FnPathByContext {
|
||||||
|
&self.home
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::LOCALES_BOOTSIER;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(AutoDefault, PartialEq)]
|
||||||
|
pub(crate) enum Toggle {
|
||||||
|
#[default]
|
||||||
|
Collapse,
|
||||||
|
Offcanvas,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl fmt::Display for Toggle {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Toggle::Collapse => write!(f, "collapse"),
|
||||||
|
Toggle::Offcanvas => write!(f, "offcanvas"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AutoDefault)]
|
||||||
|
pub struct ButtonToggler;
|
||||||
|
|
||||||
|
impl Component for ButtonToggler {
|
||||||
|
fn new() -> Self {
|
||||||
|
ButtonToggler::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||||
|
PrepareMarkup::With(html! {
|
||||||
|
button
|
||||||
|
type="button"
|
||||||
|
class="navbar-toggler"
|
||||||
|
{
|
||||||
|
span class="navbar-toggler-icon" {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonToggler {
|
||||||
|
// ButtonToggler PRIVATE RENDER.
|
||||||
|
|
||||||
|
pub(crate) fn render(
|
||||||
|
&self,
|
||||||
|
cx: &mut Context,
|
||||||
|
id_content: String,
|
||||||
|
data_bs_toggle: Toggle,
|
||||||
|
) -> Markup {
|
||||||
|
let id_content_target = join!("#", id_content);
|
||||||
|
let aria_expanded = if data_bs_toggle == Toggle::Collapse {
|
||||||
|
Some("false")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
html! {
|
||||||
|
button
|
||||||
|
type="button"
|
||||||
|
class="navbar-toggler"
|
||||||
|
data-bs-toggle=(data_bs_toggle)
|
||||||
|
data-bs-target=(id_content_target)
|
||||||
|
aria-controls=(id_content)
|
||||||
|
aria-expanded=[aria_expanded]
|
||||||
|
aria-label=[L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx)]
|
||||||
|
{
|
||||||
|
span class="navbar-toggler-icon" {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,23 +6,32 @@ use crate::LOCALES_BOOTSIER;
|
||||||
const TOGGLE_COLLAPSE: &str = "collapse";
|
const TOGGLE_COLLAPSE: &str = "collapse";
|
||||||
const TOGGLE_OFFCANVAS: &str = "offcanvas";
|
const TOGGLE_OFFCANVAS: &str = "offcanvas";
|
||||||
|
|
||||||
/// Componente para crear una **barra de navegación**.
|
#[derive(AutoDefault)]
|
||||||
///
|
pub enum NavbarToggler {
|
||||||
/// Permite mostrar enlaces, menús y una marca de identidad en distintas disposiciones (simples, con
|
#[default]
|
||||||
/// botón de despliegue o dentro de un [`offcanvas`]), controladas por [`navbar::Layout`]. También
|
Enabled,
|
||||||
/// puede fijarse en la parte superior o inferior del documento mediante [`navbar::Position`].
|
Disabled,
|
||||||
///
|
}
|
||||||
/// Ver ejemplos en el módulo [`navbar`].
|
|
||||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
#[derive(AutoDefault)]
|
||||||
|
pub enum NavbarType {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Nav(Typed<navbar::Nav>),
|
||||||
|
Offcanvas(Typed<Offcanvas>),
|
||||||
|
Text(L10n),
|
||||||
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub struct Navbar {
|
pub struct Navbar {
|
||||||
id : AttrId,
|
id : AttrId,
|
||||||
classes : AttrClasses,
|
classes : AttrClasses,
|
||||||
expand : BreakPoint,
|
expand : BreakPoint,
|
||||||
layout : navbar::Layout,
|
toggler : NavbarToggler,
|
||||||
position: navbar::Position,
|
navbar_type: NavbarType,
|
||||||
items : Children,
|
contents : Children,
|
||||||
|
brand : Typed<navbar::Brand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Navbar {
|
impl Component for Navbar {
|
||||||
|
|
@ -40,22 +49,162 @@ impl Component for Navbar {
|
||||||
[
|
[
|
||||||
"navbar".to_string(),
|
"navbar".to_string(),
|
||||||
self.expand().try_class("navbar-expand").unwrap_or_default(),
|
self.expand().try_class("navbar-expand").unwrap_or_default(),
|
||||||
match self.position() {
|
|
||||||
navbar::Position::Static => "",
|
|
||||||
navbar::Position::FixedTop => "fixed-top",
|
|
||||||
navbar::Position::FixedBottom => "fixed-bottom",
|
|
||||||
navbar::Position::StickyTop => "sticky-top",
|
|
||||||
navbar::Position::StickyBottom => "sticky-bottom",
|
|
||||||
}
|
|
||||||
.to_string(),
|
|
||||||
]
|
]
|
||||||
.join(" "),
|
.join(" "),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
// Botón de despliegue (colapso u offcanvas) para la barra.
|
let id = cx.required_id::<Self>(self.id());
|
||||||
fn button(cx: &mut Context, data_bs_toggle: &str, id_content: &str) -> Markup {
|
|
||||||
|
let navbar_type = match self.navbar_type() {
|
||||||
|
NavbarType::None => return PrepareMarkup::None,
|
||||||
|
NavbarType::Nav(nav) => {
|
||||||
|
let id_content = join!(id, "-content");
|
||||||
|
match self.toggler() {
|
||||||
|
NavbarToggler::Enabled => self.toggler_wrapper(
|
||||||
|
TOGGLE_COLLAPSE,
|
||||||
|
L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx),
|
||||||
|
id_content,
|
||||||
|
self.brand().render(cx),
|
||||||
|
nav.render(cx),
|
||||||
|
),
|
||||||
|
NavbarToggler::Disabled => nav.render(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NavbarType::Offcanvas(oc) => {
|
||||||
|
let id_content = oc.id().unwrap_or_default();
|
||||||
|
self.toggler_wrapper(
|
||||||
|
TOGGLE_OFFCANVAS,
|
||||||
|
L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx),
|
||||||
|
id_content,
|
||||||
|
self.brand().render(cx),
|
||||||
|
oc.render(cx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
NavbarType::Text(text) => html! {
|
||||||
|
span class="navbar-text" {
|
||||||
|
(text.using(cx))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.nav_wrapper(id, self.brand().render(cx), navbar_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Navbar {
|
||||||
|
pub fn with_nav(nav: navbar::Nav) -> Self {
|
||||||
|
Navbar::default().with_navbar_type(NavbarType::Nav(Typed::with(nav)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_offcanvas(offcanvas: Offcanvas) -> Self {
|
||||||
|
Navbar::default().with_navbar_type(NavbarType::Offcanvas(Typed::with(offcanvas)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar BUILDER.
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
|
self.id.alter_value(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
|
self.classes.alter_value(op, classes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_expand(mut self, bp: BreakPoint) -> Self {
|
||||||
|
self.expand = bp;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_toggler(mut self, toggler: NavbarToggler) -> Self {
|
||||||
|
self.toggler = toggler;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_navbar_type(mut self, navbar_type: NavbarType) -> Self {
|
||||||
|
self.navbar_type = navbar_type;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_content(mut self, content: navbar::Content) -> Self {
|
||||||
|
self.contents.add(Child::with(content));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_contents(mut self, op: TypedOp<navbar::Content>) -> Self {
|
||||||
|
self.contents.alter_typed(op);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_brand(mut self, brand: Option<navbar::Brand>) -> Self {
|
||||||
|
self.brand.alter_component(brand);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar GETTERS.
|
||||||
|
|
||||||
|
pub fn classes(&self) -> &AttrClasses {
|
||||||
|
&self.classes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand(&self) -> &BreakPoint {
|
||||||
|
&self.expand
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggler(&self) -> &NavbarToggler {
|
||||||
|
&self.toggler
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn navbar_type(&self) -> &NavbarType {
|
||||||
|
&self.navbar_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contents(&self) -> &Children {
|
||||||
|
&self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brand(&self) -> &Typed<navbar::Brand> {
|
||||||
|
&self.brand
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar HELPERS.
|
||||||
|
|
||||||
|
fn nav_wrapper(&self, id: String, brand: Markup, content: Markup) -> PrepareMarkup {
|
||||||
|
if content.is_empty() {
|
||||||
|
PrepareMarkup::None
|
||||||
|
} else {
|
||||||
|
PrepareMarkup::With(html! {
|
||||||
|
(brand)
|
||||||
|
nav id=(id) class=[self.classes().get()] {
|
||||||
|
div class="container-fluid" {
|
||||||
|
(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggler_wrapper(
|
||||||
|
&self,
|
||||||
|
data_bs_toggle: &str,
|
||||||
|
aria_label: Option<String>,
|
||||||
|
id_content: String,
|
||||||
|
brand: Markup,
|
||||||
|
content: Markup,
|
||||||
|
) -> Markup {
|
||||||
|
if content.is_empty() {
|
||||||
|
html! {}
|
||||||
|
} else {
|
||||||
let id_content_target = join!("#", id_content);
|
let id_content_target = join!("#", id_content);
|
||||||
let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE {
|
let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE {
|
||||||
Some("false")
|
Some("false")
|
||||||
|
|
@ -63,6 +212,7 @@ impl Component for Navbar {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
html! {
|
html! {
|
||||||
|
(brand)
|
||||||
button
|
button
|
||||||
type="button"
|
type="button"
|
||||||
class="navbar-toggler"
|
class="navbar-toggler"
|
||||||
|
|
@ -70,229 +220,14 @@ impl Component for Navbar {
|
||||||
data-bs-target=(id_content_target)
|
data-bs-target=(id_content_target)
|
||||||
aria-controls=(id_content)
|
aria-controls=(id_content)
|
||||||
aria-expanded=[aria_expanded]
|
aria-expanded=[aria_expanded]
|
||||||
aria-label=[L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx)]
|
aria-label=[aria_label]
|
||||||
{
|
{
|
||||||
span class="navbar-toggler-icon" {}
|
span class="navbar-toggler-icon" {}
|
||||||
}
|
}
|
||||||
}
|
div id=(id_content) class="collapse navbar-collapse" {
|
||||||
}
|
(content)
|
||||||
|
|
||||||
// Si no hay contenidos, no tiene sentido mostrar una barra vacía.
|
|
||||||
let items = self.items().render(cx);
|
|
||||||
if items.is_empty() {
|
|
||||||
return PrepareMarkup::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asegura que la barra tiene un id estable para poder asociarlo al colapso/offcanvas.
|
|
||||||
let id = cx.required_id::<Self>(self.id());
|
|
||||||
|
|
||||||
PrepareMarkup::With(html! {
|
|
||||||
nav id=(id) class=[self.classes().get()] {
|
|
||||||
div class="container-fluid" {
|
|
||||||
@match self.layout() {
|
|
||||||
// Barra más sencilla: sólo contenido.
|
|
||||||
navbar::Layout::Simple => {
|
|
||||||
(items)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra sencilla que se puede contraer/expandir.
|
|
||||||
navbar::Layout::SimpleToggle => {
|
|
||||||
@let id_content = join!(id, "-content");
|
|
||||||
|
|
||||||
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
|
||||||
div id=(id_content) class="collapse navbar-collapse" {
|
|
||||||
(items)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra con marca a la izquierda, siempre visible.
|
|
||||||
navbar::Layout::SimpleBrandLeft(brand) => {
|
|
||||||
(brand.render(cx))
|
|
||||||
(items)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra con marca a la izquierda y botón a la derecha.
|
|
||||||
navbar::Layout::BrandLeft(brand) => {
|
|
||||||
@let id_content = join!(id, "-content");
|
|
||||||
|
|
||||||
(brand.render(cx))
|
|
||||||
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
|
||||||
div id=(id_content) class="collapse navbar-collapse" {
|
|
||||||
(items)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra con botón a la izquierda y marca a la derecha.
|
|
||||||
navbar::Layout::BrandRight(brand) => {
|
|
||||||
@let id_content = join!(id, "-content");
|
|
||||||
|
|
||||||
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
|
||||||
(brand.render(cx))
|
|
||||||
div id=(id_content) class="collapse navbar-collapse" {
|
|
||||||
(items)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra cuyo contenido se muestra en un offcanvas, sin marca.
|
|
||||||
navbar::Layout::Offcanvas(offcanvas) => {
|
|
||||||
@let id_content = offcanvas.id().unwrap_or_default();
|
|
||||||
|
|
||||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
|
||||||
@if let Some(oc) = offcanvas.borrow() {
|
|
||||||
(oc.render_offcanvas(cx, Some(self.items())))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra con marca a la izquierda y contenido en offcanvas.
|
|
||||||
navbar::Layout::OffcanvasBrandLeft(brand, offcanvas) => {
|
|
||||||
@let id_content = offcanvas.id().unwrap_or_default();
|
|
||||||
|
|
||||||
(brand.render(cx))
|
|
||||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
|
||||||
@if let Some(oc) = offcanvas.borrow() {
|
|
||||||
(oc.render_offcanvas(cx, Some(self.items())))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Barra con contenido en offcanvas y marca a la derecha.
|
|
||||||
navbar::Layout::OffcanvasBrandRight(brand, offcanvas) => {
|
|
||||||
@let id_content = offcanvas.id().unwrap_or_default();
|
|
||||||
|
|
||||||
(button(cx, TOGGLE_OFFCANVAS, &id_content))
|
|
||||||
(brand.render(cx))
|
|
||||||
@if let Some(oc) = offcanvas.borrow() {
|
|
||||||
(oc.render_offcanvas(cx, Some(self.items())))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Navbar {
|
|
||||||
/// Crea una barra de navegación **simple**, sin marca y sin botón.
|
|
||||||
pub fn simple() -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::Simple)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación **simple pero colapsable**, con botón a la izquierda.
|
|
||||||
pub fn simple_toggle() -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::SimpleToggle)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación **con marca a la izquierda**, siempre visible.
|
|
||||||
pub fn simple_brand_left(brand: navbar::Brand) -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::SimpleBrandLeft(Typed::with(brand)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación con **marca a la izquierda** y **botón a la derecha**.
|
|
||||||
pub fn brand_left(brand: navbar::Brand) -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::BrandLeft(Typed::with(brand)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación con **botón a la izquierda** y **marca a la derecha**.
|
|
||||||
pub fn brand_right(brand: navbar::Brand) -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::BrandRight(Typed::with(brand)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación cuyo contenido se muestra en un **offcanvas**.
|
|
||||||
pub fn offcanvas(oc: Offcanvas) -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::Offcanvas(Typed::with(oc)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación con **marca a la izquierda** y contenido en **offcanvas**.
|
|
||||||
pub fn offcanvas_brand_left(brand: navbar::Brand, oc: Offcanvas) -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::OffcanvasBrandLeft(
|
|
||||||
Typed::with(brand),
|
|
||||||
Typed::with(oc),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea una barra de navegación con **marca a la derecha** y contenido en **offcanvas**.
|
|
||||||
pub fn offcanvas_brand_right(brand: navbar::Brand, oc: Offcanvas) -> Self {
|
|
||||||
Navbar::default().with_layout(navbar::Layout::OffcanvasBrandRight(
|
|
||||||
Typed::with(brand),
|
|
||||||
Typed::with(oc),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Navbar BUILDER >*************************************************************************
|
|
||||||
|
|
||||||
/// Establece el identificador único (`id`) de la barra de navegación.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
|
||||||
self.id.alter_value(id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la lista de clases CSS aplicadas a la barra de navegación.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
|
||||||
self.classes.alter_value(op, classes);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a partir de qué punto de ruptura la barra de navegación deja de colapsar.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_expand(mut self, bp: BreakPoint) -> Self {
|
|
||||||
self.expand = bp;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define el tipo de disposición que tendrá la barra de navegación.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_layout(mut self, layout: navbar::Layout) -> Self {
|
|
||||||
self.layout = layout;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define dónde se mostrará la barra de navegación dentro del documento.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_position(mut self, position: navbar::Position) -> Self {
|
|
||||||
self.position = position;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade un nuevo contenido hijo.
|
|
||||||
#[inline]
|
|
||||||
pub fn add_item(mut self, item: navbar::Item) -> Self {
|
|
||||||
self.items.add(Child::with(item));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la lista de contenidos (`children`) aplicando una operación [`TypedOp`].
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self {
|
|
||||||
self.items.alter_typed(op);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Navbar GETTERS >*************************************************************************
|
|
||||||
|
|
||||||
/// Devuelve las clases CSS asociadas a la barra de navegación.
|
|
||||||
pub fn classes(&self) -> &AttrClasses {
|
|
||||||
&self.classes
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve el punto de ruptura configurado.
|
|
||||||
pub fn expand(&self) -> &BreakPoint {
|
|
||||||
&self.expand
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la disposición configurada para la barra de navegación.
|
|
||||||
pub fn layout(&self) -> &navbar::Layout {
|
|
||||||
&self.layout
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la posición configurada para la barra de navegación.
|
|
||||||
pub fn position(&self) -> &navbar::Position {
|
|
||||||
&self.position
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la lista de contenidos (`children`).
|
|
||||||
pub fn items(&self) -> &Children {
|
|
||||||
&self.items
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
extensions/pagetop-bootsier/src/theme/navbar/content.rs
Normal file
69
extensions/pagetop-bootsier/src/theme/navbar/content.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::theme::navbar;
|
||||||
|
|
||||||
|
#[derive(AutoDefault)]
|
||||||
|
pub enum ContentType {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Brand(Typed<navbar::Brand>),
|
||||||
|
Nav(Typed<navbar::Nav>),
|
||||||
|
Text(L10n),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item.
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[derive(AutoDefault)]
|
||||||
|
pub struct Content {
|
||||||
|
content: ContentType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Content {
|
||||||
|
fn new() -> Self {
|
||||||
|
Content::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
match self.content() {
|
||||||
|
ContentType::None => PrepareMarkup::None,
|
||||||
|
ContentType::Brand(brand) => PrepareMarkup::With(html! {
|
||||||
|
(brand.render(cx))
|
||||||
|
}),
|
||||||
|
ContentType::Nav(nav) => PrepareMarkup::With(html! {
|
||||||
|
(nav.render(cx))
|
||||||
|
}),
|
||||||
|
ContentType::Text(text) => PrepareMarkup::With(html! {
|
||||||
|
span class="navbar-text" {
|
||||||
|
(text.using(cx))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn brand(content: navbar::Brand) -> Self {
|
||||||
|
Content {
|
||||||
|
content: ContentType::Brand(Typed::with(content)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nav(content: navbar::Nav) -> Self {
|
||||||
|
Content {
|
||||||
|
content: ContentType::Nav(Typed::with(content)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(content: L10n) -> Self {
|
||||||
|
Content {
|
||||||
|
content: ContentType::Text(content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content GETTERS.
|
||||||
|
|
||||||
|
pub fn content(&self) -> &ContentType {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// Elementos que puede contener una barra de navegación [`Navbar`](crate::theme::Navbar).
|
|
||||||
///
|
|
||||||
/// Cada variante determina qué se renderiza y cómo. Estos elementos se colocan **dentro del
|
|
||||||
/// contenido** de la barra (la parte colapsable, el *offcanvas* o el bloque simple), por lo que son
|
|
||||||
/// independientes de la marca o del botón que ya pueda definir el propio [`navbar::Layout`].
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub enum Item {
|
|
||||||
/// Sin contenido, no produce salida.
|
|
||||||
#[default]
|
|
||||||
Void,
|
|
||||||
/// Marca de identidad mostrada dentro del contenido de la barra de navegación.
|
|
||||||
///
|
|
||||||
/// Útil cuando el [`navbar::Layout`] no incluye marca, y se quiere incluir dentro del área
|
|
||||||
/// colapsable/*offcanvas*. Si el *layout* ya muestra una marca, esta variante no la sustituye,
|
|
||||||
/// sólo añade otra dentro del bloque de contenidos.
|
|
||||||
Brand(Typed<navbar::Brand>),
|
|
||||||
/// Representa un menú de navegación [`Nav`](crate::theme::Nav).
|
|
||||||
Nav(Typed<Nav>),
|
|
||||||
/// Representa un texto libre localizado.
|
|
||||||
Text(L10n),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Item {
|
|
||||||
fn new() -> Self {
|
|
||||||
Item::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::Void => None,
|
|
||||||
Self::Brand(brand) => brand.id(),
|
|
||||||
Self::Nav(nav) => nav.id(),
|
|
||||||
Self::Text(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
match self {
|
|
||||||
Self::Void => PrepareMarkup::None,
|
|
||||||
Self::Brand(brand) => PrepareMarkup::With(html! { (brand.render(cx)) }),
|
|
||||||
Self::Nav(nav) => {
|
|
||||||
if let Some(nav) = nav.borrow() {
|
|
||||||
let items = nav.items().render(cx);
|
|
||||||
if items.is_empty() {
|
|
||||||
return PrepareMarkup::None;
|
|
||||||
}
|
|
||||||
let classes = AttrClasses::new(
|
|
||||||
[
|
|
||||||
"navbar-nav",
|
|
||||||
match nav.nav_kind() {
|
|
||||||
nav::Kind::Default => "",
|
|
||||||
nav::Kind::Tabs => "nav-tabs",
|
|
||||||
nav::Kind::Pills => "nav-pills",
|
|
||||||
nav::Kind::Underline => "nav-underline",
|
|
||||||
},
|
|
||||||
match nav.nav_layout() {
|
|
||||||
nav::Layout::Default => "",
|
|
||||||
nav::Layout::Start => "justify-content-start",
|
|
||||||
nav::Layout::Center => "justify-content-center",
|
|
||||||
nav::Layout::End => "justify-content-end",
|
|
||||||
nav::Layout::Vertical => "flex-column",
|
|
||||||
nav::Layout::Fill => "nav-fill",
|
|
||||||
nav::Layout::Justified => "nav-justified",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
.join(" "),
|
|
||||||
);
|
|
||||||
PrepareMarkup::With(html! {
|
|
||||||
ul id=[nav.id()] class=[classes.get()] {
|
|
||||||
(items)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
PrepareMarkup::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Text(text) => PrepareMarkup::With(html! {
|
|
||||||
span class="navbar-text" {
|
|
||||||
(text.using(cx))
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Item {
|
|
||||||
/// Crea un elemento de tipo [`navbar::Brand`] para añadir en el contenido de [`Navbar`].
|
|
||||||
///
|
|
||||||
/// Pensado para barras colapsables u offcanvas donde se quiere que la marca aparezca en la zona
|
|
||||||
/// desplegable.
|
|
||||||
pub fn brand(brand: navbar::Brand) -> Self {
|
|
||||||
Self::Brand(Typed::with(brand))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea un elemento de tipo [`Nav`] para añadir al contenido de [`Navbar`].
|
|
||||||
pub fn nav(item: Nav) -> Self {
|
|
||||||
Self::Nav(Typed::with(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Crea un elemento de texto localizado, mostrado sin interacción.
|
|
||||||
pub fn text(item: L10n) -> Self {
|
|
||||||
Self::Text(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
use pagetop::prelude::*;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
// **< Layout >*************************************************************************************
|
|
||||||
|
|
||||||
/// Representa los diferentes tipos de presentación de una barra de navegación [`Navbar`].
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub enum Layout {
|
|
||||||
/// Barra simple, sin marca de identidad y sin botón de despliegue.
|
|
||||||
///
|
|
||||||
/// La barra de navegación no se colapsa.
|
|
||||||
#[default]
|
|
||||||
Simple,
|
|
||||||
|
|
||||||
/// Barra simple, con botón de despliegue a la izquierda y sin marca de identidad.
|
|
||||||
SimpleToggle,
|
|
||||||
|
|
||||||
/// Barra simple, con marca de identidad a la izquierda y sin botón de despliegue.
|
|
||||||
///
|
|
||||||
/// La barra de navegación no se colapsa.
|
|
||||||
SimpleBrandLeft(Typed<navbar::Brand>),
|
|
||||||
|
|
||||||
/// Barra con marca de identidad a la izquierda y botón de despliegue a la derecha.
|
|
||||||
BrandLeft(Typed<navbar::Brand>),
|
|
||||||
|
|
||||||
/// Barra con botón de despliegue a la izquierda y marca de identidad a la derecha.
|
|
||||||
BrandRight(Typed<navbar::Brand>),
|
|
||||||
|
|
||||||
/// Contenido en [`Offcanvas`], con botón de despliegue a la izquierda y sin marca de identidad.
|
|
||||||
Offcanvas(Typed<Offcanvas>),
|
|
||||||
|
|
||||||
/// Contenido en [`Offcanvas`], con marca de identidad a la izquierda y botón de despliegue a la
|
|
||||||
/// derecha.
|
|
||||||
OffcanvasBrandLeft(Typed<navbar::Brand>, Typed<Offcanvas>),
|
|
||||||
|
|
||||||
/// Contenido en [`Offcanvas`], con botón de despliegue a la izquierda y marca de identidad a la
|
|
||||||
/// derecha.
|
|
||||||
OffcanvasBrandRight(Typed<navbar::Brand>, Typed<Offcanvas>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Position >***********************************************************************************
|
|
||||||
|
|
||||||
/// Posición global de una barra de navegación [`Navbar`] en el documento.
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +1,4 @@
|
||||||
//! Definiciones para crear paneles laterales deslizantes [`Offcanvas`].
|
//! Definiciones para crear paneles laterales deslizantes [`Offcanvas`].
|
||||||
//!
|
|
||||||
//! # Ejemplo
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # use pagetop::prelude::*;
|
|
||||||
//! # use pagetop_bootsier::prelude::*;
|
|
||||||
//! let panel = Offcanvas::new()
|
|
||||||
//! .with_id("offcanvas_example")
|
|
||||||
//! .with_title(L10n::n("Offcanvas title"))
|
|
||||||
//! .with_placement(offcanvas::Placement::End)
|
|
||||||
//! .with_backdrop(offcanvas::Backdrop::Enabled)
|
|
||||||
//! .with_body_scroll(offcanvas::BodyScroll::Enabled)
|
|
||||||
//! .with_visibility(offcanvas::Visibility::Default)
|
|
||||||
//! .add_child(Dropdown::new()
|
|
||||||
//! .with_button_title(L10n::n("Menu"))
|
|
||||||
//! .add_item(dropdown::Item::label(L10n::n("Label")))
|
|
||||||
//! .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es"))
|
|
||||||
//! .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout"))
|
|
||||||
//! );
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::{Backdrop, BodyScroll, Placement, Visibility};
|
pub use props::{Backdrop, BodyScroll, Placement, Visibility};
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,27 @@ use crate::LOCALES_BOOTSIER;
|
||||||
/// ([`with_breakpoint()`](Self::with_breakpoint)).
|
/// ([`with_breakpoint()`](Self::with_breakpoint)).
|
||||||
/// - Asocia título y controles de accesibilidad a un identificador único y expone atributos
|
/// - Asocia título y controles de accesibilidad a un identificador único y expone atributos
|
||||||
/// adecuados para lectores de pantalla y navegación por teclado.
|
/// adecuados para lectores de pantalla y navegación por teclado.
|
||||||
|
/// - **No se renderiza** si no tiene contenido.
|
||||||
///
|
///
|
||||||
/// Ver ejemplo en el módulo [`offcanvas`].
|
/// # Ejemplo
|
||||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// let panel = Offcanvas::new()
|
||||||
|
/// .with_id("offcanvas_example")
|
||||||
|
/// .with_title(L10n::n("Offcanvas title"))
|
||||||
|
/// .with_placement(offcanvas::Placement::End)
|
||||||
|
/// .with_backdrop(offcanvas::Backdrop::Enabled)
|
||||||
|
/// .with_body_scroll(offcanvas::BodyScroll::Enabled)
|
||||||
|
/// .with_visibility(offcanvas::Visibility::Default)
|
||||||
|
/// .add_child(Dropdown::new()
|
||||||
|
/// .with_button_title(L10n::n("Menu"))
|
||||||
|
/// .add_item(dropdown::Item::label(L10n::n("Label")))
|
||||||
|
/// .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://www.google.es"))
|
||||||
|
/// .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout"))
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub struct Offcanvas {
|
pub struct Offcanvas {
|
||||||
|
|
@ -66,7 +84,54 @@ impl Component for Offcanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
PrepareMarkup::With(self.render_offcanvas(cx, None))
|
let body = self.children().render(cx);
|
||||||
|
if body.is_empty() {
|
||||||
|
return PrepareMarkup::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = cx.required_id::<Self>(self.id());
|
||||||
|
let id_label = join!(id, "-label");
|
||||||
|
let id_target = join!("#", id);
|
||||||
|
|
||||||
|
let body_scroll = match self.body_scroll() {
|
||||||
|
offcanvas::BodyScroll::Disabled => None,
|
||||||
|
offcanvas::BodyScroll::Enabled => Some("true"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let backdrop = match self.backdrop() {
|
||||||
|
offcanvas::Backdrop::Disabled => Some("false"),
|
||||||
|
offcanvas::Backdrop::Enabled => None,
|
||||||
|
offcanvas::Backdrop::Static => Some("static"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let title = self.title().using(cx);
|
||||||
|
|
||||||
|
PrepareMarkup::With(html! {
|
||||||
|
div
|
||||||
|
id=(id)
|
||||||
|
class=[self.classes().get()]
|
||||||
|
tabindex="-1"
|
||||||
|
data-bs-scroll=[body_scroll]
|
||||||
|
data-bs-backdrop=[backdrop]
|
||||||
|
aria-labelledby=(id_label)
|
||||||
|
{
|
||||||
|
div class="offcanvas-header" {
|
||||||
|
@if !title.is_empty() {
|
||||||
|
h5 class="offcanvas-title" id=(id_label) { (title) }
|
||||||
|
}
|
||||||
|
button
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="offcanvas"
|
||||||
|
data-bs-target=(id_target)
|
||||||
|
aria-label=[L10n::t("offcanvas_close", &LOCALES_BOOTSIER).lookup(cx)]
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
div class="offcanvas-body" {
|
||||||
|
(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,59 +258,4 @@ impl Offcanvas {
|
||||||
pub fn children(&self) -> &Children {
|
pub fn children(&self) -> &Children {
|
||||||
&self.children
|
&self.children
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Offcanvas HELPERS >**********************************************************************
|
|
||||||
|
|
||||||
pub(crate) fn render_offcanvas(&self, cx: &mut Context, extra: Option<&Children>) -> Markup {
|
|
||||||
let body = self.children().render(cx);
|
|
||||||
let body_extra = extra.map(|c| c.render(cx)).unwrap_or_else(|| html! {});
|
|
||||||
if body.is_empty() && body_extra.is_empty() {
|
|
||||||
return html! {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = cx.required_id::<Self>(self.id());
|
|
||||||
let id_label = join!(id, "-label");
|
|
||||||
let id_target = join!("#", id);
|
|
||||||
|
|
||||||
let body_scroll = match self.body_scroll() {
|
|
||||||
offcanvas::BodyScroll::Disabled => None,
|
|
||||||
offcanvas::BodyScroll::Enabled => Some("true"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let backdrop = match self.backdrop() {
|
|
||||||
offcanvas::Backdrop::Disabled => Some("false"),
|
|
||||||
offcanvas::Backdrop::Enabled => None,
|
|
||||||
offcanvas::Backdrop::Static => Some("static"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let title = self.title().using(cx);
|
|
||||||
|
|
||||||
html! {
|
|
||||||
div
|
|
||||||
id=(id)
|
|
||||||
class=[self.classes().get()]
|
|
||||||
tabindex="-1"
|
|
||||||
data-bs-scroll=[body_scroll]
|
|
||||||
data-bs-backdrop=[backdrop]
|
|
||||||
aria-labelledby=(id_label)
|
|
||||||
{
|
|
||||||
div class="offcanvas-header" {
|
|
||||||
@if !title.is_empty() {
|
|
||||||
h5 class="offcanvas-title" id=(id_label) { (title) }
|
|
||||||
}
|
|
||||||
button
|
|
||||||
type="button"
|
|
||||||
class="btn-close"
|
|
||||||
data-bs-dismiss="offcanvas"
|
|
||||||
data-bs-target=(id_target)
|
|
||||||
aria-label=[L10n::t("offcanvas_close", &LOCALES_BOOTSIER).lookup(cx)]
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
div class="offcanvas-body" {
|
|
||||||
(body)
|
|
||||||
(body_extra)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7
extensions/pagetop-bootsier/static/js/bootstrap.min.js
vendored
Normal file
7
extensions/pagetop-bootsier/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
extensions/pagetop-bootsier/static/js/bootstrap.min.js.map
vendored
Normal file
1
extensions/pagetop-bootsier/static/js/bootstrap.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue