1
- use super :: use_menu ;
1
+ use super :: MenuInjection ;
2
2
use crate :: { theme:: use_theme, Icon , Theme } ;
3
3
use leptos:: * ;
4
- use thaw_components:: OptionComp ;
5
- use thaw_utils:: { class_list, mount_style, OptionalMaybeSignal , OptionalProp } ;
4
+ use thaw_components:: { CSSTransition , OptionComp } ;
5
+ use thaw_utils:: { class_list, mount_style, OptionalMaybeSignal , OptionalProp , StoredMaybeSignal } ;
6
6
7
7
#[ component]
8
8
pub fn MenuItem (
9
9
#[ prop( into) ] key : MaybeSignal < String > ,
10
10
#[ prop( optional, into) ] icon : OptionalMaybeSignal < icondata_core:: Icon > ,
11
11
#[ prop( into) ] label : MaybeSignal < String > ,
12
12
#[ prop( optional, into) ] class : OptionalProp < MaybeSignal < String > > ,
13
+ #[ prop( optional) ] children : Option < Children > ,
13
14
) -> impl IntoView {
14
15
mount_style ( "menu-item" , include_str ! ( "./menu-item.css" ) ) ;
15
16
let theme = use_theme ( Theme :: light) ;
16
- let menu = use_menu ( ) ;
17
- let click_key = key. clone ( ) ;
17
+
18
+ let submenu_ref = NodeRef :: < html:: Div > :: new ( ) ;
19
+ let is_children = children. is_some ( ) ;
20
+ let menu = MenuInjection :: use_ ( ) ;
21
+ let parent_menu_item = StoredValue :: new ( MenuItemInjection :: use_ ( ) ) ;
22
+
23
+ let is_open_children = RwSignal :: new ( {
24
+ key. with_untracked ( |key| {
25
+ menu. default_expanded_keys
26
+ . with_value ( |default_expanded_keys| default_expanded_keys. contains ( key) )
27
+ } )
28
+ } ) ;
29
+ let key: StoredMaybeSignal < _ > = key. into ( ) ;
30
+ let is_selected = Memo :: new ( move |_| menu. value . with ( |value| key. with ( |key| value == key) ) ) ;
31
+ let is_submenu_selected =
32
+ Memo :: new ( move |_| menu. path . with ( |path| key. with ( |key| path. contains ( key) ) ) ) ;
33
+
18
34
let on_click = move |_| {
19
- let click_key = click_key. get ( ) ;
20
- if menu. 0 . with ( |key| key != & click_key) {
21
- menu. 0 . set ( click_key) ;
35
+ if is_children {
36
+ is_open_children. set ( !is_open_children. get_untracked ( ) ) ;
37
+ } else {
38
+ if !is_selected. get_untracked ( ) {
39
+ menu. path . update ( |path| {
40
+ path. clear ( ) ;
41
+ } ) ;
42
+ parent_menu_item. with_value ( |parent_menu_item| {
43
+ if let Some ( parent_menu_item) = parent_menu_item {
44
+ let mut item_path = vec ! [ ] ;
45
+ parent_menu_item. get_path ( & mut item_path) ;
46
+ menu. path . update ( |path| {
47
+ path. extend ( item_path) ;
48
+ } ) ;
49
+ }
50
+ } ) ;
51
+
52
+ menu. value . set ( key. get_untracked ( ) ) ;
53
+ }
22
54
}
23
55
} ;
24
56
@@ -41,8 +73,10 @@ pub fn MenuItem(
41
73
<div class="thaw-menu-item" >
42
74
<div
43
75
class=class_list![
44
- "thaw-menu-item__content" , ( "thaw-menu-item__content--selected" , move || menu. 0
45
- . get( ) == key. get( ) ) , class. map( | c | move || c. get( ) )
76
+ "thaw-menu-item__content" ,
77
+ ( "thaw-menu-item__content--selected" , move || is_selected. get( ) ) ,
78
+ ( "thaw-menu-item__content--submenu-selected" , move || is_submenu_selected. get( ) ) ,
79
+ class. map( | c | move || c. get( ) )
46
80
]
47
81
48
82
on: click=on_click
@@ -58,7 +92,68 @@ pub fn MenuItem(
58
92
}
59
93
}
60
94
{ move || label. get( ) }
95
+ {
96
+ if children. is_some( ) {
97
+ view! {
98
+ <Icon
99
+ icon=icondata_ai:: AiRightOutlined
100
+ class=Signal :: derive( move || {
101
+ let mut class = String :: from( "thaw-menu-item__arrow" ) ;
102
+ if is_open_children. get( ) {
103
+ class. push_str( " thaw-menu-item__arrow--open" ) ;
104
+ }
105
+ class
106
+ } ) />
107
+ } . into( )
108
+ } else {
109
+ None
110
+ }
111
+ }
61
112
</div>
113
+
114
+ <OptionComp value=children let : children>
115
+ <Provider value=MenuItemInjection { key, parent_menu_item } >
116
+ <CSSTransition
117
+ node_ref=submenu_ref
118
+ name="fade-in-height-expand-transition"
119
+ appear=is_open_children. get_untracked( )
120
+ show=is_open_children
121
+ let : display
122
+ >
123
+ <div
124
+ class="thaw-menu-submenu"
125
+ style=move || display. get( )
126
+ ref=submenu_ref
127
+ role="menu"
128
+ aria-expanded=move || if is_open_children. get( ) { "true" } else { "false" }
129
+ >
130
+ { children( ) }
131
+ </div>
132
+ </CSSTransition >
133
+ </Provider >
134
+ </OptionComp >
62
135
</div>
63
136
}
64
137
}
138
+
139
+ #[ derive( Clone ) ]
140
+ struct MenuItemInjection {
141
+ pub key : StoredMaybeSignal < String > ,
142
+ pub parent_menu_item : StoredValue < Option < MenuItemInjection > > ,
143
+ }
144
+
145
+ impl MenuItemInjection {
146
+ fn use_ ( ) -> Option < Self > {
147
+ use_context ( )
148
+ }
149
+
150
+ fn get_path ( & self , path : & mut Vec < String > ) {
151
+ self . parent_menu_item . with_value ( |parent_menu_item| {
152
+ if let Some ( parent_menu_item) = parent_menu_item. as_ref ( ) {
153
+ parent_menu_item. get_path ( path) ;
154
+ }
155
+ } ) ;
156
+
157
+ path. push ( self . key . get_untracked ( ) ) ;
158
+ }
159
+ }
0 commit comments