# Modal

Ein Modal zeigt Inhalte zentriert als Overlay über der Hauptseite.

```tsx
import {
  Action,
  ActionGroup,
  Button,
  Content,
  Heading,
  Label,
  Modal,
  ModalTrigger,
  Section,
  Text,
  TextField,
} from "@mittwald/flow-react-components";
import { sleepLong } from "@/content/04-components/actions/action/examples/lib";

<ModalTrigger>
  <Button>Modal öffnen</Button>
  <Modal>
    <Heading>Organisation anlegen</Heading>
    <Content>
      <Section>
        <Text>
          Eine Organisation kannst du dir wie ein
          Unternehmen vorstellen. An diesem Ort verwaltest
          du deine Mitarbeiter, Zahlungsmodalitäten und
          kannst deine Rechnungen einsehen.
        </Text>
        <TextField isRequired>
          <Label>Organisationsname</Label>
        </TextField>
      </Section>
    </Content>
    <ActionGroup>
      <Action closeModal>
        <Action onAction={sleepLong}>
          <Button color="success">
            Organisation anlegen
          </Button>
        </Action>
        <Button variant="soft" color="secondary">
          Abbrechen
        </Button>
      </Action>
    </ActionGroup>
  </Modal>
</ModalTrigger>
```

---

# Best Practices

- Öffne ein Modal nur als Reaktion auf eine bewusste Aktion des Nutzers. Ein
  unerwartet erscheinendes Modal unterbricht ihn und zieht sofort seine gesamte
  Aufmerksamkeit auf sich.
- Nutze für umfangreiche Prozesse ein OffCanvas. Ein Modal bleibt so kurz und
  fokussiert.
- Formuliere eine prägnante Überschrift und halte den Inhalt leicht
  verständlich.
- Vermeide es, mehrere Modals übereinander zu stapeln. Ein Modal über einem
  anderen ist im Einzelfall vertretbar, doch mit jeder weiteren Ebene verliert
  der Nutzer die Orientierung.

---

# OffCanvas

Nutze die Variante OffCanvas für Modals mit viel Inhalt, bei dem gescrollt
werden müsste.

```tsx
import {
  Action,
  ActionGroup,
  Button,
  ColumnLayout,
  Content,
  DatePicker,
  FieldDescription,
  Heading,
  Label,
  Modal,
  ModalTrigger,
  RadioButton,
  RadioGroup,
  Section,
  Segment,
  SegmentedControl,
  Text,
  TextField,
} from "@mittwald/flow-react-components";
import { sleepLong } from "@/content/04-components/actions/action/examples/lib";

<ModalTrigger>
  <Button>OffCanvas öffnen</Button>
  <Modal size="m" offCanvas>
    <Heading>SFTP-Benutzer anlegen</Heading>
    <Content>
      <Section>
        <Heading>Beschreibung</Heading>
        <Text>
          Mit einem SFTP-Benutzer kannst du dich mit deinem
          Projekt verbinden, um z.B. Dateien hochzuladen.
        </Text>
        <ColumnLayout m={[1, 1]}>
          <TextField isRequired>
            <Label>Bezeichnung</Label>
          </TextField>
          <DatePicker>
            <Label>Ablaufdatum</Label>
            <FieldDescription>
              Nach diesem Datum wird der SFTP-Benutzer
              gelöscht.
            </FieldDescription>
          </DatePicker>
        </ColumnLayout>

        <Heading>Authentifizierung</Heading>
        <Text>
          Wähle zwischen der Authentifikation per Passwort
          oder über einen SSH-Key.
        </Text>
        <SegmentedControl
          value="password"
          aria-label="Authentifizierung"
        >
          <Segment value="password">Passwort</Segment>
          <Segment value="ssh">SSH-Key</Segment>
        </SegmentedControl>
        <ColumnLayout s={[1, 1]}>
          <TextField isRequired>
            <Label>Passwort</Label>
          </TextField>
        </ColumnLayout>

        <Heading>Berechtigungen</Heading>
        <Text>
          Wähle hier die Berechtigungen aus, mit denen der
          SFTP-Benutzer zugreifen darf.
        </Text>
        <RadioGroup
          s={[1, 1]}
          defaultValue="read&write"
          aria-label="Berechtigungen"
        >
          <RadioButton value="write">
            <Text>Lesezugriff</Text>
            <Content>
              Der SFTP-Benutzer kann Dateien einsehen und
              herunterladen.
            </Content>
          </RadioButton>
          <RadioButton value="read&write">
            <Text>Lese- und Schreibzugriff</Text>
            <Content>
              Der SFTP-Benutzer kann Dateien einsehen,
              bearbeiten, hoch und herunterladen.
            </Content>
          </RadioButton>
        </RadioGroup>

        <Heading>Verzeichnisauswahl</Heading>
        <Text>
          Hier legst du das Verzeichnis fest, auf das der
          SFTP-Benutzer Zugriff hat.
        </Text>
        <TextField isRequired>
          <Label>Pfad</Label>
        </TextField>
      </Section>
    </Content>
    <ActionGroup>
      <Action closeModal>
        <Action onAction={sleepLong}>
          <Button color="success">
            SFTP-Benutzer anlegen
          </Button>
        </Action>
        <Button variant="soft" color="secondary">
          Abbrechen
        </Button>
      </Action>
    </ActionGroup>
  </Modal>
</ModalTrigger>
```

