Skip to content

Commit

Permalink
feat(ContextMenu): Position based on available screen real estate
Browse files Browse the repository at this point in the history
  • Loading branch information
m7kvqbe1 committed Feb 22, 2022
1 parent b1f2928 commit fcfd026
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import userEvent from '@testing-library/user-event'

import { ContextMenu, ContextMenuItem, ContextMenuDivider } from '.'
import { Link } from '../Link'
import {
CLICK_MENU_POSITION,
ClickMenuPositionType,
} from '../../hooks/useClickMenu'

const CustomLink = ({ children, onClick }: any) => {
return (
Expand Down Expand Up @@ -127,155 +123,6 @@ describe('ContextMenu', () => {
})
})

describe('when setting different positions', () => {
const ContextExample = ({
position,
}: {
position: ClickMenuPositionType
}) => {
const ref = useRef<HTMLDivElement>(null)

return (
<>
<div ref={ref}>Right click me!</div>
<ContextMenu attachedToRef={ref} position={position}>
<ContextMenuItem
link={<Link href="/hello-foo">Hello, Foo!</Link>}
/>
<ContextMenuItem
link={<Link href="/hello-bar">Hello, Bar!</Link>}
/>
</ContextMenu>
</>
)
}

describe('with a position of `ABOVE`', () => {
beforeEach(() => {
wrapper = render(
<ContextExample position={CLICK_MENU_POSITION.ABOVE} />
)

// @ts-ignore
wrapper.getByTestId('context-menu').getBoundingClientRect = () => ({
height: 100,
width: 50,
})
})

describe('and the user right clicks on the target area', () => {
beforeEach(() => {
fireEvent.contextMenu(wrapper.getByText('Right click me!'))
})

it('is is rendered above the mouse pointer', () => {
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'top',
'-100px'
)
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'left',
'0px'
)
})
})
})

describe('with a position of `RIGHT_ABOVE`', () => {
beforeEach(() => {
wrapper = render(
<ContextExample position={CLICK_MENU_POSITION.RIGHT_ABOVE} />
)

// @ts-ignore
wrapper.getByTestId('context-menu').getBoundingClientRect = () => ({
height: 100,
width: 50,
})
})

describe('and the user right clicks on the target area', () => {
beforeEach(() => {
fireEvent.contextMenu(wrapper.getByText('Right click me!'))
})

it('is is rendered above the mouse pointer', () => {
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'top',
'-100px'
)
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'left',
'0px'
)
})
})
})

describe('with a position of `LEFT_ABOVE`', () => {
beforeEach(() => {
wrapper = render(
<ContextExample position={CLICK_MENU_POSITION.LEFT_ABOVE} />
)

// @ts-ignore
wrapper.getByTestId('context-menu').getBoundingClientRect = () => ({
height: 100,
width: 50,
})
})

describe('and the user right clicks on the target area', () => {
beforeEach(() => {
fireEvent.contextMenu(wrapper.getByText('Right click me!'))
})

it('is is rendered above the mouse pointer on the left', () => {
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'top',
'-100px'
)

expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'left',
'-50px'
)
})
})
})

describe('with a position of `LEFT_BELOW`', () => {
beforeEach(() => {
wrapper = render(
<ContextExample position={CLICK_MENU_POSITION.LEFT_BELOW} />
)

// @ts-ignore
wrapper.getByTestId('context-menu').getBoundingClientRect = () => ({
height: 100,
width: 50,
})
})

describe('and the user right clicks on the target area', () => {
beforeEach(() => {
fireEvent.contextMenu(wrapper.getByText('Right click me!'))
})

it('is is rendered below the mouse pointer on the left', () => {
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'top',
'0px'
)
expect(wrapper.queryByTestId('context-menu')).toHaveStyleRule(
'left',
'-50px'
)
})
})
})
})

describe('With links, no icons and and open', () => {
beforeEach(() => {
const ContextExample = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import React from 'react'
import React, { useMemo } from 'react'

import { ComponentWithClass } from '../../common/ComponentWithClass'
import {
useClickMenu,
ClickType,
ClickMenuPositionType,
CLICK_BUTTON,
CLICK_MENU_POSITION,
} from '../../hooks/useClickMenu'
import { useClickMenu, ClickType, CLICK_BUTTON } from '../../hooks/useClickMenu'
import { StyledContextMenu } from './partials/StyledContextMenu'

export interface ContextMenuProps extends ComponentWithClass {
Expand All @@ -27,10 +21,6 @@ export interface ContextMenuProps extends ComponentWithClass {
* Optional handler function to be invoked when the component is displayed.
*/
onShow?: (e: MouseEvent) => void
/**
* Where to display the component relative to the target element.
*/
position?: ClickMenuPositionType
}

export const ContextMenu: React.FC<ContextMenuProps> = ({
Expand All @@ -39,29 +29,32 @@ export const ContextMenu: React.FC<ContextMenuProps> = ({
clickType = CLICK_BUTTON.RIGHT,
onHide,
onShow,
position = CLICK_MENU_POSITION.RIGHT_BELOW,
...rest
}) => {
const { coordinates, isOpen, menuRef } = useClickMenu<HTMLOListElement>({
attachedToRef,
clickType,
position,
onHide,
onShow,
})
const { mousePointer, isOpen, floatingElementRef, styles, attributes } =
useClickMenu({
attachedToRef,
clickType,
onHide,
onShow,
})

const hasIcons = !!React.Children.toArray(children).filter(
(child: React.ReactNode) => (child as React.ReactElement)?.props?.icon
).length
const hasIcons = useMemo(() => {
return !!React.Children.toArray(children).filter(
(child: React.ReactNode) => (child as React.ReactElement)?.props?.icon
).length
}, [children])

return (
<StyledContextMenu
ref={floatingElementRef}
$hasIcons={hasIcons}
$isOpen={isOpen}
left={mousePointer?.getBoundingClientRect().left}
top={mousePointer?.getBoundingClientRect().top}
style={styles.popper}
{...attributes.popper}
data-testid="context-menu"
left={coordinates.x}
ref={menuRef}
top={coordinates.y}
{...rest}
>
{children}
Expand Down
Loading

0 comments on commit fcfd026

Please sign in to comment.