use pagetop::prelude::*; use crate::prelude::*; use crate::LOCALES_BOOTSIER; /// 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. /// /// Si no tiene título (ver [`with_title()`](Self::with_title)) se muestra únicamente la lista de /// elementos sin ningún botón para interactuar. /// /// Si este componente se usa en un menú [`Nav`] (ver [`nav::Item::dropdown()`]) sólo se tendrán en /// 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`]. /// /// Ver ejemplo en el módulo [`dropdown`]. /// Si no contiene elementos, el componente **no se renderiza**. #[derive(AutoDefault, Getters)] pub struct Dropdown { #[getters(skip)] id: AttrId, /// Devuelve las clases CSS asociadas al menú desplegable. classes: Classes, /// Devuelve el título del menú desplegable. title: L10n, /// Devuelve el tamaño configurado del botón. button_size: ButtonSize, /// Devuelve el color/estilo configurado del botón. button_color: ButtonColor, /// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*). button_split: bool, /// Devuelve si el botón del menú está integrado en un grupo de botones. button_grouped: bool, /// Devuelve la política de cierre automático del menú desplegado. auto_close: dropdown::AutoClose, /// Devuelve la dirección de despliegue configurada. direction: dropdown::Direction, /// Devuelve la configuración de alineación horizontal del menú desplegable. menu_align: dropdown::MenuAlign, /// Devuelve la posición configurada para el menú desplegable. menu_position: dropdown::MenuPosition, /// Devuelve la lista de elementos del menú. items: Children, } impl Component for Dropdown { fn new() -> Self { Self::default() } fn id(&self) -> Option { self.id.get() } fn setup_before_prepare(&mut self, _cx: &mut Context) { self.alter_classes( ClassesOp::Prepend, self.direction().class_with(*self.button_grouped()), ); } fn prepare_component(&self, cx: &mut Context) -> Markup { // Si no hay elementos en el menú, no se prepara. let items = self.items().render(cx); if items.is_empty() { return html! {}; } // Título opcional para el menú desplegable. let title = self.title().using(cx); html! { div id=[self.id()] class=[self.classes().get()] { @if !title.is_empty() { @let mut btn_classes = Classes::new({ let mut classes = "btn".to_string(); self.button_size().push_class(&mut classes); self.button_color().push_class(&mut classes); classes }); @let pos = self.menu_position(); @let offset = pos.data_offset(); @let reference = pos.data_reference(); @let auto_close = self.auto_close.as_str(); @let menu_classes = Classes::new({ let mut classes = "dropdown-menu".to_string(); self.menu_align().push_class(&mut classes); classes }); // 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 type="button" class=[btn_classes.get()] { (title) } }; // Botón *toggle* que abre/cierra el menú asociado. @let btn_toggle = html! { button type="button" class=[btn_classes.alter_classes( ClassesOp::Add, "dropdown-toggle dropdown-toggle-split" ).get()] data-bs-toggle="dropdown" data-bs-offset=[offset] data-bs-reference=[reference] data-bs-auto-close=[auto_close] aria-expanded="false" { span class="visually-hidden" { (L10n::t("dropdown_toggle", &LOCALES_BOOTSIER).using(cx)) } } }; // Orden según dirección (en `dropstart` el *toggle* se sitúa antes). @match self.direction() { dropdown::Direction::Dropstart => { (btn_toggle) ul class=[menu_classes.get()] { (items) } (btn) } _ => { (btn) (btn_toggle) ul class=[menu_classes.get()] { (items) } } } } @else { // Botón único con funcionalidad de *toggle*. button type="button" class=[btn_classes.alter_classes( 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" { (title) } ul class=[menu_classes.get()] { (items) } } } @else { // Sin botón: sólo el listado como menú contextual. ul class="dropdown-menu" { (items) } } } } } } impl Dropdown { // **< Dropdown BUILDER >*********************************************************************** /// Establece el identificador único (`id`) del menú desplegable. #[builder_fn] pub fn with_id(mut self, id: impl AsRef) -> Self { self.id.alter_id(id); self } /// Modifica la lista de clases CSS aplicadas al menú desplegable. #[builder_fn] pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { self.classes.alter_classes(op, classes); self } /// Establece el título del menú desplegable. #[builder_fn] pub fn with_title(mut self, title: L10n) -> Self { self.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 { self.items.add(Child::with(item)); self } /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. #[builder_fn] pub fn with_items(mut self, op: TypedOp) -> Self { self.items.alter_typed(op); self } }