# Patch bay

A rack of jacks joined by patch cables: every route from a source to a destination is a cable you can see and pull. For webhook events to handlers, agent tools to agents, and inputs to outputs.

- Category: Instruments
- Docs: https://bevel-ui.vercel.app/components/patch-bay
- Import from: `@/components/ui/patch-bay`

## Install

```bash
npx shadcn@latest add @bevel/patch-bay
```

npm dependencies (installed by the CLI): `@base-ui/react`, `cn`.

## Anatomy

```tsx
<PatchBay aria-label="Webhook routing" value={routes} onValueChange={setRoutes}>
  <PatchBayPanel>
    <PatchBayRow side="source" label="Events">
      <PatchBayJack value="invoice.paid">invoice.paid</PatchBayJack>
    </PatchBayRow>
    <PatchBayRow side="destination" label="Handlers">
      <PatchBayJack value="receipt">Send receipt</PatchBayJack>
    </PatchBayRow>
  </PatchBayPanel>
  <PatchBayRoutes />
</PatchBay>
```

## Parts

| Part | Renders | Description |
| --- | --- | --- |
| `PatchBay` | div (group) | value and defaultValue: [source, destination] pairs. onValueChange, disabled. Announces each patch, and lists the routes for screen readers. Give it an aria-label. |
| `PatchBayPanel` | div | The rack panel. Draws the cables, which hang a little between the rows, in a colour per source. |
| `PatchBayRow` | div (group) | side source or destination, and a label naming the row. Sources sit on top, destinations below. |
| `PatchBayJack` | Button | value; its text is its name. Press one jack, then one in the other row, to patch them; patch the same pair again to pull it. Escape cancels; Delete pulls all its cables. Described as “Patched to Send receipt and Update ledger.” |
| `PatchBayRoutes` | ul | Optional: a visible list of routes with an Unpatch button each. Replaces the built-in screen-reader list. |

## Example: Webhook routing

Patch a billing provider's events to your handlers. One event can feed several handlers.

```tsx
"use client"

import * as React from "react"
import {
  PatchBay,
  PatchBayJack,
  PatchBayPanel,
  PatchBayRoutes,
  PatchBayRow,
  type PatchBayRoute,
} from "@/components/ui/patch-bay"

const events = ["invoice.paid", "charge.failed", "trial.ending", "sub.deleted"]
const handlers = [
  { value: "receipt", label: "Send receipt" },
  { value: "ledger", label: "Update ledger" },
  { value: "slack", label: "Slack #billing" },
  { value: "dunning", label: "Dunning email" },
  { value: "seats", label: "Revoke seats" },
]

/** Route a billing provider's webhook events to your handlers. Press an event, then a handler. */
export function PatchBayWebhooks() {
  const [routes, setRoutes] = React.useState<PatchBayRoute[]>([
    ["invoice.paid", "receipt"],
    ["invoice.paid", "ledger"],
    ["charge.failed", "dunning"],
  ])
  const unrouted = events.filter((e) => !routes.some(([from]) => from === e))
  return (
    <PatchBay aria-label="Webhook routing" value={routes} onValueChange={setRoutes} className="max-w-xl">
      <PatchBayPanel>
        <PatchBayRow side="source" label="Stripe events">
          {events.map((e) => (
            <PatchBayJack key={e} value={e}>
              {e}
            </PatchBayJack>
          ))}
        </PatchBayRow>
        <PatchBayRow side="destination" label="Handlers">
          {handlers.map((h) => (
            <PatchBayJack key={h.value} value={h.value}>
              {h.label}
            </PatchBayJack>
          ))}
        </PatchBayRow>
      </PatchBayPanel>
      <div className="flex flex-col gap-2 rounded-xl border bg-card p-4">
        <p className="text-sm font-semibold">
          {routes.length} route{routes.length === 1 ? "" : "s"}
          {unrouted.length > 0 && (
            <span className="font-normal text-muted-foreground">
              {" "}
              · {unrouted.join(", ")} {unrouted.length === 1 ? "goes" : "go"} nowhere
            </span>
          )}
        </p>
        <PatchBayRoutes />
      </div>
    </PatchBay>
  )
}
```