---

# Sizes

Ein Modal mit der Size **Small** (Breite 660 px) eignet sich gut für einfache
Abfragen oder Bestätigungsmodals. Ein Modal mit der Size **Medium** (Breite 900
px) wird für komplexere Dialoge verwendet.

```tsx
import {
  Action,
  ActionGroup,
  Button,
  ColumnLayout,
  Content,
  DatePicker,
  FieldDescription,
  Heading,
  Label,
  Link,
  Modal,
  ModalTrigger,
  Option,
  RadioButton,
  RadioGroup,
  Section,
  Segment,
  SegmentedControl,
  Select,
  Switch,
  Text,
  TextField,
} from "@mittwald/flow-react-components";
import { sleepLong } from "@/content/04-components/actions/action/examples/lib";

export default () => {
  return (
    <>
      <ModalTrigger>
        <Button>Modal S</Button>
        <Modal size="s">
          <Heading>
            Möchtest du die Bestellung wirklich abbrechen?
          </Heading>
          <Content>
            <Section>
              <Text>
                Deine eingegebenen Daten werden nicht
                gespeichert.
              </Text>
            </Section>
          </Content>
          <ActionGroup>
            <Action closeModal>
              <Action onAction={sleepLong}>
                <Button color="danger">
                  Bestellung abbrechen
                </Button>
              </Action>
              <Button variant="soft" color="secondary">
                Bestellung fortsetzen
              </Button>
            </Action>
          </ActionGroup>
        </Modal>
      </ModalTrigger>

      <ModalTrigger>
        <Button>Modal M</Button>
        <Modal size="m">
          <Heading>Backup anlegen</Heading>
          <Content>
            <Section>
              <Text>
                Das Backup enthält alle Dateien deines
                Dateisystems und den Inhalt deiner
                Datenbanken. Dei Erstellung eines Backups
                dauert in der Regel einige Minuten.
              </Text>
              <ColumnLayout m={[1, 1]}>
                <TextField>
                  <Label>Beschreibung</Label>
                </TextField>
                <Select isRequired>
                  <Label>Speicherdauer</Label>
                  <Option>7 Tage</Option>
                  <Option>14 Tage</Option>
                  <Option>30 Tage</Option>
                  <Option>6 Monate</Option>
                  <Option>12 Monate</Option>
                </Select>
              </ColumnLayout>
            </Section>
          </Content>
          <ActionGroup>
            <Action closeModal>
              <Action onAction={sleepLong}>
                <Button color="success">
                  Backup anlegen
                </Button>
              </Action>
              <Button variant="soft" color="secondary">
                Abbrechen
              </Button>
            </Action>
          </ActionGroup>
        </Modal>
      </ModalTrigger>

      <ModalTrigger>
        <Button>OffCanvas S</Button>
        <Modal size="s" offCanvas>
          <Heading>Dashboard-Einstellungen</Heading>
          <Content>
            <Section>
              <Heading>Widget-Sichtbarkeit</Heading>
              <Text>
                Aktiviere und deaktiviere die Widgets, die
                du wirklich benötigst. So bestimmst du
                selbst, wie dein Dashboard aussehen soll.
              </Text>
              <ColumnLayout s={[1]} gap="xl">
                <ColumnLayout s={[1]} gap="s">
                  <Switch>
                    <Label>Erste Schritte</Label>
                  </Switch>
                  <Text>
                    Im Onboarding erklären wir dir alles
                    Wichtige im mStudio.
                  </Text>
                  <Link>Erste Schritte starten</Link>
                </ColumnLayout>

                <ColumnLayout s={[1]} gap="s">
                  <Switch defaultSelected>
                    <Label>mittwald Status</Label>
                  </Switch>
                  <Text>
                    Wir informieren dich über Wartung und
                    Störungen.
                  </Text>
                </ColumnLayout>

                <ColumnLayout s={[1]} gap="s">
                  <Switch>
                    <Label>mittwald Produkt-Slider</Label>
                  </Switch>
                  <Text>
                    Im Produkt-Slider erhälst du
                    Informationen und einen schnellen
                    Einstieg in weitere mittwald Produkte.
                  </Text>
                </ColumnLayout>

                <ColumnLayout s={[1]} gap="s">
                  <Switch defaultSelected>
                    <Label>Neue Features</Label>
                  </Switch>
                  <Text>
                    Wir entwickeln das mStudio stetig weiter
                    Alle kommenden Features findest du auf
                    der <Link>Roadmap</Link>.
                  </Text>
                  <Link>Changelog öffnen</Link>
                </ColumnLayout>

                <ColumnLayout s={[1]} gap="s">
                  <Switch defaultSelected>
                    <Label>Neue Blogbeiträge</Label>
                  </Switch>
                  <Text>
                    Wir zeigen dir den neuesten mittwald
                    Blogartikel an.
                  </Text>
                  <Link>Blogartikel öffnen</Link>
                </ColumnLayout>

                <ColumnLayout s={[1]} gap="s">
                  <Switch>
                    <Label>Lastschift Hinweis</Label>
                  </Switch>
                  <Text>
                    Wir informieren über die neue
                    Möglichkeit, deine Rechnungen per
                    Lastschrift zu bezahlen.
                  </Text>
                </ColumnLayout>
              </ColumnLayout>
            </Section>
          </Content>
          <ActionGroup>
            <Action closeModal>
              <Button variant="soft" color="secondary">
                Schließen
              </Button>
            </Action>
          </ActionGroup>
        </Modal>
      </ModalTrigger>

      <ModalTrigger>
        <Button>OffCanvas M</Button>
        <Modal size="m" offCanvas>
          <Heading>SFTP-Benutzer anlegen</Heading>
          <Content>
            <Section>
              <Heading>Beschreibung</Heading>
              <Text>
                Mit einem SFTP-Benutzer kannst du dich mit
                deinem Projekt verbinden, um z.B. Dateien
                hochzuladen.
              </Text>
              <ColumnLayout m={[1, 1]}>
                <TextField isRequired>
                  <Label>Bezeichnung</Label>
                </TextField>
                <DatePicker>
                  <Label>Ablaufdatum</Label>
                  <FieldDescription>
                    Nach diesem Datum wird der SFTP-Benutzer
                    gelöscht.
                  </FieldDescription>
                </DatePicker>
              </ColumnLayout>

              <Heading>Authentifizierung</Heading>
              <Text>
                Wähle zwischen der Authentifikation per
                Passwort oder über einen SSH-Key.
              </Text>
              <SegmentedControl
                value="password"
                aria-label="Authentifizierung"
              >
                <Segment value="password">Passwort</Segment>
                <Segment value="ssh">SSH-Key</Segment>
              </SegmentedControl>
              <ColumnLayout s={[1, 1]}>
                <TextField isRequired>
                  <Label>Passwort</Label>
                </TextField>
              </ColumnLayout>

              <Heading>Berechtigungen</Heading>
              <Text>
                Wähle hier die Berechtigungen aus, mit denen
                der SFTP-Benutzer zugreifen darf.
              </Text>
              <RadioGroup
                s={[1, 1]}
                defaultValue="read&write"
                aria-label="Berechtigungen"
              >
                <RadioButton value="write">
                  <Text>Lesezugriff</Text>
                  <Content>
                    Der SFTP-Benutzer kann Dateien einsehen
                    und herunterladen.
                  </Content>
                </RadioButton>
                <RadioButton value="read&write">
                  <Text>Lese- und Schreibzugriff</Text>
                  <Content>
                    Der SFTP-Benutzer kann Dateien einsehen,
                    bearbeiten, hoch und herunterladen.
                  </Content>
                </RadioButton>
              </RadioGroup>

              <Heading>Verzeichnisauswahl</Heading>
              <Text>
                Hier legst du das Verzeichnis fest, auf das
                der SFTP-Benutzer Zugriff hat.
              </Text>
              <TextField isRequired>
                <Label>Pfad</Label>
              </TextField>
            </Section>
          </Content>
          <ActionGroup>
            <Action closeModal>
              <Action onAction={sleepLong}>
                <Button color="success">
                  SFTP-Benutzer anlegen
                </Button>
              </Action>
              <Button variant="soft" color="secondary">
                Abbrechen
              </Button>
            </Action>
          </ActionGroup>
        </Modal>
      </ModalTrigger>
    </>
  );
}
```

