diff --git a/src/components/Drawer/Drawer.stories.tsx b/src/components/Drawer/Drawer.stories.tsx index e0d0c09f..24cbdccb 100644 --- a/src/components/Drawer/Drawer.stories.tsx +++ b/src/components/Drawer/Drawer.stories.tsx @@ -32,11 +32,11 @@ export default { ], }; -type DrawerArgs = Pick & { +type DrawerArgs = Pick & { headerCloseBtn: boolean; }; -export const Default: StoryFn = (args) => { +export const Default: StoryFn = (args: DrawerArgs) => { const { headerCloseBtn, ...rest } = args; const [isOpen, setIsOpen] = useState(false); @@ -67,10 +67,9 @@ export const Default: StoryFn = (args) => { Default.args = { placement: "left", - showMask: true, }; -export const MultipleDrawers: StoryFn = (args) => { +export const MultipleDrawers: StoryFn = (args: DrawerArgs) => { const { headerCloseBtn, ...rest } = args; const [isFirstOpen, setIsFirstOpen] = useState(false); const [isSecondOpen, setIsSecondOpen] = useState(false); @@ -128,5 +127,4 @@ export const MultipleDrawers: StoryFn = (args) => { MultipleDrawers.args = { placement: "left", - showMask: true, }; diff --git a/src/components/Drawer/Drawer.test.tsx b/src/components/Drawer/Drawer.test.tsx index a79e9f06..47eddcdb 100644 --- a/src/components/Drawer/Drawer.test.tsx +++ b/src/components/Drawer/Drawer.test.tsx @@ -42,6 +42,86 @@ describe("", () => { expect(mockFn).toHaveBeenCalledTimes(1); }); + it("renders the visible mask when closeOnOutsideClick is false", () => { + render( + + Drawer content + , + ); + + expect(screen.getByTestId("mask")).toHaveAttribute("data-overlay", "true"); + }); + + it("does not close on outside click when closeOnOutsideClick is false", async () => { + const mockFn = jest.fn(); + + render( + + Drawer content + , + ); + + await userEvent.click(screen.getByTestId("mask")); + + expect(mockFn).not.toHaveBeenCalled(); + }); + + it("renders only one visible mask when multiple drawers are open", () => { + render( + <> + + First drawer + + + Second drawer + + , + ); + + const masks = screen.getAllByTestId("mask"); + + expect(masks).toHaveLength(2); + expect(masks.filter((mask) => mask.getAttribute("data-overlay") === "true")).toHaveLength(1); + }); + + it("promotes the visible mask when the top drawer closes", async () => { + const onCloseTop = jest.fn(); + + const { rerender } = render( + <> + + First drawer + + + Second drawer + + , + ); + + expect( + screen.getAllByTestId("mask").filter((mask) => mask.getAttribute("data-overlay") === "true"), + ).toHaveLength(1); + + rerender( + <> + + First drawer + + + Second drawer + + , + ); + + await waitFor(() => { + expect( + screen + .getAllByTestId("mask") + .filter((mask) => mask.getAttribute("data-overlay") === "true"), + ).toHaveLength(1); + }); + }); + it("renders correctly without Header and Footer", () => { const bodyTxt = faker.lorem.word(); const mockFn = jest.fn(); diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx index b6ec0b36..53c604d7 100644 --- a/src/components/Drawer/Drawer.tsx +++ b/src/components/Drawer/Drawer.tsx @@ -16,6 +16,8 @@ import Header, { HeaderProps } from "./components/Header"; import Body from "./components/Body"; import Mask from "./components/Mask"; import Footer from "./components/Footer"; +import { useDrawerStackPosition } from "./hooks/useDrawerStackPosition"; +import { DRAWER_ROOT_ID } from "./constants"; import { FCWithChildren } from "types/common"; type DialogVariants = { @@ -40,14 +42,11 @@ const dialogVariants: Variants = { }), }; -const DRAWER_ROOT = "drawerRoot"; - -const DrawerRoot: FCWithChildren = () =>
; +const DrawerRoot: FCWithChildren = () =>
; export type DrawerProps = React.HTMLAttributes & { isOpen: boolean; closeOnOutsideClick?: boolean; - showMask?: boolean; placement?: "left" | "right"; width?: string; dialogStyles?: MotionStyle; @@ -68,7 +67,6 @@ const Drawer: FCWithChildren & DrawerCompoundProps = (props) => { isOpen, placement = "left", closeOnOutsideClick = true, - showMask = true, width = "31.5rem", dialogStyles, dialogClassName, @@ -88,7 +86,7 @@ const Drawer: FCWithChildren & DrawerCompoundProps = (props) => { }) ); }); - const drawerEl = document.getElementById(DRAWER_ROOT); + const drawerEl = document.getElementById(DRAWER_ROOT_ID); const dialogClassNames = classNames({ dialog: true, "placement-left": placement === "left", @@ -96,12 +94,15 @@ const Drawer: FCWithChildren & DrawerCompoundProps = (props) => { [dialogClassName ?? ""]: dialogClassName, }); const drawerRef = useRef(null); + const { isBottomMostOpenDrawer } = useDrawerStackPosition(isOpen); - const handleClose = () => { - if (!isOpen || !closeOnOutsideClick) return; + const handleOutsideClick = () => { + if (!isOpen) return; onClose(); }; + const shouldRenderMask = closeOnOutsideClick || isBottomMostOpenDrawer; + const shouldShowVisibleMask = isBottomMostOpenDrawer; useEffect(() => { if (!isOpen) return; @@ -155,7 +156,12 @@ const Drawer: FCWithChildren & DrawerCompoundProps = (props) => { ref={drawerRef} {...rest} > - {showMask && } + {shouldRenderMask && ( + + )} matches snapshot with close button 1`] = ` data-testid="drawer" >
@@ -28,7 +29,7 @@ exports[` matches snapshot with close button 1`] = ` style="opacity: 0; transform: none;" >
@@ -68,7 +69,8 @@ exports[` matches snapshot with header, body and footer 1`] = ` id="main-drawer" >
@@ -90,7 +92,7 @@ exports[` matches snapshot with header, body and footer 1`] = ` style="opacity: 0; transform: none;" >
@@ -119,7 +121,7 @@ exports[` matches snapshot with header, body and footer 1`] = ` Test body