<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# SnapPiP

A floating picture inside a container, such as the person's own camera in a call, that they drag and throw into a corner, where it comes to rest, as a phone's picture in picture does. Put it in a positioned container (`relative`); it moves within that container's box.

```tsx
import { SnapPiP } from "@/components/ouro/snap-pip/snap-pip";
```

## Example

```tsx
<div className="relative h-dvh">
  <SpeakerStage />
  <SnapPiP label="Your camera" corner={corner} onCornerChange={setCorner} keepOut={[toolbarRef]}>
    <video ref={selfView} autoPlay muted playsInline className="size-full -scale-x-100 object-cover" />
  </SnapPiP>
</div>
```

## Usage

It follows the pointer from the first pixel and lifts while dragged. On release it works out where the throw would coast to (`project`, at scrolling's deceleration of 0.998), picks the corner whose place, clear of the keep-outs, is nearest that point, and springs there from the release speed (response 0.3s; damping 0.8 after a flick, so it overshoots a little, and 1 after a slow drop). Pressing it in flight catches it. While a pointer holds it, a keep-out or the container changing waits for the release, so it never slides out from under the finger. Dragged past the container's edge, it resists more and more (`rubberband`).

The corners sit `inset` in from the edges and keep clear of `keepOut`. It keeps its corner, not its position: when the keep-outs change it follows its corner with the spring, and while the container resizes it stays in its corner without one, placed in the same frame as the resize. While it springs it carries `data-ouro-settling`.

It is focusable: Option and an arrow key move it to the next corner that way, with the same spring, and the new corner is announced politely once it settles, never during the move; a key toward the side it is already on says the corner again. Its name, corner and `hint` are what screen readers read. The hint also shows beside it, toward the middle of the stage and never over it, when it gets keyboard focus or the pointer rests on it, and stays while the pointer is on either. Escape, a move or a press puts it away until focus and the pointer have both left. Double-click it, or press Option-Return, to toggle small and large.

With reduced motion it jumps straight to its corner: no spring, no lift and no rubberband.

Its agent side, `snapPipAx` in `snap-pip-styles.ts`, declares no reads and one action, `move`, which only the person takes: where their own picture sits is theirs. Hiding the picture is the app's, from its own menu, so the app declares that `hide` action, not SnapPiP.

## Use instead when

- Use `Popover` or `HoverCard` instead for an overlay anchored to a control, `Resizable` for a split layout the person sizes, and `Sheet` for a side panel.

## Props

Type: `SnapPiPProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aspectRatio` | `number` | `1.6` | Width over height, such as a camera's: 1.6 (16:10), or 0.5625 (9:16) for a phone held upright. |
| `children` | `ReactNode` |  | The app's content, such as a `<video>` with `className="size-full object-cover"`, which the app renders itself. |
| `className` | `string` |  | Layout only. Appearance classes warn in development; style what you put inside it instead. |
| `corner` | `"top-left" \| "top-right" \| "bottom-left" \| "bottom-right"` |  | The corner it rests in, controlled. Pass `onCornerChange` too, or a throw comes back to this corner. |
| `cornerLabels` | `SnapPiPCornerLabels` | { "top-left": "Top left", "top-right": "Top right", "bottom-left": "Bottom left", "bottom-right": "Bottom right" } | The words for each corner, read after its name and announced when it settles in a new one. |
| `defaultCorner` | `"top-left" \| "top-right" \| "bottom-left" \| "bottom-right"` | `"bottom-right"` | The corner it starts in, uncontrolled. |
| `defaultSize` | `"small" \| "large"` | `"small"` | The size it starts at, uncontrolled. |
| `hint` | `string` | `"Option-arrow keys to move it"` | How to move it from the keyboard, read after its name and corner, and shown beside it as a tooltip. Translate it with the rest of your interface. |
| `id` | `string` |  | The picture's id. |
| `inset` | `number` | `16` | How far the corners sit in from the container's edges, and from a keep-out, in px. |
| `keepOut` | `readonly SnapPiPKeepOut[]` |  | What the corners keep clear of: rectangles in the container (`{ x, y, width, height }`, px from its top-left corner), or refs to elements, such as a toolbar's band, a captions band or another SnapPiP, measured whenever they or the container resize. A ref is read again after each render and whenever the container's content changes, so an element the app shows or takes away behind the same ref, such as a captions band, counts as soon as it comes or goes. A corner that would overlap one moves just clear of it, toward the middle. When they change, the picture follows its corner with the spring. |
| `label` (required) | `string` |  | What the picture is: "Your camera". It names it for screen readers, before its corner and the hint. |
| `onCornerChange` | `((corner: SnapPiPCorner) => void)` |  | Called with the new corner when the person throws or moves it there, so the app can remember it. |
| `onSizeChange` | `((size: SnapPiPSize) => void)` |  | Called with the new size when the person double-clicks it or presses Option-Return on it. |
| `ref` | `Ref<HTMLDivElement>` |  | Reaches the rendered element. |
| `size` | `"small" \| "large"` |  | Its size, controlled. Small and large widths follow the container's width: under 900px, 160 and 240; from 900px, a sixth of it (160 to 320) and twice that, up to a third; from 1800px, 288 and 480. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `avoid`: Not a prop. Ouro UI uses `keepOut`: `<SnapPiP keepOut={[toolbarRef]}>`.
- `offset`: Not a prop. Ouro UI uses `inset`: `<SnapPiP inset={16}>`.
- `position`: Not a prop. It rests in a corner: `<SnapPiP corner="bottom-right">`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`).

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `SnapPiPCorner`

A corner a `SnapPiP` rests in: the screen's corners, the same in either reading direction.

```ts
type SnapPiPCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
```

### `SnapPiPCornerLabels`

The words screen readers hear for a corner when the picture settles in it.

```ts
type SnapPiPCornerLabels = Record<SnapPiPCorner, string>;
```

### `SnapPiPKeepOut`

Something the corners keep clear of: a rectangle in the container, or a ref to an element whose box is measured.

```ts
type SnapPiPKeepOut = SnapPiPRect | RefObject<Element | null>;
```

### `SnapPiPRect`

A rectangle in the container, in px from its top-left corner (inside its border).

```ts
interface SnapPiPRect {
  x: number;
  y: number;
  width: number;
  height: number;
}
```

### `SnapPiPSize`

The sizes a `SnapPiP` toggles between.

```ts
type SnapPiPSize = "small" | "large";
```
