Skip to content
Tier

Environment list

Pro

The environments an app is connected to, one row each: its name and kind (production marked as a warning), its connection in words ("Connected", "Connection expired", "Not connected"), when it last synced ("Never synced" when it hasn't), and a line about it. The active one says "Active" and has aria-current.

Set active is on the others; on one that isn't connected it stays, locked, saying it needs reconnecting. Reconnect is on an expired or disconnected one (open EnvironmentConnect from onReconnect). Disconnect asks first, in the row: what stops and what stays, with "Disconnect" and "Keep connected" and neither chosen for the person; focus moves to the question, so it is read once. Disconnecting the active one adds that nothing is active until another is set. "Disconnected" shows only once onDisconnect's promise resolves, until the app passes the new state; a rejection says why and that it's still connected. When a control leaves the row (Set active once it's active, Disconnect once it's disconnected), focus moves to the row's next control, never to the page's body.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0 and 4.3.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/environment-list

Brings in: bidi, button, environment-band, freshness-stamp, heading-level, heading-scope, locale, locked, team, tokens, use-announcer, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { EnvironmentList, type ConnectedEnvironment } from "@/components/candor/environment-list"

const hoursAgo = (h: number) => new Date(Date.now() - h * 3_600_000).toISOString()

const start: ConnectedEnvironment[] = [
  { id: "prod", name: "Shop", production: true, hue: 1, connection: "connected", active: true, lastSyncAt: hoursAgo(0.2), summary: "1,667 items" },
  { id: "staging", name: "Shop", detail: "staging", production: false, hue: 2, connection: "connected", lastSyncAt: hoursAgo(26), summary: "1,640 items" },
  { id: "old", name: "Shop", detail: "dev-2", production: false, hue: 3, connection: "not-connected", lastSyncAt: null },
]

export default function Example() {
  const [environments, setEnvironments] = React.useState(start)
  return (
    <EnvironmentList
      className="max-w-2xl"
      environments={environments}
      onSetActive={(id) => setEnvironments((all) => all.map((e) => ({ ...e, active: e.id === id })))}
      onReconnect={() => {}}
      // Stands in for your service: it answers after a moment, then the list shows what it reports.
      onDisconnect={async (id) => {
        await new Promise((resolve) => setTimeout(resolve, 900))
        setEnvironments((all) => all.map((e) => (e.id === id ? { ...e, connection: "not-connected", active: false } : e)))
      }}
      disconnect={{
        stops: ["Syncing stops", "Agents can no longer read or change anything there"],
        stays: ["Everything already synced, and the history of what agents did"],
      }}
      onConnect={() => {}}
    />
  )
}

<EnvironmentList>

PropTypeDefaultDescription
environmentsrequiredConnectedEnvironment[]—The environments, in order.
onSetActive((id: string) => void) | undefined—Makes one the active one. The app passes the list back with it `active`.
onReconnect((id: string) => void) | undefined—Starts reconnecting an expired or disconnected one: open `EnvironmentConnect`.
onDisconnect((id: string) => Promise<void> | void) | undefined—Disconnects one, after the person confirms. "Disconnected" shows once the promise resolves; a rejection's message says why it failed.
disconnectrequiredEnvironmentDisconnect—What disconnecting stops and what stays, shown before the person confirms.
onConnect(() => void) | undefined—Connects another: shown as "Connect another".
labelstring | undefined—The list's name. Defaults to "Connected environments", in the app's noun.
headingLevelHeadingLevel | undefined—The level of the list's heading, so it fits under the page's own headings. Default: one below the nearest `HeadingScope`, else 2 (`<h2>`). Its rows have no headings.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under environmentList in <LocaleProvider strings={{ … }}>; the table is exported as environmentListStrings.

KeyEnglish
labelConnected {environments}
emptyNo {environments} connected yet.
productionProduction
nonProductionNon-production
connectedConnected
expiredConnection expired
notConnectedNot connected
disconnectedDisconnected
activeActive
syncedSynced
setActiveSet active
setActiveLockedReconnect it before setting it active.
reconnectReconnect
disconnectDisconnect
setActiveNameSet active: {name}
reconnectNameReconnect {name}
disconnectNameDisconnect {name}
connectAnotherConnect another
disconnectTitleDisconnect {name}?
stopsWhat stops
staysWhat stays
noneActiveNothing will be active until you set another.
keepKeep connected
confirmDisconnectDisconnect
disconnectingDisconnecting…
busyWait for the disconnect to finish.
doneAnnounce{name} disconnected.
failedCouldn’t disconnect {name}: {reason}. It’s still connected.
noReasonthe service didn’t say why

Types

ConnectedEnvironment
An environment as the list shows it: which one is active, when it last synced, and a line about it.
Environment & { /** The one the app acts on now. One at most. */ active?: boolean | undefined; /** When it last synced (ISO date); null when it never has. */ lastSyncAt: string | null; /** A line about it in the app's words ("1,667 items"), drawn as plain text. */ summary?: string | undefined; }
EnvironmentDisconnect
What disconnecting stops and what stays, in the app's words, as `IntegrationConnect` says it.
{ /** What stops: "Syncing stops", "Agents lose access". */ stops: string[]; /** What stays: "History and reports stay". */ stays: string[]; }

Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

HeadingLevel from @/lib/candor/heading-level
A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.
1 | 2 | 3 | 4 | 5 | 6
Environment from @/lib/candor/team
Where agents act: a CRM instance, a database, a repository, a project. The app names them; LocaleProvider's `nouns` sets what they're called ("project", "workspace").
{ id: string; /** The name shown everywhere, e.g. "Shop". */ name: string; /** What tells same-named ones apart, e.g. "dev-2" or "staging". */ detail?: string | undefined; /** Changes here reach real users, so components warn and ask for more care. */ production: boolean; /** A chart slot (1–5) so each environment keeps one colour everywhere it appears. */ hue?: 1 | 2 | 3 | 4 | 5 | undefined; connection: "connected" | "expired" | "not-connected"; }