# Integrations 7

> Searchable connector picker with keyboard navigation and live preview panel.

A shadcn/ui block from the [@nusaiba](https://nusaiba.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/nusaiba/integrations-7).

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/integrations-7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/integrations-7-light-9c985ff0532ca774.png
- Registry item: https://nusaiba.dev/r/base-nova/integrations-7.json
- Categories: integrations
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/integrations-7.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`
- Registry items: `@nusaiba/foundation`, `input`

## Files

### `registry/blocks/integrations/integrations-data.ts`

```ts
export const integrationsEyebrow = "Integrations";

export const integrationsGridCopy = {
	title: "Connects with your stack",
	description:
		"Sync installs, trigger workflows, and keep every tool in lockstep — registry-native handoff included.",
} as const;

export const integrationsOrbitalCopy = {
	title: "Endless possibilities",
	description:
		"Enhance your ship loop with integrations that meet your team where they already work — from design to deploy.",
} as const;

export const integrationsNetworkCopy = {
	title: "Bring your whole workflow",
	description:
		"Design in Figma, ship from GitHub, notify in Slack — one registry surface ties the loop together.",
} as const;

export const integrationsSplitCopy = {
	title: "The synergy of integration",
	headline: "Streamlining installs across your toolchain",
	description:
		"Each connector ships with typed hooks, documented env vars, and Copy Prompt handoff — no bespoke glue code.",
	cta: "Browse all connectors",
} as const;

export const integrationsBentoCopy = {
	title: "Connected surfaces",
	description: "Four install paths — pick the channel that matches how your team ships.",
} as const;

export const integrationsTimelineCopy = {
	title: "Connect in minutes",
	description: "Provision, authorize, and verify — a linear path from empty to live.",
} as const;

export const integrationsPickerCopy = {
	title: "Find your connector",
	description: "Search the catalog — arrow keys to preview, Enter to open docs.",
} as const;

export const integrationsMarqueeCopy = {
	title: "Always in sync",
	description: "A quiet strip of connectors — logo and name only, no badge clutter.",
} as const;

export const integrationsDepthCopy = {
	title: "Integration depth",
	description: "Compare native registry hooks against API and webhook fallbacks.",
} as const;

export const integrationsFramedCopy = {
	eyebrow: "Pro connectors",
	title: "Constellation map",
	description: "Framed overview of commercial-grade sync paths for Pro workspaces.",
} as const;

export type IntegrationBrand = {
	name: string;
	domain: string;
	description: string;
	tag?: string;
	sync?: "Native" | "API" | "Webhook";
};

export const integrationBrands: IntegrationBrand[] = [
	{
		name: "GitHub",
		domain: "github.com",
		description: "Install blocks from CI and open PRs with registry tokens",
		tag: "CI/CD",
		sync: "Native",
	},
	{
		name: "Vercel",
		domain: "vercel.com",
		description: "Preview registry sections on every deployment",
		tag: "Deploy",
		sync: "Native",
	},
	{
		name: "Figma",
		domain: "figma.com",
		description: "Hand off frames to Copy Prompt install flows",
		tag: "Design",
		sync: "API",
	},
	{
		name: "Slack",
		domain: "slack.com",
		description: "Notify channels when Pro blocks update",
		tag: "Alerts",
		sync: "Webhook",
	},
	{
		name: "Linear",
		domain: "linear.app",
		description: "Link block issues to registry changelog entries",
		tag: "Issues",
		sync: "API",
	},
	{
		name: "Notion",
		domain: "notion.so",
		description: "Embed live block previews in team wikis",
		tag: "Docs",
		sync: "API",
	},
	{
		name: "Stripe",
		domain: "stripe.com",
		description: "Gate Pro registry installs behind license checks",
		tag: "Billing",
		sync: "Native",
	},
	{
		name: "npm",
		domain: "npmjs.com",
		description: "Publish private registry packages to your scope",
		tag: "Packages",
		sync: "Native",
	},
	{
		name: "Zapier",
		domain: "zapier.com",
		description: "Trigger automations when catalog entries change",
		tag: "Automate",
		sync: "Webhook",
	},
	{
		name: "Intercom",
		domain: "intercom.com",
		description: "Surface install help inside customer chat",
		tag: "Support",
		sync: "API",
	},
	{
		name: "HubSpot",
		domain: "hubspot.com",
		description: "Sync Pro leads from pricing block events",
		tag: "CRM",
		sync: "Webhook",
	},
	{
		name: "Asana",
		domain: "asana.com",
		description: "Create tasks when new block families ship",
		tag: "Projects",
		sync: "API",
	},
];

export const orbitalBrands = integrationBrands.slice(0, 8);

export const networkBrands = integrationBrands.slice(0, 4);

export const stackBrands = integrationBrands.slice(0, 4);

export const bentoBrands = integrationBrands.slice(0, 4);

export const timelineBrands = integrationBrands.slice(0, 5);

export const pickerBrands = integrationBrands.slice(0, 8);

export const marqueeBrands = integrationBrands.slice(0, 10);

export const depthBrands = integrationBrands.slice(0, 6);

export const constellationBrands = integrationBrands.slice(0, 6);
```

### `registry/blocks/integrations/integrations-primitives.tsx`

```tsx
"use client";

import { motion, useInView, useReducedMotion } from "motion/react";
import { useRef, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { defaultSpring, motionStagger, motionViewport } from "@/lib/motion-tokens";
import type { IntegrationBrand } from "./integrations-data";
import { integrationsEyebrow } from "./integrations-data";

export const integrationItemSpring = defaultSpring;

export function integrationRevealInitial(reduceMotion: boolean) {
	return reduceMotion
		? false
		: { opacity: 0, y: 10, filter: "blur(4px)" as const };
}

export function integrationRevealVisible(reduceMotion: boolean) {
	return reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" as const };
}

export function integrationItemVariants(reduceMotion: boolean | null) {
	return reduceMotion
		? { hidden: { opacity: 1 }, visible: { opacity: 1 } }
		: {
				hidden: { opacity: 0, y: 10, filter: "blur(4px)" },
				visible: { opacity: 1, y: 0, filter: "blur(0px)", transition: integrationItemSpring },
			};
}

export const integrationStaggerContainer = {
	hidden: {},
	visible: {
		transition: { staggerChildren: motionStagger.item, delayChildren: 0.04 },
	},
};

export function MotionStaggerGroup({
	children,
	className,
}: {
	children: ReactNode;
	className?: string;
}) {
	const ref = useRef<HTMLDivElement>(null);
	const inView = useInView(ref, motionViewport);

	return (
		<motion.div
			animate={inView ? "visible" : "hidden"}
			className={className}
			initial="hidden"
			ref={ref}
			variants={integrationStaggerContainer}
		>
			{children}
		</motion.div>
	);
}

export function MotionStaggerItem({
	children,
	className,
	style,
}: {
	children: ReactNode;
	className?: string;
	style?: React.CSSProperties;
}) {
	const reduceMotion = useReducedMotion();
	const itemVariants = integrationItemVariants(reduceMotion);

	return (
		<motion.div className={className} style={style} variants={itemVariants}>
			{children}
		</motion.div>
	);
}

export function MotionHoverLift({
	children,
	className,
}: {
	children: ReactNode;
	className?: string;
}) {
	return (
		<motion.div
			className={className}
			transition={defaultSpring}
			whileHover={{ y: -2 }}
		>
			{children}
		</motion.div>
	);
}

/** Tile flies out from hub center — use motion x/y, not CSS transform. */
export function RadialRevealTile({
	brand,
	x,
	y,
	delay = 0,
	className,
}: {
	brand: IntegrationBrand;
	x: number;
	y: number;
	delay?: number;
	className?: string;
}) {
	const reduceMotion = useReducedMotion();

	return (
		<motion.div
			className={cn("absolute top-1/2 left-1/2", className)}
			initial={
				reduceMotion
					? false
					: { x: 0, y: 0, opacity: 0, scale: 0.88, filter: "blur(6px)" }
			}
			transition={{ ...defaultSpring, delay }}
			viewport={motionViewport}
			whileInView={
				reduceMotion
					? undefined
					: { x, y, opacity: 1, scale: 1, filter: "blur(0px)" }
			}
		>
			<div className="-translate-x-1/2 -translate-y-1/2">
				<IntegrationTile brand={brand} />
			</div>
		</motion.div>
	);
}

export function HubReveal({
	children,
	className,
	delay = 0,
}: {
	children: ReactNode;
	className?: string;
	delay?: number;
}) {
	const reduceMotion = useReducedMotion();

	return (
		<motion.div
			className={cn("absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", className)}
			initial={reduceMotion ? false : { opacity: 0, scale: 0.92, filter: "blur(4px)" }}
			transition={{ ...defaultSpring, delay }}
			viewport={motionViewport}
			whileInView={reduceMotion ? undefined : { opacity: 1, scale: 1, filter: "blur(0px)" }}
		>
			{children}
		</motion.div>
	);
}

export function TimelineStepReveal({
	children,
	index,
	className,
}: {
	children: ReactNode;
	index: number;
	className?: string;
}) {
	const reduceMotion = useReducedMotion();

	return (
		<motion.li
			className={className}
			initial={integrationRevealInitial(!!reduceMotion)}
			transition={{ ...defaultSpring, delay: index * motionStagger.item }}
			viewport={motionViewport}
			whileInView={integrationRevealVisible(!!reduceMotion)}
		>
			{children}
		</motion.li>
	);
}

export function MotionIntegrationTile({
	brand,
	className,
}: {
	brand: IntegrationBrand;
	className?: string;
}) {
	return (
		<MotionHoverLift>
			<IntegrationTile brand={brand} className={className} />
		</MotionHoverLift>
	);
}

export function getLogoDevUrl(domain: string, size = "80") {
	const token = process.env.NEXT_PUBLIC_LOGO_DEV_PUBLISHABLE_KEY;
	if (!token) return null;

	const params = new URLSearchParams({
		token,
		size,
		format: "png",
		retina: "true",
		fallback: "monogram",
	});

	return `https://img.logo.dev/${domain}?${params}`;
}

export function IntegrationLogo({
	domain,
	name,
	className,
	size = "md",
}: {
	domain: string;
	name: string;
	className?: string;
	size?: "sm" | "md" | "lg";
}) {
	const src = getLogoDevUrl(domain, size === "lg" ? "96" : size === "sm" ? "64" : "80");
	const box =
		size === "lg" ? "size-9 md:size-10" : size === "sm" ? "size-6" : "size-7 md:size-8";

	if (src) {
		return (
			<img
				alt={`${name} logo`}
				className={cn("object-contain", box, className)}
				height={40}
				loading="lazy"
				referrerPolicy="origin"
				src={src}
				width={40}
			/>
		);
	}

	return (
		<span
			aria-hidden
			className={cn(
				"flex items-center justify-center rounded-md bg-muted font-medium text-foreground/70 text-xs",
				box,
				className,
			)}
		>
			{name.charAt(0)}
		</span>
	);
}

export function IntegrationTile({
	brand,
	className,
}: {
	brand: IntegrationBrand;
	className?: string;
}) {
	return (
		<div
			className={cn(
				"flex size-11 items-center justify-center rounded-xl bg-muted/30 md:size-12",
				className,
			)}
		>
			<IntegrationLogo domain={brand.domain} name={brand.name} size="sm" />
		</div>
	);
}

export function NusaibaHubMark({ className }: { className?: string }) {
	const cells = [1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1];

	return (
		<div
			className={cn(
				"flex size-12 items-center justify-center rounded-xl border border-border/80 bg-background md:size-14",
				className,
			)}
		>
			<span
				aria-hidden
				className="grid size-7 grid-cols-4 gap-px rounded-sm bg-foreground/10 p-0.5"
			>
				{cells.map((filled, index) => (
					<span
						className={cn("rounded-[1px]", filled ? "bg-foreground" : "bg-transparent")}
						key={index}
					/>
				))}
			</span>
		</div>
	);
}

export function OrbitPosition({
	angle,
	children,
	radius = 132,
}: {
	angle: number;
	children: ReactNode;
	radius?: number;
}) {
	const rad = (angle * Math.PI) / 180;
	const x = Math.cos(rad) * radius;
	const y = Math.sin(rad) * radius;

	return (
		<div
			className="absolute top-1/2 left-1/2"
			style={{ transform: `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))` }}
		>
			{children}
		</div>
	);
}

export function IntegrationCardRow({
	brand,
	className,
}: {
	brand: IntegrationBrand;
	className?: string;
}) {
	return (
		<div className={cn("flex items-center gap-3.5 py-3.5", className)}>
			<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-muted/30">
				<IntegrationLogo domain={brand.domain} name={brand.name} size="sm" />
			</div>
			<div className="min-w-0 text-left">
				<p className="font-medium text-sm">{brand.name}</p>
				<p className="mt-0.5 text-pretty text-muted-foreground text-xs leading-relaxed">
					{brand.description}
				</p>
			</div>
		</div>
	);
}

export function IntegrationsSectionHeader({
	title,
	description,
	className,
	align = "center",
}: {
	title: string;
	description: string;
	className?: string;
	align?: "center" | "left";
}) {
	return (
		<MotionReveal
			className={cn(
				"max-w-2xl",
				align === "center" ? "mx-auto text-center" : "text-left",
				className,
			)}
		>
			<p className="text-muted-foreground text-sm">{integrationsEyebrow}</p>
			<h2 className="mt-2 text-balance font-medium text-2xl tracking-tight md:text-3xl">
				{title}
			</h2>
			<p className="mt-3 text-pretty text-muted-foreground text-sm leading-relaxed">
				{description}
			</p>
		</MotionReveal>
	);
}

export function MotionReveal({
	children,
	className,
	delay = 0,
}: {
	children: ReactNode;
	className?: string;
	delay?: number;
}) {
	const reduceMotion = useReducedMotion();

	return (
		<motion.div
			className={className}
			initial={integrationRevealInitial(!!reduceMotion)}
			transition={{ ...defaultSpring, delay }}
			viewport={motionViewport}
			whileInView={integrationRevealVisible(!!reduceMotion)}
		>
			{children}
		</motion.div>
	);
}
```

### `registry/blocks/integrations/7/integrations-section.tsx`

```tsx
"use client";

import { useEffect, useMemo, useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import { SearchIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import { integrationsPickerCopy, pickerBrands } from "../integrations-data";
import { SectionShell, ContentRail } from "@/components/layout-contract";
import {
	IntegrationLogo,
	IntegrationsSectionHeader,
	integrationItemSpring,
	MotionReveal,
	MotionStaggerGroup,
	MotionStaggerItem,
} from "../integrations-primitives";

export function IntegrationsPickerSection() {
	const [query, setQuery] = useState("");
	const [activeIndex, setActiveIndex] = useState(0);

	const filtered = useMemo(() => {
		return pickerBrands.filter((brand) => {
			const haystack = `${brand.name} ${brand.tag ?? ""} ${brand.description}`.toLowerCase();
			return haystack.includes(query.trim().toLowerCase());
		});
	}, [query]);

	const active = filtered[activeIndex] ?? filtered[0];

	useEffect(() => {
		setActiveIndex(0);
	}, [query]);

	useEffect(() => {
		function onKeyDown(event: KeyboardEvent) {
			if (filtered.length === 0) return;
			if (event.key === "ArrowDown") {
				event.preventDefault();
				setActiveIndex((current) => (current + 1) % filtered.length);
			}
			if (event.key === "ArrowUp") {
				event.preventDefault();
				setActiveIndex((current) => (current - 1 + filtered.length) % filtered.length);
			}
		}

		window.addEventListener("keydown", onKeyDown);
		return () => window.removeEventListener("keydown", onKeyDown);
	}, [filtered.length]);

	return (
		<SectionShell spacingMode="section">
			<ContentRail maxWidth="max-w-2xl" className="space-y-8">
				<IntegrationsSectionHeader
					description={integrationsPickerCopy.description}
					title={integrationsPickerCopy.title}
				/>

				<MotionReveal>
					<div className="overflow-hidden rounded-xl border border-border bg-background">
						<div className="relative border-border border-b px-3 py-2">
							<SearchIcon className="pointer-events-none absolute top-1/2 left-4 size-4 -translate-y-1/2 text-muted-foreground" />
							<Input
								className="h-10 border-0 bg-transparent pl-9 shadow-none focus-visible:ring-0"
								onChange={(event) => setQuery(event.target.value)}
								placeholder="Search connectors…"
								value={query}
							/>
						</div>

						<div className="grid md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
							<MotionStaggerGroup className="max-h-64 divide-y divide-border overflow-y-auto md:border-border md:border-r">
								{filtered.length === 0 ? (
									<p className="p-5 text-center text-muted-foreground text-sm">No matches</p>
								) : (
									filtered.map((brand, index) => (
										<MotionStaggerItem key={brand.domain}>
											<button
												className={`flex min-h-11 w-full items-center gap-3 px-4 py-2.5 text-left transition-colors ${
													index === activeIndex ? "bg-muted/40" : "hover:bg-muted/25"
												}`}
												onClick={() => setActiveIndex(index)}
												type="button"
											>
												<IntegrationLogo domain={brand.domain} name={brand.name} size="sm" />
												<span className="min-w-0">
													<span className="block font-medium text-sm">{brand.name}</span>
													{brand.tag ? (
														<span className="block truncate text-muted-foreground text-xs">
															{brand.tag}
														</span>
													) : null}
												</span>
											</button>
										</MotionStaggerItem>
									))
								)}
							</MotionStaggerGroup>

							<div className="relative min-h-[10rem] p-5">
								<AnimatePresence initial={false} mode="wait">
									{active ? (
										<motion.div
											animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
											exit={{ opacity: 0, y: 4, filter: "blur(4px)" }}
											initial={{ opacity: 0, y: 4, filter: "blur(4px)" }}
											key={active.domain}
											transition={integrationItemSpring}
										>
											<div className="flex items-center gap-3">
												<div className="flex size-10 items-center justify-center rounded-xl bg-muted/30">
													<IntegrationLogo
														domain={active.domain}
														name={active.name}
														size="sm"
													/>
												</div>
												<div>
													<p className="font-medium text-sm">{active.name}</p>
													{active.tag ? (
														<p className="text-muted-foreground text-xs">{active.tag}</p>
													) : null}
												</div>
											</div>
											<p className="mt-3 text-pretty text-muted-foreground text-sm leading-relaxed">
												{active.description}
											</p>
										</motion.div>
									) : null}
								</AnimatePresence>
							</div>
						</div>
					</div>
				</MotionReveal>
			</ContentRail>
		</SectionShell>
	);
}
```
