Environment list
ProThe 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.
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
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
"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>
| Prop | Type | Default | Description |
|---|---|---|---|
| environmentsrequired | ConnectedEnvironment[] | — | 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. |
| disconnectrequired | EnvironmentDisconnect | — | What disconnecting stops and what stays, shown before the person confirms. |
| onConnect | (() => void) | undefined | — | Connects another: shown as "Connect another". |
| label | string | undefined | — | The list's name. Defaults to "Connected environments", in the app's noun. |
| headingLevel | HeadingLevel | 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. |
| className | string | 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.
| Key | English |
|---|---|
| label | Connected {environments} |
| empty | No {environments} connected yet. |
| production | Production |
| nonProduction | Non-production |
| connected | Connected |
| expired | Connection expired |
| notConnected | Not connected |
| disconnected | Disconnected |
| active | Active |
| synced | Synced |
| setActive | Set active |
| setActiveLocked | Reconnect it before setting it active. |
| reconnect | Reconnect |
| disconnect | Disconnect |
| setActiveName | Set active: {name} |
| reconnectName | Reconnect {name} |
| disconnectName | Disconnect {name} |
| connectAnother | Connect another |
| disconnectTitle | Disconnect {name}? |
| stops | What stops |
| stays | What stays |
| noneActive | Nothing will be active until you set another. |
| keep | Keep connected |
| confirmDisconnect | Disconnect |
| disconnecting | Disconnecting… |
| busy | Wait for the disconnect to finish. |
| doneAnnounce | {name} disconnected. |
| failed | Couldn’t disconnect {name}: {reason}. It’s still connected. |
| noReason | the 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"; }