---

# Ungespeicherte Änderungen

Eingaben in einem Modal gehen beim Schließen verloren. Damit das nicht unbemerkt
passiert, kann bei Escape oder einem Klick außerhalb des Modals zuerst ein
Bestätigungs-Dialog erscheinen. Aktionen im Footer und der Close-Button in der
Überschrift schließen dagegen sofort.

Enthält das Modal eine
[Form (React Hook Form)](/04-components/react-hook-form/form), passiert das
automatisch, sobald sie ungespeicherte Änderungen enthält. Ohne Form – oder wenn
du selbst entscheiden willst, wann es etwas zu verlieren gibt – setzt du
`confirmOnClose`.

```tsx
import {
  Action,
  ActionGroup,
  Button,
  Content,
  Heading,
  Label,
  Modal,
  ModalTrigger,
  Section,
  Text,
  TextField,
} from "@mittwald/flow-react-components";
import { useState } from "react";

export default () => {
  const [description, setDescription] = useState("");

  return (
    <ModalTrigger>
      <Button>Modal öffnen</Button>

      <Modal
        confirmOnClose={description !== ""}
        onClose={() => setDescription("")}
      >
        <Heading>Projekt-Beschreibung</Heading>

        <Content>
          <Section>
            <Text>
              Gib eine Beschreibung ein und schließe das
              Modal anschließend mit Escape oder per Klick
              außerhalb – das Verwerfen der Änderungen muss
              bestätigt werden.
            </Text>

            <TextField
              value={description}
              onChange={setDescription}
            >
              <Label>Beschreibung</Label>
            </TextField>
          </Section>
        </Content>

        <ActionGroup>
          <Action closeModal>
            <Button color="success">Speichern</Button>
            <Button color="secondary" variant="soft">
              Abbrechen
            </Button>
          </Action>
        </ActionGroup>
      </Modal>
    </ModalTrigger>
  );
}
```

