Anchor Port

Port marker for object inputs, outputs, and bidirectional links on the canvas.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/anchor-port.json
bash

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

import { forwardRef } from "react";

import { cn } from "../../lib/utils";

export type AnchorPortProps = React.ComponentPropsWithoutRef<"span"> & {
  side?: "bottom" | "left" | "right" | "top";
  state?: "active" | "blocked" | "idle";
  tone?: "bidirectional" | "input" | "output";
};

const toneClasses: Record<NonNullable<AnchorPortProps["tone"]>, string> = {
  bidirectional:
    "border-violet-500/40 bg-violet-500/15 text-violet-700 dark:text-violet-300",
  input: "border-sky-500/40 bg-sky-500/15 text-sky-700 dark:text-sky-300",
  output:
    "border-emerald-500/40 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
};

const stateClasses: Record<NonNullable<AnchorPortProps["state"]>, string> = {
  active: "scale-100 opacity-100",
  blocked: "opacity-60 saturate-50",
  idle: "opacity-80",
};

const sideClasses: Record<NonNullable<AnchorPortProps["side"]>, string> = {
  bottom: "self-end",
  left: "self-start",
  right: "self-end",
  top: "self-start",
};

const AnchorPort = forwardRef<HTMLSpanElement, AnchorPortProps>(
  (
    {
      className,
      side = "right",
      state = "idle",
      tone = "bidirectional",
      ...props
    },
    ref,
  ) => (
    <span
      aria-label={props["aria-label"] ?? `${tone} ${side} port ${state}`}
      className={cn(
        "inline-flex size-7 items-center justify-center rounded-full border shadow-sm",
        toneClasses[tone],
        stateClasses[state],
        sideClasses[side],
        className,
      )}
      data-side={side}
      data-state={state}
      data-tone={tone}
      ref={ref}
      role="img"
      {...props}
    >
      <span className="size-2.5 rounded-full bg-current" />
    </span>
  ),
);

AnchorPort.displayName = "AnchorPort";

export { AnchorPort };
typescript

Dependencies

  • @vllnt/ui@^0.2.1