Merge pull request #13038 from overleaf/ii-history-react-dropdowns

[web] Adjust dropdowns placement in history list

GitOrigin-RevId: 050e16b97630eb37c890ad9a97a56ba882cca06b
This commit is contained in:
ilkin-overleaf 2023-05-12 12:19:05 +03:00 committed by Copybot
parent 47f541690f
commit 32160a8c80
6 changed files with 139 additions and 28 deletions

View file

@ -14,7 +14,10 @@ function ChangeList() {
)}
</div>
{!error && (
<div className="history-version-list-container">
<div
className="history-version-list-container"
data-history-version-list-container
>
{labelsOnly ? <LabelsList /> : <AllHistoryList />}
</div>
)}

View file

@ -1,42 +1,69 @@
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Dropdown } from 'react-bootstrap'
import DropdownToggleWithTooltip from '../../../../../shared/components/dropdown/dropdown-toggle-with-tooltip'
import Icon from '../../../../../shared/components/icon'
import DropdownMenuWithRef from '../../../../../shared/components/dropdown/dropdown-menu-with-ref'
type DropdownMenuProps = {
id: string
children: React.ReactNode
parentSelector?: string
}
function ActionsDropdown({ id, children }: DropdownMenuProps) {
function ActionsDropdown({ id, children, parentSelector }: DropdownMenuProps) {
const { t } = useTranslation()
const [isOpened, setIsOpened] = useState(false)
const menuRef = useRef<HTMLElement>()
// handle the placement of the dropdown above or below the toggle button
useEffect(() => {
if (menuRef.current && parentSelector) {
const parent = menuRef.current.closest(parentSelector)
if (!parent) {
return
}
const parentBottom = parent.getBoundingClientRect().bottom
const { top, height } = menuRef.current.getBoundingClientRect()
if (top + height > parentBottom) {
menuRef.current.style.bottom = '100%'
menuRef.current.style.top = 'auto'
} else {
menuRef.current.style.bottom = 'auto'
menuRef.current.style.top = '100%'
}
}
})
return (
<>
<Dropdown
id={`history-version-dropdown-${id}`}
pullRight
open={isOpened}
onToggle={open => setIsOpened(open)}
<Dropdown
id={`history-version-dropdown-${id}`}
pullRight
open={isOpened}
onToggle={open => setIsOpened(open)}
>
<DropdownToggleWithTooltip
bsRole="toggle"
className="history-version-dropdown-menu-btn"
tooltipProps={{
id,
description: t('more_actions'),
overlayProps: { placement: 'bottom', trigger: ['hover'] },
}}
>
<DropdownToggleWithTooltip
bsRole="toggle"
className="history-version-dropdown-menu-btn"
tooltipProps={{
id,
description: t('more_actions'),
overlayProps: { placement: 'bottom', trigger: ['hover'] },
}}
>
<Icon type="ellipsis-v" accessibilityLabel={t('more_actions')} />
</DropdownToggleWithTooltip>
<Dropdown.Menu className="history-version-dropdown-menu">
{children}
</Dropdown.Menu>
</Dropdown>
</>
<Icon type="ellipsis-v" accessibilityLabel={t('more_actions')} />
</DropdownToggleWithTooltip>
<DropdownMenuWithRef
bsRole="menu"
className="history-version-dropdown-menu"
menuRef={menuRef}
>
{children}
</DropdownMenuWithRef>
</Dropdown>
)
}

View file

@ -22,7 +22,10 @@ function HistoryVersionDropdown({
updateMetaEndTimestamp,
}: HistoryVersionDropdownProps) {
return (
<ActionsDropdown id={id}>
<ActionsDropdown
id={id}
parentSelector="[data-history-version-list-container]"
>
<AddLabel projectId={projectId} version={toV} />
<Download projectId={projectId} version={toV} />
{!isComparing && !isSelected && (

View file

@ -20,7 +20,10 @@ function LabelDropdown({
updateMetaEndTimestamp,
}: LabelDropdownProps) {
return (
<ActionsDropdown id={id}>
<ActionsDropdown
id={id}
parentSelector="[data-history-version-list-container]"
>
<Download projectId={projectId} version={version} />
{!isComparing && !isSelected && (
<Compare

View file

@ -0,0 +1,72 @@
import { forwardRef } from 'react'
import classnames from 'classnames'
// @ts-ignore
// eslint-disable-next-line import/no-extraneous-dependencies
import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper'
import { DropdownProps } from 'react-bootstrap'
import { MergeAndOverride } from '../../../../../types/utils'
type DropdownMenuWithRefProps = MergeAndOverride<
Pick<DropdownProps, 'bsClass' | 'open' | 'pullRight' | 'onClose'>,
{
children: React.ReactNode
bsRole: 'menu'
menuRef: React.MutableRefObject<HTMLElement | undefined>
className?: string
// The props below are passed by react-bootstrap
labelledBy?: string | undefined
rootCloseEvent?: 'click' | 'mousedown' | undefined
}
>
const DropdownMenuWithRef = forwardRef<
HTMLUListElement,
DropdownMenuWithRefProps
>(function (props, ref) {
const {
children,
bsRole,
bsClass,
className,
open,
pullRight,
labelledBy,
menuRef,
onClose,
rootCloseEvent,
...rest
} = props
// expose the menu reference to both the `menuRef` and `ref callback` from react-bootstrap
const handleRefs = (node: HTMLUListElement) => {
if (typeof ref === 'function') {
ref(node)
}
menuRef.current = node
}
// Implementation as suggested in
// https://react-bootstrap-v3.netlify.app/components/dropdowns/#btn-dropdowns-props-dropdown
return (
<RootCloseWrapper
disabled={!open}
onRootClose={onClose}
event={rootCloseEvent}
>
<ul
role={bsRole}
className={classnames(className, bsClass, {
'dropdown-menu-right': pullRight,
})}
aria-labelledby={labelledBy}
ref={handleRefs}
{...rest}
>
{children}
</ul>
</RootCloseWrapper>
)
})
DropdownMenuWithRef.displayName = 'DropdownMenuWithRef'
export default DropdownMenuWithRef

View file

@ -5,7 +5,7 @@ import { DropdownProps } from 'react-bootstrap'
import { MergeAndOverride } from '../../../../../types/utils'
type CustomToggleProps = MergeAndOverride<
Pick<DropdownProps, 'bsClass'>,
Pick<DropdownProps, 'bsClass' | 'open'>,
{
children: React.ReactNode
bsRole: 'toggle'
@ -23,6 +23,7 @@ const DropdownToggleWithTooltip = forwardRef<
children,
bsClass,
className,
open,
bsRole: _bsRole,
...rest
} = props
@ -33,6 +34,8 @@ const DropdownToggleWithTooltip = forwardRef<
type="button"
ref={ref}
className={classnames(bsClass, 'btn', className)}
aria-expanded={open}
aria-haspopup="true"
{...rest}
>
{children}