---

# Show CloseButton

Standardmäßig wird der Close-Button in der Überschrift ausgeblendet, sobald das
Modal eine `<ActionGroup />` enthält – die Aktionen im Footer übernehmen dann
das Schließen. Über das `showCloseButton` Property lässt sich das überschreiben.

```tsx
import {
  Action,
  ActionGroup,
  Button,
  Content,
  Heading,
  Modal,
  ModalTrigger,
  Section,
  Text,
} from "@mittwald/flow-react-components";

<ModalTrigger>
  <Button>Modal öffnen</Button>
  <Modal showCloseButton>
    <Heading>Datenbank verbinden</Heading>
    <Content>
      <Section>
        <Text>
          Verbinde deine Datenbank mit der Anwendung, um
          Inhalte zu speichern und abzurufen.
        </Text>
      </Section>
    </Content>
    <ActionGroup>
      <Action closeModal>
        <Button color="success">Verbinden</Button>
        <Button variant="soft" color="secondary">
          Abbrechen
        </Button>
      </Action>
    </ActionGroup>
  </Modal>
</ModalTrigger>
```

---

# Controller

Neben dem `<ModalTrigger />` kann das Modal auch über einen Controller gesteuert
werden.

Dieser Controller steht auch in Modals zur Verfügung, die über den ModalTrigger
geöffnet wurden.

