# CoachMark

Ein CoachMark macht auf neue Features oder verschobene Bedienelemente aufmerksam.

```tsx
import {
  Action,
  Button,
  CoachMark,
  Heading,
  Section,
  Text,
  useOverlayController,
} from "@mittwald/flow-react-components";
import { useRef } from "react";

export default () => {
  const anchor = useRef<HTMLButtonElement>(null);
  const controller = useOverlayController("CoachMark", {
    isDefaultOpen: true,
  });

  return (
    <Section style={{ paddingBlockEnd: 200 }}>
      <Button
        ref={anchor}
        onPress={() => controller.open()}
      >
        Domain verbinden
      </Button>

      {/* Pinned downwards so the hint stays inside this example's frame. */}
      <CoachMark
        shouldFlip={false}
        anchorRef={anchor}
        controller={controller}
      >
        <Heading>Neu: Domain verbinden</Heading>
        <Text>
          Du kannst deine Domain jetzt direkt hier verbinden
          – ohne den Umweg über die Domain-Übersicht.
        </Text>
        <Action closeOverlay="CoachMark">
          <Button>Verstanden</Button>
        </Action>
      </CoachMark>
    </Section>
  );
}
```

---

# Best Practices

- Zeige höchstens ein CoachMark pro Seite. Mehrere konkurrieren um dieselbe
  Aufmerksamkeit und heben sich gegenseitig auf.
- Beschränke den Inhalt auf einen Gedanken. Benenne, was neu ist und welchen
  Vorteil es bringt.
- Blende ein bestätigtes CoachMark dauerhaft aus. Ein Hinweis, der bei jedem
  Aufruf erneut erscheint, kann als störend wahrgenommen werden.
- Platziere unerlässliche Informationen direkt in der Oberfläche. Ein CoachMark
  sollte nicht die einzige Quelle für wichtige Informationen sein.
- Platziere das CoachMark direkt hinter seinem Ankerelement. So wird es in der
  Vorlesereihenfolge an der passenden Stelle erreicht. Steht keine Referenz zur
  Verfügung, gib den Anker als `id` an.

## CoachMark vs. ContextualHelp

