WIP: Añade componente para la gestión de menús #8

Draft
manuelcillero wants to merge 54 commits from add-menu-component into main
8 changed files with 654 additions and 72 deletions
Showing only changes of commit 76bece7540 - Show all commits

View file

@ -1,3 +1,6 @@
# Dropdown
dropdown_toggle = Toggle Dropdown
# Offcanvas # Offcanvas
close = Close close = Close

View file

@ -1,3 +1,6 @@
# Dropdown
dropdown_toggle = Mostrar/ocultar menú
# Offcanvas # Offcanvas
close = Cerrar close = Cerrar

View file

@ -1,11 +1,11 @@
//! Coleción de elementos auxiliares de Bootstrap para Bootsier. //! Colección de elementos auxiliares de Bootstrap para Bootsier.
mod breakpoint; mod breakpoint;
pub use breakpoint::BreakPoint; pub use breakpoint::BreakPoint;
mod color; mod color;
pub use color::Color; pub use color::Color;
pub use color::{BgColor, BorderColor, TextColor}; pub use color::{BgColor, BorderColor, ButtonColor, TextColor};
mod opacity; mod opacity;
pub use opacity::Opacity; pub use opacity::Opacity;
@ -16,3 +16,6 @@ pub use border::{Border, BorderSize};
mod rounded; mod rounded;
pub use rounded::{Rounded, RoundedRadius}; pub use rounded::{Rounded, RoundedRadius};
mod size;
pub use size::ButtonSize;

View file

@ -110,6 +110,35 @@ impl fmt::Display for BorderColor {
} }
} }
// **< ButtonColor >********************************************************************************
/// Variantes de color (`btn-*`) para **botones**.
///
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
/// - `Background(Color)` genera `btn-{color}` (botón relleno).
/// - `Outline(Color)` genera `btn-outline-{color}` (contorno: texto y borde, fondo transparente).
/// - `Link` aplica estilo de enlace (`btn-link`), sin caja ni fondo, heredando el color de texto.
#[derive(AutoDefault)]
pub enum ButtonColor {
#[default]
Default,
Background(Color),
Outline(Color),
Link,
}
#[rustfmt::skip]
impl fmt::Display for ButtonColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Background(c) => write!(f, "btn-{c}"),
Self::Outline(c) => write!(f, "btn-outline-{c}"),
Self::Link => f.write_str("btn-link"),
}
}
}
// **< TextColor >********************************************************************************** // **< TextColor >**********************************************************************************
/// Colores de texto y fondos de texto (`text-*`). /// Colores de texto y fondos de texto (`text-*`).

View file

@ -0,0 +1,31 @@
use pagetop::prelude::*;
use std::fmt;
// **< ButtonSize >*********************************************************************************
/// Tamaño visual de un botón.
///
/// Controla la escala del botón según el diseño del tema:
///
/// - `Default`, tamaño por defecto del tema (no añade clase).
/// - `Small`, botón compacto.
/// - `Large`, botón destacado/grande.
#[derive(AutoDefault)]
pub enum ButtonSize {
#[default]
Default,
Small,
Large,
}
#[rustfmt::skip]
impl fmt::Display for ButtonSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => Ok(()),
Self::Small => f.write_str("btn-sm"),
Self::Large => f.write_str("btn-lg"),
}
}
}

View file

@ -1,5 +1,18 @@
//! Definiciones para crear menús desplegables [`Dropdown`].
//!
//! Cada [`dropdown::Item`](crate::theme::dropdown::Item) representa un elemento individual del
//! desplegable [`Dropdown`], con distintos comportamientos según su finalidad: enlaces de
//! navegación, botones de acción, encabezados o divisores visuales.
//!
//! 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).
//!
//! Su propósito es ofrecer una base uniforme sobre la que construir menús consistentes, adaptados
//! al contexto de cada aplicación.
mod component; mod component;
pub use component::Dropdown; pub use component::Dropdown;
pub use component::{AutoClose, Direction, MenuAlign, MenuPosition};
mod item; mod item;
pub use item::Item; pub use item::{Item, ItemKind};

View file

