Axon 911 Frontend Docs
    Loading navigation…
U
User
Switch to light mode
  1. Blocks
  2. Call Dock
  3. Usage

Call Dock

Usage

Import @prepared911/ui/blocks/call-dock from its subpath. Load @prepared911/ui/styles.css once at the document root. In a Module Federation shell that is the shell's one stylesheet import, and a remote imports no stylesheet. CallDock is the tray of the one active call, in a bar centered on the block-end edge of its shell. It is an inverted surface, light on a dark page and dark on a light page. The shell owns the frame and decides when it is there; a module (in the product, a module-federation remote) fills it. It slides in and out as a call starts and ends. There is no queue, and nothing in the API can express a minimized call or panel.
Shell usage
  • open is the whole presence rule. Derive it from call-taking's call-taking.call-plane runtime store, read through the stores domain: activeCallId is a string while a call is active. The tray shows while open is true (including while the module is still loading or showing a failure state), and renders nothing at all while it is false: no landmark, no reserved space, and no child mounted.
  • RemoteComponent takes id, loading and children; every other prop is forwarded to the remote. There is no errorFallback: the mount's error boundary is built in (see Loading and failure). Pass CallDockSkeleton as loading.
  • id is typed against mfe-core's ID_REGISTRY, so an unregistered id does not compile. The dock content is its own component view of the call-taking remote, call-taking/dock-view, which renders only the parts; its owners expose it with the add-module skill before a shell mounts it. Do not mount call-taking/view in the tray: it is the dashboard panel, a full surface rather than the dock parts.
  • The block needs no provider, context, portal root or store of its own. Beyond the stylesheet, the shell supplies data-theme on an ancestor (the document element, as every shell's applyInitialAppearance writes it), which the inverted region reads; without one the page counts as light.
  • Keep the element mounted and let open go false when the call ends; do not wrap it in call ? <CallDock … /> : null, which removes it without its exit.
  • Mount it in the shell's dock slot, after the scroll region in a column. It sits in normal flow, so the region above gives up exactly the space the tray takes and is never covered. It is always centered on the inline axis, so it mirrors under dir="rtl" with no prop.
  • label is the landmark's name and stays the shell's, from its own formatMessage. Whoever renders CallDockError imports its glyph (@prepared911/ui/icons/error-circle); a block cannot import glyph modules.
open turning true enters the tray from beyond the block-end edge on the glide spring with a short fade. open turning false makes the tray inert at once, holds its size, slides it back beyond the edge on the glide exit timing, and removes it from the tree after the exit. Opening again during the exit cancels it and releases the held size. A tray present on first render, or on the server, is at rest: nothing animates on first paint. Under reduced motion, from the operating system or from data-motion="reduced" on the html element, the slide and the width morph collapse to instant and only the fade remains. Size follows content. When the call-state composition changes width (for example Incoming to Active), the tray's inline size springs on --motion-spring-morph when growing and --motion-spring-smooth when shrinking, through MorphWidth and the registered --ds-content-inline-size variable. Under reduced motion the width snaps with the motion tokens.
Remote authors
A remote renders the parts from one mount, and only the parts it has:
  • Render CallDockDetails and CallDockControls anywhere under the mount. The tray lays them out the same behind any number of display: contents wrappers (the mount wrapper, Suspense and error boundaries add none of their own), so the remote never needs to know what sits above it.
  • A remote with only controls renders only CallDockControls; with only details, only CallDockDetails. The vertical rule leads the controls while the tray also holds details, and is not drawn otherwise.
  • Keep rendering the last call until unmount. When the call ends the shell closes the tray, and the tray plays its exit with whatever the remote still renders. Rendering nothing the moment activeCallId goes null makes the controls vanish while the tray slides out. The tray holds its size for the exit, so an emptied remote cannot collapse it, but it still leaves empty. Hold the last call's data until the remote unmounts.
  • Do not read the open state or render the tray yourself; read the call from the call-plane store through the stores domain.
  • Use the ordinary text tiers inside the tray (Foreground.Default, Subtle, Muted). The content is a theme region in the opposite appearance of the page, so Button, Text, Icon, Counter and Badge resolve their ordinary tokens for the inverted surface. The Transient* foregrounds name the page's inverted values and would resolve to the wrong appearance inside the region.
Call states
The block has no mode for a call state (CONTRACT D42). Idle, Calling, Incoming, Active (with Muted) and On Hold are compositions a module renders into the same two slots, from one description of the call that the module owns. The dock never changes state itself and never counts time: every transition and every timer string comes from the module. The reference composition is CallDockCallView in stories/fixtures.tsx, built only from shipped parts and typed props, with no className or style. call-dock-call-states.test.tsx, the Interactions and Composition stories and the browser test pass it changing state the way a remote would, and assert every documented behavior ([A1] to [A18] in CONTRACT.md).
StateDetails partControls part
IdlenoneA Field with type={FieldType.Tel} (InputSize.ExtraLarge, labelHidden, placeholder copy) and Place Call as direct children of CallDockControls. Place Call is disabled until the mask reports dialable digits and is ButtonIntent.Accent solid once enabled; Enter submits. onPlaceCall receives E.164 when the mask resolves it, otherwise raw digits.
CallingnoneThe 16px phone glyph, "Calling" and the number, the caller line, End Call.
IncomingnoneThe 16px phone glyph, the number and caller line, Answer with the ring timer, and a circular back-to-queue button whose tooltip reads "Back to queue".
ActiveA pill of the number and the call timer, the caller line under itMute, Link, Hold, Park, Transfer in InlineSize.ControlColumn columns, the more menu, and End Call as direct CallDockControls children (--space-12 group gap only).
MutedAs ActiveMute becomes Unmute: an accent solid circle with the mic-off glyph.
On HoldAccent pill with 24px between the number and a 12px timer-plus-chip groupResume, Park, Transfer, the more menu, End Call; same fixed columns and group gap as Active.
Transitions, owned by the module (the telephony side causes the ones marked "plane"):
  • Idle to Calling: Place Call with a non-empty number. Calling to Idle: End Call. Calling to Active: the callee answers (plane).
  • Idle to Incoming: a call rings (plane). Incoming to Active: Answer. Incoming to Idle: the back-to-queue button.
  • Active to Muted and back: Mute and Unmute. Active to On Hold: Hold; On Hold to Active: Resume. A mute set before Hold is kept through Resume.
  • End Call from Active, Muted or On Hold returns to Idle.
Wiring a remote
A remote maps what it owns onto the plain description below and renders the parts for that state. Nothing about the call lives in the dock. Against call-taking's call-plane store (call-taking.call-plane), state.call carries the phase and the other party, state.canDial says whether Place Call can work, and the commands are dial, answer, decline, hangUp, hold, mute and park:
  • Render only the parts of the current state: Idle, Calling and Incoming have no details, so no rule is drawn; Active and On Hold render both.
  • Format the timers in the remote (mm:ss from clockStartedAt and a hold start) and pass them as display text. A timer is a monospace Text (TextType.Code2), muted in the pill and TextType.Code1 inside the Answer button.
  • Keep the same CallDock mounted across states: the tray is one region for the whole call, so a state change never re-enters the tray and never moves focus.
  • Disable what the module cannot do. call-taking's state.actions says which verbs are allowed now (for example answer is false while the queue auto-answers); pass disabled to the matching Button.
  • Back to queue: the Figma sends the ringing call back to the queue and returns the dock to Idle. call-taking's closest command is decline; its owners decide whether that is the same thing (open question in CONTRACT.md).
  • Idle has to be visible while no call exists, so the shell keeps open true while the plane can be dialed instead of deriving it from activeCallId alone (open question in CONTRACT.md).
Icons
Every control uses the glyph the Figma component draws: call (Place Call, Answer), phone (the 16px glyph on Calling and Incoming), mic and mic-off (Mute, Unmute), paper-plane (Link), hold (Hold), circle-parking (Park), call-made (Transfer), call-received (Resume), arrow-turn-left (back to queue) and more-vertical (the menu). hold is a custom glyph made from the Figma vector (two vertical bars at a 1.5 stroke), because the published icon catalog has no such icon; a glyph module is imported by the module that renders the control, never by the block.
Typed props the compositions use
ButtonSize.ExtraLarge and InputSize.ExtraLarge (the 40px control tier), ButtonShape.Pill (the circular buttons), BlockSize.ExtraLargeControl (the pill's height) and Background.AccentTint (the hold pill's wash) fill the gaps the Call Dock needs. No composition uses className or style.
Loading and failure
CallDockSkeleton is the loading content: placeholders at the tray's block size (one row at the control height), hidden from assistive technology, pulsing like every Skeleton and static under reduced motion. Pass it as the loading of the mount. CallDockError is the failure content while a call is active: a glyph, a short message and, with onRetry and retryLabel, a Retry Button. The glyph and message form one polite status region, announced once when the state appears; Retry is a tab stop of its own. Without onRetry there is no button.
  • The remote failed to load or render. RemoteComponent's built-in boundary shows the platform's shared remote fallback, with its own retry, inside the tray. The tray stays, because open is still true. The shell passes nothing for this case.
  • The remote is loaded and sees a failure, such as the call plane reporting disconnected or a store read that errors. The remote renders CallDockError in place of the parts, with its own copy and retry, and imports the glyph once (@prepared911/ui/icons/error-circle).
Popovers from dock controls
A Tooltip or Menu opened from a dock control portals outside the dock, so it renders in the page appearance, not the inverted one. Tooltips are dark in either appearance. This is by design; there is no prop to carry the dock's appearance into a portal.
Details and controls
Both wrap inside the tray at narrow widths instead of scrolling sideways. Button, Text and the other children keep their own sizes: controls stay on the control-height scale.
Previous
Call Card / Accessibility
Next
Call Dock / API and Development
On this page
Shell usage
Remote authors
Call states
Wiring a remote
Icons
Typed props the compositions use
Loading and failure
Popovers from dock controls
Details and controls