Verwende ein CoachMark, um den User proaktiv auf ein neues oder verschobenes
Feature aufmerksam zu machen. Für weiterführende Informationen ist ein
[ContextualHelp](https://flow.mittwald.de/components/overlays/contextual-help) die bessere Wahl, da der
User selbst entscheiden kann, ob er diese Information braucht.

**Verwende ein CoachMark, um z. B. ...**

- auf eine neue oder verschobene Funktion hinzuweisen.
- eine Funktion zu zeigen, die leicht zu übersehen ist.
- einen Hinweis zu geben, ohne die Arbeit zu unterbrechen.

**Verwende ein ContextualHelp, um z. B. ...**

- einen Fachbegriff zu erklären.
- Hintergrund zu einem Formularfeld anzubieten.
- eine Einstellung mit ihren Folgen zu beschreiben.

---

# Anker

Ein CoachMark zeigt auf ein Element, nicht auf die Seite. Einen Trigger gibt es
nicht, da das CoachMark nicht vom User geöffnet wird. Stattdessen wird sein
Ankerelement auf einem von zwei Wegen angegeben:

- `anchorRef` ist die Referenz auf das Element. Das ist der Normalfall in einer
  React-Anwendung.
- `anchor` ist die `id` des Elements, nachgeschlagen beim Öffnen. Sie hilft
  dort, wo sich keine Referenz teilen lässt.

Das CoachMark wird direkt hinter seinem Ankerelement im DOM gerendert und folgt
diesem in der Vorlesereihenfolge. Über `aria-details` verweist der Anker
zusätzlich auf das CoachMark. Es wird nicht automatisch angekündigt, sondern
beim Lesen erreicht.

Das CoachMark ist absolut positioniert und benötigt keinen eigenen Platz.
`overflow: hidden` oder ein eigener Stacking Context eines Vorfahren kann es
abschneiden.

Beim Scrollen folgt das CoachMark seinem Anker und bleibt geöffnet.

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

export default () => (
  <Section style={{ paddingBlockEnd: 200 }}>
    <Button id="backup-button">Backup erstellen</Button>

    {/* Pinned downwards so the hint stays inside this example's frame. */}
    <CoachMark
      shouldFlip={false}
      anchor="backup-button"
      isDefaultOpen
    >
      <Heading>Neu: Backups planen</Heading>
      <Text>
        Lege fest, wann ein Backup automatisch erstellt
        wird.
      </Text>
      <Action closeOverlay="CoachMark">
        <Button>Verstanden</Button>
      </Action>
    </CoachMark>
  </Section>
)
```

---

# Schließen

Den Button zum Schließen bringt das CoachMark nicht selbst mit, er wird
komponiert: Ein [Action](https://flow.mittwald.de/components/actions/action) mit `closeOverlay` um einen
[Button](https://flow.mittwald.de/components/actions/button) schließt das CoachMark. Größe und
Platzierung gibt das CoachMark dem Button vor. Escape schließt es ebenfalls.

`onOpenChange` meldet jede Änderung, unabhängig davon, welcher Weg sie ausgelöst
hat. Das ist der Platz, um die Bestätigung zu speichern; beim nächsten Aufruf
unterdrückt `isDefaultOpen={false}` den Hinweis dann.

```tsx
import {
  Action,
  Button,
  CoachMark,
  Heading,
  Section,
  Text,
  useOverlayController,
} from "@mittwald/flow-react-components";
import { useRef } from "react";

export default () => {
  const anchor = useRef<HTMLButtonElement>(null);
  const controller = useOverlayController("CoachMark", {
    isDefaultOpen: true,
  });

  return (
    <Section style={{ paddingBlockEnd: 200 }}>
      <Button
        ref={anchor}
        onPress={() => controller.open()}
      >
        Backup erstellen
      </Button>

      {/* Pinned downwards so the hint stays inside this example's frame. */}
      <CoachMark
        shouldFlip={false}
        anchorRef={anchor}
        controller={controller}
      >
        <Heading>Neu: Backups planen</Heading>
        <Text>
          Lege fest, wann ein Backup automatisch erstellt
          wird.
        </Text>
        <Action closeOverlay="CoachMark">
          <Button>Ausprobieren</Button>
        </Action>
      </CoachMark>
    </Section>
  );
}
```

---

# Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `anchor` | `string` | - | The `id` of the element the coach mark points at, looked up once the element exists. Use it where a ref cannot be shared — an mStudio extension renders in a different context than the host, so a ref never arrives, while an id does. |
| `anchorRef` | `RefObject<Element \| null>` | - | The element the coach mark points at. It is an anchor, not a trigger — nothing about it opens the coach mark. Give it either this or `anchor`. |
| `arrowBoundaryOffset` | `number` | `0` | The minimum distance the arrow's edge should be from the edge of the overlay element. |
| `arrowRef` | `RefObject<Element \| null>` | - | A ref for the popover arrow element. |
| `boundaryElement` | `Element` | `document.body` | Element that that serves as the positioning boundary. |
| `children` | `ReactNode` | - | - |
| `className` | `ClassNameOrFunction<PopoverRenderProps>` | `'react-aria-Popover'` | The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state. |
| `containerPadding` | `number` | `12` | The placement padding that should be applied between the element and its surrounding container. |
| `controller` | `OverlayController` | - | An overlay controller to control the popover state. |
| `crossOffset` | `number` | `0` | The additional offset applied along the cross axis between the element and its anchor element. |
| `dir` | `string` | - | - |
| `hidden` | `boolean` | - | - |
| `inert` | `boolean` | - | - |
| `isDefaultOpen` | `boolean` | `false` | Whether the popover is open initially. Use it for an uncontrolled popover. |
| `isEntering` | `boolean` | - | Whether the popover is currently performing an entry animation. |
| `isExiting` | `boolean` | - | Whether the popover is currently performing an exit animation. |
| `isOpen` | `boolean` | - | Whether the popover is open. Use it to control the popover state – then `onOpenChange` must update the state this value comes from. |
| `lang` | `string` | - | - |
| `offset` | `number` | `8` | The additional offset applied along the main axis between the element and its anchor element. |
| `placement` | `"bottom" \| "bottom left" \| "bottom right" \| "bottom start" \| "bottom end" \| "top" \| "top left" \| "top right" \| "top start" \| "top end" \| "left" \| "left top" \| "left bottom" \| "start" \| "start top" \| "start bottom" \| "right" \| "right top" \| "right bottom" \| "end" \| "end top" \| "end bottom"` | `'bottom'` | The placement of the element with respect to its anchor element. |
| `render` | `DOMRenderFunction<"div", TooltipRenderProps>` | - | Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components. Requirements: - You must render the expected element type (e.g. if `<button>` is expected, you cannot render an `<a>`). - Only a single root DOM element can be rendered (no fragments). - You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate. |
| `scrollRef` | `RefObject<Element \| null>` | `overlayRef` | A ref for the scrollable region within the overlay. |
| `shouldFlip` | `boolean` | `true` | Whether the element should flip its orientation (e.g. top to bottom or left to right) when there is insufficient room for it to render completely. |
| `shouldSkipAnimation` | `boolean` | - | Whether the popover should appear and disappear without an entry or exit animation. This is used by components such as PreviewTrigger to skip animations when quickly swapping between overlays. |
| `shouldUpdatePosition` | `boolean` | `true` | Whether the overlay should update its position automatically. |
| `slot` | `string` | - | A slot name for the component. Slots allow the component to receive props from a parent component. An explicit `null` value indicates that the local props completely override all props received from a parent. |
| `style` | `StyleOrFunction<TooltipRenderProps>` | - | The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state. |
| `translate` | `"yes" \| "no"` | - | - |
| `trigger` | `string` | - | The name of the component that triggered the popover. This is reflected on the element as the `data-trigger` attribute, and can be used to provide specific styles for the popover depending on which element triggered it. |
| `width` | `string \| number` | - | A fixed width for the popover. |
| `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. `null` takes a wrapper a surrounding props context set back off, the way `tunnel: null` does for a tunnel. |

### Events

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `onBlurWithin` | `((e: FocusEvent<Element, Element>) => void)` | - | Handler that is called when the target element and all descendants lose focus. |
| `onClick` | `MouseEventHandler<HTMLDivElement>` | - | - |
| `onFocusWithin` | `((e: FocusEvent<Element, Element>) => void)` | - | Handler that is called when the target element or a descendant receives focus. |
| `onFocusWithinChange` | `((isFocusWithin: boolean) => void)` | - | Handler that is called when the the focus within state changes. |
| `onOpenChange` | `((isOpen: boolean) => void)` | - | Called with the new open state whenever the popover is opened or closed – on every path, including a close triggered through the controller. It only reports the change; it never performs or suppresses it. |

### Accessibility

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-describedby` | `string` | - | Identifies the element (or elements) that describes the object. |
| `aria-details` | `string` | - | Identifies the element (or elements) that provide a detailed, extended description for the object. |
| `aria-label` | `string` | - | Defines a string value that labels the current element. |
| `aria-labelledby` | `string` | - | Identifies the element (or elements) that labels the current element. |

