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