WIP: Añade componente para la gestión de menús #8
10 changed files with 0 additions and 1035 deletions
|
|
@ -62,5 +62,3 @@ pub use poweredby::PoweredBy;
|
||||||
|
|
||||||
mod icon;
|
mod icon;
|
||||||
pub use icon::{Icon, IconKind};
|
pub use icon::{Icon, IconKind};
|
||||||
|
|
||||||
pub mod menu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
mod menu_menu;
|
|
||||||
pub use menu_menu::Menu;
|
|
||||||
|
|
||||||
mod item;
|
|
||||||
pub use item::{Item, ItemKind};
|
|
||||||
|
|
||||||
mod submenu;
|
|
||||||
pub use submenu::Submenu;
|
|
||||||
|
|
||||||
mod megamenu;
|
|
||||||
pub use megamenu::Megamenu;
|
|
||||||
|
|
||||||
mod group;
|
|
||||||
pub use group::Group;
|
|
||||||
|
|
||||||
mod element;
|
|
||||||
pub use element::{Element, ElementType};
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
type Content = Typed<Html>;
|
|
||||||
type SubmenuItems = Typed<menu::Submenu>;
|
|
||||||
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub enum ElementType {
|
|
||||||
#[default]
|
|
||||||
Void,
|
|
||||||
Html(Content),
|
|
||||||
Submenu(SubmenuItems),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Element {
|
|
||||||
element_type: ElementType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Element {
|
|
||||||
fn new() -> Self {
|
|
||||||
Element::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
match self.element_type() {
|
|
||||||
ElementType::Void => PrepareMarkup::None,
|
|
||||||
ElementType::Html(content) => PrepareMarkup::With(html! {
|
|
||||||
(content.render(cx))
|
|
||||||
}),
|
|
||||||
ElementType::Submenu(submenu) => PrepareMarkup::With(html! {
|
|
||||||
(submenu.render(cx))
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element {
|
|
||||||
pub fn html(content: Html) -> Self {
|
|
||||||
Element {
|
|
||||||
element_type: ElementType::Html(Content::with(content)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submenu(submenu: menu::Submenu) -> Self {
|
|
||||||
Element {
|
|
||||||
element_type: ElementType::Submenu(SubmenuItems::with(submenu)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Element GETTERS >************************************************************************
|
|
||||||
|
|
||||||
pub fn element_type(&self) -> &ElementType {
|
|
||||||
&self.element_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Group {
|
|
||||||
id : AttrId,
|
|
||||||
elements: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Group {
|
|
||||||
fn new() -> Self {
|
|
||||||
Group::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
self.id.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
PrepareMarkup::With(html! {
|
|
||||||
div id=[self.id()] class="menu__group" {
|
|
||||||
(self.elements().render(cx))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Group {
|
|
||||||
// **< Group BUILDER >**************************************************************************
|
|
||||||
|
|
||||||
/// Establece el identificador único (`id`) del grupo.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
|
||||||
self.id.alter_value(id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade un nuevo elemento al menú.
|
|
||||||
pub fn add_element(mut self, element: menu::Element) -> Self {
|
|
||||||
self.elements
|
|
||||||
.alter_typed(TypedOp::Add(Typed::with(element)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`].
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_elements(mut self, op: TypedOp<menu::Element>) -> Self {
|
|
||||||
self.elements.alter_typed(op);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Group GETTERS >**************************************************************************
|
|
||||||
|
|
||||||
/// Devuelve la lista de elementos (`children`) del grupo.
|
|
||||||
pub fn elements(&self) -> &Children {
|
|
||||||
&self.elements
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
type Label = L10n;
|
|
||||||
type Content = Typed<Html>;
|
|
||||||
type SubmenuItems = Typed<menu::Submenu>;
|
|
||||||
type MegamenuGroups = Typed<menu::Megamenu>;
|
|
||||||
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub enum ItemKind {
|
|
||||||
#[default]
|
|
||||||
Void,
|
|
||||||
Label(Label),
|
|
||||||
Link(Label, FnPathByContext),
|
|
||||||
LinkBlank(Label, FnPathByContext),
|
|
||||||
Html(Content),
|
|
||||||
Submenu(Label, SubmenuItems),
|
|
||||||
Megamenu(Label, MegamenuGroups),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Item {
|
|
||||||
item_kind : ItemKind,
|
|
||||||
description: AttrL10n,
|
|
||||||
left_icon : Typed<Icon>,
|
|
||||||
right_icon : Typed<Icon>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Item {
|
|
||||||
fn new() -> Self {
|
|
||||||
Item::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
let description = self.description().lookup(cx);
|
|
||||||
let left_icon = self.left_icon().render(cx);
|
|
||||||
let right_icon = self.right_icon().render(cx);
|
|
||||||
|
|
||||||
match self.item_kind() {
|
|
||||||
ItemKind::Void => PrepareMarkup::None,
|
|
||||||
ItemKind::Label(label) => PrepareMarkup::With(html! {
|
|
||||||
li class="menu__item menu__item--label" {
|
|
||||||
span title=[description] {
|
|
||||||
(left_icon)
|
|
||||||
span class="menu__label" { (label.using(cx)) }
|
|
||||||
(right_icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
ItemKind::Link(label, path) => PrepareMarkup::With(html! {
|
|
||||||
li class="menu__item menu__item--link" {
|
|
||||||
a class="menu__link" href=(path(cx)) title=[description] {
|
|
||||||
(left_icon)
|
|
||||||
span class="menu__label" { (label.using(cx)) }
|
|
||||||
(right_icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
ItemKind::LinkBlank(label, path) => PrepareMarkup::With(html! {
|
|
||||||
li class="menu__item menu__item--link" {
|
|
||||||
a class="menu__link" href=(path(cx)) title=[description] target="_blank" {
|
|
||||||
(left_icon)
|
|
||||||
span class="menu__label" { (label.using(cx)) }
|
|
||||||
(right_icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
ItemKind::Html(content) => PrepareMarkup::With(html! {
|
|
||||||
li class="menu__item menu__item--html" {
|
|
||||||
(content.render(cx))
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
ItemKind::Submenu(label, submenu) => PrepareMarkup::With(html! {
|
|
||||||
li class="menu__item menu__item--children" {
|
|
||||||
button type="button" class="menu__link" title=[description] {
|
|
||||||
(left_icon)
|
|
||||||
span class="menu__label" { (label.using(cx)) }
|
|
||||||
(Icon::svg(html! {
|
|
||||||
path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {}
|
|
||||||
}).render(cx))
|
|
||||||
}
|
|
||||||
div class="menu__children menu__children--submenu" {
|
|
||||||
(submenu.render(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
ItemKind::Megamenu(label, megamenu) => PrepareMarkup::With(html! {
|
|
||||||
li class="menu__item menu__item--children" {
|
|
||||||
button type="button" class="menu__link" title=[description] {
|
|
||||||
(left_icon)
|
|
||||||
span class="menu__label" { (label.using(cx)) }
|
|
||||||
(Icon::svg(html! {
|
|
||||||
path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {}
|
|
||||||
}).render(cx))
|
|
||||||
}
|
|
||||||
div class="menu__children menu__children--mega" {
|
|
||||||
(megamenu.render(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Item {
|
|
||||||
pub fn label(label: L10n) -> Self {
|
|
||||||
Item {
|
|
||||||
item_kind: ItemKind::Label(label),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link(label: L10n, path: FnPathByContext) -> Self {
|
|
||||||
Item {
|
|
||||||
item_kind: ItemKind::Link(label, path),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_blank(label: L10n, path: FnPathByContext) -> Self {
|
|
||||||
Item {
|
|
||||||
item_kind: ItemKind::LinkBlank(label, path),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn html(content: Html) -> Self {
|
|
||||||
Item {
|
|
||||||
item_kind: ItemKind::Html(Content::with(content)),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submenu(label: L10n, submenu: menu::Submenu) -> Self {
|
|
||||||
Item {
|
|
||||||
item_kind: ItemKind::Submenu(label, SubmenuItems::with(submenu)),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn megamenu(label: L10n, megamenu: menu::Megamenu) -> Self {
|
|
||||||
Item {
|
|
||||||
item_kind: ItemKind::Megamenu(label, MegamenuGroups::with(megamenu)),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Item BUILDER >***************************************************************************
|
|
||||||
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_description(mut self, text: L10n) -> Self {
|
|
||||||
self.description.alter_value(text);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_left_icon<I: Into<Icon>>(mut self, icon: Option<I>) -> Self {
|
|
||||||
self.left_icon.alter_component(icon.map(Into::into));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_right_icon<I: Into<Icon>>(mut self, icon: Option<I>) -> Self {
|
|
||||||
self.right_icon.alter_component(icon.map(Into::into));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Item GETTERS >***************************************************************************
|
|
||||||
|
|
||||||
pub fn item_kind(&self) -> &ItemKind {
|
|
||||||
&self.item_kind
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn description(&self) -> &AttrL10n {
|
|
||||||
&self.description
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn left_icon(&self) -> &Typed<Icon> {
|
|
||||||
&self.left_icon
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn right_icon(&self) -> &Typed<Icon> {
|
|
||||||
&self.right_icon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Megamenu {
|
|
||||||
id : AttrId,
|
|
||||||
groups: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Megamenu {
|
|
||||||
fn new() -> Self {
|
|
||||||
Megamenu::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
self.id.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
PrepareMarkup::With(html! {
|
|
||||||
div id=[self.id()] class="menu__mega" {
|
|
||||||
(self.groups().render(cx))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Megamenu {
|
|
||||||
// **< Megamenu BUILDER >***********************************************************************
|
|
||||||
|
|
||||||
/// Establece el identificador único (`id`) del megamenú.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
|
||||||
self.id.alter_value(id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade un nuevo grupo al menú.
|
|
||||||
pub fn add_group(mut self, group: menu::Group) -> Self {
|
|
||||||
self.groups.alter_typed(TypedOp::Add(Typed::with(group)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la lista de grupos (`children`) aplicando una operación [`TypedOp`].
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_groups(mut self, op: TypedOp<menu::Group>) -> Self {
|
|
||||||
self.groups.alter_typed(op);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Megamenu GETTERS >***********************************************************************
|
|
||||||
|
|
||||||
/// Devuelve la lista de grupos (`children`) del megamenú.
|
|
||||||
pub fn groups(&self) -> &Children {
|
|
||||||
&self.groups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Menu {
|
|
||||||
id : AttrId,
|
|
||||||
classes: AttrClasses,
|
|
||||||
items : Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Menu {
|
|
||||||
fn new() -> Self {
|
|
||||||
Menu::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
self.id.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
|
||||||
self.alter_classes(ClassesOp::Prepend, "menu");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
// cx.set_param::<bool>(PARAM_BASE_INCLUDE_MENU_ASSETS, &true);
|
|
||||||
// cx.set_param::<bool>(PARAM_BASE_INCLUDE_ICONS, &true);
|
|
||||||
|
|
||||||
PrepareMarkup::With(html! {
|
|
||||||
div id=[self.id()] class=[self.classes().get()] {
|
|
||||||
div class="menu__wrapper" {
|
|
||||||
div class="menu__panel" {
|
|
||||||
div class="menu__overlay" {}
|
|
||||||
nav class="menu__nav" {
|
|
||||||
div class="menu__header" {
|
|
||||||
button type="button" class="menu__back" {
|
|
||||||
(Icon::svg(html! {
|
|
||||||
path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0" {}
|
|
||||||
}).render(cx))
|
|
||||||
}
|
|
||||||
div class="menu__title" {}
|
|
||||||
button type="button" class="menu__close" {
|
|
||||||
(Icon::svg(html! {
|
|
||||||
path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" {}
|
|
||||||
}).render(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ul class="menu__list" {
|
|
||||||
(self.items().render(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button
|
|
||||||
type="button"
|
|
||||||
class="menu__trigger"
|
|
||||||
title=[L10n::l("menu_toggle").lookup(cx)]
|
|
||||||
{
|
|
||||||
(Icon::svg(html! {
|
|
||||||
path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5" {}
|
|
||||||
}).render(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Menu {
|
|
||||||
// **< Menu BUILDER >***************************************************************************
|
|
||||||
|
|
||||||
/// Establece el identificador único (`id`) del menú.
|
|
||||||
#[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 menú.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
|
||||||
self.classes.alter_value(op, classes);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade un nuevo ítem al menú.
|
|
||||||
pub fn add_item(mut self, item: menu::Item) -> Self {
|
|
||||||
self.items.alter_typed(TypedOp::Add(Typed::with(item)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`].
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_items(mut self, op: TypedOp<menu::Item>) -> Self {
|
|
||||||
self.items.alter_typed(op);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Menu GETTERS >***************************************************************************
|
|
||||||
|
|
||||||
/// Devuelve las clases CSS asociadas al menú.
|
|
||||||
pub fn classes(&self) -> &AttrClasses {
|
|
||||||
&self.classes
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la lista de ítems (`children`) del menú.
|
|
||||||
pub fn items(&self) -> &Children {
|
|
||||||
&self.items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Submenu {
|
|
||||||
id : AttrId,
|
|
||||||
title: AttrL10n,
|
|
||||||
items: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Submenu {
|
|
||||||
fn new() -> Self {
|
|
||||||
Submenu::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> Option<String> {
|
|
||||||
self.id.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
|
||||||
PrepareMarkup::With(html! {
|
|
||||||
div id=[self.id()] class="menu__submenu" {
|
|
||||||
@if let Some(title) = self.title().lookup(cx) {
|
|
||||||
h4 class="menu__submenu-title" { (title) }
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
(self.items().render(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Submenu {
|
|
||||||
// **< Submenu BUILDER >************************************************************************
|
|
||||||
|
|
||||||
/// Establece el identificador único (`id`) del submenú.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
|
||||||
self.id.alter_value(id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_title(mut self, title: L10n) -> Self {
|
|
||||||
self.title.alter_value(title);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade un nuevo ítem al submenú.
|
|
||||||
pub fn add_item(mut self, item: menu::Item) -> Self {
|
|
||||||
self.items.alter_typed(TypedOp::Add(Typed::with(item)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`].
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_items(mut self, op: TypedOp<menu::Item>) -> Self {
|
|
||||||
self.items.alter_typed(op);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Submenu GETTERS >************************************************************************
|
|
||||||
|
|
||||||
pub fn title(&self) -> &AttrL10n {
|
|
||||||
&self.title
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la lista de ítems (`children`) del submenú.
|
|
||||||
pub fn items(&self) -> &Children {
|
|
||||||
&self.items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,384 +0,0 @@
|
||||||
/* Aislamiento & normalización */
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
isolation: isolate;
|
|
||||||
}
|
|
||||||
@supports (all: revert) {
|
|
||||||
.menu {
|
|
||||||
all: revert;
|
|
||||||
display: block; }
|
|
||||||
}
|
|
||||||
.menu {
|
|
||||||
box-sizing: border-box;
|
|
||||||
line-height: var(--val-menu--line-height, 1.5);
|
|
||||||
color: var(--val-color--text);
|
|
||||||
text-align: left;
|
|
||||||
text-transform: none;
|
|
||||||
letter-spacing: normal;
|
|
||||||
word-spacing: normal;
|
|
||||||
white-space: normal;
|
|
||||||
cursor: default;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
z-index: 9999;
|
|
||||||
border: 0;
|
|
||||||
background: var(--val-menu--color-bg);
|
|
||||||
}
|
|
||||||
.menu *,
|
|
||||||
.menu *::before,
|
|
||||||
.menu *::after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
.menu :where(a, button) {
|
|
||||||
appearance: none;
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
.menu :where(a, button):focus-visible {
|
|
||||||
outline: 2px solid var(--val-menu--color-highlight);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
.menu :where(ul, ol) {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.menu svg {
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Estructura */
|
|
||||||
|
|
||||||
.menu__wrapper {
|
|
||||||
padding-right: var(--val-gap);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__nav li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
margin-inline-start: 1.5rem;
|
|
||||||
padding: 0;
|
|
||||||
line-height: var(--val-menu--item-height);
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__item--label,
|
|
||||||
.menu__nav li > .menu__link {
|
|
||||||
position: relative;
|
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
font-size: 1.45rem;
|
|
||||||
}
|
|
||||||
.menu__nav li > .menu__link {
|
|
||||||
transition: color 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
.menu__nav li:hover > .menu__link,
|
|
||||||
.menu__nav li > .menu__link:focus {
|
|
||||||
color: var(--val-menu--color-highlight);
|
|
||||||
}
|
|
||||||
.menu__nav li > .menu__link > svg.icon {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__children {
|
|
||||||
position: absolute;
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
padding: var(--val-gap-0-5) var(--val-gap-1-5);
|
|
||||||
border: 0;
|
|
||||||
background: var(--val-menu--color-bg);
|
|
||||||
border-top: 3px solid var(--val-menu--color-highlight);
|
|
||||||
z-index: 500;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
box-shadow: 0 4px 6px -1px var(--val-menu--color-border), 0 2px 4px -1px var(--val-menu--color-shadow);
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__item--children:hover > .menu__children,
|
|
||||||
.menu__item--children > .menu__link:focus + .menu__children,
|
|
||||||
.menu__item--children .menu__children:focus-within {
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__submenu {
|
|
||||||
min-width: var(--val-menu--item-width-min);
|
|
||||||
max-width: var(--val-menu--item-width-max);
|
|
||||||
}
|
|
||||||
.menu__submenu-title {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0;
|
|
||||||
padding: var(--val-menu--line-padding) 0;
|
|
||||||
line-height: var(--val-menu--line-height);
|
|
||||||
border: 0;
|
|
||||||
color: var(--val-menu--color-highlight);
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
.menu__submenu li {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__children--mega {
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__mega {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__header,
|
|
||||||
.menu__trigger {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive <= 62rem (992px) */
|
|
||||||
|
|
||||||
@media (max-width: 62rem) {
|
|
||||||
.menu__wrapper {
|
|
||||||
padding-right: var(--val-gap-0-5);
|
|
||||||
}
|
|
||||||
.menu__trigger {
|
|
||||||
width: var(--val-menu--trigger-width);
|
|
||||||
height: var(--val-menu--item-height);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.menu__trigger svg.icon {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__nav,
|
|
||||||
.menu__children {
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__nav {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: var(--val-menu--side-width);
|
|
||||||
height: 100%;
|
|
||||||
z-index: 9099;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--val-menu--color-bg);
|
|
||||||
transform: translate(-100%);
|
|
||||||
transition: transform .5s ease-in-out, opacity .5s ease-in-out;
|
|
||||||
will-change: transform;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
visibility: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.menu__nav.active {
|
|
||||||
transform: translate(0%);
|
|
||||||
visibility: visible;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__nav li {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
line-height: var(--val-menu--line-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__item--label,
|
|
||||||
.menu__nav li > .menu__link {
|
|
||||||
display: block;
|
|
||||||
text-align: inherit;
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap);
|
|
||||||
border-bottom: 1px solid var(--val-menu--color-border);
|
|
||||||
}
|
|
||||||
.menu__nav li ul li.menu__item--label,
|
|
||||||
.menu__nav li ul li > .menu__link {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
.menu__nav li > .menu__link > svg.icon {
|
|
||||||
position: absolute;
|
|
||||||
top: var(--val-menu--line-padding);
|
|
||||||
right: var(--val-menu--line-padding);
|
|
||||||
height: var(--val-menu--line-height);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__children {
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
max-width: none;
|
|
||||||
min-width: auto;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0;
|
|
||||||
border-top: 0;
|
|
||||||
opacity: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
visibility: visible;
|
|
||||||
transform: translateX(0%);
|
|
||||||
box-shadow: none;
|
|
||||||
transition: opacity .5s ease-in-out, transform .5s ease-in-out, margin-top .5s ease-in-out;
|
|
||||||
}
|
|
||||||
.menu__children.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.menu__children > :first-child {
|
|
||||||
margin-top: 2.675rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__submenu-title {
|
|
||||||
padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__mega {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__header {
|
|
||||||
position: sticky;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
top: 0;
|
|
||||||
height: var(--val-menu--item-height);
|
|
||||||
border-bottom: 1px solid var(--val-menu--color-border);
|
|
||||||
background: var(--val-menu--color-bg);
|
|
||||||
z-index: 501;
|
|
||||||
}
|
|
||||||
.menu__title {
|
|
||||||
padding: var(--val-menu--line-padding);
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-size: 1.45rem;
|
|
||||||
font-weight: normal;
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(.25rem);
|
|
||||||
transition: opacity .5s ease-in-out, transform .5s ease-in-out;
|
|
||||||
will-change: opacity, transform;
|
|
||||||
}
|
|
||||||
.menu__header.active .menu__title {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
.menu__close,
|
|
||||||
.menu__back {
|
|
||||||
width: var(--val-menu--item-height);
|
|
||||||
min-width: var(--val-menu--item-height);
|
|
||||||
height: var(--val-menu--item-height);
|
|
||||||
line-height: var(--val-menu--item-height);
|
|
||||||
color: var(--val-color--text);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: var(--val-menu--color-bg);
|
|
||||||
}
|
|
||||||
.menu__close {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
border: 1px solid var(--val-menu--color-border) !important;
|
|
||||||
border-width: 0 0 1px 1px !important;
|
|
||||||
}
|
|
||||||
.menu__back {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
border: 1px solid var(--val-menu--color-border) !important;
|
|
||||||
border-width: 0 1px 1px 0 !important;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.menu__header.active .menu__back {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__list {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu__overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 9098;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
background: rgba(0, 0, 0, 0.55);
|
|
||||||
transition: opacity .5s ease-in-out, visibility 0s linear .5s;
|
|
||||||
}
|
|
||||||
.menu__overlay.active {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
transition-delay: 0s, 0s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) and (pointer: fine) {
|
|
||||||
.menu__item--children:hover > .menu__children {
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.menu.menu--closing .menu__children {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
opacity: 0 !important;
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.menu__nav,
|
|
||||||
.menu__children,
|
|
||||||
.menu__title,
|
|
||||||
.menu__overlay {
|
|
||||||
transition: none !important;
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animaciones */
|
|
||||||
|
|
||||||
@keyframes slideLeft {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideRight {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
const getTitle = (li) => li.querySelector('.menu__label')?.textContent.trim() ?? '';
|
|
||||||
|
|
||||||
function menu__showChildren(nav, children) {
|
|
||||||
const li = children[0];
|
|
||||||
const submenu = li.querySelector('.menu__children');
|
|
||||||
submenu.classList.add('active');
|
|
||||||
submenu.style.animation = 'slideLeft 0.5s ease forwards';
|
|
||||||
|
|
||||||
nav.querySelector('.menu__title').textContent = getTitle(li);;
|
|
||||||
nav.querySelector('.menu__header').classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
function menu__hideChildren(nav, children) {
|
|
||||||
const submenu = children[0].querySelector('.menu__children');
|
|
||||||
submenu.style.animation = 'slideRight 0.5s ease forwards';
|
|
||||||
setTimeout(() => {
|
|
||||||
submenu.classList.remove('active');
|
|
||||||
submenu.style.removeProperty('animation');
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
children.shift();
|
|
||||||
if (children.length > 0) {
|
|
||||||
nav.querySelector('.menu__title').textContent = getTitle(children[0]);
|
|
||||||
} else {
|
|
||||||
nav.querySelector('.menu__header').classList.remove('active');
|
|
||||||
nav.querySelector('.menu__title').textContent = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function menu__toggle(nav, overlay) {
|
|
||||||
nav.classList.toggle('active');
|
|
||||||
overlay.classList.toggle('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
function menu__reset(menu, nav, overlay) {
|
|
||||||
menu__toggle(nav, overlay);
|
|
||||||
setTimeout(() => {
|
|
||||||
nav.querySelector('.menu__header').classList.remove('active');
|
|
||||||
nav.querySelector('.menu__title').textContent = '';
|
|
||||||
menu.querySelectorAll('.menu__children').forEach(submenu => {
|
|
||||||
submenu.classList.remove('active');
|
|
||||||
submenu.style.removeProperty('animation');
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('.menu').forEach(menu => {
|
|
||||||
let menuChildren = [];
|
|
||||||
const menuNav = menu.querySelector('.menu__nav');
|
|
||||||
const menuOverlay = menu.querySelector('.menu__overlay');
|
|
||||||
|
|
||||||
menu.querySelector('.menu__list').addEventListener('click', (e) => {
|
|
||||||
if (menuNav.classList.contains('active')) {
|
|
||||||
let target = e.target.closest('.menu__item--children');
|
|
||||||
if (target && target != menuChildren[0]) {
|
|
||||||
menuChildren.unshift(target);
|
|
||||||
menu__showChildren(menuNav, menuChildren);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.querySelector('.menu__back').addEventListener('click', () => {
|
|
||||||
menu__hideChildren(menuNav, menuChildren);
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.querySelector('.menu__close').addEventListener('click', () => {
|
|
||||||
menuChildren = menu__reset(menu, menuNav, menuOverlay);
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.querySelectorAll('.menu__item--link > a[target="_blank"]').forEach(link => {
|
|
||||||
link.addEventListener('click', (e) => {
|
|
||||||
menuChildren = menu__reset(menu, menuNav, menuOverlay);
|
|
||||||
e.target.blur();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.querySelector('.menu__trigger').addEventListener('click', () => {
|
|
||||||
menu__toggle(menuNav, menuOverlay);
|
|
||||||
});
|
|
||||||
|
|
||||||
menuOverlay.addEventListener('click', () => {
|
|
||||||
menu__toggle(menuNav, menuOverlay);
|
|
||||||
});
|
|
||||||
|
|
||||||
let resizeTimeout;
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
if (menuNav.classList.contains('active')) {
|
|
||||||
clearTimeout(resizeTimeout);
|
|
||||||
resizeTimeout = setTimeout(() => {
|
|
||||||
menuChildren = menu__reset(menu, menuNav, menuOverlay);
|
|
||||||
}, 150);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue