# Scope breadcrumb

Where the person is, level by level (client, then workspace, then project), each level a menu to move to another place at that level. A trigger shows the current value and its name starts with it ("Northwind, client: change"); a level with no value says "Choose client", never picking one for the person. The last level is marked as the current page.

It draws the levels it is given: when a higher level changes, the app clears or resets the deeper ones. Names are drawn as text in their own isolate, since they can be other tenants'. Its menus open through `LayerPortal`, so they sit above the app's own dialogs and sheets.

- Registry name: `scope-breadcrumb` (`@candor/scope-breadcrumb`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/scope-breadcrumb

## Install

```bash
npx candor-ui add scope-breadcrumb
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/scope-breadcrumb`.
It brings in: bidi, breadcrumb, dropdown-menu, link, locale, locked, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { ScopeBreadcrumb, type ScopeLevel } from "@/components/candor/scope-breadcrumb"
import { TooltipProvider } from "@/components/candor/ui/tooltip"

const clients = [
  { id: "acme", label: "Acme" },
  { id: "globex", label: "Globex" },
]
const workspaces: Record<string, { id: string; label: string }[]> = {
  acme: [
    { id: "acme-prod", label: "Production" },
    { id: "acme-sandbox", label: "Sandbox" },
  ],
  globex: [{ id: "globex-prod", label: "Production" }],
}

export default function Example() {
  const [client, setClient] = React.useState("acme")
  const [workspace, setWorkspace] = React.useState<string | null>("acme-prod")
  const options = Object.hasOwn(workspaces, client) ? workspaces[client]! : []
  const levels: ScopeLevel[] = [
    { id: "client", kind: "client", value: clients.find((c) => c.id === client) ?? null, options: clients },
    { id: "workspace", kind: "workspace", value: options.find((w) => w.id === workspace) ?? null, options },
    { id: "region", kind: "region", value: { id: "eu", label: "EU West" }, locked: "Data stays in the region the workspace was created in." },
  ]
  return (
    <TooltipProvider>
      <div className="rounded-xl border bg-card p-3">
        <ScopeBreadcrumb
          levels={levels}
          onChange={(level, id) => {
            if (level === "client") {
              setClient(id)
              // A new client clears the deeper level: the person chooses the workspace.
              setWorkspace(null)
            } else if (level === "workspace") setWorkspace(id)
          }}
        />
      </div>
    </TooltipProvider>
  )
}
```

## <ScopeBreadcrumb>

Props:

- `levels: ScopeLevel[]`: The levels, outermost first.
- `onChange?: ((levelId: string, optionId: string) => void) | undefined`: Called with the level's id and the option's id when the person moves to another place at that level.
- `label?: string | undefined`: The navigation's accessible name. "Scope" by default.
- `className?: string | undefined`: Classes for the navigation, merged with Candor's.

## Strings (`scopeBreadcrumb`)

The fixed words, English by default. Override any of them under `scopeBreadcrumb` in `<LocaleProvider strings={{ scopeBreadcrumb: { … } }}>`; the table is exported as `scopeBreadcrumbStrings`.

- `label`: Scope
- `change`: {value}, {kind}: change
- `choose`: Choose {kind}
- `unset`: No {kind} chosen

## Types

### ScopeOption

One place a level can be set to: an id for `onChange`, its name, and a link to it when choosing it navigates.

```ts
{
  /** Passed to `onChange` when it is chosen. */
  id: string;
  /** Its name, drawn as text (it can be another tenant's). */
  label: string;
  /** A link to the scope, when choosing it navigates (the item is then a link the browser can open in a new tab). */
  href?: string | undefined;
}
```

### ScopeLevel

One level of a `ScopeBreadcrumb`: a client, a workspace, a project, each the app's word.

```ts
{
  /** Passed to `onChange` with the option chosen. */
  id: string;
  /** The app's word for the level as it reads inside a sentence ("client", "workspace"), in the trigger's name and in "Choose client". */
  kind: string;
  /** The scope the person is in at this level, or null when none is chosen yet. */
  value: {
    id: string;
    label: string;
  } | null;
  /** What the level can be changed to. With options (or a lock) the level is a menu; without, plain text. */
  options?: ScopeOption[] | undefined;
  /** Why this level can't be changed now. The trigger stays focusable, `aria-disabled`, and says why. `null` or left out: unlocked. */
  locked?: string | null | undefined;
}
```

## Motion

- **tint** (tint): A level's trigger changes colour when the pointer is over it, and a ring fades in when it takes keyboard focus. Its menu opens as `DropdownMenu`'s does.

Set with `--cd-motion-scope-breadcrumb-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
