sdxc

Type to search, or start from one of these:

DropIndicator

A thin bar rendered in the gap between two items of a reorderable list, marking where a dragged item will land.

Release checklist
Drag a grip
  1. 1. Lint and typecheck

    Runs on CI

  2. 2. Unit and integration tests

    Runs on CI

  3. 3. Run pending migrations

    Runs on Platform

  4. 4. Deploy to production

    Runs on Platform

View code
let session = new DragSession();
let steps = [...STEPS];

// ref() fires on insert, so the subscription is set up in the browser and torn
// down with the node — the server renders the list without one.
let followDrag = ref((_node, signal) => {
	session.addEventListener("change", () => void handle.update(), { signal });
});

function isGapActive(stepId: string, side: "before" | "after") {
	return session.target?.key === stepId && session.target.position === side;
}

<div mix={[followDrag]}>
	<Header>Release checklist</Header>
	<Badge color="neutral">{session.active ? "Dragging" : "Drag a grip"}</Badge>

	<ol
		mix={[
			// A pointer drag over text would select it instead, so the list opts out.
			userSelect("none"),
			dragReorder(session),
			on<HTMLOListElement, "ui:reorder">("ui:reorder", (event) => {
				steps = reorder(steps, event.sourceKey, event.targetKey, event.position, (s) => s.id);
				void handle.update();
			}),
		]}
	>
		{steps.map((step, index) => (
			<li key={step.id} data-rmx-key={step.id}>
				{/* One bar per gap: the first row opens the list, and every row closes its own gap. */}
				{index === 0 ? <DropIndicator isDropTarget={isGapActive(step.id, "before")} /> : null}
				<Item>
					<Item.Media>
						<button
							type="button"
							data-drag-handle
							aria-label={`Reorder ${step.name}`}
							mix={[cursor("grab"), touchAction("none")]}
						>
							<GripVerticalIcon />
						</button>
					</Item.Media>
					<Item.Content>
						<Item.Title>
							{index + 1}. {step.name}
						</Item.Title>
						<Item.Description>Runs on {step.owner}</Item.Description>
					</Item.Content>
				</Item>
				<DropIndicator isDropTarget={isGapActive(step.id, "after")} />
			</li>
		))}
	</ol>
</div>

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

npm add @sdxc/ui
import "@sdxc/ui/theme.css";

Usage

import { DropIndicator } from "@sdxc/ui";

Renders a full-width, hairline-thick, fully rounded bar shaped for the gap between two items in a reorderable list. The host's data-drop-target attribute is what paints it: transparent without it, the primary solid color with it.

Examples

<DropIndicator
	isDropTarget={isCurrentTarget}
	mix={css({ position: "absolute", insetInline: "0", insetBlockStart: "-1px" })}
/>

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

PropTypeDescription
isDropTarget?booleanWhether this bar marks the pointer's current drop target. Renders the host's data-drop-target attribute when true, which is what draws the bar. Defaults to DEFAULT_IS_DROP_TARGET.

Also accepts everything in TagProps<"div">.