# Dock window

Content the person can float in a window over the page and bring back: a running agent's log, a call, a draft they want beside another page. Docked, the content is in place with a Float button. Floating, a short note in place says where it went, with Bring back, and the content is in a non-modal window (`role="dialog"`, no focus trap, the page still usable) that floats inside the page, over everything, through `LayerPortal`, so it works inside another library's dialog too.

The content is rendered once, through a portal into an element of its own, and that element is moved between the two places, so it is never remounted: a form keeps what was typed, a stream keeps its place, and its events reach React wherever it is. Where the browser has `moveBefore`, the move keeps an iframe's page loaded, both ways: as the window closes, the content is moved back before the window leaves the page. On the server and while hydrating, the content is drawn in place; it is mounted once more, into its own element, as hydration ends.

When the person floats the content or brings it back, or the app changes `docked`, focus moves to the window's title or back to Float in place. Content that starts floating takes no focus as the page loads.

The window moves by dragging its title bar, or with the arrow keys on its move handle (16 px a press, 64 px with Shift), and is kept inside the viewport. The arrows move it on the screen, so Left moves it left in a right-to-left page too. `locked` says why the content can't float now; Float stays focusable and says so.

- Registry name: `dock-window` (`@candor/dock-window`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/dock-window

## Install

```bash
npx candor-ui add dock-window
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/dock-window`.
It brings in: button, layer, locale, locked, motion, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { DockWindow } from "@/components/candor/dock-window"
import { TooltipProvider } from "@/components/candor/ui/tooltip"

const lines = ["Building storefront@418", "Installing dependencies (1.2s)", "Compiling 214 modules", "Uploading to Staging", "Running smoke tests"]

export default function Example() {
  const [shown, setShown] = React.useState(2)
  const [deploying, setDeploying] = React.useState(false)
  return (
    <TooltipProvider>
      <div className="flex flex-col gap-3">
        <label className="flex items-center gap-1.5 text-xs text-muted-foreground">
          <input type="checkbox" checked={deploying} onChange={(e) => setDeploying(e.target.checked)} />
          A deploy is starting
        </label>
        <div className="h-56 rounded-lg border p-3">
          {/* The log is rendered once: floated and brought back, it keeps its lines and the note typed into it. */}
          <DockWindow title="Deploy log" locked={deploying ? "The log can't float while a deploy is starting." : undefined}>
            <div className="flex flex-col gap-2 p-1 text-xs">
              <ol className="flex flex-col gap-0.5 font-mono">
                {lines.slice(0, shown).map((line) => (
                  <li key={line}>{line}</li>
                ))}
              </ol>
              <button type="button" className="self-start text-primary underline-offset-2 hover:underline" onClick={() => setShown((n) => Math.min(lines.length, n + 1))}>
                Show the next line
              </button>
              <input aria-label="Note on this deploy" placeholder="Note on this deploy" className="h-8 rounded-md border bg-card px-2 text-sm" />
            </div>
          </DockWindow>
        </div>
        <p className="text-xs text-muted-foreground">Floating, drag the window by its title bar, or focus its handle and use the arrow keys (Shift moves further).</p>
      </div>
    </TooltipProvider>
  )
}
```

## <DockWindow>

Props:

- `title: string`: What the content is ("Deploy log"): the window's name, and in the buttons' names.
- `docked?: boolean | undefined`: Whether the content is in place, when you control it. Leave it out and DockWindow keeps its own state.
- `defaultDocked?: boolean | undefined` (default `true`): Whether the content starts in place, when DockWindow keeps its state. Defaults to true.
- `onDockedChange?: ((docked: boolean) => void) | undefined`: Called when the person floats the content or brings it back.
- `placement?: "end-bottom" | "end-top" | "start-bottom" | "start-top" | undefined` (default `"end-bottom"`): The corner of the viewport where the window first floats, on the inline start or end. Defaults to "end-bottom".
- `locked?: string | undefined`: Why the content can't float now. Float stays focusable, marked `aria-disabled`, and says why.
- `className?: string | undefined`: Classes for the box in the page.
- `children: React.ReactNode`: The content, rendered once and kept whether it is in place or floating.

## Functions

### useDockMorph

```ts
useDockMorph(docked: boolean)
```

A FLIP move between an app's docked box and its floating one, for your own docking: put `sourceRef` on the box in the page, `targetRef` on the floating box, and `style` on both. When `docked` changes, the box that now shows starts where the other one was and slides to its own place, through the move family (`dock-window`'s `morph` moment). Under reduced motion, `data-motion="none"` or a brand's level 0 it jumps. DockWindow uses it.

## Strings (`dockWindow`)

The fixed words, English by default. Override any of them under `dockWindow` in `<LocaleProvider strings={{ dockWindow: { … } }}>`; the table is exported as `dockWindowStrings`.

- `float`: Float
- `floatTitle`: Float {title}
- `floating`: {title} is in a floating window
- `bringBack`: Bring back
- `bringBackTitle`: Bring back {title}
- `move`: Move {title}
- `moveHint`: Arrow keys move the window, Shift with an arrow moves it further.

## Motion

- **morph** (move): The window slides from the content's place in the page to where it floats, and back. Under reduced motion it jumps.

Set with `--cd-motion-dock-window-<moment>-*` in your brand file; the families are in the design reference.

## Terms used here

- **Calm level**: What Candor's motion does for a person whose operating system asks for reduced motion: the highest-precedence setting, above the app's own motion level (`none`, `calm`, `subtle` or `expressive`) and the brand file. A theme tunes how a change looks, never when it happens, so nothing depends on seeing the motion. (https://candor.themethodic.com/glossary.md#calm-level)

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
