Use flip and arrow middleware
This commit is contained in:
parent
f068d6280d
commit
ce2e7f3785
1 changed files with 44 additions and 16 deletions
|
@ -1,7 +1,7 @@
|
|||
import { offset, Placement, useFloating } from '@floating-ui/react';
|
||||
import { offset, Placement, useFloating, flip, arrow } from '@floating-ui/react';
|
||||
import clsx from 'clsx';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { closeDropdownMenu, openDropdownMenu } from 'soapbox/actions/dropdown-menu';
|
||||
|
@ -40,7 +40,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
|
|||
onClose,
|
||||
onOpen,
|
||||
onShiftClick,
|
||||
placement = 'top',
|
||||
placement: initialPlacement = 'top',
|
||||
src = require('@tabler/icons/dots.svg'),
|
||||
title = 'Menu',
|
||||
...filteredProps
|
||||
|
@ -51,14 +51,21 @@ const DropdownMenu = (props: IDropdownMenu) => {
|
|||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const arrowRef = useRef<HTMLDivElement>(null);
|
||||
const activeElement = useRef<Element | null>(null);
|
||||
const target = useRef<Element>(null);
|
||||
|
||||
const isOnMobile = isUserTouching();
|
||||
|
||||
const { x, y, strategy, refs } = useFloating<HTMLButtonElement>({
|
||||
placement,
|
||||
middleware: [offset(12)],
|
||||
const { x, y, strategy, refs, middlewareData, placement } = useFloating<HTMLButtonElement>({
|
||||
placement: initialPlacement,
|
||||
middleware: [
|
||||
offset(12),
|
||||
flip(),
|
||||
arrow({
|
||||
element: arrowRef,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const handleClick: React.EventHandler<
|
||||
|
@ -211,6 +218,32 @@ const DropdownMenu = (props: IDropdownMenu) => {
|
|||
}
|
||||
};
|
||||
|
||||
const arrowProps: React.CSSProperties = useMemo(() => {
|
||||
if (middlewareData.arrow) {
|
||||
const { x, y } = middlewareData.arrow;
|
||||
|
||||
const staticPlacement = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[placement.split('-')[0]];
|
||||
|
||||
return {
|
||||
left: x !== null ? `${x}px` : '',
|
||||
top: y !== null ? `${y}px` : '',
|
||||
// Ensure the static side gets unset when
|
||||
// flipping to other placements' axes.
|
||||
right: '',
|
||||
bottom: '',
|
||||
[staticPlacement as string]: `${(-(arrowRef.current?.offsetWidth || 0)) / 2}px`,
|
||||
transform: 'rotate(45deg)',
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}, [middlewareData.arrow, placement]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(closeDropdownMenu());
|
||||
|
@ -263,7 +296,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
|
|||
data-testid='dropdown-menu'
|
||||
ref={refs.setFloating}
|
||||
className={
|
||||
clsx('relative z-[1001] w-56 rounded-md bg-white py-1 shadow-lg transition-opacity duration-100 focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700', {
|
||||
clsx('z-[1001] w-56 rounded-md bg-white py-1 shadow-lg transition-opacity duration-100 focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700', {
|
||||
'opacity-0 pointer-events-none': !isOpen,
|
||||
})
|
||||
}
|
||||
|
@ -273,7 +306,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
|
|||
left: x ?? 0,
|
||||
}}
|
||||
>
|
||||
<ul>
|
||||
<ul className='z-10'>
|
||||
{items.map((item, idx) => (
|
||||
<DropdownMenuItem
|
||||
key={idx}
|
||||
|
@ -286,14 +319,9 @@ const DropdownMenu = (props: IDropdownMenu) => {
|
|||
|
||||
{/* Arrow */}
|
||||
<div
|
||||
className={
|
||||
clsx({
|
||||
'absolute w-0 h-0 border-0 border-solid border-transparent': true,
|
||||
'border-t-white dark:border-t-gray-900 -bottom-[5px] -ml-[5px] left-[calc(50%-2.5px)] border-t-[5px] border-x-[5px] border-b-0': placement === 'top',
|
||||
'border-b-white dark:border-b-gray-900 -top-[5px] -ml-[5px] left-[calc(50%-2.5px)] border-t-0 border-x-[5px] border-b-[5px]': placement === 'bottom',
|
||||
'border-b-white dark:border-b-gray-900 -top-[5px] -ml-[5px] left-[92.5%] border-t-0 border-x-[5px] border-b-[5px]': placement === 'bottom-end',
|
||||
})
|
||||
}
|
||||
ref={arrowRef}
|
||||
style={arrowProps}
|
||||
className='pointer-events-none absolute z-[-1] h-3 w-3 bg-white dark:bg-gray-900'
|
||||
/>
|
||||
</div>
|
||||
</Portal>
|
||||
|
|
Loading…
Reference in a new issue