📝 Repasa doc de Dropdown, Nav y Offcanvas

This commit is contained in:
Manuel Cillero 2025-11-02 12:42:36 +01:00
parent 749e182619
commit 93804721e1
8 changed files with 124 additions and 108 deletions

View file

@ -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};

View file

@ -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 {

View file

@ -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
}

View file

@ -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};

View file

@ -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 {

View file

@ -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
}

View file

@ -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};

View file

@ -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>(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>(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)
}
}
}
}
}