mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 22:23:42 +01:00
Hide tooltips on the Esc key (#26305)
* Hide the tooltip when Esc key is pressed * Simplify ActionsDropdown * Rename to tooltipDescription * Use OLTooltip instead of Tooltip GitOrigin-RevId: ee27cde2735ae3a0de5e37bfb8ab1dd99069742c
This commit is contained in:
+19
-11
@@ -2,20 +2,21 @@ import React, { ReactNode } from 'react'
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
DropdownToggle,
|
||||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||||
import DropdownToggleWithTooltip from '@/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip'
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
|
||||||
type ActionDropdownProps = {
|
type ActionDropdownProps = {
|
||||||
id: string
|
id: string
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
isOpened: boolean
|
isOpened: boolean
|
||||||
iconTag: ReactNode
|
iconTag: ReactNode
|
||||||
toolTipDescription: string
|
tooltipDescription: string
|
||||||
setIsOpened: (isOpened: boolean) => void
|
setIsOpened: (isOpened: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActionsDropdown(props: ActionDropdownProps) {
|
function ActionsDropdown(props: ActionDropdownProps) {
|
||||||
const { id, children, isOpened, iconTag, setIsOpened, toolTipDescription } =
|
const { id, children, isOpened, iconTag, setIsOpened, tooltipDescription } =
|
||||||
props
|
props
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -24,16 +25,23 @@ function ActionsDropdown(props: ActionDropdownProps) {
|
|||||||
show={isOpened}
|
show={isOpened}
|
||||||
onToggle={open => setIsOpened(open)}
|
onToggle={open => setIsOpened(open)}
|
||||||
>
|
>
|
||||||
<DropdownToggleWithTooltip
|
<OLTooltip
|
||||||
id={`history-version-dropdown-${id}`}
|
id={`history-version-dropdown-${id}`}
|
||||||
className="history-version-dropdown-menu-btn"
|
description={tooltipDescription}
|
||||||
aria-label={toolTipDescription}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
toolTipDescription={toolTipDescription}
|
hidden={isOpened}
|
||||||
overlayTriggerProps={{ placement: 'bottom' }}
|
|
||||||
tooltipProps={{ hidden: isOpened }}
|
|
||||||
>
|
>
|
||||||
{iconTag}
|
{/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
|
||||||
</DropdownToggleWithTooltip>
|
<span>
|
||||||
|
<DropdownToggle
|
||||||
|
id={`history-version-dropdown-toggle-${id}`}
|
||||||
|
className="history-version-dropdown-menu-btn"
|
||||||
|
as="button"
|
||||||
|
>
|
||||||
|
{iconTag}
|
||||||
|
</DropdownToggle>
|
||||||
|
</span>
|
||||||
|
</OLTooltip>
|
||||||
<DropdownMenu className="history-version-dropdown-menu">
|
<DropdownMenu className="history-version-dropdown-menu">
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ function CompareVersionDropdown({
|
|||||||
id={id}
|
id={id}
|
||||||
isOpened={isOpened}
|
isOpened={isOpened}
|
||||||
setIsOpened={setIsOpened}
|
setIsOpened={setIsOpened}
|
||||||
toolTipDescription={t('compare')}
|
tooltipDescription={t('compare')}
|
||||||
iconTag={
|
iconTag={
|
||||||
<MaterialIcon
|
<MaterialIcon
|
||||||
type="align_space_even"
|
type="align_space_even"
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ function HistoryDropdown({
|
|||||||
<ActionsDropdown
|
<ActionsDropdown
|
||||||
id={id}
|
id={id}
|
||||||
isOpened={isOpened}
|
isOpened={isOpened}
|
||||||
toolTipDescription={t('more_actions')}
|
tooltipDescription={t('more_actions')}
|
||||||
setIsOpened={setIsOpened}
|
setIsOpened={setIsOpened}
|
||||||
iconTag={
|
iconTag={
|
||||||
<MaterialIcon type="more_vert" accessibilityLabel={t('more_actions')} />
|
<MaterialIcon type="more_vert" accessibilityLabel={t('more_actions')} />
|
||||||
|
|||||||
+2
-2
@@ -34,7 +34,7 @@ function CompareItems({
|
|||||||
toVTimestamp: selRange.toVTimestamp,
|
toVTimestamp: selRange.toVTimestamp,
|
||||||
}}
|
}}
|
||||||
closeDropdown={closeDropdown}
|
closeDropdown={closeDropdown}
|
||||||
toolTipDescription={t('history_compare_from_this_version')}
|
tooltipDescription={t('history_compare_from_this_version')}
|
||||||
icon={
|
icon={
|
||||||
<MaterialIcon
|
<MaterialIcon
|
||||||
type="align_end"
|
type="align_end"
|
||||||
@@ -52,7 +52,7 @@ function CompareItems({
|
|||||||
toVTimestamp: updateRange.toVTimestamp,
|
toVTimestamp: updateRange.toVTimestamp,
|
||||||
}}
|
}}
|
||||||
closeDropdown={closeDropdown}
|
closeDropdown={closeDropdown}
|
||||||
toolTipDescription={t('history_compare_up_to_this_version')}
|
tooltipDescription={t('history_compare_up_to_this_version')}
|
||||||
icon={
|
icon={
|
||||||
<MaterialIcon
|
<MaterialIcon
|
||||||
type="align_start"
|
type="align_start"
|
||||||
|
|||||||
+4
-4
@@ -6,14 +6,14 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
|||||||
type CompareProps = {
|
type CompareProps = {
|
||||||
comparisonRange: UpdateRange
|
comparisonRange: UpdateRange
|
||||||
icon: ReactNode
|
icon: ReactNode
|
||||||
toolTipDescription?: string
|
tooltipDescription?: string
|
||||||
closeDropdown: () => void
|
closeDropdown: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function Compare({
|
function Compare({
|
||||||
comparisonRange,
|
comparisonRange,
|
||||||
closeDropdown,
|
closeDropdown,
|
||||||
toolTipDescription,
|
tooltipDescription,
|
||||||
icon,
|
icon,
|
||||||
}: CompareProps) {
|
}: CompareProps) {
|
||||||
const { setSelection } = useHistoryContext()
|
const { setSelection } = useHistoryContext()
|
||||||
@@ -32,12 +32,12 @@ function Compare({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OLTooltip
|
<OLTooltip
|
||||||
description={toolTipDescription}
|
description={tooltipDescription}
|
||||||
id="compare-btn"
|
id="compare-btn"
|
||||||
overlayProps={{ placement: 'left' }}
|
overlayProps={{ placement: 'left' }}
|
||||||
>
|
>
|
||||||
<button className="history-compare-btn" onClick={handleCompareVersion}>
|
<button className="history-compare-btn" onClick={handleCompareVersion}>
|
||||||
<span className="visually-hidden">{toolTipDescription}</span>
|
<span className="visually-hidden">{tooltipDescription}</span>
|
||||||
{icon}
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
</OLTooltip>
|
</OLTooltip>
|
||||||
|
|||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
import { ReactNode, forwardRef } from 'react'
|
|
||||||
import { BsPrefixRefForwardingComponent } from 'react-bootstrap/helpers'
|
|
||||||
import type { DropdownToggleProps } from '@/features/ui/components/types/dropdown-menu-props'
|
|
||||||
import {
|
|
||||||
DropdownToggle as BS5DropdownToggle,
|
|
||||||
OverlayTrigger,
|
|
||||||
OverlayTriggerProps,
|
|
||||||
Tooltip,
|
|
||||||
} from 'react-bootstrap'
|
|
||||||
import type { MergeAndOverride } from '../../../../../../types/utils'
|
|
||||||
|
|
||||||
type DropdownToggleWithTooltipProps = MergeAndOverride<
|
|
||||||
DropdownToggleProps,
|
|
||||||
{
|
|
||||||
children: ReactNode
|
|
||||||
overlayTriggerProps?: Omit<OverlayTriggerProps, 'overlay' | 'children'>
|
|
||||||
toolTipDescription: string
|
|
||||||
tooltipProps?: Omit<React.ComponentProps<typeof Tooltip>, 'children'>
|
|
||||||
'aria-label'?: string
|
|
||||||
}
|
|
||||||
>
|
|
||||||
const DropdownToggleWithTooltip = forwardRef<
|
|
||||||
BsPrefixRefForwardingComponent<'button', DropdownToggleProps>,
|
|
||||||
DropdownToggleWithTooltipProps
|
|
||||||
>(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
children,
|
|
||||||
toolTipDescription,
|
|
||||||
overlayTriggerProps,
|
|
||||||
tooltipProps,
|
|
||||||
id: _id,
|
|
||||||
...toggleProps
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<OverlayTrigger
|
|
||||||
overlay={<Tooltip {...tooltipProps}>{toolTipDescription}</Tooltip>}
|
|
||||||
{...overlayTriggerProps}
|
|
||||||
>
|
|
||||||
<BS5DropdownToggle {...toggleProps} ref={ref}>
|
|
||||||
{children}
|
|
||||||
</BS5DropdownToggle>
|
|
||||||
</OverlayTrigger>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownToggleWithTooltip.displayName = 'DropdownToggleWithTooltip'
|
|
||||||
|
|
||||||
export default DropdownToggleWithTooltip
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import { cloneElement, useEffect, forwardRef } from 'react'
|
import {
|
||||||
|
cloneElement,
|
||||||
|
useEffect,
|
||||||
|
forwardRef,
|
||||||
|
useState,
|
||||||
|
useCallback,
|
||||||
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
OverlayTrigger,
|
OverlayTrigger,
|
||||||
OverlayTriggerProps,
|
OverlayTriggerProps,
|
||||||
@@ -41,6 +47,30 @@ function Tooltip({
|
|||||||
overlayProps,
|
overlayProps,
|
||||||
hidden,
|
hidden,
|
||||||
}: TooltipProps) {
|
}: TooltipProps) {
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
if (show && e.key === 'Escape') {
|
||||||
|
setShow(false)
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[show, setShow]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', handleKeyDown, true)
|
||||||
|
return () => document.removeEventListener('keydown', handleKeyDown, true)
|
||||||
|
}, [handleKeyDown])
|
||||||
|
|
||||||
|
const hideTooltip = (e: React.MouseEvent) => {
|
||||||
|
if (e.currentTarget instanceof HTMLElement) {
|
||||||
|
e.currentTarget.blur()
|
||||||
|
}
|
||||||
|
setShow(false)
|
||||||
|
}
|
||||||
|
|
||||||
const delay = overlayProps?.delay
|
const delay = overlayProps?.delay
|
||||||
let delayShow = 300
|
let delayShow = 300
|
||||||
let delayHide = 300
|
let delayHide = 300
|
||||||
@@ -49,12 +79,6 @@ function Tooltip({
|
|||||||
delayHide = typeof delay === 'number' ? delay : delay.hide
|
delayHide = typeof delay === 'number' ? delay : delay.hide
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideTooltip = (e: React.MouseEvent) => {
|
|
||||||
if (e.currentTarget instanceof HTMLElement) {
|
|
||||||
e.currentTarget.blur()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
overlay={
|
overlay={
|
||||||
@@ -69,6 +93,8 @@ function Tooltip({
|
|||||||
{...overlayProps}
|
{...overlayProps}
|
||||||
delay={{ show: delayShow, hide: delayHide }}
|
delay={{ show: delayShow, hide: delayHide }}
|
||||||
placement={overlayProps?.placement || 'top'}
|
placement={overlayProps?.placement || 'top'}
|
||||||
|
show={show}
|
||||||
|
onToggle={setShow}
|
||||||
>
|
>
|
||||||
{cloneElement(children, {
|
{cloneElement(children, {
|
||||||
onClick: callFnsInSequence(children.props.onClick, hideTooltip),
|
onClick: callFnsInSequence(children.props.onClick, hideTooltip),
|
||||||
|
|||||||
@@ -32,4 +32,33 @@ describe('<OLTooltip />', function () {
|
|||||||
cy.get('@blurHandler').should('have.been.calledOnce')
|
cy.get('@blurHandler').should('have.been.calledOnce')
|
||||||
cy.findByText(description).should('not.exist')
|
cy.findByText(description).should('not.exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('hides the tooltip when Escape key is pressed', function () {
|
||||||
|
const description = 'Press Escape to close'
|
||||||
|
const btnText = 'Hover me!'
|
||||||
|
|
||||||
|
cy.mount(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OLTooltip id="esc" description={description}>
|
||||||
|
<button>{btnText}</button>
|
||||||
|
</OLTooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: btnText }).as('button')
|
||||||
|
cy.get('@button').trigger('mouseover')
|
||||||
|
cy.findByText(description)
|
||||||
|
cy.get('@button').trigger('mouseout')
|
||||||
|
cy.get('@button').focus()
|
||||||
|
cy.findByText(description)
|
||||||
|
cy.get('body').type('{esc}')
|
||||||
|
cy.findByText(description).should('not.exist')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ describe('change list (Bootstrap 5)', function () {
|
|||||||
cy.findAllByTestId('history-version-details')
|
cy.findAllByTestId('history-version-details')
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get('[aria-label="Compare"]').click()
|
cy.findByRole('button', { name: /compare/i }).click()
|
||||||
cy.findByRole('menu').within(() => {
|
cy.findByRole('menu').within(() => {
|
||||||
cy.findByRole('menuitem', {
|
cy.findByRole('menuitem', {
|
||||||
name: /compare up to this version/i,
|
name: /compare up to this version/i,
|
||||||
|
|||||||
Reference in New Issue
Block a user