1
- import React from 'react'
1
+ import React , { useState } from 'react'
2
2
import produce , { Draft } from 'immer'
3
- import getEventPath , { handleRefs , getDirection } from './utils'
3
+ import { usePopper } from 'react-popper'
4
+ import { Placement } from '@popperjs/core'
5
+ import { Options } from '@popperjs/core/lib/modifiers/offset'
4
6
7
+ import getEventPath , { handleRefs , getDirection } from './utils'
5
8
export interface MenuItem {
6
9
id : string
7
10
label : string
@@ -35,8 +38,6 @@ interface ClosePathAction {
35
38
36
39
type Action = ToggleAction | OpenPathAction | ClosePathAction
37
40
38
- type Placement = 'top' | 'bottom' | 'start' | 'end'
39
-
40
41
/**
41
42
* @ignore
42
43
*/
@@ -45,7 +46,7 @@ interface NestedMenuState {
45
46
isOpen : boolean
46
47
currentPath : string [ ]
47
48
currentPathItems : MenuItem [ ]
48
- placement : Placement
49
+ placement ? : Placement
49
50
}
50
51
51
52
/**
@@ -83,6 +84,7 @@ interface NestedMenuProps {
83
84
isOpen ?: boolean
84
85
defaultOpenPath ?: string [ ]
85
86
placement ?: Placement
87
+ offset ?: Options [ 'offset' ]
86
88
}
87
89
88
90
// interface HitAreaProps {
@@ -98,7 +100,8 @@ export const useNestedMenu = ({
98
100
items = [ ] ,
99
101
isOpen = false ,
100
102
defaultOpenPath = [ ] ,
101
- placement = 'end' ,
103
+ placement,
104
+ offset,
102
105
} : NestedMenuProps ) => {
103
106
const [ state , dispatch ] = React . useReducer ( reducer , {
104
107
items,
@@ -171,15 +174,30 @@ export const useNestedMenu = ({
171
174
} )
172
175
173
176
const menuRefs = React . useRef < { [ key : string ] : HTMLElement } > ( { } )
174
-
175
- const getMenuProps = ( item ?: MenuItem ) => ( {
176
- key : item ?. id || 'root' ,
177
- ref : handleRefs ( ( itemNode ) => {
178
- if ( itemNode ) {
179
- menuRefs . current [ item ?. id || 'root' ] = itemNode
177
+ const [ popperElement , setPopperElement ] = useState < HTMLElement | null > ( null )
178
+
179
+ const getMenuProps = ( item ?: MenuItem ) => {
180
+ if ( item ) {
181
+ return {
182
+ key : item . id ,
183
+ ref : handleRefs ( ( itemNode ) => {
184
+ if ( itemNode ) {
185
+ menuRefs . current [ item . id ] = itemNode
186
+ }
187
+ } ) ,
188
+ style : getMenuOffsetStyles ( item ) ,
180
189
}
181
- } ) ,
182
- } )
190
+ } else {
191
+ return {
192
+ key : 'root' ,
193
+ ref : handleRefs ( ( itemNode ) => {
194
+ setPopperElement ( itemNode )
195
+ } ) ,
196
+ style : styles . popper ,
197
+ ...attributes . popper ,
198
+ }
199
+ }
200
+ }
183
201
184
202
const itemRefs = React . useRef < { [ key : string ] : HTMLElement } > ( { } )
185
203
@@ -192,63 +210,16 @@ export const useNestedMenu = ({
192
210
} ) ,
193
211
} )
194
212
195
- const getMenuOffsetStyles = ( currentItem ?: MenuItem ) => {
196
- const item = currentItem ? itemRefs . current [ currentItem . id ] : null
197
- const button = toggleButtonRef . current as HTMLElement
213
+ const getMenuOffsetStyles = ( currentItem ?: MenuItem ) : React . CSSProperties => {
214
+ if ( ! currentItem ) return { }
215
+ const item = itemRefs . current [ currentItem . id ]
198
216
199
217
const dir = getDirection ( )
200
- const rootXEnd =
201
- dir === 'ltr'
202
- ? button . getBoundingClientRect ( ) . right
203
- : window . innerWidth - button . getBoundingClientRect ( ) . left
204
-
205
- let vertical : string = 'top'
206
- let horizontal : string = dir === 'ltr' ? 'left' : 'right'
207
- let verticalValue = item ? 0 : button . getBoundingClientRect ( ) . top
208
- let horizontalValue = item ? item . getBoundingClientRect ( ) . width : rootXEnd
209
-
210
- if ( dir === 'ltr' ) {
211
- if ( placement === 'top' ) {
212
- vertical = item ? 'top' : 'bottom'
213
- verticalValue = item ? 0 : window . innerHeight - button . getBoundingClientRect ( ) . top
214
- horizontalValue = item
215
- ? item . getBoundingClientRect ( ) . width
216
- : button . getBoundingClientRect ( ) . left
217
- } else if ( placement === 'bottom' ) {
218
- verticalValue = item ? 0 : button . getBoundingClientRect ( ) . bottom
219
- horizontalValue = item
220
- ? item . getBoundingClientRect ( ) . width
221
- : button . getBoundingClientRect ( ) . left
222
- } else if ( placement === 'start' ) {
223
- horizontal = item ? 'left' : 'right'
224
- horizontalValue = item
225
- ? item . getBoundingClientRect ( ) . width
226
- : window . innerWidth - button . getBoundingClientRect ( ) . left
227
- }
228
- } else {
229
- if ( placement === 'top' ) {
230
- vertical = item ? 'top' : 'bottom'
231
- horizontal = 'right'
232
- verticalValue = item ? 0 : window . innerHeight - button . getBoundingClientRect ( ) . top
233
- horizontalValue = item
234
- ? item . getBoundingClientRect ( ) . width
235
- : window . innerWidth - button . getBoundingClientRect ( ) . right
236
- } else if ( placement === 'bottom' ) {
237
- verticalValue = item ? 0 : button . getBoundingClientRect ( ) . bottom
238
- horizontalValue = item
239
- ? item . getBoundingClientRect ( ) . width
240
- : window . innerWidth - button . getBoundingClientRect ( ) . right
241
- } else if ( placement === 'start' ) {
242
- horizontal = item ? 'right' : 'left'
243
- horizontalValue = item
244
- ? item . getBoundingClientRect ( ) . width
245
- : button . getBoundingClientRect ( ) . right
246
- }
247
- }
248
218
249
219
return {
250
- [ vertical ] : verticalValue ,
251
- [ horizontal ] : horizontalValue ,
220
+ position : 'absolute' ,
221
+ top : 0 ,
222
+ [ dir === 'ltr' ? 'left' : 'right' ] : item . clientWidth ,
252
223
}
253
224
}
254
225
@@ -294,6 +265,22 @@ export const useNestedMenu = ({
294
265
const anchorRef = React . useRef < HTMLElement > ( )
295
266
const menuRef = React . useRef < HTMLElement > ( )
296
267
268
+ const { styles, attributes } = usePopper ( toggleButtonRef . current , popperElement , {
269
+ placement : 'right-start' ,
270
+ modifiers : [
271
+ {
272
+ name : 'offset' ,
273
+ options : {
274
+ offset : offset ,
275
+ } ,
276
+ } ,
277
+ {
278
+ name : 'flip' ,
279
+ enabled : false ,
280
+ } ,
281
+ ] ,
282
+ } )
283
+
297
284
return {
298
285
getToggleButtonProps,
299
286
getMenuProps,
0 commit comments