```tsx
import {
  Action,
  ActionGroup,
  Button,
  Content,
  Heading,
  Label,
  Modal,
  Section,
  Text,
  TextField,
  useModalController,
} from "@mittwald/flow-react-components";
import { sleepLong } from "@/content/04-components/actions/action/examples/lib";

export default () => {
  const controller = useModalController();

  return (
    <>
      <Button onPress={controller.open}>
        Modal öffnen
      </Button>

      <Modal controller={controller}>
        <Heading>Organisation anlegen</Heading>
        <Content>
          <Section>
            <Text>
              Eine Organisation kannst du dir wie ein
              Unternehmen vorstellen. An diesem Ort
              verwaltest du deine Mitarbeiter,
              Zahlungsmodalitäten und kannst deine
              Rechnungen einsehen.
            </Text>
            <TextField isRequired>
              <Label>Organisationsname</Label>
            </TextField>
          </Section>
        </Content>
        <ActionGroup>
          <Action closeModal>
            <Action onAction={sleepLong}>
              <Button color="success">
                Organisation anlegen
              </Button>
            </Action>
            <Button variant="soft" color="secondary">
              Abbrechen
            </Button>
          </Action>
        </ActionGroup>
      </Modal>
    </>
  );
}
```

---

# Kombiniere mit …

## React Hook Form

Weitere Details zur Formularlogik und -validierung findest du in der Component
[Form (React Hook Form)](/04-components/react-hook-form/form).

```tsx
import {
  Action,
  ActionGroup,
  Button,
  Content,
  Heading,
  Label,
  Modal,
  TextField,
  useModalController,
} from "@mittwald/flow-react-components";
import { useForm } from "react-hook-form";
import {
  Form,
  SubmitButton,
  typedField,
} from "@mittwald/flow-react-components/react-hook-form";

export default () => {
  const controller = useModalController();

  const form = useForm<{ name: string }>();

  const Field = typedField(form);

  const handleSubmit = async () => {
    /** ... submit logic */
    return () => {
      // Close after successful submission
      controller.close();
    };
  };

  return (
    <>
      <Button onPress={controller.open}>
        Modal öffnen
      </Button>

      <Modal controller={controller}>
        <Form form={form} onSubmit={handleSubmit}>
          <Heading>Organisation anlegen</Heading>

          <Content>
            <Field
              name="name"
              rules={{
                required: "Bitte gib einen Namen ein",
              }}
            >
              <TextField>
                <Label>Name</Label>
              </TextField>
            </Field>
          </Content>

          <ActionGroup>
            <SubmitButton>Speichern</SubmitButton>
            <Action closeModal>
              <Button color="secondary" variant="soft">
                Abbrechen
              </Button>
            </Action>
          </ActionGroup>
        </Form>
      </Modal>
    </>
  );
}
```

---

# Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"s" \| "m" \| "l"` | `"s"` | The size of the modal. |
| `offCanvas` | `boolean` | - | Whether the modal should be displayed as an off canvas. |
| `offCanvasOrientation` | `"left" \| "right"` | `"right"` | Whether the off canvas should be displayed on the right or left side of the screen. |
| `controller` | `OverlayController` | - | An overlay controller to control the modal state. |
| `slot` | `string` | - | Accepts "actionConfirm" to use the modal as a confirmation modal for an action. |
| `isDismissable` | `boolean` | - | Whether the modal can be closed by clicking outside of it. |
| `showCloseButton` | `boolean` | - | Whether the close button should be visible |
| `confirmOnClose` | `boolean` | - | Whether closing the modal must be confirmed – use it to protect unsaved changes. |
| `children` | `ReactNode` | - | - |
| `wrapWith` | `ReactElement<unknown, string \| JSXElementConstructor<any>>` | - | A React element the component is wrapped with. The element is cloned and receives the component as its only child — useful to render the component inside a link, a tooltip trigger or any other wrapper without changing the surrounding markup. |
| `ref` | `Ref<HTMLSpanElement>` | - | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see [React Docs](https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom) |
| `key` | `Key` | - | - |
| `className` | `string` | - | The elements class name. |
| `isOpen` | `boolean` | - | Whether the overlay is open. Use it to control the overlay state. |
| `isDefaultOpen` | `boolean` | `false` | Whether the overlay is open initially. Use it for an uncontrolled overlay. |

### Events

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `onOpenChange` | `OverlayOpenStateHandler` | - | Called with the new open state whenever the overlay is opened or closed. |
| `onClose` | `OverlayCloseHandler` | - | Called when the overlay is closed. |
| `onOpen` | `OverlayOpenHandler` | - | Called when the overlay is opened. |

