Your app in the middle of eight tools on an orbit, data moving both ways: syncs come in as dots, pushes go out as squares, and the count climbs; one tool connects as you watch, its link drawing in. Tooltips, a lit link on hover, pause, and icons only on phones.
When to use
- Your app in the middle of the tools it connects to, data moving both ways.
- Integration pages, and a live view of syncs in an integrations settings page.
When not to use
- Products with one or two integrations: a short list with their logos is clearer.
- Managing integrations (connecting, keys, scopes): a settings list does that work; the hub only shows it.
Usage
Integrations hub with your own data: every prop has the sample as its default, so pass only what you have.
import { useEffect, useRef } from "react"
import { IntegrationsHub, type HubHandle } from "@/components/dashral/blocks/integrations-hub"
const hub = useRef<HubHandle>(null)
useEffect(() => syncs.on("sync", (s) => hub.current?.sync(s.integration, s.direction)), [])
<IntegrationsHub ref={hub} mode="live" integrations={integrations} />API reference
IntegrationsHub
The section: a heading, the hub in a dotted panel with a Live badge, a quiet message as an integration connects, and Pause, then a row of figures. Every part comes from props, with a sample hub by default.
| Prop | Type | Default |
|---|---|---|
eyebrow | React.ReactNode | "Integrations" |
title | React.ReactNode | sample |
description | React.ReactNode | sample |
integrations | Integration[]The integrations round your app; a | sample |
syncs | numberSyncs counted before the first one on screen. | 1284 |
connectedMessage | (integration: Integration) => stringWhat the bar says as an integration connects. | sample |
stats | { value: string; label: string }[]The figures under the diagram; none hides the row. | sample |
mode | HubProps["mode"]With your own syncs and connections instead of sample ones (see Hub's mode="live"). | — |
ref | React.Ref<HubHandle> | — |
Hub
Your product in the middle of its integrations, with data moving both ways: syncs come in as hollow dots, pushes go out as solid squares, and the count climbs. A pending integration connects partway through each loop, its link drawing in from the hub. One requestAnimationFrame loop writes the SVG directly; React doesn't render per frame. Sample syncs by default; with mode="live", only the ones you send (see HubHandle). It stops off screen, in a hidden tab, when paused and (live) with nothing moving; with reduced motion everything is connected and still (live, as you've connected it).
| Prop | Type | Default |
|---|---|---|
integrations | Integration[]Required. | — |
syncs | numberSyncs counted before the first one on screen; live, every sync that lands adds one. | 1284 |
speed | numberHow fast it runs: 1 is the usual pace, 2 twice as fast, 0 holds it still. | 1 |
paused | booleanBrings it to rest where it is (easing down, not stopping in one frame). | false |
onConnect | (integration: Integration, connected: boolean) => voidCalled when the pending integration connects, and again (with false) when the loop starts over (live: when you disconnect it). | — |
mode | "demo" | "live""demo" plays sample syncs on its own; "live" moves one only when you send it (see HubHandle). | "demo" |
ref | React.Ref<HubHandle> | — |
className | string | — |
type Integration
| Prop | Type | Default |
|---|---|---|
id | string | — |
label | string | — |
detail | stringMore about it, in a tooltip on hover and keyboard focus (touch screens don't show it, so nothing essential). | — |
mark | React.ReactNodeIts mark (white, on | — |
color | string | — |
pending | booleanNot connected yet: in the demo it connects partway through each loop; live, when you call connect(). | — |
type HubHandle
Moves syncs and connects integrations in a live hub (mode="live").
| Prop | Type | Default |
|---|---|---|
sync | (id: string, direction?: "in" | "out") => voidSends one sync along an integration's link: "in" from it to your app (a hollow dot), "out" from your app to it (a solid square). A pending integration's syncs are ignored until it's connected. Each one adds to the count as it lands; off screen, in a hidden tab or with reduced motion it isn't drawn, but it's counted at once. | — |
connect | (id: string) => voidConnects an integration: its link draws in from the hub and it turns live (onConnect hears it). | — |
disconnect | (id: string) => voidLets an integration go: its link turns dashed and quiet again. | — |