Skip to content

Commit

Permalink
fix(fuselage): MenuV2 behavior (#1089)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
  • Loading branch information
juliajforesti and ggazzo committed Jun 28, 2023
1 parent 779ba02 commit a0e4bb9
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 55 deletions.
4 changes: 2 additions & 2 deletions packages/fuselage/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, Ref, RefObject } from 'react';
import React, { forwardRef } from 'react';

import { DropdownDesktop } from './DropdownDesktop';
import { DropdownDesktopWrapper } from './DropdownDesktopWrapper';
import { DropdownMobile } from './DropdownMobile';

export const Dropdown = forwardRef(function Dropdown<
Expand All @@ -24,7 +24,7 @@ export const Dropdown = forwardRef(function Dropdown<
const notSmall = useMediaQuery('(min-width: 500px)');

return notSmall ? (
<DropdownDesktop
<DropdownDesktopWrapper
reference={reference}
children={children}
placement={placement}
Expand Down
15 changes: 5 additions & 10 deletions packages/fuselage/src/components/Dropdown/DropdownDesktop.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import { usePosition } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, Ref, RefObject } from 'react';
import type { CSSProperties, ReactNode, Ref } from 'react';
import React, { forwardRef } from 'react';

import { Box, Tile } from '..';

export const DropdownDesktop = forwardRef(function DropdownDesktop<
T extends HTMLElement,
R extends HTMLElement
>(
{
children,
reference,
placement = 'bottom-start',
style,
...props
}: {
reference: RefObject<T>;
placement?: UsePositionOptions['placement'];
children: ReactNode;
style?: CSSProperties;
},
ref: Ref<R>
) {
const { style } = usePosition(reference, ref as RefObject<R>, { placement });

return (
<Tile
style={style}
Expand All @@ -33,6 +27,7 @@ export const DropdownDesktop = forwardRef(function DropdownDesktop<
flexDirection='column'
overflow='auto'
data-testid='dropdown'
{...props}
>
<Box flexShrink={1} pb='x12'>
{(style as any).visibility === 'hidden' ? null : children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import { usePosition } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, Ref, RefObject } from 'react';
import React, { forwardRef } from 'react';

import { DropdownDesktop } from './DropdownDesktop';

export const DropdownDesktopWrapper = forwardRef(
function DropdownDesktopWrapper<T extends HTMLElement, R extends HTMLElement>(
{
children,
reference,
placement = 'bottom-start',
...props
}: {
reference: RefObject<T>;
placement?: UsePositionOptions['placement'];
children: ReactNode;
},
ref: Ref<R>
) {
const { style } = usePosition(reference, ref as RefObject<R>, {
placement,
});

return (
<DropdownDesktop style={style} children={children} ref={ref} {...props} />
);
}
);
4 changes: 3 additions & 1 deletion packages/fuselage/src/components/Dropdown/DropdownMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const DropdownMobile = forwardRef(function DropdownMobile<
>(
{
children,
...props
}: {
children: ReactNode;
},
Expand All @@ -19,7 +20,7 @@ export const DropdownMobile = forwardRef(function DropdownMobile<
elevation='2'
pi='0'
pb='0'
w='100%'
w='100vw'
maxHeight='80%'
position='fixed'
display='flex'
Expand All @@ -28,6 +29,7 @@ export const DropdownMobile = forwardRef(function DropdownMobile<
style={{ bottom: 0, left: 0 }}
zIndex={2}
data-testid='dropdown'
{...props}
>
<Box flexShrink={1} pb='x16'>
{children}
Expand Down
7 changes: 2 additions & 5 deletions packages/fuselage/src/components/Menu/V2/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@ export default {
decorators: [
(story) => (
<Box
position='relative'
minHeight={50}
height='full'
minWidth={100}
maxWidth={250}
width={'full'}
display='flex'
alignItems='center'
>
{story()}
</Box>
Expand All @@ -53,7 +50,7 @@ export default {
} as MenuStories;

export const Simple: ComponentStory<typeof Menu> = (args) => (
<Menu {...args}>
<Menu {...args} placement='right-start'>
<MenuItem key='1'>Profile</MenuItem>
<MenuItem key='2'>Chats</MenuItem>
<MenuItem key='3'>Settings</MenuItem>
Expand Down Expand Up @@ -116,7 +113,7 @@ export const MenuDisplayExample: ComponentStory<typeof Menu> = (args) => {
const [groupByTypes, setGroupByTypes] = useState(false);

return (
<Menu selectionMode='multiple' {...args}>
<Menu selectionMode='multiple' placement='top-start' {...args}>
<MenuSection title='Display'>
<MenuItem key='extended'>
<MenuItemIcon name='extended-view' />
Expand Down
53 changes: 16 additions & 37 deletions packages/fuselage/src/components/Menu/V2/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@ import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps, ElementType } from 'react';
import React, { useRef } from 'react';
import type { AriaMenuProps } from 'react-aria';
import {
mergeProps,
useMenuTrigger,
FocusScope,
usePress,
DismissButton,
useOverlay,
} from 'react-aria';
import { useButton, useMenuTrigger } from 'react-aria';
import type { MenuTriggerProps } from 'react-stately';
import { useMenuTriggerState } from 'react-stately';

import { IconButton } from '../../Button';
import { Dropdown } from '../../Dropdown';
import MenuDropDown from './MenuDropdown';
import MenuPopover from './MenuPopover';
import { getPlacement } from './helpers/helpers';

interface MenuButtonProps<T> extends AriaMenuProps<T>, MenuTriggerProps {
icon?: ComponentProps<typeof IconButton>['icon'];
Expand All @@ -40,44 +34,29 @@ const Menu = <T extends object>({
}: MenuButtonProps<T>) => {
const state = useMenuTriggerState(props);

const trigger = useRef(null);
const target = useRef(null);
const { overlayProps } = useOverlay(
{
isOpen: state.isOpen,
onClose: state.close,
shouldCloseOnBlur: true,
isDismissable: true,
},
target
);

const { menuProps, menuTriggerProps } = useMenuTrigger<T>({}, state, trigger);
const ref = useRef(null);
const { menuTriggerProps, menuProps } = useMenuTrigger<T>({}, state, ref);

const { pressProps } = usePress(menuTriggerProps);
const { buttonProps } = useButton(menuTriggerProps, ref);

return (
<>
<MenuButton
{...buttonProps}
ref={ref}
icon={icon}
ref={trigger}
small
title={title}
className={className}
{...mergeProps(pressProps)}
title={title}
small
/>
{state.isOpen && (
<Dropdown
{...overlayProps}
ref={target}
reference={trigger}
placement={placement}
<MenuPopover
state={state}
triggerRef={ref}
placement={getPlacement(placement)}
>
<FocusScope restoreFocus>
<MenuDropDown {...props} {...menuProps} />
<DismissButton onDismiss={state.close} />
</FocusScope>
</Dropdown>
<MenuDropDown {...props} {...menuProps} />
</MenuPopover>
)}
</>
);
Expand Down
38 changes: 38 additions & 0 deletions packages/fuselage/src/components/Menu/V2/MenuPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useBreakpoints } from '@rocket.chat/fuselage-hooks';
import React from 'react';
import { usePopover } from 'react-aria';
import type { AriaPopoverProps } from 'react-aria';
import type { OverlayTriggerState } from 'react-stately';

import { DropdownDesktop } from '../../Dropdown/DropdownDesktop';
import { DropdownMobile } from '../../Dropdown/DropdownMobile';

interface PopoverProps extends Omit<AriaPopoverProps, 'popoverRef'> {
children: React.ReactNode;
state: OverlayTriggerState;
}

function MenuPopover({ children, state, offset = 4, ...props }: PopoverProps) {
const popoverRef = React.useRef(null);
const { popoverProps } = usePopover(
{
...props,
offset,
popoverRef,
},
state
);

const breakpoints = useBreakpoints();
const isMobile = !breakpoints.includes('sm');

if (isMobile) {
const mobileProps = { ...popoverProps, style: { bottom: 0, left: 0 } };
return <DropdownMobile children={children} {...mobileProps} />;
}

return (
<DropdownDesktop children={children} ref={popoverRef} {...popoverProps} />
);
}
export default MenuPopover;
61 changes: 61 additions & 0 deletions packages/fuselage/src/components/Menu/V2/helpers/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';

type ReactAriaPlacement =
| 'bottom'
| 'bottom left'
| 'bottom right'
| 'bottom start'
| 'bottom end'
| 'top'
| 'top left'
| 'top right'
| 'top start'
| 'top end'
| 'left'
| 'left top'
| 'left bottom'
| 'start'
| 'start top'
| 'start bottom'
| 'right'
| 'right top'
| 'right bottom'
| 'end'
| 'end top'
| 'end bottom';

export const getPlacement = (
placement: UsePositionOptions['placement']
): ReactAriaPlacement => {
// switch case for placement from usePosition placement to react-aria
switch (placement) {
case 'bottom':
return 'bottom';

case 'bottom-start':
return 'bottom start';
case 'bottom-end':
return 'bottom end';
case 'top':
return 'top';

case 'top-start':
return 'top start';
case 'top-end':
return 'top end';
case 'left':
return 'left';
case 'left-start':
return 'left top';
case 'left-end':
return 'left bottom';
case 'right':
return 'right';
case 'right-start':
return 'right top';
case 'right-end':
return 'right bottom';
default:
return 'bottom start';
}
};

0 comments on commit a0e4bb9

Please sign in to comment.