File size: 3,423 Bytes
13555f3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {useRef, useState, useEffect, useCallback} from 'react'

import './menuWrapper.scss'

type Props = {
    children?: React.ReactNode
    stopPropagationOnToggle?: boolean
    className?: string
    disabled?: boolean
    isOpen?: boolean
    onToggle?: (open: boolean) => void
    label?: string
}

const MenuWrapper = (props: Props) => {
    const node = useRef<HTMLDivElement>(null)
    const [open, setOpen] = useState(Boolean(props.isOpen))

    if (!Array.isArray(props.children) || props.children.length !== 2) {
        throw new Error('MenuWrapper needs exactly 2 children')
    }

    const close = useCallback((): void => {
        if (open) {
            setOpen(false)
            props.onToggle && props.onToggle(false)
        }
    }, [props.onToggle, open])

    const closeOnBlur = useCallback((e: Event) => {
        if (e.target && node.current?.contains(e.target as Node)) {
            return
        }

        close()
    }, [close])

    const keyboardClose = useCallback((e: KeyboardEvent) => {
        if (e.key === 'Escape') {
            close()
        }

        if (e.key === 'Tab') {
            closeOnBlur(e)
        }
    }, [close, closeOnBlur])

    const toggle = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
        if (props.disabled) {
            return
        }

        /**
         * This is only here so that we can toggle the menus in the sidebar, because the default behavior of the mobile
         * version (ie the one that uses a modal) needs propagation to close the modal after selecting something
         * We need to refactor this so that the modal is explicitly closed on toggle, but for now I am aiming to preserve the existing logic
         * so as to not break other things
        **/
        if (props.stopPropagationOnToggle) {
            e.preventDefault()
            e.stopPropagation()
        }
        setOpen(!open)
        props.onToggle && props.onToggle(!open)
    }, [props.onToggle, open, props.disabled])

    useEffect(() => {
        if (open) {
            document.addEventListener('menuItemClicked', close, true)
            document.addEventListener('click', closeOnBlur, true)
            document.addEventListener('keyup', keyboardClose, true)
        }
        return () => {
            if (open) {
                document.removeEventListener('menuItemClicked', close, true)
                document.removeEventListener('click', closeOnBlur, true)
                document.removeEventListener('keyup', keyboardClose, true)
            }
        }
    }, [open, close, closeOnBlur, keyboardClose])

    const {children} = props
    let className = 'MenuWrapper'
    if (props.disabled) {
        className += ' disabled'
    }
    if (open) {
        className += ' override menuOpened'
    }
    if (props.className) {
        className += ' ' + props.className
    }

    return (
        <div
            role='button'
            aria-label={props.label || 'menuwrapper'}
            className={className}
            onClick={toggle}
            ref={node}
        >
            {children ? Object.values(children)[0] : null}
            {children && !props.disabled && open ? Object.values(children)[1] : null}
        </div>
    )
}

export default React.memo(MenuWrapper)