Skip to content

Commit

Permalink
fix: omit showing Tooltip after Dialog or Drawer got closed (#2375)
Browse files Browse the repository at this point in the history
* fix: omit showing Tooltip after Dialog or Drawer got closed

* Enhance and generalise a11y advice
  • Loading branch information
tujoworker committed May 31, 2023
1 parent c7f2760 commit 7c30151
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 29 deletions.
31 changes: 22 additions & 9 deletions packages/dnb-eufemia/src/components/modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class Modal extends React.PureComponent<
close_button_attributes: null,
prevent_close: false,
prevent_core_style: false,
animation_duration: ANIMATION_DURATION, // Not documented!
animation_duration: ANIMATION_DURATION,
no_animation: false,
no_animation_on_mobile: false,
fullscreen: 'auto',
Expand Down Expand Up @@ -271,7 +271,7 @@ class Modal extends React.PureComponent<

handleSideEffects = () => {
const { modalActive } = this.state
const { close_modal } = this.props
const { close_modal, open_state, animation_duration } = this.props

if (modalActive) {
if (typeof close_modal === 'function') {
Expand All @@ -285,20 +285,33 @@ class Modal extends React.PureComponent<

this.setActiveState(this._id)
} else if (modalActive === false) {
if (this._triggerRef && this._triggerRef.current) {
this._triggerRef.current.focus({ preventScroll: true })
const focus = (elem: HTMLElement) => {
// So we can omit showing a Tooltip on the trigger button
elem.setAttribute('data-autofocus', 'true')

elem.focus({ preventScroll: true })

return new Promise<void>((resolve) => {
setTimeout(() => {
elem?.removeAttribute('data-autofocus')
resolve()
}, parseFloat(String(animation_duration)) / 3)
})
}

if (this._triggerRef?.current) {
focus(this._triggerRef.current)
}

// because the open_state was set to opened, we force
if (
(this.props.open_state === 'opened' ||
this.props.open_state === true) &&
this.activeElement &&
(open_state === 'opened' || open_state === true) &&
this.activeElement instanceof HTMLElement
) {
try {
this.activeElement.focus({ preventScroll: true })
this.activeElement = null
focus(this.activeElement).then(() => {
this.activeElement = null
})
} catch (e) {
//
}
Expand Down
10 changes: 2 additions & 8 deletions packages/dnb-eufemia/src/components/modal/ModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,8 @@ export default class ModalContent extends React.PureComponent<
focusElement.focus()

const noH1Elem = elem.querySelector('h1, h2, h3')
if (
typeof noH1Elem?.tagName !== 'undefined' &&
noH1Elem?.tagName !== 'H1'
) {
warn(
'You have to provide a h1 element at first – instead of:',
noH1Elem
)
if (noH1Elem?.tagName !== 'H1') {
warn('A Dialog or Drawer needs a h1 as its first element!')
}
} catch (e) {
warn(e)
Expand Down
44 changes: 34 additions & 10 deletions packages/dnb-eufemia/src/components/modal/__tests__/Modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
attachToBody, // in order to use document.activeElement properly
loadScss,
} from '../../../core/jest/jestSetup'
import { fireEvent, render } from '@testing-library/react'
import { fireEvent, render, waitFor } from '@testing-library/react'
import Input from '../../input/Input'
import Component, { OriginalComponent } from '../Modal'
import Button from '../../button/Button'
Expand Down Expand Up @@ -330,31 +330,55 @@ describe('Modal component', () => {
).toBe(true)
})

it('will set "data-autofocus" attribute on focusing the trigger when closed', async () => {
render(
<Component no_animation={true} animation_duration={3}>
<DialogContent />
</Component>
)

fireEvent.click(document.querySelector('button'))

fireEvent.keyDown(document.querySelector('div.dnb-dialog'), {
key: 'Esc',
keyCode: 27,
})

expect(document.activeElement.getAttribute('data-autofocus')).toBe(
'true'
)
expect(
document.activeElement.classList.contains('dnb-modal__trigger')
).toBe(true)

await wait(1)

expect(
document.activeElement.hasAttribute('data-autofocus')
).toBeFalsy()
})

it('will warn if first heading is not h1', async () => {
jest.spyOn(helpers, 'warn')
const log = global.console.log
global.console.log = jest.fn()

const H2 = <h2>h2</h2>
const H2 = <h2 className="custom-h2">h2</h2>

const Comp = mount(
render(
<Component no_animation={true}>
<DialogContent>
<Component.Header>{H2}</Component.Header>
</DialogContent>
</Component>,
{ attachTo: attachToBody() }
</Component>
)

// open
Comp.find('button').simulate('click')

await wait(1)
await waitFor(() => fireEvent.click(document.querySelector('button')))

expect(helpers.warn).toHaveBeenCalledTimes(1)
expect(helpers.warn).toHaveBeenCalledWith(
'You have to provide a h1 element at first – instead of:',
expect.anything()
'A Dialog or Drawer needs a h1 as its first element!'
)

global.console.log = log
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import TooltipPortal from './TooltipPortal'
import { TooltipProps } from './types'

type TooltipWithEventsProps = {
target: HTMLElement | React.ReactElement
target: HTMLElement
active: boolean
internalId: string
}
Expand Down Expand Up @@ -127,8 +127,13 @@ function TooltipWithEvents(props: TooltipProps & TooltipWithEventsProps) {

const onMouseEnter = (e: MouseEvent) => {
try {
const elem = e.currentTarget as HTMLElement

if (elem.getAttribute('data-autofocus')) {
return // stop here
}

if (isTouch(e.type)) {
const elem = e.currentTarget as HTMLElement
elem.style.userSelect = 'none'
}
} catch (e) {
Expand Down

0 comments on commit 7c30151

Please sign in to comment.