import {
  Story,
  ArgTypes,
  Canvas,
  Unstyled,
  Meta,
} from '@storybook/addon-docs/blocks';
import ComposedModal from '../ComposedModal';
import { InlineNotification } from '../Notification';
import CodeSnippet from '../CodeSnippet';
import * as ComposedModalStories from './ComposedModal.stories.js';
import './ComposedModal.stories.scss';
import { stackblitzPrefillConfig } from '../../../previewer/codePreviewer';

<Meta isTemplate />

# ComposedModal

[Source code](https://github.com/carbon-design-system/carbon/tree/main/packages/react/src/components/ComposedModal)
&nbsp;|&nbsp;
[Usage guidelines](https://www.carbondesignsystem.com/components/modal/usage)
&nbsp;|&nbsp;
[Accessibility](https://www.carbondesignsystem.com/components/modal/accessibility)

{/* <!-- START doctoc generated TOC please keep comment here to allow auto update --> */}
{/* <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> */}

## Table of Contents

- [Overview](#overview)
- [Component API](#component-api)
- [Opening/closing modal](#openingclosing-modal)
  - [`preventCloseOnClickOutside`](#preventcloseonclickoutside)
- [Modal sizes](#modal-sizes)
  - [Alignment](#alignment)
  - [Overflow content](#overflow-content)
- [Modal button variants](#modal-button-variants)
- [Using modal title as message](#using-modal-title-as-message)
- [Focus management](#focus-management)
  - [Setting `data-modal-primary-focus` attribute to the target element](#setting-data-modal-primary-focus-attribute-to-the-target-element)
  - [Specifying a query selector to the target element](#specifying-a-query-selector-to-the-target-element)
- [References](#references)
- [Feedback](#feedback)

{/* <!-- END doctoc generated TOC please keep comment here to allow auto update --> */}

## Overview

`<ComposedModal>` provides more flexibility when it comes to custom content.
Modal content can be customized via the `<ModalHeader>`, `<ModalBody>` and
`<ModalFooter>` components.

<Canvas
  of={ComposedModalStories.WithStateManager}
  additionalActions={[
    {
      title: 'Open in Stackblitz',
      onClick: () =>
        stackblitzPrefillConfig(
          ComposedModalStories.WithStateManager,
          "import ReactDOM from 'react-dom';"
        ),
    },
  ]}
/>

## Component API

<ArgTypes />

## Opening/closing modal

For both modal variants, you can open/close the modal by changing the `open`
prop. For example, you can implement a launcher button with a React state and a
`<Button>` that changes the state:

```jsx
function ModalStateManager() {
  const [open, setOpen] = useState(false);
  return (
    <>
      {typeof document === 'undefined'
        ? null
        : ReactDOM.createPortal(
            <ComposedModal open={open} onClose={() => setOpen(false)}>
              ...
            </ComposedModal>,
            document.body
          )}
      <Button kind="primary" onClick={() => setOpen(true)}>
        Open modal
      </Button>
    </>
  );
}
```

You can create an abstract version of such state manager, as shown below.

```jsx
const ModalStateManager = ({
  renderLauncher: LauncherContent,
  children: ModalContent,
}) => {
  const [open, setOpen] = useState(false);
  return (
    <>
      {!ModalContent || typeof document === 'undefined'
        ? null
        : ReactDOM.createPortal(
            <ModalContent open={open} setOpen={setOpen} />,
            document.body
          )}
      {LauncherContent && <LauncherContent open={open} setOpen={setOpen} />}
    </>
  );
};
```

{/* <!-- prettier-ignore-start --> */}

<Unstyled>
  <InlineNotification
    kind="warning"
    title="Warning"
    className="sb-notification">
    <CodeSnippet type="inline" hideCopyButton>
      Modal
    </CodeSnippet>
    and
    <CodeSnippet type="inline" hideCopyButton>
      ComposedModal
    </CodeSnippet>
    have to be put at the top level in a React tree. The easiest way to ensure
    that is using a React portal, as shown in the example above.
  </InlineNotification>
</Unstyled>

{/* <!-- prettier-ignore-end --> */}

### `preventCloseOnClickOutside`

This prop controls what happens when a user clicks outside the bounds of the
modal. When true, clicks outside the modal will not trigger close. When false,
clicks outside the modal will trigger close. When left undefined, the modal type
determines the default behavior.

Passive modals by default close on outside clicks. For transactional,
non-passive modals that include `ModalFooter`, the
[design spec for Modal](https://carbondesignsystem.com/components/modal/usage/#transactional-modal)
calls for these to not be dissmissable via outside clicks.

Setting `preventCloseOnClickOutside` to `false` to override the default for
non-passive modals is allowed but not recommended, and will trigger a warning.

## Modal sizes

There are four responsive
[modal sizes](https://www.carbondesignsystem.com/components/modal/usage/#modal-sizes):
`xs`, `small`, default, and `large`. You can set it via the `size` prop.

```jsx
<ComposedModal size="lg">
  <ModalHeader />
  <ModalBody>
    <p className="cds--modal-content__text">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
      cursus fermentum risus, sit amet fringilla nunc pellentesque quis. Duis
      quis odio ultrices, cursus lacus ac, posuere felis. Donec dignissim libero
      in augue mattis, a molestie metus vestibulum. Aliquam placerat felis
      ultrices lorem condimentum, nec ullamcorper felis porttitor.
    </p>
  </ModalBody>
</ComposedModal>
```

### Alignment

Depending on the modal size of your choice and the viewport size,
`<Modal>`/`<ComposedModal>` adds 20% padding at the right of the modal body
content. Carbon design specifies that such 20% padding shouldn't be applied to
form elements. You can set `hasForm` prop to `<Modal>`/`<ModalBody>` to remove
the padding, and use `cds--modal-content__regular-content` class to apply the
20% padding to non-form contents, as shown below.

```jsx
<ComposedModal>
  <ModalHeader />
  <ModalBody hasForm>
    <TextInput data-modal-primary-focus labelText="Enter something" />
    <p className="cds--modal-content__text cds--modal-content__regular-content">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
      cursus fermentum risus, sit amet fringilla nunc pellentesque quis. Duis
      quis odio ultrices, cursus lacus ac, posuere felis. Donec dignissim libero
      in augue mattis, a molestie metus vestibulum. Aliquam placerat felis
      ultrices lorem condimentum, nec ullamcorper felis porttitor.
    </p>
  </ModalBody>
</ComposedModal>
```

### Overflow content

In cases where the largest modal size cannot fit your modal body content in,
Carbon design specifies to have a "visual fade" look at the end of the modal
body area to indicate there is additional content out of view. You can set
`hasScrollingContent` prop to `<Modal>`/`<ModalBody>` to do that, as shown
below.

```jsx
<ComposedModal size="large">
  <ModalHeader />
  <ModalBody hasScrollingContent>
    <p className=".cds--modal-content__text">Some very large contents...</p>
  </ModalBody>
</ComposedModal>
```

## Modal button variants

With `<Modal>`, you have limited control over the set of buttons. The following
table shows the supported patterns.

| Button group variant                                                                                 | Usage                                     |
| ---------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| No button ([passive modal](https://www.carbondesignsystem.com/components/modal/usage#passive-modal)) | Use `passiveModal` prop in `<Modal>`      |
| One button                                                                                           | Use `<ComposedModal>`                     |
| Two buttons                                                                                          | Use `<Modal>` without `passiveModal` prop |
| Two buttons with danger button                                                                       | Use `danger` prop in `<Modal>`            |

```jsx
<Modal danger>
  <ModalHeader />
  <p className="cds--modal-content__text">The modal body content</p>
</Modal>
```

With `<ComposedModal>`, you can control the buttons with the following code.

| Button group variant                                                                                 | Usage                                                                                                                                                                                                                                                           |
| ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No button ([passive modal](https://www.carbondesignsystem.com/components/modal/usage#passive-modal)) | Use `<ComposedModal>` without `<ModalFooter>`                                                                                                                                                                                                                   |
| One button                                                                                           | Use `primaryButtonText` in `<ModalFooter>`                                                                                                                                                                                                                      |
| Two buttons                                                                                          | Use `primaryButtonText` and `secondaryButtonText` in `<ModalFooter>`                                                                                                                                                                                            |
| Three buttons                                                                                        | Put three buttons as children of `<ModalFooter>`, instead of using `primaryButtonText` or `secondaryButtonText`. Using this option requires style adjustment defined in application-level CSS for `<ModalFooter>`, e.g. `.cds--modal-footer { padding: 25%; }`. |
| Two buttons with danger button                                                                       | Set `danger` prop in `<ModalFooter>` (like below)                                                                                                                                                                                                               |

```jsx
<ComposedModal>
  ...
  <ModalFooter danger primaryButtonText="OK" secondaryButtonText="Cancel" />
</ComposedModal>
```

{/* <!-- prettier-ignore-start --> */}

<Unstyled>
  <InlineNotification
    kind="warning"
    title="Warning"
    className="sb-notification">
    As the instructions for the three buttons imply,
    <CodeSnippet type="inline" hideCopyButton>
      ModalFooter
    </CodeSnippet>
    is flexible with the buttons you render using
    <CodeSnippet type="inline" hideCopyButton>
      Button
    </CodeSnippet>
    components. In this case, your application code is responsible for handling
    button actions, such as closing the modal.
  </InlineNotification>
</Unstyled>

{/* <!-- prettier-ignore-end --> */}

```jsx
<ComposedModal>
  ...
  <ModalFooter>
    <Button
      kind="secondary"
      onClick={() => { (Run some action...) setOpen(false); }}>
      Another button
    </Button>
    <Button
      kind="secondary"
      onClick={() => { (Run some action...) setOpen(false); }}>
      Secondary button
    </Button>
    <Button
      kind="primary"
      onClick={() => { (Run some action...) setOpen(false); }}>
      Primary button
    </Button>
  </ModalFooter>
</ComposedModal>
```

## Using modal title as message

For short, direct messages the title can include the whole message to add visual
clarity to an otherwise repetitive title and body message.

To use this pattern with `<Modal>`, you can omit the `children` prop.

```jsx
const modalHeading =
  'Are you sure you want to add the "Speech to Text" service ' +
  'to the node-test app?';
...
<Modal
  modalHeading={modalHeading}
  secondaryButtonText="Cancel"
  primaryButtonText="Add"
/>
```

To use this pattern with `<ComposedModal>`, you can omit `<ModalBody>`.

```jsx
<ComposedModal>
  <ModalHeader label="Modal label">
    <h1>
      Are you sure you want to add the "Speech to Text" service to the node-test
      app?
    </h1>
  </ModalHeader>
  <ModalFooter primaryButtonText="OK" secondaryButtonText="Cancel" />
</ComposedModal>
```

## Focus management

For both modal variants, once the modal is open, keyboard focus will be
restricted inside modal. This means:

- If you press `Tab` at the last keyboard-focusable element in modal, the first
  keyboard-focusable element in modal will get focus.
- If you press `Shift-Tab` at the first keyboard-focusable element in the modal,
  the last keyboard-focusable element in the modal will get focus.

We take extra step here to ensure such behavior works with floating menus, given
floating menu is placed outside of modal in DOM. If you use any non-Carbon
floating menus in your application, set
`selectorsFloatingMenus={['.cds--overflow-menu-options', '.cds--tooltip', '.flatpickr-calendar', '.your-floating-menu-foo', '.your-floating-menu-bar']}`
to `<Modal>`/`<ComposedModal>`.

Also for both modal variants, you can set the DOM element that gets focus when
the modal gets open, by either of the following ways:

### Setting `data-modal-primary-focus` attribute to the target element

<br />

```jsx
<ComposedModal>
  <ModalBody hasForm>
    <TextInput data-modal-primary-focus labelText="Enter something" />
  </ModalBody>
</ComposedModal>
```

### Specifying a query selector to the target element

<br />

```jsx
{
  /* `.cds--text-input` selects the `<input>` in `<TextInput>` */
}
<ComposedModal selectorPrimaryFocus=".cds--text-input">
  <ModalBody hasForm>
    <TextInput labelText="Enter something" />
  </ModalBody>
</ComposedModal>;
```

## References

Check out the
[usage guidelines](https://www.carbondesignsystem.com/components/modal/usage/)
on the Carbon Design System website.

## Feedback

Help us improve this component by providing feedback, asking questions on Slack,
or updating this file on
[GitHub](https://github.com/carbon-design-system/carbon/edit/main/packages/react/src/components/ComposedModal/ComposedModal.mdx).
