From 93804721e161e5e1d52800df9147b55cdd02eeb8 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 2 Nov 2025 12:42:36 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Repasa=20doc=20de=20`Dropdown`,?= =?UTF-8?q?=20`Nav`=20y=20`Offcanvas`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetop-bootsier/src/theme/dropdown.rs | 17 +++ .../src/theme/dropdown/component.rs | 18 +-- .../src/theme/dropdown/item.rs | 4 +- extensions/pagetop-bootsier/src/theme/nav.rs | 20 +++ .../src/theme/nav/component.rs | 21 +-- .../pagetop-bootsier/src/theme/nav/item.rs | 6 +- .../pagetop-bootsier/src/theme/offcanvas.rs | 20 +++ .../src/theme/offcanvas/component.rs | 126 ++++++++---------- 8 files changed, 124 insertions(+), 108 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index eb00e4c..ed4cbec 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -6,6 +6,23 @@ //! //! 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). +//! +//! # 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; pub use props::{AutoClose, Direction, MenuAlign, MenuPosition}; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 984a3e0..c29ac14 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -17,22 +17,8 @@ use crate::LOCALES_BOOTSIER; /// 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`]. /// -/// # 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"))); -/// ``` +/// Ver ejemplo en el módulo [`dropdown`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Dropdown { diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 1aed83b..a13058d 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -79,7 +79,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && current_path.map_or(false, |p| p == path); + let is_current = !*disabled && (current_path == Some(path)); let mut classes = "dropdown-item".to_string(); if is_current { @@ -274,7 +274,7 @@ impl Item { &self.classes } - /// Devuelve el tipo de elemento representado por este elemento. + /// Devuelve el tipo de elemento representado. pub fn item_kind(&self) -> &ItemKind { &self.item_kind } diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs index b540c32..c74ab3b 100644 --- a/extensions/pagetop-bootsier/src/theme/nav.rs +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -6,6 +6,26 @@ //! //! 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). +//! +//! # 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; pub use props::{Kind, Layout}; diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index 34c33b9..d4cf2c8 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -8,25 +8,8 @@ use crate::prelude::*; /// 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)). /// -/// # 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"), |_| "#")); -/// ``` +/// Ver ejemplo en el módulo [`nav`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Nav { diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 63248f8..bc097e0 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -72,7 +72,7 @@ impl Component for Item { ItemKind::Label(label) => PrepareMarkup::With(html! { li id=[self.id()] class=[self.classes().get()] { - span { + span class="nav-link disabled" aria-disabled="true" { (label.using(cx)) } } @@ -86,7 +86,7 @@ impl Component for Item { } => { let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && current_path.map_or(false, |p| p == path); + let is_current = !*disabled && (current_path == Some(path)); let mut classes = "nav-link".to_string(); if is_current { @@ -250,7 +250,7 @@ impl Item { &self.classes } - /// Devuelve el tipo de elemento representado por este elemento. + /// Devuelve el tipo de elemento representado. pub fn item_kind(&self) -> &ItemKind { &self.item_kind } diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index 560bd30..dc90534 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -1,4 +1,24 @@ //! 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; pub use props::{Backdrop, BodyScroll, Placement, Visibility}; diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 7cd7dff..61e6fae 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -18,27 +18,9 @@ use crate::LOCALES_BOOTSIER; /// ([`with_breakpoint()`](Self::with_breakpoint)). /// - Asocia título y controles de accesibilidad a un identificador único y expone atributos /// adecuados para lectores de pantalla y navegación por teclado. -/// - **No se renderiza** si no tiene contenido. /// -/// # 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")) -/// ); -/// ``` +/// Ver ejemplo en el módulo [`offcanvas`]. +/// Si no contiene elementos, el componente **no se renderiza**. #[rustfmt::skip] #[derive(AutoDefault)] pub struct Offcanvas { @@ -84,54 +66,7 @@ impl Component for Offcanvas { } fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let body = self.children().render(cx); - if body.is_empty() { - return PrepareMarkup::None; - } - - let id = cx.required_id::(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) - } - } - }) + PrepareMarkup::With(self.render_offcanvas(cx, None)) } } @@ -258,4 +193,59 @@ impl Offcanvas { pub fn 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.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) + } + } + } + } }