npm.io
22.0.2 • Published 1 month ago

@leafygreen-ui/modal

Licence
Apache-2.0
Version
22.0.2
Deps
9
Size
408 kB
Vulns
0
Weekly
0
Stars
274

Modal

npm (scoped)

View on MongoDB.design

Installation

PNPM
pnpm add @leafygreen-ui/modal
Yarn
yarn add @leafygreen-ui/modal
NPM
npm install @leafygreen-ui/modal

Example

v20+
import { css } from '@leafygreen-ui/emotion';
import Modal from '@leafygreen-ui/modal';

const customModalStyles = css`
  // modal root styles

  &::backdrop {
    // modal backdrop styles
  }
`;

function ExampleComponent() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(curr => !curr)}>Open Modal</button>
      <Modal open={open} setOpen={setOpen} className={customModalStyles}>
        <h2>Modal title</h2>
        <p>More modal content</p>
        <button onClick={() => {}} autoFocus>
          Modal action
        </button>
      </Modal>
    </>
  );
}
Pre-v20
import { css } from '@leafygreen-ui/emotion';
import Modal from '@leafygreen-ui/modal';

const customModalRootStyles = css`
  // modal root styles
`;

const customBackdropStyles = css`
  // modal backdrop styles
`;

function ExampleComponent() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(curr => !curr)}>Open Modal</button>
      <Modal
        open={open}
        setOpen={setOpen}
        className={customBackdropStyles}
        contentClassName={customModalRootStyles}
        initialFocus="#init-focus-el"
      >
        <h2>Modal title</h2>
        <p>More modal content</p>
        <button id="init-focus-el" onClick={() => {}}>
          Modal action
        </button>
      </Modal>
    </>
  );
}

Properties

Prop Type Description Default
children ReactNode Content that will appear inside of the Modal component.
closeIconColor (optional) 'default' | 'dark' | 'light' Determines the color of the close icon. 'default'
darkMode (optional) boolean Determines if the component will appear in dark mode. false
id (optional) string Unique identifier for the Modal.
initialFocus (optional) 'auto' | string | React.RefObject<HTMLElement> | null Specifies which element should receive focus when the modal opens. See Initial focus behavior for details. 'auto'
open (optional) boolean Determines the open state of the modal. false
setOpen (optional) (open: boolean) => void | React.Dispatch<SetStateAction<boolean>> Callback to change the open state of the Modal. () => {}
shouldClose (optional) () => boolean Callback to determine whether or not Modal should close when user tries to close it. () => true
size (optional) 'small' | 'default' | 'large' Specifies the size of the Modal. 'default'
v20+
Prop Type Description Default
className (optional) string Applies a className to the Modal root element.
backdropClassName (optional) string @deprecated (optional) Applies a className to the Modal backdrop. Use CSS ::backdrop pseudo-element instead. This prop will be removed in a future version.
pre-v20
Prop Type Description Default
className (optional) string Applies a className to the Modal backdrop.
contentClassName (optional) string Applies a className to the Modal content wrapper element

Additional notes

Popover children elements

When using popover elements (tooltips, selects, etc.) inside a Modal, it is recommended that they use the top layer to ensure proper stacking and avoid rendering issues.

For LeafyGreen UI popover components, this is handled automatically in the latest versions as they default to renderMode="top-layer". If you're using custom popover implementations or need to explicitly set the render mode, ensure they use the top layer.

Initial focus behavior

When a Modal opens, it automatically manages focus to ensure proper accessibility. You can control this behavior using the initialFocus prop.

Focus Priority Order
  1. initialFocus prop: The specified element receives focus
    • String selector: initialFocus="#submit-button"
    • React ref: initialFocus={submitButtonRef} (recommended over string selector for better type safety)
  2. autoFocus attribute: If any child element has the autoFocus attribute, that element receives focus
  3. First focusable element: If initialFocus is "auto" and no child element has the autoFocus attribute, the first focusable element receives focus
  4. No focus: If initialFocus is null, no automatic focus occurs
Using Clipboard.js inside Modal

To directly use the Clipboard.js library inside of Modal, rather than using the Copyable component, the reference value of the Modal should be used as the container when Clipboard.js is instantiated. You can get the reference value by consuming the usePopoverPortalContainer hook:

import { usePopoverPortalContainer } from '@leafygreen-ui/leafygreen-provider';

function ExampleComponent() {
  const { portalContainer } = usePopoverPortalContainer();

  const clipboard = new ClipboardJS(buttonRef, {
    container: portalContainer,
  });
}
Backdrop styling

The preferred approach for styling the modal backdrop is to use the native CSS ::backdrop pseudo-element:

.my-modal::backdrop {
  background-color: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(4px);
}

The deprecated backdropClassName prop exists only to ease migration from previous Modal implementations and will be removed in a future version.

Test Harnesses

getTestUtils()

getTestUtils() is a util that allows consumers to reliably interact with LG Modal in a product test suite. If the Modal component cannot be found, an error will be thrown.

Usage

import {Modal} from '@leafygreen-ui/modal';
import { getTestUtils } from '@leafygreen-ui/modal/testing';

const utils = getTestUtils(lgId?: string); // lgId refers to the custom `data-lgid` attribute passed to `Modal`. It defaults to 'lg-modal' if left empty.
Single Modal
import { render } from '@testing-library/react';
import {Modal} from '@leafygreen-ui/modal';
import { getTestUtils } from '@leafygreen-ui/modal/testing';

...

test('modal', () => {
  render(
    <Modal open>
      <p>Modal content</p>
    </Modal>
    );
  const { getModal } = getTestUtils();
  expect(getModal()).toBeInTheDocument();
});
Test Utils
const { findModal, getModal, queryModal } = getTestUtils();
Util Description Returns
findModal Returns a promise that resolves to the modal element HTMLElement
getModal Returns the modal element. Will throw if no elements match. HTMLElement
queryModal Returns the modal element or null if no elements match. Will throw if more than one match is found. HTMLElement

Testing Considerations

The Modal component uses the native HTMLDialogElement API for better accessibility and browser-native behavior. However, JSDOM (used by Jest and other test runners) does not fully support this API. You'll need to mock the show, showModal, and close methods in your test setup:

beforeAll(() => {
  HTMLDialogElement.prototype.show = jest.fn(function mock(
    this: HTMLDialogElement,
  ) {
    this.open = true;
  });

  HTMLDialogElement.prototype.showModal = jest.fn(function mock(
    this: HTMLDialogElement,
  ) {
    this.open = true;
  });

  HTMLDialogElement.prototype.close = jest.fn(function mock(
    this: HTMLDialogElement,
  ) {
    this.open = false;
  });
});

This mock can be placed in a beforeAll block in your test file, or in a global test setup file.