# LoadingOverlay

**When to use:** Covering content while it reloads: a table refreshing, a form saving, a panel fetching its next page. It frosts over what it wraps and keeps its shape in view. For content not there yet a [Skeleton.md](Skeleton.md) in its shape reads better, and a lone indicator with nothing to cover is a [Loader.md](Loader.md).

**Keywords:** spinner, frosted glass, scrim, dim, disable interaction, pending, mask

**Import:** `import { LoadingOverlay, Loader, LoaderType } from '@ahrowe/ui'`  
**Types:** `import type { LoadingOverlayProps } from '@ahrowe/ui'`

```tsx
import { LoadingOverlay, Loader, LoaderType, AnimatedLogo, ThemeColor } from '@ahrowe/ui';

// Wrap what is loading. It needs no positioned parent: it is one
<LoadingOverlay isLoading={isFetching}>
  <InvoiceTable rows={rows} />
</LoadingOverlay>

// A message under the loader, with a highlight sweeping across it
<LoadingOverlay isLoading={isSaving} message="Saving changes…">
  <ProfileForm />
</LoadingOverlay>

// Any loader in place of the default one
<LoadingOverlay
  isLoading={isConnecting}
  message="Connecting to the printer…"
  loader={<Loader type={LoaderType.Pulse} color={ThemeColor.Success} size="3.5em" />}
>
  <PrinterPanel />
</LoadingOverlay>

// A branded one
<LoadingOverlay isLoading={isBooting} loader={<AnimatedLogo size="4em" />}>
  <Dashboard />
</LoadingOverlay>
```

## Timing

The overlay waits `delay` (200ms) after `isLoading` turns true before it shows, so a load that
finishes faster never flashes it. Once shown it stays at least 400ms, so one that finishes just
after it appeared does not flash it either, then fades out. Loading again while it fades brings it
straight back. `delay={0}` shows it at once, from the first render on.

The content is marked `aria-busy` the moment `isLoading` is true, delay or not.

## What it blocks

The backdrop takes every pointer event over the content and shows a `progress` cursor. It does not
take the focus or block the keyboard: making the content `inert` would drop the focus of whoever
pressed the button that started the load. An action that must not run twice should still check its
own loading state.

**Wrapping:** the root is a `div` with `position: relative`, and the content sits in a `div` of its
own, so anything inside with a `z-index` (a sticky header) stays under the backdrop. The backdrop
takes the root's `border-radius`, so a `className` that rounds the root rounds the overlay too.
Content taller than the screen keeps the loader in view rather than at its middle.

**Scrolling content:** give the scrolling to `classNames.content` (its `overflow` and its height
limit), not to the root. The backdrop covers the root's box, so in a root that scrolls it would
scroll away with the content and leave the rest uncovered.

**Theme variables:** `--loading-overlay-background` (the veil, default `--background` at 50%) and
`--loading-overlay-blur` (default `4px`, `0px` turns the blur off). See
[ThemeProvider.md](ThemeProvider.md).

**App-wide:** `LoadingOverlay` takes `defaultProps` like any other component, so one
`AhroweProvider` can give every overlay the same `loader` or `delay`. Its default loader is a
`Loader`, which follows `defaultProps.Loader` as well.

**Key props:**

| Prop | Type | Description |
|------|------|-------------|
| `isLoading` | `boolean` | Covers the content, blocks the pointer and marks it `aria-busy` (default `false`) |
| `children` | `ReactNode` | What the overlay covers |
| `message` | `ReactNode` | Text under the loader. Also the default loader's accessible name |
| `loader` | `ReactNode` | Shown in place of the default `<Loader />` |
| `delay` | `number` | Milliseconds before it shows (default `200`) |
| `className` / `style` | | Root `div` |

**Note:** under `prefers-reduced-motion` the panel fades in without rising and the message stands
still. The loader itself keeps moving, see [Loader.md](Loader.md).

**Slots:** `content` (the `div` around `children`) `backdrop` (the veil over it) `panel` (the column holding the loader and the message, for a card behind them) `message`
