File size: 1,928 Bytes
f23825d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import styled from "@emotion/styled"
import * as Portal from "@radix-ui/react-portal"
import { FC, ReactNode, useEffect } from "react"
import { IPoint } from "../common/geometry"

export const ContextMenuHotKey = styled.div`
  font-size: 0.9em;
  flex-grow: 1;
  text-align: right;
  color: ${({ theme }) => theme.secondaryTextColor};
  margin-left: 2em;
`

const Wrapper = styled.div`
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
`

const Content = styled.div`
  position: absolute;
  background: ${({ theme }) => theme.secondaryBackgroundColor};
  border-radius: 0.5rem;
  box-shadow: 0 1rem 3rem ${({ theme }) => theme.shadowColor};
  border: 1px solid ${({ theme }) => theme.backgroundColor};
  padding: 0.5rem 0;
`

const List = styled.ul`
  list-style: none;
  padding: 0;
  margin: 0;
`

export interface ContextMenuProps {
  isOpen: boolean
  position: IPoint
  handleClose: () => void
  children?: ReactNode
}

const estimatedWidth = 200

export const ContextMenu: FC<ContextMenuProps> = ({
  isOpen,
  handleClose,
  position,
  children,
}) => {
  // Menu cannot handle keydown while disabling focus, so we deal with global keydown event
  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (isOpen && e.key === "Escape") {
        handleClose()
      }
    }
    document.addEventListener("keydown", onKeyDown)

    return () => {
      document.removeEventListener("keydown", onKeyDown)
    }
  }, [isOpen])

  if (!isOpen) {
    return <></>
  }

  // fix position to avoid placing menu outside of the screen
  const fixedX = Math.min(position.x, window.innerWidth - estimatedWidth)

  return (
    <Portal.Root>
      <Wrapper onClick={handleClose}>
        <Content
          style={{ left: fixedX, top: position.y }}
          onClick={(e) => e.stopPropagation()}
        >
          <List>{children}</List>
        </Content>
      </Wrapper>
    </Portal.Root>
  )
}