[bootsier] Añade componente Nav

- Adapta `Dropdown` para poder integrarlo en los menús `Nav`.
- Actualiza `Children` para soportar operaciones múltiples.
This commit is contained in:
Manuel Cillero 2025-10-29 13:47:59 +01:00
parent 42860c6dae
commit 2f41f166f3
12 changed files with 602 additions and 226 deletions

View file

@ -1,113 +0,0 @@
use pagetop::prelude::*;
use crate::theme::Dropdown;
type Label = L10n;
#[derive(AutoDefault)]
pub enum ItemType {
#[default]
Void,
Label(Label),
Link(Label, FnPathByContext),
LinkBlank(Label, FnPathByContext),
Dropdown(Typed<Dropdown>),
}
// Item.
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Item {
item_type: ItemType,
}
impl Component for Item {
fn new() -> Self {
Item::default()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let description: Option<String> = None;
// Obtiene la URL actual desde `cx.request`.
let current_path = cx.request().map(|request| request.path());
match self.item_type() {
ItemType::Void => PrepareMarkup::None,
ItemType::Label(label) => PrepareMarkup::With(html! {
li class="nav-item" {
span title=[description] {
//(left_icon)
(label.using(cx))
//(right_icon)
}
}
}),
ItemType::Link(label, path) => {
let item_path = path(cx);
let (class, aria) = if current_path == Some(item_path) {
("nav-item active", Some("page"))
} else {
("nav-item", None)
};
PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] {
a class="nav-link" href=(item_path) title=[description] {
//(left_icon)
(label.using(cx))
//(right_icon)
}
}
})
}
ItemType::LinkBlank(label, path) => {
let item_path = path(cx);
let (class, aria) = if current_path == Some(item_path) {
("nav-item active", Some("page"))
} else {
("nav-item", None)
};
PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] {
a class="nav-link" href=(item_path) title=[description] target="_blank" {
//(left_icon)
(label.using(cx))
//(right_icon)
}
}
})
}
ItemType::Dropdown(menu) => PrepareMarkup::With(html! { (menu.render(cx)) }),
}
}
}
impl Item {
pub fn label(label: L10n) -> Self {
Item {
item_type: ItemType::Label(label),
..Default::default()
}
}
pub fn link(label: L10n, path: FnPathByContext) -> Self {
Item {
item_type: ItemType::Link(label, path),
..Default::default()
}
}
pub fn link_blank(label: L10n, path: FnPathByContext) -> Self {
Item {
item_type: ItemType::LinkBlank(label, path),
..Default::default()
}
}
// Item GETTERS.
pub fn item_type(&self) -> &ItemType {
&self.item_type
}
}

View file

@ -1,75 +0,0 @@
use pagetop::prelude::*;
use crate::theme::navbar;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Nav {
id : AttrId,
classes: AttrClasses,
items : Children,
}
impl Component for Nav {
fn new() -> Self {
Nav::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(ClassesOp::Prepend, "navbar-nav");
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let items = self.items().render(cx);
if items.is_empty() {
return PrepareMarkup::None;
}
PrepareMarkup::With(html! {
ul id=[self.id()] class=[self.classes().get()] {
(items)
}
})
}
}
impl Nav {
// Nav 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
}
pub fn with_item(mut self, item: navbar::Item) -> Self {
self.items.add(Child::with(item));
self
}
#[builder_fn]
pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self {
self.items.alter_typed(op);
self
}
// Nav GETTERS.
pub fn classes(&self) -> &AttrClasses {
&self.classes
}
pub fn items(&self) -> &Children {
&self.items
}
}