SwapOverlay

SwapOverlay draws a dashed ghost over the window that a drop would swap with. WindowManager shows it while a docked window is dragged onto an occupied slot.

import { SwapOverlay } from 'jarvis-react-ui';

Swap ghost

When active is on and ghostRect is set, a dashed outline covers that rect; hovered gives it a stronger fill and glow. Like SnapOverlay, the overlay is a position: fixed layer over its containing block and ignores the pointer. The ghost gets its rect as the CSS variables --lib-swap-ghost-x, -y, -w and -h.

import { useState } from 'react';
import { SwapOverlay, Switch, Window } from 'jarvis-react-ui';

const TARGET = { x: 0, y: 0, w: 300, h: 160 };

export default function Basic() {
    const [active, setActive] = useState(true);
    const [hovered, setHovered] = useState(false);

    return (
        <div className="flex flex-col items-center gap-4">
            <div className="flex gap-6">
                <Switch label="Active" checked={active} onCheckedChange={setActive} />
                <Switch label="Hovered" checked={hovered} onCheckedChange={setHovered} />
            </div>
            {/* transform-gpu makes this box the containing block of the fixed overlay. */}
            <div className="relative h-[160px] w-[300px] transform-gpu">
                <Window
                    id="agenda"
                    title="Agenda"
                    ix="▦"
                    position={TARGET}
                    draggable={false}
                    itemRenderer={() => '09:30 · Standup'}
                />
                <SwapOverlay active={active} ghostRect={TARGET} hovered={hovered} />
            </div>
        </div>
    );
}

In WindowManager

During a drag of a compact window, WindowManager checks whether the slot under the pointer holds another window. If it does, SwapOverlay is turned on with hovered, and ghostRect is that window's slot, so the user sees which window will trade places. SnapOverlay keeps highlighting the target slot at the same time.

Standalone use

With useSlotDrag, slotDragState.swapTarget is the id of the window in the hovered slot. Look up its slot rect (see the slot grid helpers) and pass it as ghostRect:

const { swapTarget } = slotDragState;
const swapRect = swapTarget !== null ? slotRects[assignments[swapTarget]] : null;

<SwapOverlay active={swapRect !== null} ghostRect={swapRect} hovered />;

Accessibility

The overlay is decorative: its root is aria-hidden and it takes no pointer events.

API

Prop Type Default Description
active (required) boolean Whether the ghost is drawn; nothing is drawn while false or without a ghostRect.
ghostRect (required) SlotRect | null Rect of the window that would be swapped, or null for no ghost.
className string Extra classes for the root <div> element (the fixed full-size layer).
hovered boolean false Highlights the ghost with a stronger fill and glow, e.g. while the drop would swap.

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.