Spaces:
Running
Running
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> | |
) | |
} | |