Skip to content

Commit

Permalink
feat(tooltip): accessibility improvements for tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
alaa-yahia committed Mar 18, 2024
1 parent 2ce1318 commit 72461f2
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 9 deletions.
49 changes: 40 additions & 9 deletions components/tooltip/src/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const TOOLTIP_OFFSET = 4

const popperStyle = resolve`
z-index: ${layers.applicationTop};
pointer-events: none;
// Hide popper when reference component is obscured (https://popper.js.org/docs/v2/modifiers/hide/)
div[data-popper-reference-hidden="true"] {
visibility: hidden;
Expand Down Expand Up @@ -66,33 +64,63 @@ const Tooltip = ({
}, closeDelay)
}

useEffect(
() => () => {
const onFocus = () => {
clearTimeout(closeTimerRef.current)

openTimerRef.current = setTimeout(() => {
setOpen(true)
}, openDelay)
}

const onBlur = () => {
clearTimeout(openTimerRef.current)

closeTimerRef.current = setTimeout(() => {
setOpen(false)
}, closeDelay)
}

const handleKeyDown = (event) => {
if (event.key === 'Escape') {
closeTimerRef.current = setTimeout(() => {
setOpen(false)
}, closeDelay)
}
}

useEffect(() => {
document.addEventListener('keydown', handleKeyDown)

return () => {
clearTimeout(openTimerRef.current)
clearTimeout(closeTimerRef.current)
},
[]
)
document.removeEventListener('keydown', handleKeyDown)
}
}, [])

return (
<>
{typeof children === 'function' ? (
children({
onMouseOver: onMouseOver,
onMouseOut: onMouseOut,
onFocus: { onFocus },
onBlur: { onBlur },
ref: popperReference,
})
) : (
<span
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
onFocus={onFocus}
onBlur={onBlur}
ref={popperReference}
aria-describedby={open ? 'tooltipContenDhis2Ui' : ''}
data-test={`${dataTest}-reference`}
>
{children}
</span>
)}

{open && (
<Portal>
<Popper
Expand All @@ -102,8 +130,11 @@ const Tooltip = ({
modifiers={[offsetModifier, flipModifier, hideModifier]}
>
<div
className={className}
id="tooltipContenDhis2Ui"
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
data-test={`${dataTest}-content`}
role="tooltip"
>
{content}
</div>
Expand Down
42 changes: 42 additions & 0 deletions components/tooltip/src/tooltip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,45 @@ describe('it handles custom open and close delays', () => {
expect(document.querySelector(tooltipContentSelector)).toBe(null)
})
})

describe('Keyboard interaction', () => {
it('opens on keyboard focus and closes on blur', () => {
wrapper = mount(<Tooltip content={tooltipContent}>Hi hi!</Tooltip>)
wrapper.simulate('focus')

expect(document.querySelector(tooltipContentSelector)).toBe(null)

// wait for 'open delay' to elapse to open tooltip
act(() => {
jest.advanceTimersByTime(200)
})

// expect tooltip to be open after delay
const res = document.querySelector(tooltipContentSelector)
expect(res).not.toBe(null)
expect(res.textContent).toBe(tooltipContent)

wrapper.simulate('blur')
act(() => {
jest.advanceTimersByTime(200) // Assuming default closeDelay is 200ms
})
expect(document.querySelector(tooltipContentSelector)).toBe(null)
// this last part clears a warning about "code should be wrapped in `act(...)`"
// and clears the tooltip
act(() => {
jest.runAllTimers()
})
})

it('closes when escape key is pressed', () => {
wrapper = mount(<Tooltip content={tooltipContent}>Hi hi!</Tooltip>)

// open tooltip
wrapper.simulate('mouseover')
act(() => {
jest.advanceTimersByTime(200) // open the tooltip
})
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
expect(document.querySelector(tooltipContentSelector)).toBe(null)
})
})

0 comments on commit 72461f2

Please sign in to comment.