Skip to content

Commit

Permalink
feat: MenuItem adds children (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
luoxiaozero committed Jun 17, 2024
1 parent a9f02ed commit 2f96fec
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 23 deletions.
26 changes: 18 additions & 8 deletions demo_markdown/docs/menu/mod.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@
let value = create_rw_signal(String::from("o"));

view! {
<Menu value>
<Menu value default_expanded_keys=vec![String::from("area")]>
<MenuItem key="a" label="And"/>
<MenuItem key="o" label="Or"/>
<MenuItem icon=icondata::AiAreaChartOutlined key="area" label="Area Chart"/>
<MenuItem icon=icondata::AiPieChartOutlined key="pie" label="Pie Chart"/>
<MenuItem icon=icondata::AiAreaChartOutlined key="area" label="Area Chart">
<MenuItem key="target" label="Target"/>
<MenuItem key="above" label="Above"/>
<MenuItem key="below" label="Below"/>
</MenuItem>
<MenuItem icon=icondata::AiPieChartOutlined key="pie" label="Pie Chart">
<MenuItem key="pie-target" label="Target"/>
<MenuItem key="pie-above" label="Above"/>
<MenuItem key="pie-below" label="Below"/>
</MenuItem>
<MenuItem icon=icondata::AiGithubOutlined key="github" label="Github"/>
<MenuItem icon=icondata::AiChromeOutlined key="chrome" label="Chrome"/>
</Menu>
Expand All @@ -17,11 +25,12 @@ view! {

### Menu Props

| Name | Type | Default | Description |
| -------- | ----------------------------------- | -------------------- | --------------------------------------- |
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the menu element. |
| value | `Model<String>` | `Default::default()` | The selected item key of the menu. |
| children | `Children` | | Menu's content. |
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the menu element. |
| value | `Model<String>` | `Default::default()` | The selected item key of the menu. |
| default_expanded_keys | `Vec<String>` | `Default::default()` | The default expanded submenu keys. |
| children | `Children` | | Menu's content. |

### MenuGroup Props

Expand All @@ -39,3 +48,4 @@ view! {
| label | `MaybeSignal<String>` | `Default::default()` | The label of the menu item. |
| key | `MaybeSignal<String>` | `Default::default()` | The indentifier of the menu item. |
| icon | `OptionalMaybeSignal<icondata_core::Icon>` | `None` | The icon of the menu item. |
| children | `Option<Children>` | `None` | MenuItem's content. |
48 changes: 47 additions & 1 deletion thaw/src/menu/menu-item.css
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
115 changes: 105 additions & 10 deletions thaw/src/menu/menu_item.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,56 @@
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(
#[prop(into)] key: MaybeSignal<String>,
#[prop(optional, into)] icon: OptionalMaybeSignal<icondata_core::Icon>,
#[prop(into)] label: MaybeSignal<String>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional)] children: Option<Children>,
) -> 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::<html::Div>::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());
}
}
};

Expand All @@ -41,8 +73,10 @@ pub fn MenuItem(
<div class="thaw-menu-item">
<div
class=class_list![
"thaw-menu-item__content", ("thaw-menu-item__content--selected", move || menu.0
.get() == key.get()), class.map(| c | move || c.get())
"thaw-menu-item__content",
("thaw-menu-item__content--selected", move || is_selected.get()),
("thaw-menu-item__content--submenu-selected", move || is_submenu_selected.get()),
class.map(| c | move || c.get())
]

on:click=on_click
Expand All @@ -58,7 +92,68 @@ pub fn MenuItem(
}
}
{move || label.get()}
{
if children.is_some() {
view! {
<Icon
icon=icondata_ai::AiRightOutlined
class=Signal::derive(move || {
let mut class = String::from("thaw-menu-item__arrow");
if is_open_children.get() {
class.push_str(" thaw-menu-item__arrow--open");
}
class
})/>
}.into()
} else {
None
}
}
</div>

<OptionComp value=children let:children>
<Provider value=MenuItemInjection { key, parent_menu_item }>
<CSSTransition
node_ref=submenu_ref
name="fade-in-height-expand-transition"
appear=is_open_children.get_untracked()
show=is_open_children
let:display
>
<div
class="thaw-menu-submenu"
style=move || display.get()
ref=submenu_ref
role="menu"
aria-expanded=move || if is_open_children.get() { "true" } else { "false" }
>
{children()}
</div>
</CSSTransition>
</Provider>
</OptionComp>
</div>
}
}

#[derive(Clone)]
struct MenuItemInjection {
pub key: StoredMaybeSignal<String>,
pub parent_menu_item: StoredValue<Option<MenuItemInjection>>,
}

impl MenuItemInjection {
fn use_() -> Option<Self> {
use_context()
}

fn get_path(&self, path: &mut Vec<String>) {
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());
}
}
18 changes: 14 additions & 4 deletions thaw/src/menu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional)] default_expanded_keys: Vec<String>,
children: Children,
) -> impl IntoView {
let path = RwSignal::new(BTreeSet::<String>::new());

view! {
<Provider value=MenuInjection(value)>
<Provider value=MenuInjection { value, path, default_expanded_keys: StoredValue::new(default_expanded_keys) }>
<div class=class_list!["thaw-menu", class.map(| c | move || c.get())]>{children()}</div>
</Provider>
}
}

#[derive(Clone)]
pub(crate) struct MenuInjection(pub Model<String>);
pub(crate) struct MenuInjection {
pub value: Model<String>,
pub path: RwSignal<BTreeSet<String>>,
pub default_expanded_keys: StoredValue<Vec<String>>,
}

pub(crate) fn use_menu() -> MenuInjection {
expect_context()
impl MenuInjection {
pub fn use_() -> Self {
expect_context()
}
}

0 comments on commit 2f96fec

Please sign in to comment.