This repository has been archived by the owner on Mar 19, 2021. It is now read-only.
/
index.js
116 lines (98 loc) · 2.69 KB
/
index.js
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
113
114
115
116
// @flow
import * as React from "react"
import ReactDom from "react-dom"
import styled, { css } from "styled-components"
import useOnClickOutside from "use-onclickoutside"
import {
MenuContainer as Container,
MenuSeparator as Separator,
MenuItem as Item,
} from "@howtocards/ui"
export { Separator, Item }
type Props = {
trigger: React.Node,
menu: ({ close: () => * }) => React.Node,
as?: string | React.ComponentType<any>,
}
type Ref<T> = {| current: T |}
export const Context = ({ as, trigger, menu }: Props) => {
const Wrapper = as || "div"
const rootElement = document.querySelector("#context-menu")
const wrapperRef: Ref<?HTMLElement> = React.useRef(null)
const menuRef: Ref<?HTMLElement> = React.useRef(null)
const [opened, toggle] = React.useReducer((prev) => !prev, false)
const [position, setPosition] = React.useState(null)
useOnClickOutside(menuRef, toggle)
React.useEffect(() => {})
React.useLayoutEffect(() => {
if (opened) {
const menuCur = menuRef.current
const wrapperCur = wrapperRef.current
const bodyCur = document.body
if (menuCur && wrapperCur && bodyCur) {
const wrapperRect = wrapperCur.getBoundingClientRect()
const menuRect = menuCur.getBoundingClientRect()
const bodyRect = bodyCur.getBoundingClientRect()
const newPosition = {
top: wrapperRect.top + wrapperRect.height,
left: wrapperRect.left - menuRect.width + wrapperRect.width,
}
const isMenuOverTop = newPosition.top < 0
const isMenuOverBottom =
newPosition.top + menuRect.height > bodyRect.height
if (isMenuOverBottom) {
newPosition.top -= menuRect.height
}
if (isMenuOverTop) {
newPosition.top = 0
}
setPosition(newPosition)
}
}
}, [opened, wrapperRef.current])
return (
<>
<Wrapper
// $FlowIssue
ref={wrapperRef}
onClick={toggle}
>
{trigger}
</Wrapper>
{opened &&
rootElement &&
ReactDom.createPortal(
<Background>
<Positioner position={position}>
<Container ref={menuRef}>{menu({ close: toggle })}</Container>
</Positioner>
</Background>,
rootElement,
)}
</>
)
}
Context.defaultProps = {
as: "div",
}
const Positioner = styled.div`
position: relative;
display: inline-block;
${(p) =>
p.position
? css`
left: ${p.position.left}px;
top: ${p.position.top}px;
`
: css`
visibility: hidden;
`}
`
const Background = styled.div`
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 1000;
`