Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/components/Drawer/Drawer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export default {
],
};

type DrawerArgs = Pick<DrawerProps, "placement" | "showMask"> & {
type DrawerArgs = Pick<DrawerProps, "placement"> & {
headerCloseBtn: boolean;
};

export const Default: StoryFn<DrawerArgs> = (args) => {
export const Default: StoryFn<DrawerArgs> = (args: DrawerArgs) => {
const { headerCloseBtn, ...rest } = args;
const [isOpen, setIsOpen] = useState(false);

Expand Down Expand Up @@ -67,10 +67,9 @@ export const Default: StoryFn<DrawerArgs> = (args) => {

Default.args = {
placement: "left",
showMask: true,
};

export const MultipleDrawers: StoryFn<DrawerArgs> = (args) => {
export const MultipleDrawers: StoryFn<DrawerArgs> = (args: DrawerArgs) => {
const { headerCloseBtn, ...rest } = args;
const [isFirstOpen, setIsFirstOpen] = useState(false);
const [isSecondOpen, setIsSecondOpen] = useState(false);
Expand Down Expand Up @@ -128,5 +127,4 @@ export const MultipleDrawers: StoryFn<DrawerArgs> = (args) => {

MultipleDrawers.args = {
placement: "left",
showMask: true,
};
80 changes: 80 additions & 0 deletions src/components/Drawer/Drawer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,86 @@ describe("<Drawer/>", () => {
expect(mockFn).toHaveBeenCalledTimes(1);
});

it("renders the visible mask when closeOnOutsideClick is false", () => {
render(
<Drawer isOpen closeOnOutsideClick={false} onClose={jest.fn()}>
<Drawer.Body>Drawer content</Drawer.Body>
</Drawer>,
);

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 isOpen closeOnOutsideClick={false} onClose={mockFn}>
<Drawer.Body>Drawer content</Drawer.Body>
</Drawer>,
);

await userEvent.click(screen.getByTestId("mask"));

expect(mockFn).not.toHaveBeenCalled();
});

it("renders only one visible mask when multiple drawers are open", () => {
render(
<>
<Drawer isOpen onClose={jest.fn()}>
<Drawer.Body>First drawer</Drawer.Body>
</Drawer>
<Drawer isOpen onClose={jest.fn()}>
<Drawer.Body>Second drawer</Drawer.Body>
</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(
<>
<Drawer isOpen onClose={jest.fn()}>
<Drawer.Body>First drawer</Drawer.Body>
</Drawer>
<Drawer isOpen onClose={onCloseTop}>
<Drawer.Body>Second drawer</Drawer.Body>
</Drawer>
</>,
);

expect(
screen.getAllByTestId("mask").filter((mask) => mask.getAttribute("data-overlay") === "true"),
).toHaveLength(1);

rerender(
<>
<Drawer isOpen onClose={jest.fn()}>
<Drawer.Body>First drawer</Drawer.Body>
</Drawer>
<Drawer isOpen={false} onClose={onCloseTop}>
<Drawer.Body>Second drawer</Drawer.Body>
</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();
Expand Down
24 changes: 15 additions & 9 deletions src/components/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -40,14 +42,11 @@ const dialogVariants: Variants = {
}),
};

const DRAWER_ROOT = "drawerRoot";

const DrawerRoot: FCWithChildren = () => <div id={DRAWER_ROOT} />;
const DrawerRoot: FCWithChildren = () => <div id={DRAWER_ROOT_ID} />;

export type DrawerProps = React.HTMLAttributes<HTMLDivElement> & {
isOpen: boolean;
closeOnOutsideClick?: boolean;
showMask?: boolean;
placement?: "left" | "right";
width?: string;
dialogStyles?: MotionStyle;
Expand All @@ -68,7 +67,6 @@ const Drawer: FCWithChildren<DrawerProps> & DrawerCompoundProps = (props) => {
isOpen,
placement = "left",
closeOnOutsideClick = true,
showMask = true,
width = "31.5rem",
dialogStyles,
dialogClassName,
Expand All @@ -88,20 +86,23 @@ const Drawer: FCWithChildren<DrawerProps> & DrawerCompoundProps = (props) => {
})
);
});
const drawerEl = document.getElementById(DRAWER_ROOT);
const drawerEl = document.getElementById(DRAWER_ROOT_ID);
const dialogClassNames = classNames({
dialog: true,
"placement-left": placement === "left",
"placement-right": placement === "right",
[dialogClassName ?? ""]: dialogClassName,
});
const drawerRef = useRef<HTMLDivElement>(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;
Expand Down Expand Up @@ -155,7 +156,12 @@ const Drawer: FCWithChildren<DrawerProps> & DrawerCompoundProps = (props) => {
ref={drawerRef}
{...rest}
>
{showMask && <Mask onClose={handleClose} />}
{shouldRenderMask && (
<Mask
visible={shouldShowVisibleMask}
onClick={closeOnOutsideClick ? handleOutsideClick : undefined}
/>
)}
<ReactFocusLock returnFocus disabled={!isOpen || disableFocusLock}>
<m.dialog
id="drawer-dialog"
Expand Down
25 changes: 15 additions & 10 deletions src/components/Drawer/__snapshots__/Drawer.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ exports[`<Drawer/> matches snapshot with close button 1`] = `
data-testid="drawer"
>
<div
class="css-tigpv4-maskContainer"
class="css-1h50gx7-maskContainer-maskContainer"
data-overlay="true"
data-testid="mask"
style="opacity: 0;"
/>
Expand All @@ -28,7 +29,7 @@ exports[`<Drawer/> matches snapshot with close button 1`] = `
style="opacity: 0; transform: none;"
>
<div
class="drawer-header css-1ki9nyc-drawerHeader-drawerHeader"
class="drawer-header css-1bphbgo-drawerHeader-drawerHeader"
data-testid="drawer-header"
id="drawer-title"
>
Expand Down Expand Up @@ -68,7 +69,8 @@ exports[`<Drawer/> matches snapshot with header, body and footer 1`] = `
id="main-drawer"
>
<div
class="css-tigpv4-maskContainer"
class="css-1h50gx7-maskContainer-maskContainer"
data-overlay="true"
data-testid="mask"
style="opacity: 0;"
/>
Expand All @@ -90,7 +92,7 @@ exports[`<Drawer/> matches snapshot with header, body and footer 1`] = `
style="opacity: 0; transform: none;"
>
<div
class="drawer-header css-1ki9nyc-drawerHeader-drawerHeader"
class="drawer-header css-1bphbgo-drawerHeader-drawerHeader"
data-testid="drawer-header"
id="drawer-title"
>
Expand Down Expand Up @@ -119,7 +121,7 @@ exports[`<Drawer/> matches snapshot with header, body and footer 1`] = `
Test body
</div>
<div
class="drawer-footer css-i1ukdc-footerContainer-footerContainer"
class="drawer-footer css-1pvwwi7-footerContainer-footerContainer"
data-testid="drawer-footer"
>
Test footer
Expand All @@ -140,7 +142,8 @@ exports[`<Drawer/> matches snapshot with noGutters Header 1`] = `
data-testid="drawer"
>
<div
class="css-tigpv4-maskContainer"
class="css-1h50gx7-maskContainer-maskContainer"
data-overlay="true"
data-testid="mask"
style="opacity: 0;"
/>
Expand All @@ -162,7 +165,7 @@ exports[`<Drawer/> matches snapshot with noGutters Header 1`] = `
style="opacity: 0; transform: none;"
>
<div
class="drawer-header css-qa9tzz-drawerHeader-drawerHeader"
class="drawer-header css-1ni8vhz-drawerHeader-drawerHeader"
data-testid="drawer-header"
id="drawer-title"
>
Expand Down Expand Up @@ -201,7 +204,8 @@ exports[`<Drawer/> matches snapshot with right placement 1`] = `
data-testid="drawer"
>
<div
class="css-tigpv4-maskContainer"
class="css-1h50gx7-maskContainer-maskContainer"
data-overlay="true"
data-testid="mask"
style="opacity: 0;"
/>
Expand All @@ -223,7 +227,7 @@ exports[`<Drawer/> matches snapshot with right placement 1`] = `
style="opacity: 0; transform: translateX(100%) translateZ(0);"
>
<div
class="drawer-header css-1ki9nyc-drawerHeader-drawerHeader"
class="drawer-header css-1bphbgo-drawerHeader-drawerHeader"
data-testid="drawer-header"
id="drawer-title"
>
Expand Down Expand Up @@ -262,7 +266,8 @@ exports[`<Drawer/> matches snapshot without header and footer 1`] = `
data-testid="drawer"
>
<div
class="css-tigpv4-maskContainer"
class="css-1h50gx7-maskContainer-maskContainer"
data-overlay="true"
data-testid="mask"
style="opacity: 0;"
/>
Expand Down
10 changes: 6 additions & 4 deletions src/components/Drawer/components/Mask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ const maskVariants: Variants = {
};

export type MaskProps = {
onClose: (e: MouseEvent) => void;
visible?: boolean;
onClick?: (e: MouseEvent) => void;
};

const Mask: FC<MaskProps> = ({ onClose }) => (
const Mask: FC<MaskProps> = ({ visible = true, onClick }) => (
<m.div
css={maskContainer}
onClick={onClose}
css={maskContainer(visible)}
onClick={onClick}
initial="hidden"
animate="expanded"
exit="hidden"
variants={maskVariants}
data-testid="mask"
data-overlay={visible}
/>
);

Expand Down
17 changes: 9 additions & 8 deletions src/components/Drawer/components/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ export const drawerBody = css`
overflow-y: auto;
`;

export const maskContainer = css`
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.45);
`;
export const maskContainer = (visible: boolean): SerializedStyles =>
css`
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: ${visible ? "rgba(0, 0, 0, 0.45)" : "transparent"};
`;

export const footerContainer = css`
padding: 1rem;
Expand Down
1 change: 1 addition & 0 deletions src/components/Drawer/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DRAWER_ROOT_ID = "drawerRoot";
Loading
Loading