SnapOverlay
SnapOverlay draws the nine slots of the window grid as glowing drop zones and highlights the one under the pointer. WindowManager shows it while a docked window is dragged.
import { SnapOverlay } from 'jarvis-react-ui';
Drop zones
While active, every slot in slotRects is drawn as an outlined zone, and hoveredSlot gets a
stronger fill and a pulse. The overlay is a position: fixed layer over its containing block (the
viewport, or the nearest ancestor with a transform) and ignores the pointer, so it never blocks
the drag below it. Each zone gets its rect as the CSS variables --lib-snap-zone-x, -y, -w
and -h. Move the pointer over the stage.
import { useEffect, useRef, useState } from 'react';
import type { PointerEvent } from 'react';
import { Mono, SLOT_IDS, SnapOverlay, computeSlot, slotAtPoint } from 'jarvis-react-ui';
import type { SlotId, SlotRect } from 'jarvis-react-ui';
// The slot grid is made for a full viewport; to fit the demo frame it is computed at 2× and halved.
// In an app, pass the viewport size: computeAllSlots(innerWidth, innerHeight).
function demoSlots(w: number, h: number): Record<SlotId, SlotRect> {
const rects = {} as Record<SlotId, SlotRect>;
for (const id of SLOT_IDS) {
const r = computeSlot(id, w * 2, h * 2);
rects[id] = { x: r.x / 2, y: r.y / 2, w: r.w / 2, h: r.h / 2 };
}
return rects;
}
export default function HoverSlots() {
const stageRef = useRef<HTMLDivElement>(null);
const [size, setSize] = useState({ w: 0, h: 0 });
const [hovered, setHovered] = useState<SlotId | null>(null);
useEffect(() => {
const stage = stageRef.current;
if (!stage) return;
const observer = new ResizeObserver(() =>
setSize({ w: stage.clientWidth, h: stage.clientHeight }),
);
observer.observe(stage);
return () => observer.disconnect();
}, []);
const slotRects = demoSlots(size.w, size.h);
const handlePointerMove = (e: PointerEvent<HTMLDivElement>) => {
const box = e.currentTarget.getBoundingClientRect();
setHovered(slotAtPoint(e.clientX - box.left, e.clientY - box.top, slotRects));
};
return (
<div className="flex w-full flex-col gap-3">
<Mono size="sm" secondary>
Move the pointer over the stage · hovered: {hovered ?? 'none'}
</Mono>
{/* transform-gpu makes the stage the containing block of the fixed overlay. */}
<div
ref={stageRef}
className="relative h-[300px] w-full overflow-hidden transform-gpu"
onPointerMove={handlePointerMove}
onPointerLeave={() => setHovered(null)}
>
<SnapOverlay active={size.w > 0} slotRects={slotRects} hoveredSlot={hovered} />
</div>
</div>
);
}
In WindowManager
WindowManager renders SnapOverlay for every drag of a compact window: active is on for the whole
drag, slotRects is the grid computed from the WindowManager's size, and hoveredSlot is the slot
under the pointer. When that slot is occupied, SwapOverlay also marks
the window that would be swapped. Expanded windows drag freely and show no overlay.
Standalone use
Use SnapOverlay when you lay out Window elements yourself:
- Compute the rects with
computeAllSlots(width, height)from the slot grid helpers. - Track the drag with useSlotDrag: pass
slotDragState.draggingtoactiveandslotDragState.snapTargettohoveredSlot. - On drop, assign the window to the final
snapTarget.
import { SnapOverlay, computeAllSlots } from 'jarvis-react-ui';
const slotRects = computeAllSlots(window.innerWidth, window.innerHeight);
<SnapOverlay
active={slotDragState.dragging}
slotRects={slotRects}
hoveredSlot={slotDragState.snapTarget}
/>;
Accessibility
The overlay is decorative: its root is aria-hidden and it takes no pointer events. Announce the
result of a drop elsewhere if it matters, and offer a keyboard alternative to dragging.
API
| Prop | Type | Default | Description |
|---|---|---|---|
active (required) |
boolean |
Whether the slot zones are drawn; while false only an empty layer is rendered. |
|
hoveredSlot (required) |
SlotId | null |
Slot under the pointer; its zone is highlighted and pulses. null highlights none. |
|
slotRects (required) |
Record<SlotId, SlotRect> |
Rect of every slot, usually from computeAllSlots(width, height). |
|
className |
string |
Extra classes for the root <div> element (the fixed full-size layer). |
Type SlotId
Fixed 3×3 Dock grid slot geometry.
Left column (top → bottom): L1, L2, L3 Right column (top → bottom): R1, R2, R3 Bottom strip (left → right): B1, B2, B3
type SlotId = 'L1' | 'L2' | 'L3' | 'R1' | 'R2' | 'R3' | 'B1' | 'B2' | 'B3';
Type SlotRect
Rectangular geometry (pixel-absolute, viewport-relative).
| Field | Type | Default | Description |
|---|---|---|---|
h (required) |
number |
Height in px. | |
w (required) |
number |
Width in px. | |
x (required) |
number |
Left edge in px. | |
y (required) |
number |
Top edge in px. |