From 2f96fec20d87df0164f26502ec46e98989eaf159 Mon Sep 17 00:00:00 2001 From: luoxiaozero <48741584+luoxiaozero@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:29:16 +0800 Subject: [PATCH] feat: MenuItem adds children (#207) --- demo_markdown/docs/menu/mod.md | 26 +++++--- thaw/src/menu/menu-item.css | 48 +++++++++++++- thaw/src/menu/menu_item.rs | 115 ++++++++++++++++++++++++++++++--- thaw/src/menu/mod.rs | 20 ++++-- 4 files changed, 185 insertions(+), 24 deletions(-) diff --git a/demo_markdown/docs/menu/mod.md b/demo_markdown/docs/menu/mod.md index b8e2768..19d107d 100644 --- a/demo_markdown/docs/menu/mod.md +++ b/demo_markdown/docs/menu/mod.md @@ -4,11 +4,19 @@ let value = create_rw_signal(String::from("o")); view! { - + - - + + + + + + + + + + @@ -17,11 +25,12 @@ view! { ### Menu Props -| Name | Type | Default | Description | -| -------- | ----------------------------------- | -------------------- | --------------------------------------- | -| class | `OptionalProp>` | `Default::default()` | Addtional classes for the menu element. | -| value | `Model` | `Default::default()` | The selected item key of the menu. | -| children | `Children` | | Menu's content. | +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| class | `OptionalProp>` | `Default::default()` | Addtional classes for the menu element. | +| value | `Model` | `Default::default()` | The selected item key of the menu. | +| default_expanded_keys | `Vec` | `Default::default()` | The default expanded submenu keys. | +| children | `Children` | | Menu's content. | ### MenuGroup Props @@ -39,3 +48,4 @@ view! { | label | `MaybeSignal` | `Default::default()` | The label of the menu item. | | key | `MaybeSignal` | `Default::default()` | The indentifier of the menu item. | | icon | `OptionalMaybeSignal` | `None` | The icon of the menu item. | +| children | `Option` | `None` | MenuItem's content. | diff --git a/thaw/src/menu/menu-item.css b/thaw/src/menu/menu-item.css index 29b9e72..97bca84 100644 --- a/thaw/src/menu/menu-item.css +++ b/thaw/src/menu/menu-item.css @@ -1,7 +1,11 @@ +.thaw-menu { + padding-bottom: 0.3rem; +} + .thaw-menu-item__content { display: flex; align-items: center; - margin: 0.3rem 0.4rem; + margin: 0.3rem 0.4rem 0; padding: 0.5rem 0.75rem; color: var(--thaw-font-color); cursor: pointer; @@ -22,3 +26,45 @@ color: var(--thaw-font-color-active); background-color: var(--thaw-background-color); } + +.thaw-menu-item__content--submenu-selected { + color: var(--thaw-font-color-active); +} + +.thaw-menu-item__arrow { + font-size: 18px; + margin-inline-start: auto; + transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); + transform: rotate(0deg); +} + +.thaw-menu-item__arrow--open { + transform: rotate(90deg); +} + +.thaw-menu-submenu { + margin-left: 1.6rem; +} + +.thaw-menu-submenu.fade-in-height-expand-transition-leave-from, +.thaw-menu-submenu.fade-in-height-expand-transition-enter-to { + opacity: 1; +} + +.thaw-menu-submenu.fade-in-height-expand-transition-leave-to, +.thaw-menu-submenu.fade-in-height-expand-transition-enter-from { + opacity: 0; + max-height: 0; +} + +.thaw-menu-submenu.fade-in-height-expand-transition-leave-active { + overflow: hidden; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0s, + opacity 0.2s cubic-bezier(0, 0, 0.2, 1) 0s; +} + +.thaw-menu-submenu.fade-in-height-expand-transition-enter-active { + overflow: hidden; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s cubic-bezier(0.4, 0, 1, 1); +} diff --git a/thaw/src/menu/menu_item.rs b/thaw/src/menu/menu_item.rs index 168a8ca..0b723c8 100644 --- a/thaw/src/menu/menu_item.rs +++ b/thaw/src/menu/menu_item.rs @@ -1,8 +1,8 @@ -use super::use_menu; +use super::MenuInjection; use crate::{theme::use_theme, Icon, Theme}; use leptos::*; -use thaw_components::OptionComp; -use thaw_utils::{class_list, mount_style, OptionalMaybeSignal, OptionalProp}; +use thaw_components::{CSSTransition, OptionComp}; +use thaw_utils::{class_list, mount_style, OptionalMaybeSignal, OptionalProp, StoredMaybeSignal}; #[component] pub fn MenuItem( @@ -10,15 +10,47 @@ pub fn MenuItem( #[prop(optional, into)] icon: OptionalMaybeSignal, #[prop(into)] label: MaybeSignal, #[prop(optional, into)] class: OptionalProp>, + #[prop(optional)] children: Option, ) -> impl IntoView { mount_style("menu-item", include_str!("./menu-item.css")); let theme = use_theme(Theme::light); - let menu = use_menu(); - let click_key = key.clone(); + + let submenu_ref = NodeRef::::new(); + let is_children = children.is_some(); + let menu = MenuInjection::use_(); + let parent_menu_item = StoredValue::new(MenuItemInjection::use_()); + + let is_open_children = RwSignal::new({ + key.with_untracked(|key| { + menu.default_expanded_keys + .with_value(|default_expanded_keys| default_expanded_keys.contains(key)) + }) + }); + let key: StoredMaybeSignal<_> = key.into(); + let is_selected = Memo::new(move |_| menu.value.with(|value| key.with(|key| value == key))); + let is_submenu_selected = + Memo::new(move |_| menu.path.with(|path| key.with(|key| path.contains(key)))); + let on_click = move |_| { - let click_key = click_key.get(); - if menu.0.with(|key| key != &click_key) { - menu.0.set(click_key); + if is_children { + is_open_children.set(!is_open_children.get_untracked()); + } else { + if !is_selected.get_untracked() { + menu.path.update(|path| { + path.clear(); + }); + parent_menu_item.with_value(|parent_menu_item| { + if let Some(parent_menu_item) = parent_menu_item { + let mut item_path = vec![]; + parent_menu_item.get_path(&mut item_path); + menu.path.update(|path| { + path.extend(item_path); + }); + } + }); + + menu.value.set(key.get_untracked()); + } } }; @@ -41,8 +73,10 @@ pub fn MenuItem(
+ }.into() + } else { + None + } + }
+ + + + + + + +
} } + +#[derive(Clone)] +struct MenuItemInjection { + pub key: StoredMaybeSignal, + pub parent_menu_item: StoredValue>, +} + +impl MenuItemInjection { + fn use_() -> Option { + use_context() + } + + fn get_path(&self, path: &mut Vec) { + self.parent_menu_item.with_value(|parent_menu_item| { + if let Some(parent_menu_item) = parent_menu_item.as_ref() { + parent_menu_item.get_path(path); + } + }); + + path.push(self.key.get_untracked()); + } +} diff --git a/thaw/src/menu/mod.rs b/thaw/src/menu/mod.rs index 08598dc..f8925ee 100644 --- a/thaw/src/menu/mod.rs +++ b/thaw/src/menu/mod.rs @@ -7,24 +7,34 @@ pub use menu_item::*; pub use theme::MenuTheme; use leptos::*; +use std::collections::BTreeSet; use thaw_utils::{class_list, Model, OptionalProp}; #[component] pub fn Menu( #[prop(optional, into)] value: Model, #[prop(optional, into)] class: OptionalProp>, + #[prop(optional)] default_expanded_keys: Vec, children: Children, ) -> impl IntoView { + let path = RwSignal::new(BTreeSet::::new()); + view! { - +
{children()}
} } #[derive(Clone)] -pub(crate) struct MenuInjection(pub Model); - -pub(crate) fn use_menu() -> MenuInjection { - expect_context() +pub(crate) struct MenuInjection { + pub value: Model, + pub path: RwSignal>, + pub default_expanded_keys: StoredValue>, +} + +impl MenuInjection { + pub fn use_() -> Self { + expect_context() + } }