mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(tabs): unresponsive modal after switching tabs inside (#5582)
* fix(tabs): unresponsive modal after switching tabs inside * chore(deps): remove self
This commit is contained in:
parent
97a1c4a4f1
commit
8eb269df9e
5
.changeset/nine-moles-sort.md
Normal file
5
.changeset/nine-moles-sort.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@heroui/tabs": patch
|
||||
---
|
||||
|
||||
fix unresponsive modal after switching tabs inside (#5543)
|
||||
@ -2,10 +2,12 @@ import type {UserEvent} from "@testing-library/user-event";
|
||||
import type {TabsProps} from "../src";
|
||||
|
||||
import * as React from "react";
|
||||
import {act, render, fireEvent, within} from "@testing-library/react";
|
||||
import {act, render, fireEvent, within, waitFor} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import {focus} from "@heroui/test-utils";
|
||||
import {spy, shouldIgnoreReactWarning} from "@heroui/test-utils";
|
||||
import {Modal, ModalContent, ModalHeader, ModalBody, ModalFooter} from "@heroui/modal";
|
||||
import {Button} from "@heroui/button";
|
||||
|
||||
import {Tabs, Tab} from "../src";
|
||||
|
||||
@ -435,4 +437,106 @@ describe("Tabs", () => {
|
||||
expect(item2Click).toHaveBeenCalledTimes(2);
|
||||
expect(tab2).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
|
||||
it("should allow reopening modal with tabs without blocking", async () => {
|
||||
const TestComponent = () => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button data-testid="open-modal-btn" onPress={() => setIsOpen(true)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal data-testid="test-modal" isOpen={isOpen} onOpenChange={setIsOpen}>
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader>Test Modal with Tabs</ModalHeader>
|
||||
<ModalBody>
|
||||
<Tabs aria-label="Test tabs" data-testid="modal-tabs">
|
||||
<Tab key="tab1" data-testid="tab-1" title="Tab 1">
|
||||
<div data-testid="tab1-content">Content for Tab 1</div>
|
||||
</Tab>
|
||||
<Tab key="tab2" data-testid="tab-2" title="Tab 2">
|
||||
<div data-testid="tab2-content">Content for Tab 2</div>
|
||||
</Tab>
|
||||
<Tab key="tab3" data-testid="tab-3" title="Tab 3">
|
||||
<div data-testid="tab3-content">Content for Tab 3</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button data-testid="close-modal-btn" onPress={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const {getByTestId, getByRole, queryByRole} = render(<TestComponent />);
|
||||
|
||||
const openButton = getByTestId("open-modal-btn");
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(openButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const modal = getByRole("dialog");
|
||||
|
||||
expect(modal).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const tabButtons = getByRole("dialog").querySelectorAll('[role="tab"]');
|
||||
|
||||
expect(tabButtons).toHaveLength(3);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(tabButtons[1]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(tabButtons[1]).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
|
||||
const closeButton = getByTestId("close-modal-btn");
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(closeButton);
|
||||
});
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(queryByRole("dialog")).not.toBeInTheDocument();
|
||||
},
|
||||
{timeout: 1000},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(openButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const modal = getByRole("dialog");
|
||||
|
||||
expect(modal).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const newTabButtons = getByRole("dialog").querySelectorAll('[role="tab"]');
|
||||
|
||||
expect(newTabButtons).toHaveLength(3);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(newTabButtons[2]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(newTabButtons[2]).toHaveAttribute("aria-selected", "true");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -61,6 +61,7 @@
|
||||
"@heroui/input": "workspace:*",
|
||||
"@heroui/test-utils": "workspace:*",
|
||||
"@heroui/button": "workspace:*",
|
||||
"@heroui/modal": "workspace:*",
|
||||
"@heroui/shared-icons": "workspace:*",
|
||||
"clean-package": "2.2.0",
|
||||
"react": "18.3.0",
|
||||
|
||||
@ -76,6 +76,8 @@ const Tab = forwardRef<"button", TabItemProps>((props, ref) => {
|
||||
rerender: true,
|
||||
});
|
||||
|
||||
const isInModal = domRef?.current?.closest('[aria-modal="true"]') !== null;
|
||||
|
||||
const handleClick = () => {
|
||||
if (!domRef?.current || !listRef?.current) return;
|
||||
|
||||
@ -120,7 +122,7 @@ const Tab = forwardRef<"button", TabItemProps>((props, ref) => {
|
||||
title={otherProps?.titleValue}
|
||||
type={Component === "button" ? "button" : undefined}
|
||||
>
|
||||
{isSelected && !disableAnimation && !disableCursorAnimation && isMounted ? (
|
||||
{isSelected && !disableAnimation && !disableCursorAnimation && isMounted && !isInModal ? (
|
||||
// use synchronous loading for domMax here
|
||||
// since lazy loading produces different behaviour
|
||||
<LazyMotion features={domMax}>
|
||||
|
||||
@ -21,6 +21,7 @@ const Tabs = forwardRef(function Tabs<T extends object>(
|
||||
Component,
|
||||
values,
|
||||
state,
|
||||
domRef,
|
||||
destroyInactiveTabPanel,
|
||||
getBaseProps,
|
||||
getTabListProps,
|
||||
@ -32,7 +33,9 @@ const Tabs = forwardRef(function Tabs<T extends object>(
|
||||
|
||||
const layoutId = useId();
|
||||
|
||||
const layoutGroupEnabled = !props.disableAnimation && !props.disableCursorAnimation;
|
||||
const isInModal = domRef?.current?.closest('[aria-modal="true"]') !== null;
|
||||
|
||||
const layoutGroupEnabled = !props.disableAnimation && !props.disableCursorAnimation && !isInModal;
|
||||
|
||||
const tabsProps = {
|
||||
state,
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -2811,6 +2811,9 @@ importers:
|
||||
'@heroui/input':
|
||||
specifier: workspace:*
|
||||
version: link:../input
|
||||
'@heroui/modal':
|
||||
specifier: workspace:*
|
||||
version: link:../modal
|
||||
'@heroui/shared-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/shared-icons
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user