a1ui
6 new components shipped

Integration Hub

A connected workspace with six tools and animated pulses flowing into a central product.

Read agent guide

Demo

RelayYour team's shared brainReceiving signals

Installation

pnpm dlx shadcn@latest add https://ui.arjayby1.xyz/r/integration-hub.json

Code

"use client";

import { useState } from "react";
import { Code, FileText, PenTool, Folder, Layers, MessageSquare, Radio } from "lucide-react";
import { IntegrationHub, type IntegrationHubTool } from "@/components/ui/integration-hub";

const tools: IntegrationHubTool[] = [
  { id: "slack", name: "Slack", description: "Conversations", icon: <MessageSquare />, color: "#be6b96" },
  { id: "notion", name: "Notion", description: "Knowledge", icon: <FileText />, color: "#909486" },
  { id: "figma", name: "Figma", description: "Design files", icon: <PenTool />, color: "#c17f5f" },
  { id: "github", name: "GitHub", description: "Code", icon: <Code />, color: "#899776" },
  { id: "linear", name: "Linear", description: "Projects", icon: <Layers />, color: "#8b83d7" },
  { id: "drive", name: "Google Drive", description: "Files", icon: <Folder />, color: "#699f88" },
];

export function ConnectedWorkspace() {
  const [paused, setPaused] = useState(false);
  const [activeToolId, setActiveToolId] = useState<string | null>(null);

  return (
    <div className="flex flex-col gap-4">
      <IntegrationHub
        tools={tools}
        product={{ name: "Relay", description: "Shared context", icon: <Radio /> }}
        activeToolId={activeToolId}
        onToolSelect={(tool) => setActiveToolId((current) => (current === tool.id ? null : tool.id))}
        paused={paused}
      />
      <button type="button" onClick={() => setPaused((current) => !current)} aria-pressed={paused}>
        {paused ? "Resume pulses" : "Pause pulses"}
      </button>
    </div>
  );
}

API reference

PropTypeDefaultPurpose
toolsreadonly IntegrationHubTool[]RequiredUp to six tools, split into left and right columns. Extra entries are ignored.
product{ name: string; icon: ReactNode; description?: string; status?: string }RequiredContent of the center tile.
activeToolIdstring | nullnullHighlights one tool and its connection. A missing or unknown ID shows all connections.
onToolSelect(tool: IntegrationHubTool) => voidNoneMakes tool cards keyboard-accessible buttons. Update activeToolId in this callback to select a connection.
pausedbooleanfalseFreezes the pulses in place. Clearing it resumes their progress.
durationnumber4Seconds per pulse cycle, including a short rest. Finite values are clamped to at least one second; non-finite values use four.
classNamestringNoneAdds classes to the outer element.
styleCSSPropertiesNoneInline styles and custom properties.

Each tool has a unique id, a name, an icon as a React node, and optional description and CSS color. Use compact, non-interactive icons and short names. The first half of the array goes on the left, the rest on the right. One tool sits at the middle of its column. Empty arrays leave only the product visible.

Pulses travel from every tool toward the product, including the right column. The SVG paths and cards share proportional coordinates so their endpoints stay attached when the container resizes. Below a container width of 580 pixels, cards stack their icon and name and hide the secondary description. The component needs no measurement observers or animation library. Its scoped styles ship with the source.

The component inherits the page font and the --background, --foreground, --border, and --muted-foreground theme tokens. Override --integration-hub-surface, --integration-hub-foreground, --integration-hub-border, --integration-hub-product-background, --integration-hub-product-foreground, and --integration-hub-accent on a parent to customize it.

The product tile uses the surface color as its background, the foreground color for text and its icon, and the theme border color. It follows --radius for corners and --font-mono for typography, falling back to the inherited font. These defaults adapt to light and dark themes. The product name is bold and uppercase. The documentation demo uses Commit Mono throughout.

Provide a pause control for continuous motion, as in the example. Reduced motion hides the traveling pulses and retains the static connections. Tool selection still works. Without onToolSelect, the tool cards render as plain content with no extra tab stops.

Relay is a fictional product in the demo. The component visualizes connections; it does not connect accounts, sync files, or measure live activity. Supply real integration data and status from your app. The demo's tool artwork and controls are separate from the installed component. The demo uses brand icons from SVG Logos; source links and the collection's license are included with the demo assets.