feat: responsive popovers & menus (#1323)
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { Disclosure, DisclosurePanel } from '@reach/disclosure'
|
||||
import { memo, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '../../Icon/Icon'
|
||||
import { DisplayOptionsMenuPositionProps } from './DisplayOptionsMenuProps'
|
||||
import DisplayOptionsMenuPortal from './DisplayOptionsMenuPortal'
|
||||
import StyledDisplayOptionsButton from './StyledDisplayOptionsButton'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import Popover from '@/Components/Popover/Popover'
|
||||
import DisplayOptionsMenu from './DisplayOptionsMenu'
|
||||
|
||||
type Props = {
|
||||
application: {
|
||||
@@ -26,21 +25,12 @@ const ContentListHeader = ({
|
||||
isFilesSmartView,
|
||||
optionsSubtitle,
|
||||
}: Props) => {
|
||||
const [displayOptionsMenuPosition, setDisplayOptionsMenuPosition] = useState<DisplayOptionsMenuPositionProps>()
|
||||
const displayOptionsContainerRef = useRef<HTMLDivElement>(null)
|
||||
const displayOptionsButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
||||
|
||||
const toggleDisplayOptionsMenu = useCallback(() => {
|
||||
if (displayOptionsButtonRef.current) {
|
||||
const buttonBoundingRect = displayOptionsButtonRef.current.getBoundingClientRect()
|
||||
setDisplayOptionsMenuPosition({
|
||||
top: buttonBoundingRect.bottom,
|
||||
left: buttonBoundingRect.right - buttonBoundingRect.width,
|
||||
})
|
||||
}
|
||||
|
||||
setShowDisplayOptionsMenu((show) => !show)
|
||||
}, [])
|
||||
|
||||
@@ -52,24 +42,30 @@ const ContentListHeader = ({
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="relative" ref={displayOptionsContainerRef}>
|
||||
<Disclosure open={showDisplayOptionsMenu} onChange={toggleDisplayOptionsMenu}>
|
||||
<StyledDisplayOptionsButton $pressed={showDisplayOptionsMenu} ref={displayOptionsButtonRef}>
|
||||
<Icon type="sort-descending" />
|
||||
</StyledDisplayOptionsButton>
|
||||
<DisclosurePanel>
|
||||
{showDisplayOptionsMenu && displayOptionsMenuPosition && (
|
||||
<DisplayOptionsMenuPortal
|
||||
application={application}
|
||||
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
|
||||
containerRef={displayOptionsContainerRef}
|
||||
isOpen={showDisplayOptionsMenu}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
top={displayOptionsMenuPosition.top}
|
||||
left={displayOptionsMenuPosition.left}
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
<button
|
||||
className={classNames(
|
||||
'bg-text-padding flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast',
|
||||
showDisplayOptionsMenu && 'bg-contrast',
|
||||
)}
|
||||
onClick={toggleDisplayOptionsMenu}
|
||||
ref={displayOptionsButtonRef}
|
||||
>
|
||||
<Icon type="sort-descending" />
|
||||
</button>
|
||||
<Popover
|
||||
open={showDisplayOptionsMenu}
|
||||
anchorElement={displayOptionsButtonRef.current}
|
||||
togglePopover={toggleDisplayOptionsMenu}
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<DisplayOptionsMenu
|
||||
application={application}
|
||||
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
isOpen={showDisplayOptionsMenu}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
<button
|
||||
className="ml-3 flex h-8 min-w-8 cursor-pointer items-center justify-center rounded-full border border-solid border-transparent bg-info text-info-contrast hover:brightness-125"
|
||||
|
||||
@@ -97,14 +97,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
|
||||
}, [application, hideEditorIcon])
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={
|
||||
'slide-down-animation z-index-dropdown-menu flex min-w-70 flex-col overflow-y-auto rounded border border-solid border-border bg-default py-1 text-sm shadow-main transition-transform duration-150'
|
||||
}
|
||||
a11yLabel="Notes list options menu"
|
||||
closeMenu={closeDisplayOptionsMenu}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<Menu className="text-sm" a11yLabel="Notes list options menu" closeMenu={closeDisplayOptionsMenu} isOpen={isOpen}>
|
||||
<div className="my-1 px-3 text-xs font-semibold uppercase text-text">Sort by</div>
|
||||
<MenuItem
|
||||
className="py-2"
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import styled from 'styled-components'
|
||||
import { DisplayOptionsMenuPositionProps, DisplayOptionsMenuProps } from './DisplayOptionsMenuProps'
|
||||
import DisplayOptionsMenu from './DisplayOptionsMenu'
|
||||
import { useRef, useEffect, RefObject } from 'react'
|
||||
|
||||
type Props = DisplayOptionsMenuProps &
|
||||
DisplayOptionsMenuPositionProps & {
|
||||
containerRef: RefObject<HTMLDivElement>
|
||||
}
|
||||
|
||||
const PositionedOptionsMenu = styled.div<DisplayOptionsMenuPositionProps>`
|
||||
position: absolute;
|
||||
top: ${(props) => props.top}px;
|
||||
left: ${(props) => props.left}px;
|
||||
z-index: var(--z-index-dropdown-menu);
|
||||
`
|
||||
|
||||
const DisplayOptionsMenuPortal = ({
|
||||
application,
|
||||
closeDisplayOptionsMenu,
|
||||
containerRef,
|
||||
isFilesSmartView,
|
||||
isOpen,
|
||||
top,
|
||||
left,
|
||||
}: Props) => {
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const closeIfClickedOutside = (event: MouseEvent) => {
|
||||
const isDescendantOfMenu = menuRef.current?.contains(event.target as Node)
|
||||
const isDescendantOfContainer = containerRef.current?.contains(event.target as Node)
|
||||
|
||||
if (!isDescendantOfMenu && !isDescendantOfContainer) {
|
||||
closeDisplayOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', closeIfClickedOutside, { capture: true })
|
||||
return () => {
|
||||
document.removeEventListener('click', closeIfClickedOutside, {
|
||||
capture: true,
|
||||
})
|
||||
}
|
||||
}, [closeDisplayOptionsMenu, containerRef])
|
||||
|
||||
return createPortal(
|
||||
<PositionedOptionsMenu top={top} left={left} ref={menuRef}>
|
||||
<div className="sn-component">
|
||||
<DisplayOptionsMenu
|
||||
application={application}
|
||||
closeDisplayOptionsMenu={closeDisplayOptionsMenu}
|
||||
isFilesSmartView={isFilesSmartView}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</div>
|
||||
</PositionedOptionsMenu>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
|
||||
export default DisplayOptionsMenuPortal
|
||||
Reference in New Issue
Block a user