Skip to content

Commit 0932a25

Browse files
feat(modals): agregamos soporte returnFocusOnClose (#787)
1 parent f73e542 commit 0932a25

7 files changed

Lines changed: 140 additions & 3 deletions

File tree

src/organisms/Modals/Modal/Modal.test.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { useState } from 'react'
12
import { ChakraProvider } from '@chakra-ui/react'
2-
import { render, screen } from '@testing-library/react'
3+
import { render, screen, waitFor } from '@testing-library/react'
34
import userEvent from '@testing-library/user-event'
45

56
import { Modal } from './Modal'
@@ -17,6 +18,30 @@ const renderWithChakra = (ui: React.ReactElement): any => {
1718
return render(<ChakraProvider>{ui}</ChakraProvider>)
1819
}
1920

21+
const ModalFocusHarness = ({
22+
returnFocusOnClose = true,
23+
}: {
24+
returnFocusOnClose?: boolean
25+
}): JSX.Element => {
26+
const [isOpen, setIsOpen] = useState(false)
27+
28+
return (
29+
<>
30+
<button type="button" onClick={() => setIsOpen(true)}>
31+
Open Modal
32+
</button>
33+
<Modal
34+
isOpen={isOpen}
35+
onClose={() => setIsOpen(false)}
36+
title="Focus Modal"
37+
returnFocusOnClose={returnFocusOnClose}
38+
>
39+
<div>Focus Content</div>
40+
</Modal>
41+
</>
42+
)
43+
}
44+
2045
// Props por defecto para reducir repetición
2146
const defaultProps = {
2247
isOpen: true,
@@ -98,6 +123,22 @@ describe('Modal Component', () => {
98123
await user.keyboard('{Escape}')
99124
expect(onCloseMock).not.toHaveBeenCalled()
100125
})
126+
127+
it('does not restore focus to trigger when returnFocusOnClose is false', async () => {
128+
const user = userEvent.setup()
129+
renderWithChakra(<ModalFocusHarness returnFocusOnClose={false} />)
130+
131+
const trigger = screen.getByRole('button', { name: 'Open Modal' })
132+
trigger.focus()
133+
134+
await user.click(trigger)
135+
await user.click(screen.getByLabelText('Close'))
136+
137+
await waitFor(() => {
138+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
139+
})
140+
expect(trigger).not.toHaveFocus()
141+
})
101142
})
102143