@ -1,12 +1,133 @@
use pagetop::prelude::*; use pagetop::prelude::*;
use crate::prelude::*; use crate::prelude::*;
use crate::LOCALES_BOOTSIER;
// **< AutoClose >**********************************************************************************
/// Estrategia para el cierre automático de un menú [`Dropdown`].
///
/// Define cuándo se cierra el menú desplegado según la interacción del usuario.
#[derive(AutoDefault)]
pub enum AutoClose {
/// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`.
#[default]
Default,
/// Sólo se cierra con clics dentro del menú.
ClickableInside,
/// Sólo se cierra con clics fuera del menú.
ClickableOutside,
/// Cierre manual, no se cierra con clics; sólo al pulsar nuevamente el botón del menú
/// (*toggle*), o pulsando `Esc`.
ManualClose,
}
// **< Direction >**********************************************************************************
/// Dirección de despliegue de un menú [`Dropdown`].
///
/// Controla desde qué posición se muestra el menú respecto al botón.
#[derive(AutoDefault)]
pub enum Direction {
/// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial,
/// respetando LTR/RTL).
#[default]
Default,
/// Centra horizontalmente el menú respecto al botón.
Centered,
/// Despliega el menú hacia arriba.
Dropup,
/// Despliega el menú hacia arriba y centrado.
DropupCentered,
/// Despliega el menú desde el lateral final, respetando LTR/RTL.
Dropend,
/// Despliega el menú desde el lateral inicial, respetando LTR/RTL.
Dropstart,
}
// **< MenuAlign >**********************************************************************************
/// Alineación horizontal del menú desplegable [`Dropdown`].
///
/// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una
/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]).
#[derive(AutoDefault)]
pub enum MenuAlign {
/// Alineación al inicio (comportamiento por defecto).
#[default]
Start,
/// Alineación al inicio a partir del punto de ruptura indicado.
StartAt(BreakPoint),
/// Alineación al inicio por defecto, y al final a partir de un punto de ruptura válido.
StartAndEnd(BreakPoint),
/// Alineación al final.
End,
/// Alineación al final a partir del punto de ruptura indicado.
EndAt(BreakPoint),
/// Alineación al final por defecto, y al inicio a partir de un punto de ruptura válido.
EndAndStart(BreakPoint),
}
// **< MenuPosition >*******************************************************************************
/// Posición relativa del menú desplegable [`Dropdown`].
///
/// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el
/// cálculo de la posición.
#[derive(AutoDefault)]
pub enum MenuPosition {
/// Posicionamiento automático por defecto.
#[default]
Default,
/// Desplazamiento manual en píxeles `(x, y)` aplicado al menú. Se admiten valores negativos.
Offset(i8, i8),
/// Posiciona el menú tomando como referencia el botón padre. Especialmente útil cuando
/// [`button_split()`](crate::theme::Dropdown::button_split) es `true`.
Parent,
}
// **< Dropdown >**********************************************************************************
/// Componente para crear un **menú desplegable**.
///
/// Renderiza un botón (único o desdoblado, ver [`with_button_split()`](Self::with_button_split))
/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra/oculta según la
/// interacción del usuario. Admite variaciones de tamaño/color del botón, también dirección de
/// apertura, alineación o política de cierre.
///
/// Sin título para el botón (ver [`with_button_title()`](Self::with_button_title)) se muestra
/// únicamente la lista de elementos sin ningún botón para interactuar.
///
/// # Ejemplo
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// let dd = Dropdown::new()
/// .with_button_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 {
id : AttrId, id : AttrId,
classes : AttrClasses, classes : AttrClasses,
button_title : L10n,
button_size : ButtonSize,
button_color : ButtonColor,
button_split : bool,
button_grouped: bool,
auto_close : dropdown::AutoClose,
direction : dropdown::Direction,
menu_align : dropdown::MenuAlign,
menu_position : dropdown::MenuPosition,
items : Children, items : Children,
} }
@ -19,42 +140,134 @@ impl Component for Dropdown {
self.id.get() self.id.get()
} }
#[rustfmt::skip]
fn setup_before_prepare(&mut self, _cx: &mut Context) { fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(ClassesOp::Prepend, "dropdown"); let g = self.button_grouped();
self.alter_classes(ClassesOp::Prepend, [
if g { "btn-group" } else { "" },
match self.direction() {
Direction::Default if g => "",
Direction::Default => "dropdown",
Direction::Centered => "dropdown-center",
Direction::Dropup => "dropup",
Direction::DropupCentered => "dropup-center",
Direction::Dropend => "dropend",
Direction::Dropstart => "dropstart",
}
].join(" "));
} }
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
// Si no hay elementos en el menú, no se prepara.
let items = self.items().render(cx); let items = self.items().render(cx);
if items.is_empty() { if items.is_empty() {
return PrepareMarkup::None; return PrepareMarkup::None;
} }
// Título opcional para el menú desplegable.
let button_title = self.button_title().using(cx);
PrepareMarkup::With(html! { PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] { div id=[self.id()] class=[self.classes().get()] {
@if !button_title.is_empty() {
@let mut btn_classes = AttrClasses::new([
"btn",
&self.button_size().to_string(),
&self.button_color().to_string(),
].join(" "));
@let (offset, reference) = match self.menu_position() {
MenuPosition::Default => (None, None),
MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None),
MenuPosition::Parent => (None, Some("parent")),
};
@let auto_close = match self.auto_close {
AutoClose::Default => None,
AutoClose::ClickableInside => Some("inside"),
AutoClose::ClickableOutside => Some("outside"),
AutoClose::ManualClose => Some("false"),
};
@let menu_classes = AttrClasses::new("dropdown-menu")
.with_value(ClassesOp::Add, match self.menu_align() {
MenuAlign::Start => String::new(),
MenuAlign::StartAt(bp) => bp.try_class("dropdown-menu")
.map_or(String::new(), |class| join!(class, "-start")),
MenuAlign::StartAndEnd(bp) => bp.try_class("dropdown-menu")
.map_or(
"dropdown-menu-start".into(),
|class| join!("dropdown-menu-start ", class, "-end")
),
MenuAlign::End => "dropdown-menu-end".into(),
MenuAlign::EndAt(bp) => bp.try_class("dropdown-menu")
.map_or(String::new(), |class| join!(class, "-end")),
MenuAlign::EndAndStart(bp) => bp.try_class("dropdown-menu")
.map_or(
"dropdown-menu-end".into(),
|class| join!("dropdown-menu-end ", class, "-start")
),
});
// Renderizado en modo split (dos botones) o simple (un botón).
@if self.button_split() {
// Botón principal (acción/etiqueta).
@let btn = html! {
button button
type="button" type="button"
class="btn btn-secondary dropdown-toggle" class=[btn_classes.get()]
{
(button_title)
}
};
// Botón *toggle* que abre/cierra el menú asociado.
@let btn_toggle = html! {
button
type="button"
class=[btn_classes.alter_value(
ClassesOp::Add, "dropdown-toggle dropdown-toggle-split"
).get()]
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
data-bs-offset=[offset]
data-bs-reference=[reference]
data-bs-auto-close=[auto_close]
aria-expanded="false" aria-expanded="false"
{ {
("Dropdown button") span class="visually-hidden" {
} (L10n::t("dropdown_toggle", &LOCALES_BOOTSIER).using(cx))
ul class="dropdown-menu" {
li {
a class="dropdown-item" href="#" {
("Action")
} }
} }
li { };
a class="dropdown-item" href="#" { // Orden según dirección (en `dropstart` el *toggle* se sitúa antes).
("Another action") @match self.direction() {
Direction::Dropstart => {
(btn_toggle)
ul class=[menu_classes.get()] { (items) }
(btn)
}
_ => {
(btn)
(btn_toggle)
ul class=[menu_classes.get()] { (items) }
} }
} }
li { } @else {
a class="dropdown-item" href="#" { // Botón único con funcionalidad de *toggle*.
("Something else here") button
type="button"
class=[btn_classes.alter_value(
ClassesOp::Add, "dropdown-toggle"
).get()]
data-bs-toggle="dropdown"
data-bs-offset=[offset]
data-bs-reference=[reference]
data-bs-auto-close=[auto_close]
aria-expanded="false"
{
(button_title)
} }
ul class=[menu_classes.get()] { (items) }
} }
} @else {
// Sin botón: sólo el listado como menú contextual.
ul class="dropdown-menu" { (items) }
} }
} }
}) })
@ -64,23 +277,91 @@ impl Component for Dropdown {
impl Dropdown { impl Dropdown {
// **< Dropdown BUILDER >*********************************************************************** // **< Dropdown BUILDER >***********************************************************************
/// Establece el identificador único (`id`) del menú desplegable.
#[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
} }
/// Modifica la lista de clases CSS aplicadas al menú desplegable.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_value(op, classes);
self self
} }
/// Establece el título del botón.
#[builder_fn]
pub fn with_button_title(mut self, title: L10n) -> Self {
self.button_title = title;
self
}
/// Ajusta el tamaño del botón.
#[builder_fn]
pub fn with_button_size(mut self, size: ButtonSize) -> Self {
self.button_size = size;
self
}
/// Define el color/estilo del botón.
#[builder_fn]
pub fn with_button_color(mut self, color: ButtonColor) -> Self {
self.button_color = color;
self
}
/// Activa/desactiva el modo *split* (botón de acción + *toggle*).
#[builder_fn]
pub fn with_button_split(mut self, split: bool) -> Self {
self.button_split = split;
self
}
/// Indica si el botón del menú está integrado en un grupo de botones.
#[builder_fn]
pub fn with_button_grouped(mut self, grouped: bool) -> Self {
self.button_grouped = grouped;
self
}
/// Establece la política de cierre automático del menú desplegable.
#[builder_fn]
pub fn with_auto_close(mut self, auto_close: dropdown::AutoClose) -> Self {
self.auto_close = auto_close;
self
}
/// Establece la dirección de despliegue del menú.
#[builder_fn]
pub fn with_direction(mut self, direction: dropdown::Direction) -> Self {
self.direction = direction;
self
}
/// Configura la alineación horizontal (con posible comportamiento *responsive* adicional).
#[builder_fn]
pub fn with_menu_align(mut self, align: dropdown::MenuAlign) -> Self {
self.menu_align = align;
self
}
/// Configura la posición del menú.
#[builder_fn]
pub fn with_menu_position(mut self, position: dropdown::MenuPosition) -> Self {
self.menu_position = position;
self
}
/// Añade un nuevo elemento hijo al menú.
#[inline]
pub fn add_item(mut self, item: dropdown::Item) -> Self { pub fn add_item(mut self, item: dropdown::Item) -> Self {
self.items.add(Child::with(item)); self.items.add(Child::with(item));
self self
} }
/// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`].
#[builder_fn] #[builder_fn]
pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self { pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self {
self.items.alter_typed(op); self.items.alter_typed(op);
@ -89,10 +370,57 @@ impl Dropdown {
// **< Dropdown GETTERS >*********************************************************************** // **< Dropdown GETTERS >***********************************************************************
/// Devuelve las clases CSS asociadas al menú desplegable.
pub fn classes(&self) -> &AttrClasses { pub fn classes(&self) -> &AttrClasses {
&self.classes &self.classes
} }
/// Devuelve el título del botón.
pub fn button_title(&self) -> &L10n {
&self.button_title
}
/// Devuelve el tamaño configurado del botón.
pub fn button_size(&self) -> &ButtonSize {
&self.button_size
}
/// Devuelve el color/estilo configurado del botón.
pub fn button_color(&self) -> &ButtonColor {
&self.button_color
}
/// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*).
pub fn button_split(&self) -> bool {
self.button_split
}
/// Devuelve si el botón del menú está integrado en un grupo de botones.
pub fn button_grouped(&self) -> bool {
self.button_grouped
}
/// Devuelve la política de cierre automático del menú desplegado.
pub fn auto_close(&self) -> &dropdown::AutoClose {
&self.auto_close
}
/// Devuelve la dirección de despliegue configurada.
pub fn direction(&self) -> &dropdown::Direction {
&self.direction
}
/// Devuelve la configuración de alineación horizontal del menú desplegable.
pub fn menu_align(&self) -> &dropdown::MenuAlign {
&self.menu_align
}
/// Devuelve la posición configurada para el menú desplegable.
pub fn menu_position(&self) -> &dropdown::MenuPosition {
&self.menu_position
}
/// Devuelve la lista de elementos (`children`) del menú.
pub fn items(&self) -> &Children { pub fn items(&self) -> &Children {
&self.items &self.items
} }

View file

@ -1,22 +1,53 @@
use pagetop::prelude::*; use pagetop::prelude::*;
// **< ItemType >*********************************************************************************** // **< ItemKind >***********************************************************************************
/// Tipos de [`dropdown::Item`](crate::theme::dropdown::Item) disponibles en un menú desplegable
/// [`Dropdown`](crate::theme::Dropdown).
///
/// Define internamente la naturaleza del elemento y su comportamiento al mostrarse o interactuar
/// con él.
#[derive(AutoDefault)] #[derive(AutoDefault)]
pub enum ItemType { pub enum ItemKind {
/// Elemento vacío, no produce salida.
#[default] #[default]
Void, Void,
/// Etiqueta sin comportamiento interactivo.
Label(L10n), Label(L10n),
Link(L10n, FnPathByContext), /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar
LinkBlank(L10n, FnPathByContext), /// inicialmente deshabilitado.
Link {
label: L10n,
path: FnPathByContext,
blank: bool,
disabled: bool,
},
/// Acción ejecutable en la propia página, sin navegación asociada. Inicialmente puede estar
/// deshabilitado.
Button { label: L10n, disabled: bool },
/// Título o encabezado que separa grupos de opciones.
Header(L10n),
/// Separador visual entre bloques de elementos.
Divider,
} }
// **< Item >*************************************************************************************** // **< Item >***************************************************************************************
/// Representa un **elemento individual** de un menú desplegable
/// [`Dropdown`](crate::theme::Dropdown).
///
/// Cada instancia de [`dropdown::Item`](crate::theme::dropdown::Item) se traduce en un componente
/// visible que puede comportarse como texto, enlace, botón, encabezado o separador, según su
/// [`ItemKind`].
///
/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada,
/// manteniendo una interfaz común para renderizar todos los ítems del menú.
#[rustfmt::skip] #[rustfmt::skip]
#[derive(AutoDefault)] #[derive(AutoDefault)]
pub struct Item { pub struct Item {
item_type: ItemType, id : AttrId,
classes : AttrClasses,
item_kind: ItemKind,
} }
impl Component for Item { impl Component for Item {
@ -24,86 +55,227 @@ impl Component for Item {
Item::default() Item::default()
} }
fn id(&self) -> Option<String> {
self.id.get()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let description: Option<String> = None; match self.item_kind() {
ItemKind::Void => PrepareMarkup::None,
// Obtiene la URL actual desde `cx.request`. ItemKind::Label(label) => PrepareMarkup::With(html! {
let current_path = cx.request().map(|request| request.path()); li id=[self.id()] class=[self.classes().get()] {
span class="dropdown-item-text" {
match self.item_type() {
ItemType::Void => PrepareMarkup::None,
ItemType::Label(label) => PrepareMarkup::With(html! {
li class="dropdown-item" {
span title=[description] {
//(left_icon)
(label.using(cx)) (label.using(cx))
//(right_icon)
} }
} }
}), }),
ItemType::Link(label, path) => {
let item_path = path(cx); ItemKind::Link {
let (class, aria) = if current_path == Some(item_path) { label,
("dropdown-item active", Some("page")) path,
} else { blank,
("dropdown-item", None) disabled,
}; } => {
let path = path(cx);
let current_path = cx.request().map(|request| request.path());
let is_current = !*disabled && current_path.map(|p| p == path).unwrap_or(false);
let mut classes = "dropdown-item".to_string();
if is_current {
classes.push_str(" active");
}
if *disabled {
classes.push_str(" disabled");
}
let href = (!disabled).then_some(path);
let target = (!disabled && *blank).then_some("_blank");
let rel = (!disabled && *blank).then_some("noopener noreferrer");
let aria_current = (href.is_some() && is_current).then_some("page");
let aria_disabled = disabled.then_some("true");
let tabindex = disabled.then_some("-1");
PrepareMarkup::With(html! { PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] { li id=[self.id()] class=[self.classes().get()] {
a class="nav-link" href=(item_path) title=[description] { a
//(left_icon) class=(classes)
href=[href]
target=[target]
rel=[rel]
aria-current=[aria_current]
aria-disabled=[aria_disabled]
tabindex=[tabindex]
{
(label.using(cx)) (label.using(cx))
//(right_icon)
} }
} }
}) })
} }
ItemType::LinkBlank(label, path) => {
let item_path = path(cx); ItemKind::Button { label, disabled } => {
let (class, aria) = if current_path == Some(item_path) { let mut classes = "dropdown-item".to_owned();
("dropdown-item active", Some("page")) if *disabled {
} else { classes.push_str(" disabled");
("dropdown-item", None) }
};
let aria_disabled = disabled.then_some("true");
let disabled_attr = disabled.then_some("disabled");
PrepareMarkup::With(html! { PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] { li id=[self.id()] class=[self.classes().get()] {
a class="nav-link" href=(item_path) title=[description] target="_blank" { button
//(left_icon) class=(classes)
type="button"
aria-disabled=[aria_disabled]
disabled=[disabled_attr]
{
(label.using(cx)) (label.using(cx))
//(right_icon)
} }
} }
}) })
} }
ItemKind::Header(label) => PrepareMarkup::With(html! {
li id=[self.id()] class=[self.classes().get()] {
h6 class="dropdown-header" {
(label.using(cx))
}
}
}),
ItemKind::Divider => PrepareMarkup::With(html! {
li id=[self.id()] class=[self.classes().get()] { hr class="dropdown-divider" {} }
}),
} }
} }
} }
impl Item { impl Item {
/// Crea un elemento de tipo texto, mostrado sin interacción.
pub fn label(label: L10n) -> Self { pub fn label(label: L10n) -> Self {
Item { Item {
item_type: ItemType::Label(label), item_kind: ItemKind::Label(label),
..Default::default() ..Default::default()
} }
} }
/// Crea un enlace para la navegación.
pub fn link(label: L10n, path: FnPathByContext) -> Self { pub fn link(label: L10n, path: FnPathByContext) -> Self {
Item { Item {
item_type: ItemType::Link(label, path), item_kind: ItemKind::Link {
label,
path,
blank: false,
disabled: false,
},
..Default::default() ..Default::default()
} }
} }
/// Crea un enlace deshabilitado que no permite la interacción.
pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self {
Item {
item_kind: ItemKind::Link {
label,
path,
blank: false,
disabled: true,
},
..Default::default()
}
}
/// Crea un enlace que se abre en una nueva ventana o pestaña.
pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { pub fn link_blank(label: L10n, path: FnPathByContext) -> Self {
Item { Item {
item_type: ItemType::LinkBlank(label, path), item_kind: ItemKind::Link {
label,
path,
blank: true,
disabled: false,
},
..Default::default() ..Default::default()
} }
} }
// Item GETTERS. /// Crea un enlace deshabilitado que se abriría en una nueva ventana.
pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self {
Item {
item_kind: ItemKind::Link {
label,
path,
blank: true,
disabled: true,
},
..Default::default()
}
}
pub fn item_type(&self) -> &ItemType { /// Crea un botón de acción local, sin navegación asociada.
&self.item_type pub fn button(label: L10n) -> Self {
Item {
item_kind: ItemKind::Button {
label,
disabled: false,
},
..Default::default()
}
}
/// Crea un botón deshabilitado.
pub fn button_disabled(label: L10n) -> Self {
Item {
item_kind: ItemKind::Button {
label,
disabled: true,
},
..Default::default()
}
}
/// Crea un encabezado para un grupo de elementos dentro del menú.
pub fn header(label: L10n) -> Self {
Item {
item_kind: ItemKind::Header(label),
..Default::default()
}
}
/// Crea un separador visual entre bloques de elementos.
pub fn divider() -> Self {
Item {
item_kind: ItemKind::Divider,
..Default::default()
}
}
// **< Item BUILDER >***************************************************************************
/// Establece el identificador único (`id`) del elemento.
#[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 al elemento.
#[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes);
self
}
// **< Item GETTERS >***************************************************************************
/// Devuelve las clases CSS asociadas al elemento.
pub fn classes(&self) -> &AttrClasses {
&self.classes
}
/// Devuelve el tipo de elemento representado por este ítem.
pub fn item_kind(&self) -> &ItemKind {
&self.item_kind
} }
} }