Connector Edge

Curved edge between canvas objects with optional inline label state.

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/connector-edge.json

Storybook

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

View in Storybook

Code

import { forwardRef } from "react"; import type { CSSProperties } from "react"; import { cn } from "../../lib/utils"; import { EdgeLabel } from "../edge-label/edge-label"; export type ConnectorEdgePoint = { x: number; y: number; }; export type ConnectorEdgeProps = React.ComponentPropsWithoutRef<"div"> & { end: ConnectorEdgePoint; label?: string; start: ConnectorEdgePoint; state?: "active" | "blocked" | "idle"; }; const strokeClasses: Record< NonNullable<ConnectorEdgeProps["state"]>, string > = { active: "stroke-sky-500", blocked: "stroke-amber-500", idle: "stroke-muted-foreground/60", }; const ConnectorEdge = forwardRef<HTMLDivElement, ConnectorEdgeProps>( ({ className, end, label, start, state = "idle", ...props }, ref) => { const width = Math.max(Math.abs(end.x - start.x), 32); const height = Math.max(Math.abs(end.y - start.y), 32); const midX = width / 2; const startX = start.x <= end.x ? 4 : width - 4; const endX = start.x <= end.x ? width - 4 : 4; const startY = start.y <= end.y ? 4 : height - 4; const endY = start.y <= end.y ? height - 4 : 4; const path = `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`; const style = { height, width, } satisfies CSSProperties; return ( <div className={cn("relative inline-flex", className)} ref={ref} style={style} {...props} > <svg className="overflow-visible" height={height} viewBox={`0 0 ${width} ${height}`} width={width} > <path className={cn("fill-none stroke-[2.5]", strokeClasses[state])} d={path} strokeDasharray={state === "blocked" ? "4 4" : undefined} strokeLinecap="round" /> </svg> {label ? ( <EdgeLabel className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" emphasis={state === "active" ? "active" : "subtle"} > {label} </EdgeLabel> ) : null} </div> ); }, ); ConnectorEdge.displayName = "ConnectorEdge"; export { ConnectorEdge };

Dependencies

  • @vllnt/ui@^0.2.1