103144
describe('Modal Configuration', () => {

src/organisms/Modals/Modal/Modal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const Modal = ({
2121
scrollBehavior = 'outside',
2222
fixedButtons = false,
2323
autoFocus = false,
24+
returnFocusOnClose = true,
2425
minWidth,
2526
maxWidth,
2627
minHeight,
@@ -48,6 +49,7 @@ export const Modal = ({
4849
onClose={onClose}
4950
scrollBehavior={isInside ? 'inside' : 'outside'}
5051
autoFocus={autoFocus}
52+
returnFocusOnClose={returnFocusOnClose}
5153
blockScrollOnMount={false}
5254
>
5355
<ModalOverlay />

src/organisms/Modals/ModalAlert/ModalAlert.test.tsx

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { render, screen } from '@testing-library/react'
1+
import { useState } from 'react'
2+
import { render, screen, waitFor } from '@testing-library/react'
23
import userEvent from '@testing-library/user-event'
34
import { ChakraProvider } from '@chakra-ui/react'
45

@@ -17,6 +18,34 @@ const renderWithChakra = (ui: React.ReactElement): any => {
1718
return render(<ChakraProvider>{ui}</ChakraProvider>)
1819
}
1920

21+
const ModalAlertFocusHarness = ({
22+
returnFocusOnClose = true,
23+
}: {
24+
returnFocusOnClose?: boolean
25+
}): JSX.Element => {
26+
const [isOpen, setIsOpen] = useState(false)
27+
28+
return (
29+
<>
30+
<button type="button" onClick={() => setIsOpen(true)}>
31+
Open Alert
32+
</button>
33+
<ModalAlertNew
34+
isOpen={isOpen}
35+
onClose={() => setIsOpen(false)}
36+
title="Focus Alert"
37+
type="info"
38+
status="info"
39+
returnFocusOnClose={returnFocusOnClose}
40+
>
41+
<button type="button" onClick={() => setIsOpen(false)}>
42+
Close Alert
43+
</button>
44+
</ModalAlertNew>
45+
</>
46+
)
47+
}
48+
2049
describe('ModalAlertNew Component', () => {
2150
it('renders with title and description', () => {
2251
renderWithChakra(
@@ -111,4 +140,20 @@ describe('ModalAlertNew Component', () => {
111140

112141
expect(screen.getByText('Custom Description Node')).toBeInTheDocument()
113142
})
143+
144+
it('does not restore focus to trigger when returnFocusOnClose is false', async () => {
145+
const user = userEvent.setup()
146+
renderWithChakra(<ModalAlertFocusHarness returnFocusOnClose={false} />)
147+
148+
const trigger = screen.getByRole('button', { name: 'Open Alert' })
149+
trigger.focus()
150+
151+
await user.click(trigger)
152+
await user.click(screen.getByRole('button', { name: 'Close Alert' }))
153+
154+
await waitFor(() => {
155+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
156+
})
157+
expect(trigger).not.toHaveFocus()
158+
})
114159
})

src/organisms/Modals/ModalAlert/ModalAlert.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useModalAlertConfig } from './useModalAlertConfig'
88
export const ModalAlertNew = ({
99
autoFocus = false,
1010
type,
11+
returnFocusOnClose = true,
1112
isOpen,
1213
onClose,
1314
children,
@@ -25,6 +26,7 @@ export const ModalAlertNew = ({
2526
onClose={onClose}
2627
closeOnEsc={type !== 'loading'}
2728
autoFocus={autoFocus}
29+
returnFocusOnClose={returnFocusOnClose}
2830
>
2931
<ModalOverlay />
3032
<ModalContent {...modalConfig.contentProps}>

src/organisms/Modals/ModalMultiple/ModalMultiple.test.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { useState } from 'react'
12
import { ChakraProvider } from '@chakra-ui/react'
2-
import { render, screen } from '@testing-library/react'
3+
import { render, screen, waitFor } from '@testing-library/react'
34
import userEvent from '@testing-library/user-event'
45

56
import { ModalMultiple } from './ModalMultiple'
@@ -17,6 +18,31 @@ const renderWithChakra = (ui: React.ReactElement): any => {
1718
return render(<ChakraProvider>{ui}</ChakraProvider>)
1819
}
1920

21+
const ModalMultipleFocusHarness = ({
22+
returnFocusOnClose = true,
23+
}: {
24+
returnFocusOnClose?: boolean
25+
}): JSX.Element => {
26+
const [isOpen, setIsOpen] = useState(false)
27+
28+
return (
29+
<>
30+
<button type="button" onClick={() => setIsOpen(true)}>
31+
Open Multiple
32+
</button>
33+
<ModalMultiple
34+
type="modal"
35+
isOpen={isOpen}
36+
onClose={() => setIsOpen(false)}
37+
title="Focus Multiple"
38+
returnFocusOnClose={returnFocusOnClose}
39+
>
40+
<div>Focus Content</div>
41+
</ModalMultiple>
42+
</>
43+
)
44+
}
45+
2046
describe('ModalMultiple Component', () => {
2147
beforeEach(() => {
2248
jest.clearAllMocks()
@@ -119,6 +145,22 @@ describe('ModalMultiple Component', () => {
119145
await user.keyboard('{Escape}')
120146
expect(onCloseMock).not.toHaveBeenCalled()
121147
})
148+
149+
it('does not restore focus to trigger when returnFocusOnClose is false', async () => {
150+
const user = userEvent.setup()
151+
renderWithChakra(<ModalMultipleFocusHarness returnFocusOnClose={false} />)
152+
153+
const trigger = screen.getByRole('button', { name: 'Open Multiple' })
154+
trigger.focus()
155+
156+
await user.click(trigger)
157+
await user.click(screen.getByLabelText('Close'))
158+
159+
await waitFor(() => {
160+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
161+
})
162+
expect(trigger).not.toHaveFocus()
163+
})
122164
})
123165

124166
/* -------------------------------------------------------------------------- */

src/organisms/Modals/ModalMultiple/ModalMultiple.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type BaseProps = ModalDefaultProps & {
2525
isOpen: boolean
2626
onClose: () => void
2727
autoFocus?: boolean
28+
returnFocusOnClose?: boolean
2829
}
2930

3031
type ModalProps = BaseProps & {
@@ -85,6 +86,7 @@ export const ModalMultiple = (props: ModalMultipleProps): JSX.Element => {
8586
isOpen,
8687
onClose,
8788
autoFocus = false,
89+
returnFocusOnClose = true,
8890
children,
8991
title,
9092
closeOnOverlayClick = true,
@@ -149,6 +151,7 @@ export const ModalMultiple = (props: ModalMultipleProps): JSX.Element => {
149151
onClose={onClose}
150152
motionPreset="scale"
151153
autoFocus={autoFocus}
154+
returnFocusOnClose={returnFocusOnClose}
152155
closeOnOverlayClick={modalConfig.closeOnOverlayClick}
153156
closeOnEsc={modalConfig.closeOnEsc}
154157
scrollBehavior={modalConfig.scrollBehavior}

src/organisms/Modals/types.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface IModal {
1414
/** Si esta activo se fija el footer */
1515
fixedButtons?: boolean
1616
autoFocus?: boolean
17+
returnFocusOnClose?: boolean
1718
minWidth?: string | number
1819
maxWidth?: string | number
1920
minHeight?: string | number
@@ -59,6 +60,7 @@ export interface IModalButtons {
5960

6061
export interface IModalAlert {
6162
autoFocus?: boolean
63+
returnFocusOnClose?: boolean
6264
children?: React.ReactNode
6365
isOpen: boolean
6466
onClose: () => void

0 commit comments

Comments
 (0)