TerraUI v0.1.1

OverlayHost

The one layer where every Dialog, Menu and toast appears, above native sheets. Mount it once at the root; open overlays from anywhere.

Usage

Mount one OverlayHost at the root of your app, after your navigator and inside ThemeProvider:

import { OverlayHost, ThemeProvider, type Theme } from "@terratcg/ui";
import { useSafeAreaInsets } from "react-native-safe-area-context";

export function Root({ theme }: { theme: Theme }) {
  const insets = useSafeAreaInsets();
  return (
    <ThemeProvider theme={theme}>
      <Navigator />
      <OverlayHost
        failureText="That didn't go through. Try again."
        insets={insets}
        toastInsetTop={insets.top}
      />
    </ThemeProvider>
  );
}

Then open overlays from anywhere, like Alert.alert, with no state or component of your own:

const left = await showDialog({
  title: "Leave the call?",
  body: "The others stay connected.",
  confirm: "Leave",
  cancel: "Stay",
  tone: "destructive",
});
if (left) navigation.goBack();

showDialog, showMenu, showToast and showTopToast queue their overlay for the host. Each component's page covers its options.

Props

Prop

Type

What the host does

  • Above everything: Dialogs and Menus are drawn in a FullWindowOverlay on iOS and a transparent Modal on Android, so they cover native sheets and headers. The layer is mounted only while something shows.
  • One at a time: Dialogs and Menus wait their turn. Opening a Dialog dismisses the keyboard.
  • Toasts: one queue at the top of the window, one toast at a time. Toasts wait while a Dialog or Menu is up, or while holdToasts is set; one already in view steps aside and comes back with its whole time.
  • Failures: when a Dialog's onConfirm rejects, the Dialog stays open and shows failureText, a sentence in the user's language. The error's own message is written for developers and never shown. onFailure hears every rejection, where you report it, and may return a sentence to show instead.
  • Theme and feedback: overlays wear the surrounding theme, or the host's theme when the screen in front wears another one. Their keys report to the surrounding feedback handler.
  • Knowing one is up: useOverlayUp() is true while a Dialog or Menu is up anywhere, the same signal that holds toasts. Use it to wait before showing something of your own, such as a system prompt.
  • Closing everything: dismissOverlays() closes every open and queued overlay as if the user had backed out. Call it when a session ends or a screen that owns a pending action goes away.

Accessibility

  • Focus: screen-reader focus moves to a Dialog's or Menu's title when it opens. Pass the control that opened it as trigger, and focus returns there when it closes.
  • Back: Android Back cancels the Dialog or closes the Menu in front.
  • Toasts: announced once when they first show. They never take screen-reader focus or block touches around their card.

Platform differences

  • iOS: the layer is a FullWindowOverlay from react-native-screens. In a native sheet, wrap the sheet's content in SheetOrigin so a Menu is placed next to its trigger: iOS measures inside a sheet from the sheet's top.
  • Android: the layer is a transparent Modal that brings Back and focus with it. SheetOrigin is a plain view there.
  • Tests: in Jest, mock FullWindowOverlay from react-native-screens with a plain View, since the test renderer has no native layer.

Rules

  • Mount exactly one OverlayHost, at the root, inside ThemeProvider and after the navigator.
  • Never present a Modal or a navigator from inside an overlay.
  • Navigate after showDialog resolves, not inside onConfirm: a screen presented while the Dialog is up lands above it.
  • Write failureText in the user's language, as a neutral sentence.
  • Pass the safe area through insets and toastInsetTop. TerraUI has no safe area of its own.

On this page