Node Status Indicator

A node wrapper that has multiple states for indicating the status of a node. Status can be one of the following: "success", "loading", "error" and "initial".

Additionally, the NodeStatusIndicator component supports different loading variants: "border" and "overlay", which can be set using the loadingVariant prop.

  • The "border" variant is the default and shows a spinning border around the node when it is in loading state.
  • The "overlay" variant shows a full overlay, with an animated spinner on the node when it is in loading state.

UI Component: node-status-indicator

index.tsx
import { type ReactNode } from "react";
import { LoaderCircle } from "lucide-react";
 
import { cn } from "@/lib/utils";
 
export type NodeStatus = "loading" | "success" | "error" | "initial";
 
export type NodeStatusVariant = "overlay" | "border";
 
export type NodeStatusIndicatorProps = {
  status?: NodeStatus;
  variant?: NodeStatusVariant;
  children: ReactNode;
};
 
export const SpinnerLoadingIndicator = ({
  children,
}: {
  children: ReactNode;
}) => {
  return (
    <div className="relative">
      <StatusBorder className="border-blue-700/40">{children}</StatusBorder>
 
      <div className="bg-background/50 backdrop-blur-xs absolute inset-0 z-50 rounded-[9px]" />
      <div className="absolute inset-0 z-50">
        <span className="absolute left-[calc(50%-1.25rem)] top-[calc(50%-1.25rem)] inline-block h-10 w-10 animate-ping rounded-full bg-blue-700/20" />
 
        <LoaderCircle className="absolute left-[calc(50%-0.75rem)] top-[calc(50%-0.75rem)] size-6 animate-spin text-blue-700" />
      </div>
    </div>
  );
};
 
export const BorderLoadingIndicator = ({
  children,
}: {
  children: ReactNode;
}) => {
  return (
    <>
      <div className="absolute -left-px -top-px h-[calc(100%+2px)] w-[calc(100%+2px)]">
        <style>
          {`
        @keyframes spin {
          from { transform: translate(-50%, -50%) rotate(0deg); }
          to { transform: translate(-50%, -50%) rotate(360deg); }
        }
        .spinner {
          animation: spin 2s linear infinite;
          position: absolute;
          left: 50%;
          top: 50%;
          width: 140%;
          aspect-ratio: 1;
          transform-origin: center;
        }
      `}
        </style>
        <div className="absolute inset-0 overflow-hidden rounded-[9px]">
          <div className="spinner rounded-full bg-[conic-gradient(from_0deg_at_50%_50%,rgb(42,67,233)_0deg,rgba(42,138,246,0)_360deg)]" />
        </div>
      </div>
      {children}
    </>
  );
};
 
const StatusBorder = ({
  children,
  className,
}: {
  children: ReactNode;
  className?: string;
}) => {
  return (
    <>
      <div
        className={cn(
          "absolute -left-px -top-px h-[calc(100%+2px)] w-[calc(100%+2px)] rounded-[9px] border-2",
          className,
        )}
      />
      {children}
    </>
  );
};
 
export const NodeStatusIndicator = ({
  status,
  variant = "border",
  children,
}: NodeStatusIndicatorProps) => {
  switch (status) {
    case "loading":
      switch (variant) {
        case "overlay":
          return <SpinnerLoadingIndicator>{children}</SpinnerLoadingIndicator>;
        case "border":
          return <BorderLoadingIndicator>{children}</BorderLoadingIndicator>;
        default:
          return <>{children}</>;
      }
    case "success":
      return (
        <StatusBorder className="border-emerald-600">{children}</StatusBorder>
      );
    case "error":
      return <StatusBorder className="border-red-400">{children}</StatusBorder>;
    default:
      return <>{children}</>;
  }
};
component-example.tsx
import { BaseNode, BaseNodeContent } from "@/registry/components/base-node";
import { NodeStatusIndicator } from "@/registry/components/node-status-indicator";
 
export const LoadingNode = () => {
  return (
    <NodeStatusIndicator status="loading" variant="border">
      <BaseNode>
        <BaseNodeContent>This node is loading...</BaseNodeContent>
      </BaseNode>
    </NodeStatusIndicator>
  );
};