diff --git a/packages/pagetop-bootsier/src/bs.rs b/packages/pagetop-bootsier/src/bs.rs index a85bffc4..4b688d80 100644 --- a/packages/pagetop-bootsier/src/bs.rs +++ b/packages/pagetop-bootsier/src/bs.rs @@ -20,6 +20,10 @@ pub use offcanvas::{ pub mod navbar; pub use navbar::{Navbar, NavbarContent, NavbarToggler}; +// Dropdown. +pub mod dropdown; +pub use dropdown::Dropdown; + /// Define los puntos de interrupción (*breakpoints*) usados por Bootstrap para diseño responsivo. #[rustfmt::skip] #[derive(AutoDefault)] diff --git a/packages/pagetop-bootsier/src/bs/dropdown.rs b/packages/pagetop-bootsier/src/bs/dropdown.rs new file mode 100644 index 00000000..a04f8dd6 --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/dropdown.rs @@ -0,0 +1,5 @@ +mod component; +pub use component::Dropdown; + +mod item; +pub use item::Item; diff --git a/packages/pagetop-bootsier/src/bs/dropdown/component.rs b/packages/pagetop-bootsier/src/bs/dropdown/component.rs new file mode 100644 index 00000000..97d131dd --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/dropdown/component.rs @@ -0,0 +1,99 @@ +use pagetop::prelude::*; + +use crate::bs::dropdown; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Dropdown { + id : OptionId, + classes: OptionClasses, + items : Children, +} + +impl ComponentTrait for Dropdown { + fn new() -> Self { + Dropdown::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes(ClassesOp::Prepend, "dropdown"); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let items = self.items().render(cx); + if items.is_empty() { + return PrepareMarkup::None; + } + + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] { + button + type="button" + class="btn btn-secondary dropdown-toggle" + data-bs-toggle="dropdown" + aria-expanded="false" + { + ("Dropdown button") + } + ul class="dropdown-menu" { + li { + a class="dropdown-item" href="#" { + ("Action") + } + } + li { + a class="dropdown-item" href="#" { + ("Another action") + } + } + li { + a class="dropdown-item" href="#" { + ("Something else here") + } + } + } + } + }) + } +} + +impl Dropdown { + // Dropdown BUILDER. + + #[fn_builder] + pub fn with_id(mut self, id: impl Into) -> Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn with_classes(mut self, op: ClassesOp, classes: impl Into) -> Self { + self.classes.alter_value(op, classes); + self + } + + pub fn with_item(mut self, item: dropdown::Item) -> Self { + self.items.add(Child::with(item)); + self + } + + #[fn_builder] + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); + self + } + + // Dropdown GETTERS. + + pub fn classes(&self) -> &OptionClasses { + &self.classes + } + + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/packages/pagetop-bootsier/src/bs/dropdown/item.rs b/packages/pagetop-bootsier/src/bs/dropdown/item.rs new file mode 100644 index 00000000..ca6ebf28 --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/dropdown/item.rs @@ -0,0 +1,109 @@ +use pagetop::prelude::*; + +type Label = L10n; + +#[derive(AutoDefault)] +pub enum ItemType { + #[default] + Void, + Label(Label), + Link(Label, FnContextualPath), + LinkBlank(Label, FnContextualPath), +} + +// Item. + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Item { + item_type: ItemType, +} + +impl ComponentTrait for Item { + fn new() -> Self { + Item::default() + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let description: Option = None; + + // Obtiene la URL actual desde `cx.request`. + let current_path = cx.request().path(); + + match self.item_type() { + ItemType::Void => PrepareMarkup::None, + ItemType::Label(label) => PrepareMarkup::With(html! { + li class="dropdown-item" { + span title=[description] { + //(left_icon) + (label.escaped(cx.langid())) + //(right_icon) + } + } + }), + ItemType::Link(label, path) => { + let item_path = path(cx); + let (class, aria) = if item_path == current_path { + ("dropdown-item active", Some("page")) + } else { + ("dropdown-item", None) + }; + PrepareMarkup::With(html! { + li class=(class) aria-current=[aria] { + a class="nav-link" href=(item_path) title=[description] { + //(left_icon) + (label.escaped(cx.langid())) + //(right_icon) + } + } + }) + } + ItemType::LinkBlank(label, path) => { + let item_path = path(cx); + let (class, aria) = if item_path == current_path { + ("dropdown-item active", Some("page")) + } else { + ("dropdown-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.escaped(cx.langid())) + //(right_icon) + } + } + }) + } + } + } +} + +impl Item { + pub fn label(label: L10n) -> Self { + Item { + item_type: ItemType::Label(label), + ..Default::default() + } + } + + pub fn link(label: L10n, path: FnContextualPath) -> Self { + Item { + item_type: ItemType::Link(label, path), + ..Default::default() + } + } + + pub fn link_blank(label: L10n, path: FnContextualPath) -> Self { + Item { + item_type: ItemType::LinkBlank(label, path), + ..Default::default() + } + } + + // Item GETTERS. + + pub fn item_type(&self) -> &ItemType { + &self.item_type + } +} diff --git a/packages/pagetop-bootsier/src/bs/navbar/item.rs b/packages/pagetop-bootsier/src/bs/navbar/item.rs index 89b42b45..db16721b 100644 --- a/packages/pagetop-bootsier/src/bs/navbar/item.rs +++ b/packages/pagetop-bootsier/src/bs/navbar/item.rs @@ -1,5 +1,7 @@ use pagetop::prelude::*; +use crate::bs::Dropdown; + type Label = L10n; #[derive(AutoDefault)] @@ -9,6 +11,7 @@ pub enum ItemType { Label(Label), Link(Label, FnContextualPath), LinkBlank(Label, FnContextualPath), + Dropdown(Typed), } // Item. @@ -75,6 +78,7 @@ impl ComponentTrait for Item { } }) } + ItemType::Dropdown(menu) => PrepareMarkup::With(html! { (menu.render(cx)) }), } } }