1
1
import { Menu , MenuDivider , MenuGroup , MenuGroupHeader , MenuItem , MenuList , MenuListProps , MenuPopover , menuPopoverClassNames , MenuPopoverProps , MenuProps , MenuTrigger } from '@fluentui/react-components' ;
2
- import { ChevronLeftRegular , ChevronRightRegular } from '@fluentui/react-icons' ;
3
2
import { IDictionary , isNotEmptyArray , isNotEmptyString , isNullOrEmptyString , isNullOrUndefined , isNumber , isString , isUndefined , jsonClone , stopEvent } from '@kwiz/common' ;
4
3
import React from 'react' ;
5
- import { useStateEX } from '../helpers' ;
4
+ import { useClickableDiv , useStateEX } from '../helpers' ;
6
5
import { useKWIZFluentContext } from '../helpers/context-internal' ;
7
6
import { ButtonEX , ButtonEXProps } from './button' ;
7
+ import { DividerEX } from './divider' ;
8
8
import { Horizontal } from './horizontal' ;
9
9
import { Search } from './search' ;
10
- import { Section } from './section' ;
11
10
12
11
interface iMenuItemEXItem {
13
12
type ?: "item" ;
@@ -24,7 +23,8 @@ interface iMenuItemEXSeparator {
24
23
interface iMenuItemEXGroup {
25
24
type : "group" ;
26
25
title : string ;
27
- items : iMenuItemEX [ ] ;
26
+ //can't nest groups
27
+ items : ( iMenuItemEX & { type ?: "separator" | "item" } ) [ ] ;
28
28
}
29
29
export type iMenuItemEX = iMenuItemEXItem | iMenuItemEXSeparator | iMenuItemEXGroup ;
30
30
@@ -50,6 +50,8 @@ export const MenuEx: React.FunctionComponent<React.PropsWithChildren<IProps>> =
50
50
const [ keepOpen , setKeepOpen ] = useStateEX < IDictionary < boolean > > ( { } ) ;
51
51
const [ opened , setOpened ] = useStateEX < IDictionary < boolean > > ( { } ) ;
52
52
53
+ const clickableDiv = useClickableDiv ( ) ;
54
+
53
55
React . useEffect ( ( ) => {
54
56
window . setTimeout ( ( ) => {
55
57
var menus = document . querySelectorAll ( `.${ menuPopoverClassNames . root } ` ) ;
@@ -65,18 +67,34 @@ export const MenuEx: React.FunctionComponent<React.PropsWithChildren<IProps>> =
65
67
66
68
function renderItems ( items : iMenuItemEX [ ] , level : number ) {
67
69
const myLevelFilter = filterPerLevel [ level ] ;
68
- //get rid of empty/null items
69
- items = items . filter ( i => ! isNullOrUndefined ( i ) && ( isNotEmptyString ( i . type ) || isNotEmptyString ( ( i as iMenuItemEXItem ) . title ) ) )
70
- if ( isNotEmptyString ( myLevelFilter ) ) {
71
- items = items . filter ( i => i . type !== "separator" && i . title . toLowerCase ( ) . indexOf ( myLevelFilter ) >= 0 ) ;
70
+
71
+ const showItem = ( i : iMenuItemEX ) => {
72
+ //get rid of empty/null items
73
+ let show = ! isNullOrUndefined ( i ) && ( isNotEmptyString ( i . type ) || isNotEmptyString ( ( i as iMenuItemEXItem ) . title ) ) ;
74
+ if ( show && isNotEmptyString ( myLevelFilter ) ) {
75
+ if ( i . type === "separator" ) show = false ;
76
+ else if ( i . type === "group" ) {
77
+ //only show group if 1 or more results are in it
78
+ return i . items . filter ( sub => showItem ( sub ) ) . length > 0 ;
79
+ }
80
+ else
81
+ show = i . title . toLowerCase ( ) . indexOf ( myLevelFilter ) >= 0 ;
82
+ }
83
+ return show ;
72
84
}
73
85
86
+ //inject group items into this level - so we share the filter/next functionality. it looks wierd if filter/paging is done per group if they are displayed inline.
87
+ items = items . map ( i => i . type === "group" && isNotEmptyArray ( i . items ) ? [ i , ...i . items ] : i )
88
+ . flat ( )
89
+ //filter empty item or based on text filter
90
+ . filter ( i => showItem ( i ) ) ;
91
+
74
92
let menuItems = items . map ( ( item , index ) => {
75
93
switch ( item . type ) {
76
94
case "group" :
95
+ //todo: technically group items should be nested inside the group for better screen reder support
77
96
return < MenuGroup key = { index } >
78
97
< MenuGroupHeader > { item . title } </ MenuGroupHeader >
79
- { renderItems ( item . items , level + 1 ) }
80
98
</ MenuGroup > ;
81
99
case "separator" :
82
100
return < MenuDivider key = { index } /> ;
@@ -113,36 +131,40 @@ export const MenuEx: React.FunctionComponent<React.PropsWithChildren<IProps>> =
113
131
114
132
const paged = menuItems . length > pageSize ;
115
133
const filtered = menuItems . length > filterThreshold || ! isNullOrEmptyString ( myLevelFilter ) ;
116
- const filterControl = filtered && < Search defaultValue = { myLevelFilter || "" } onChangeDeferred = { ( newValue ) => {
117
- const s = jsonClone ( filterPerLevel ) ;
118
- s [ level ] = newValue ? newValue . toLowerCase ( ) : "" ;
119
- setFilterPerLevel ( s ) ;
120
- } } /> ;
134
+
121
135
if ( paged ) {
122
136
let start = startIndexPerLevel [ level ] ;
123
137
if ( isNullOrUndefined ( start ) ) start = 0 ;
124
138
let hasMore = menuItems . length > start + pageSize ;
125
139
menuItems = menuItems . slice ( start , start + pageSize ) ;
126
- if ( start > 0 || hasMore ) menuItems . splice ( 0 , 0 , < Horizontal key = '$next' >
127
- < ButtonEX disabled = { start < 1 } icon = { < ChevronLeftRegular /> } title = 'previous' onClick = { ( ) => {
128
- const s = jsonClone ( startIndexPerLevel ) ;
129
- s [ level ] = start - pageSize ;
130
- setStartIndexPerLevel ( s ) ;
131
- } } />
132
- < Section main >
133
- { filterControl }
134
- </ Section >
135
- < ButtonEX disabled = { ! hasMore } icon = { < ChevronRightRegular /> } title = 'next' onClick = { ( ) => {
136
- const s = jsonClone ( startIndexPerLevel ) ;
137
- s [ level ] = start + pageSize ;
138
- setStartIndexPerLevel ( s ) ;
139
- } } />
140
- </ Horizontal > ) ;
140
+ if ( start > 0 ) {
141
+ menuItems . splice ( 0 , 0 , < DividerEX key = "$prev" title = 'Previous'
142
+ { ...clickableDiv }
143
+ onClick = { ( ) => {
144
+ const s = jsonClone ( startIndexPerLevel ) ;
145
+ s [ level ] = start - pageSize ;
146
+ setStartIndexPerLevel ( s ) ;
147
+ } }
148
+ > previous</ DividerEX > ) ;
149
+ }
150
+ if ( hasMore )
151
+ menuItems . push ( < DividerEX key = "$next" title = 'Next'
152
+ { ...clickableDiv }
153
+ onClick = { ( ) => {
154
+ const s = jsonClone ( startIndexPerLevel ) ;
155
+ s [ level ] = start + pageSize ;
156
+ setStartIndexPerLevel ( s ) ;
157
+ } }
158
+ > next</ DividerEX > ) ;
141
159
}
142
- else if ( filtered ) {
160
+ if ( filtered ) {
143
161
//just filter - no paging
144
- menuItems . splice ( 0 , 0 , < Horizontal key = '$next' >
145
- { filterControl }
162
+ menuItems . splice ( 0 , 0 , < Horizontal key = '$search' >
163
+ < Search defaultValue = { myLevelFilter || "" } onChangeDeferred = { ( newValue ) => {
164
+ const s = jsonClone ( filterPerLevel ) ;
165
+ s [ level ] = newValue ? newValue . toLowerCase ( ) : "" ;
166
+ setFilterPerLevel ( s ) ;
167
+ } } />
146
168
</ Horizontal > ) ;
147
169
}
148
170
return menuItems ;
0 commit comments