Window
Window is the draggable, resizable panel shell that WindowManager places on its slot grid. Use it directly when you build your own window layout; for the standard nine-slot HUD, use WindowManager.
import { Window } from 'jarvis-react-ui';
Basic
A Window is absolutely positioned at position (x, y, w, h in pixels) inside its nearest
positioned ancestor. The header shows ix, title and badge, and itemRenderer renders the
body.
import { Window } from 'jarvis-react-ui';
export default function Basic() {
return (
<div className="relative h-[200px] w-[320px]">
<Window
id="system"
title="System"
ix="◈"
badge="LIVE"
position={{ x: 0, y: 0, w: 320, h: 200 }}
draggable={false}
itemRenderer={() => 'CPU 42% · RAM 61% · NET 12 MB/s'}
/>
</div>
);
}
Compact and expanded
Every window has a mode:
compactis the docked state. Resize handles are hidden, and the header shows an Undock button when you passonModeToggle.expandedis the free-floating state. The window gets an accent hairline and glow, the eight edge and corner handles become active (whenresizable), and the header shows a Dock button.
Window does not switch modes itself. Clicking the dock/undock button or double-clicking the header
calls onModeToggle, and you set the new mode. itemRenderer receives { mode, focused, dragging }, so the body can show a compact summary while docked and more detail while expanded.
WindowManager uses the modes like this: a compact window lives in a slot, and dragging its header moves it to another slot or swaps it with the window there, with SnapOverlay and SwapOverlay as feedback. An expanded window floats above the grid at a larger size, can be dragged anywhere and resized, and the reset button docks it back into its home slot.
Dragging and resizing
The window never moves itself: position is controlled. onDragMove and onResizeMove report the
pointer offset from the start of the gesture (dx, dy), and onResizeMove also reports the
handle direction (n, se, ...). Remember the rect when the gesture starts and apply the offset
to it. onDragStart and onResizeStart receive the native pointerdown PointerEvent, like the
move and end callbacks. Drag the header below, undock the window, then drag its edges.
import { useRef, useState } from 'react';
import { Window } from 'jarvis-react-ui';
import type { PanelMode, ResizeDir } from 'jarvis-react-ui';
type Rect = { x: number; y: number; w: number; h: number };
const START: Rect = { x: 16, y: 16, w: 300, h: 150 };
function resize(r: Rect, dir: ResizeDir, dx: number, dy: number): Rect {
const next = { ...r };
if (dir.includes('e')) next.w = Math.max(200, r.w + dx);
if (dir.includes('s')) next.h = Math.max(100, r.h + dy);
if (dir.includes('w')) next.w = Math.max(200, r.w - dx);
if (dir.includes('n')) next.h = Math.max(100, r.h - dy);
if (dir.includes('w')) next.x = r.x + r.w - next.w;
if (dir.includes('n')) next.y = r.y + r.h - next.h;
return next;
}
export default function DragAndResize() {
const [rect, setRect] = useState<Rect>(START);
const [mode, setMode] = useState<PanelMode>('compact');
const origin = useRef(rect);
return (
<div className="relative h-[340px] w-full">
<Window
id="notes"
title="Notes"
ix="▸"
position={rect}
mode={mode}
onDragStart={() => {
origin.current = rect;
}}
onDragMove={(_id, dx, dy) =>
setRect({
...origin.current,
x: origin.current.x + dx,
y: origin.current.y + dy,
})
}
onResizeStart={() => {
origin.current = rect;
}}
onResizeMove={(_id, dir, dx, dy) => setRect(resize(origin.current, dir, dx, dy))}
onModeToggle={() => setMode((m) => (m === 'compact' ? 'expanded' : 'compact'))}
onReset={() => {
setRect(START);
setMode('compact');
}}
itemRenderer={({ mode: m }) =>
m === 'compact'
? 'Drag the header. Undock to resize.'
: 'Expanded: drag the edges and corners to resize.'
}
/>
</div>
);
}
During a gesture the root element gets data-state="dragging" or "resizing". The other values of
state (snap-preview, swap-preview, settling, focused) are styling hooks for the parent:
settling animates position and size changes, and focused raises the window above its siblings.
Position and size reach the stylesheet as the CSS variables --lib-window-x, --lib-window-y,
--lib-window-w and --lib-window-h on the root element.
To build drag-and-drop between slots, combine Window with useSlotDrag and
the slot grid helpers.
Header actions
The reset, dock/undock and close buttons are only rendered when you pass onReset, onModeToggle
or onClose. Pressing them never starts a drag. stackIndex raises an expanded window above
others with a lower index; WindowManager sets it for you.
import { useState } from 'react';
import { Button, Window } from 'jarvis-react-ui';
export default function HeaderActions() {
const [open, setOpen] = useState(true);
const [resets, setResets] = useState(0);
if (!open) return <Button onClick={() => setOpen(true)}>Reopen window</Button>;
return (
<div className="relative h-[180px] w-[320px]">
<Window
id="agenda"
title="Agenda"
ix="▦"
position={{ x: 0, y: 0, w: 320, h: 180 }}
draggable={false}
onReset={() => setResets((n) => n + 1)}
onClose={() => setOpen(false)}
itemRenderer={() => `09:30 · Standup — reset ${resets}×`}
/>
</div>
);
}
Sound
Through the nearest SfxProvider:
- Dragging the header plays
drag_start, loopsdrag_moveand playsdrag_end. - Resizing loops
resizeuntil the pointer is released. - The reset button plays
clickandrecall; the dock/undock button playsclickplusexpand(undock) orcollapse(dock); the close button playsclickandmenu_close. - Double-clicking the header plays the same
expandorcollapsesound as the dock/undock button (without theclick). - Hovering the window plays
hover_panel; hovering a header button playshover_button.
See Sound effects.
Accessibility
- The root is a non-modal
role="region"landmark.aria-labelledbypoints at the title element, so anytitle(string or element) names it; without a title the region is unnamed. - Window does not move or trap focus; Tab moves through its content in document order like any other part of the page.
- The header buttons are native buttons named Reset window, Undock window / Dock window and Close window, so they work with the keyboard. The dock/undock button is the keyboard equivalent of the header double-click.
- With
closeOnEscapeandonClose, the window is focusable (tabIndex={-1}) and Escape callsonClosewhile focus is inside it. - Dragging and resizing are pointer-only: there are no arrow-key or other keyboard shortcuts to move or resize a window. Offer another way to move or resize content when keyboard users need it.
API
| Prop | Type | Default | Description |
|---|---|---|---|
id (required) |
string |
Unique window id; passed back as the first argument of every callback. | |
itemRenderer (required) |
(props: PanelContentRenderProps) => ReactNode |
Renders the window body; receives the current mode, focused and dragging. |
|
position (required) |
{ x: number; y: number; w: number; h: number; } |
Position and size in pixels, relative to the nearest positioned ancestor. The window does not move itself: apply the deltas from onDragMove / onResizeMove to this rect. |
|
badge |
ReactNode |
Tag shown at the right end of the header. | |
className |
string |
Extra classes for the root <div role="region"> element. |
|
closeOnEscape |
boolean |
false |
Calls onClose when Escape is pressed while focus is inside the window. |
draggable |
boolean |
true |
Whether the header can be dragged. |
focused |
boolean |
false |
Whether the window is focused; highlights the panel chrome. |
ix |
ReactNode |
Index glyph shown before the title in the header. | |
mode |
'compact' | 'expanded' |
'compact' |
compact hides the resize handles and shows the undock button, expanded shows the handles and the dock button. |
onClose |
((id: string) => void) |
Shows a close button in the header and is called when it is clicked. Closing plays the menu_close sound. |
|
onDragEnd |
((id: string, e: PointerEvent) => void) |
Called when the header drag ends (pointer up or cancel). | |
onDragMove |
((id: string, dx: number, dy: number, e: PointerEvent) => void) |
Called on every pointer move of a header drag with the offset from the drag start. | |
onDragStart |
((id: string, e: PointerEvent) => void) |
Called with the native pointerdown event when a header drag starts (not on the header buttons). |
|
onFocus |
((id: string) => void) |
Called with the id on any pointer down inside the window. | |
onModeToggle |
((id: string) => void) |
Shows a dock/undock button in the header; called when it is clicked or the header is double-clicked. Both play the expand / collapse sound. |
|
onReset |
((id: string) => void) |
Shows a reset button in the header and is called when it is clicked. | |
onResizeEnd |
((id: string, e: PointerEvent) => void) |
Called when the resize ends. | |
onResizeMove |
((id: string, dir: ResizeDir, dx: number, dy: number, e: PointerEvent) => void) |
Called on every pointer move of a resize with the handle direction and offset. | |
onResizeStart |
((id: string, dir: ResizeDir, e: PointerEvent) => void) |
Called with the native pointerdown event when a resize starts on one of the eight edge and corner handles. |
|
resizable |
boolean |
true |
Whether the resize handles are rendered; they are visible in expanded mode only. |
stackIndex |
number |
0 |
Stacking rank among expanded windows; a higher rank is drawn on top. Set by WindowManager to bring the last used floating window to the front. |
state |
'idle' | 'dragging' | 'snap-preview' | 'swap-preview' | 'settling' | 'focused' | 'resizing' |
'idle' |
Visual state; dragging and resizing are also set automatically during a gesture. |
title |
ReactNode |
Header title. It also names the window region (aria-labelledby points at it). |
Type PanelContentRenderProps
Window state passed to itemRenderer, so content can adapt to docked or floating mode.
| Field | Type | Default | Description |
|---|---|---|---|
dragging (required) |
boolean |
Whether the window is being dragged or resized right now. | |
focused (required) |
boolean |
Whether the window is currently focused. | |
mode (required) |
PanelMode |
compact while docked in a slot, expanded while free-floating. |
Type ResizeDir
Edge or corner a window is resized from, as compass directions (se = bottom-right corner).
type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
Type PanelMode
compact: docked in a slot. expanded: free-floating and resizable.
type PanelMode = 'compact' | 'expanded';