# Resolved field

Resolved field is a neobrutalist React component from Cruise UI. Shows the result of a lookup under its input, e.g. the account name for an account number: "Resolving…" with a spinner, then the value or an error with Try again.

Install (with the @cruiseui registry in components.json, see Installation):

```bash
npx shadcn@latest add @cruiseui/cruise-resolved-field
```

Import: `import { CruiseResolvedField } from "@/components/cruise/resolved-field";`

## When to use

Account-name lookups in transfers, business-name checks, any value fetched from what someone typed.

## When not to

Validating the input's own format (use the field's error).

## Usage

- Keep the status in your component: set status to "resolving" when the account number and bank are complete, then "resolved" with the name or "error".
- Debounce the lookup (about 400ms) and ignore answers that arrive after the input changed.

## Example (React)

```tsx
"use client";

import { useEffect, useState } from "react";
import { CruiseInput } from "@/components/cruise/input";
import { CruiseResolvedField, type CruiseResolveStatus } from "@/components/cruise/resolved-field";

// A pretend lookup: in your app, call your bank-lookup API.
const fakeLookup = (account: string) =>
  new Promise<string | null>((resolve) => setTimeout(() => resolve(account.startsWith("0") ? "ADA OKAFOR" : null), 900));

export default function ResolvedFieldExample() {
  const [account, setAccount] = useState("");
  const [result, setResult] = useState<{ status: CruiseResolveStatus; name?: string; for?: string }>({ status: "idle" });
  const complete = /^\d{10}$/.test(account);
  // Shown state: idle until 10 digits; otherwise the lookup for this number.
  const status: CruiseResolveStatus = !complete ? "idle" : result.for === account ? result.status : "resolving";

  useEffect(() => {
    if (!complete) return;
    let live = true;
    fakeLookup(account).then((name) => live && setResult({ status: name ? "resolved" : "error", name: name ?? undefined, for: account }));
    return () => {
      live = false;
    };
  }, [account, complete]);

  return (
    <div className="flex max-w-md flex-col gap-4">
      <CruiseInput label="Account number" inputMode="numeric" maxLength={10} hint="Try 0123456789 (found) or 9876543210 (not found)." value={account} onChange={(e) => setAccount(e.target.value.replace(/\D/g, ""))} />
      <CruiseResolvedField status={status} value={result.name} onRetry={() => setResult({ status: "idle" })} />
    </div>
  );
}
```

## HTML

Needs theme.css (https://cruiseui.dev/theme.css).

```html
<span class="field-label" id="acct-label">Account name</span>
<div class="resolved" data-state="resolved" role="status" aria-live="polite" aria-describedby="acct-label">
  <svg aria-hidden="true"><!-- circle-check --></svg><span>ADA OKAFOR</span>
</div>
```

## Props: CruiseResolvedField

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `string` | `"Account name"` | What the value is, shown above it. |
| `status` | `CruiseResolveStatus` | — | — |
| `value?` | `ReactNode` | — | The resolved value, e.g. "ADA OKAFOR". |
| `error?` | `ReactNode` | `"We couldn't find that account. Check the number and bank."` | Shown when status is "error". |
| `placeholder?` | `ReactNode` | `"Enter the account number to see the name."` | Shown before a lookup starts (status "idle"). |
| `resolvingText?` | `ReactNode` | `"Resolving…"` | — |
| `onRetry?` | `(() => void)` | — | Adds a "Try again" button to the error. |
| `className?` | `string` | — | — |

## Accessibility

- role="status" with aria-live="polite": each change is read out.
- Resolved and error states use an icon and words, not colour alone.
- "Try again" is a real button.
