diff --git a/src/organisms/Modals/Modal/Modal.test.tsx b/src/organisms/Modals/Modal/Modal.test.tsx
index cdaf9513..8244f5fc 100644
--- a/src/organisms/Modals/Modal/Modal.test.tsx
+++ b/src/organisms/Modals/Modal/Modal.test.tsx
@@ -1,5 +1,6 @@
+import { useState } from 'react'
import { ChakraProvider } from '@chakra-ui/react'
-import { render, screen } from '@testing-library/react'
+import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Modal } from './Modal'
@@ -17,6 +18,30 @@ const renderWithChakra = (ui: React.ReactElement): any => {
return render({ui})
}
+const ModalFocusHarness = ({
+ returnFocusOnClose = true,
+}: {
+ returnFocusOnClose?: boolean
+}): JSX.Element => {
+ const [isOpen, setIsOpen] = useState(false)
+
+ return (
+ <>
+
+ setIsOpen(false)}
+ title="Focus Modal"
+ returnFocusOnClose={returnFocusOnClose}
+ >
+ Focus Content
+
+ >
+ )
+}
+
// Props por defecto para reducir repetición
const defaultProps = {
isOpen: true,
@@ -98,6 +123,22 @@ describe('Modal Component', () => {
await user.keyboard('{Escape}')
expect(onCloseMock).not.toHaveBeenCalled()
})
+
+ it('does not restore focus to trigger when returnFocusOnClose is false', async () => {
+ const user = userEvent.setup()
+ renderWithChakra()
+
+ const trigger = screen.getByRole('button', { name: 'Open Modal' })
+ trigger.focus()
+
+ await user.click(trigger)
+ await user.click(screen.getByLabelText('Close'))
+
+ await waitFor(() => {
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ })
+ expect(trigger).not.toHaveFocus()
+ })
})
describe('Modal Configuration', () => {
diff --git a/src/organisms/Modals/Modal/Modal.tsx b/src/organisms/Modals/Modal/Modal.tsx
index 94c4ed4b..b37947cb 100644
--- a/src/organisms/Modals/Modal/Modal.tsx
+++ b/src/organisms/Modals/Modal/Modal.tsx
@@ -21,6 +21,7 @@ export const Modal = ({
scrollBehavior = 'outside',
fixedButtons = false,
autoFocus = false,
+ returnFocusOnClose = true,
minWidth,
maxWidth,
minHeight,
@@ -48,6 +49,7 @@ export const Modal = ({
onClose={onClose}
scrollBehavior={isInside ? 'inside' : 'outside'}
autoFocus={autoFocus}
+ returnFocusOnClose={returnFocusOnClose}
blockScrollOnMount={false}
>
diff --git a/src/organisms/Modals/ModalAlert/ModalAlert.test.tsx b/src/organisms/Modals/ModalAlert/ModalAlert.test.tsx
index bdfa19a9..75f80959 100644
--- a/src/organisms/Modals/ModalAlert/ModalAlert.test.tsx
+++ b/src/organisms/Modals/ModalAlert/ModalAlert.test.tsx
@@ -1,4 +1,5 @@
-import { render, screen } from '@testing-library/react'
+import { useState } from 'react'
+import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ChakraProvider } from '@chakra-ui/react'
@@ -17,6 +18,34 @@ const renderWithChakra = (ui: React.ReactElement): any => {
return render({ui})
}
+const ModalAlertFocusHarness = ({
+ returnFocusOnClose = true,
+}: {
+ returnFocusOnClose?: boolean
+}): JSX.Element => {
+ const [isOpen, setIsOpen] = useState(false)
+
+ return (
+ <>
+
+ setIsOpen(false)}
+ title="Focus Alert"
+ type="info"
+ status="info"
+ returnFocusOnClose={returnFocusOnClose}
+ >
+
+
+ >
+ )
+}
+
describe('ModalAlertNew Component', () => {
it('renders with title and description', () => {
renderWithChakra(
@@ -111,4 +140,20 @@ describe('ModalAlertNew Component', () => {
expect(screen.getByText('Custom Description Node')).toBeInTheDocument()
})
+
+ it('does not restore focus to trigger when returnFocusOnClose is false', async () => {
+ const user = userEvent.setup()
+ renderWithChakra()
+
+ const trigger = screen.getByRole('button', { name: 'Open Alert' })
+ trigger.focus()
+
+ await user.click(trigger)
+ await user.click(screen.getByRole('button', { name: 'Close Alert' }))
+
+ await waitFor(() => {
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ })
+ expect(trigger).not.toHaveFocus()
+ })
})
diff --git a/src/organisms/Modals/ModalAlert/ModalAlert.tsx b/src/organisms/Modals/ModalAlert/ModalAlert.tsx
index b2c1df61..b4341739 100644
--- a/src/organisms/Modals/ModalAlert/ModalAlert.tsx
+++ b/src/organisms/Modals/ModalAlert/ModalAlert.tsx
@@ -8,6 +8,7 @@ import { useModalAlertConfig } from './useModalAlertConfig'
export const ModalAlertNew = ({
autoFocus = false,
type,
+ returnFocusOnClose = true,
isOpen,
onClose,
children,
@@ -25,6 +26,7 @@ export const ModalAlertNew = ({
onClose={onClose}
closeOnEsc={type !== 'loading'}
autoFocus={autoFocus}
+ returnFocusOnClose={returnFocusOnClose}
>
diff --git a/src/organisms/Modals/ModalMultiple/ModalMultiple.test.tsx b/src/organisms/Modals/ModalMultiple/ModalMultiple.test.tsx
index d61f9e12..b7b92257 100644
--- a/src/organisms/Modals/ModalMultiple/ModalMultiple.test.tsx
+++ b/src/organisms/Modals/ModalMultiple/ModalMultiple.test.tsx
@@ -1,5 +1,6 @@
+import { useState } from 'react'
import { ChakraProvider } from '@chakra-ui/react'
-import { render, screen } from '@testing-library/react'
+import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ModalMultiple } from './ModalMultiple'
@@ -17,6 +18,31 @@ const renderWithChakra = (ui: React.ReactElement): any => {
return render({ui})
}
+const ModalMultipleFocusHarness = ({
+ returnFocusOnClose = true,
+}: {
+ returnFocusOnClose?: boolean
+}): JSX.Element => {
+ const [isOpen, setIsOpen] = useState(false)
+
+ return (
+ <>
+
+ setIsOpen(false)}
+ title="Focus Multiple"
+ returnFocusOnClose={returnFocusOnClose}
+ >
+ Focus Content
+
+ >
+ )
+}
+
describe('ModalMultiple Component', () => {
beforeEach(() => {
jest.clearAllMocks()
@@ -119,6 +145,22 @@ describe('ModalMultiple Component', () => {
await user.keyboard('{Escape}')
expect(onCloseMock).not.toHaveBeenCalled()
})
+
+ it('does not restore focus to trigger when returnFocusOnClose is false', async () => {
+ const user = userEvent.setup()
+ renderWithChakra()
+
+ const trigger = screen.getByRole('button', { name: 'Open Multiple' })
+ trigger.focus()
+
+ await user.click(trigger)
+ await user.click(screen.getByLabelText('Close'))
+
+ await waitFor(() => {
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ })
+ expect(trigger).not.toHaveFocus()
+ })
})
/* -------------------------------------------------------------------------- */
diff --git a/src/organisms/Modals/ModalMultiple/ModalMultiple.tsx b/src/organisms/Modals/ModalMultiple/ModalMultiple.tsx
index 2a08d7f2..1c43f335 100644
--- a/src/organisms/Modals/ModalMultiple/ModalMultiple.tsx
+++ b/src/organisms/Modals/ModalMultiple/ModalMultiple.tsx
@@ -25,6 +25,7 @@ type BaseProps = ModalDefaultProps & {
isOpen: boolean
onClose: () => void
autoFocus?: boolean
+ returnFocusOnClose?: boolean
}
type ModalProps = BaseProps & {
@@ -85,6 +86,7 @@ export const ModalMultiple = (props: ModalMultipleProps): JSX.Element => {
isOpen,
onClose,
autoFocus = false,
+ returnFocusOnClose = true,
children,
title,
closeOnOverlayClick = true,
@@ -149,6 +151,7 @@ export const ModalMultiple = (props: ModalMultipleProps): JSX.Element => {
onClose={onClose}
motionPreset="scale"
autoFocus={autoFocus}
+ returnFocusOnClose={returnFocusOnClose}
closeOnOverlayClick={modalConfig.closeOnOverlayClick}
closeOnEsc={modalConfig.closeOnEsc}
scrollBehavior={modalConfig.scrollBehavior}
diff --git a/src/organisms/Modals/types.d.ts b/src/organisms/Modals/types.d.ts
index c87b5386..317f1027 100644
--- a/src/organisms/Modals/types.d.ts
+++ b/src/organisms/Modals/types.d.ts
@@ -14,6 +14,7 @@ export interface IModal {
/** Si esta activo se fija el footer */
fixedButtons?: boolean
autoFocus?: boolean
+ returnFocusOnClose?: boolean
minWidth?: string | number
maxWidth?: string | number
minHeight?: string | number
@@ -59,6 +60,7 @@ export interface IModalButtons {
export interface IModalAlert {
autoFocus?: boolean
+ returnFocusOnClose?: boolean
children?: React.ReactNode
isOpen: boolean
onClose: () => void