# Smart Pick

> A follow-up question that opens where a change landed, with the app's best guesses first and a search for anyone else.

- Name: `smart-pick`
- Status: beta
- Categories: forms
- File: the install writes `smart-pick.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Other registry items: `@arbokui/motion`, `@arbokui/button`, `@arbokui/input`, `@arbokui/tooltip`. The install adds them.
- Gallery: https://arbokui.com/components/smart-pick
- Registry item: https://arbokui.com/r/smart-pick.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/smart-pick
   ```

## Use it for

- Ask the one thing a change left open, right where it landed, like who should review a ticket that was just filed.
- Rank suggestions with what the app already knows, best first, and say why in reason, like "Reviewed the last two".
- The 1, 2, and 3 keys pick the first three guesses from anywhere on the page. Each guess's tooltip teaches its key. The keys are never printed.
- Pass options, the full list, to add a search field for anyone else.
- Show it, and what replaces it once picked, inside \<PanelSwitch effect="fade">, so the height glides instead of jumping.
- onDismiss runs on Esc and on the close button. Leave the answer empty then, and let people fill it in later.
- DeltaBump, from the same file, shows a change like "+5" rising from the value it changed. Give it a new key to play it again.

## Avoid it for

- A question that must be answered before going on. Use a Dialog.
- More than three or four suggestions. Past three, they have no key, and the guess is not a guess.
- Opening it on page load. It follows a change the person just made.
- Two open at once. Only the newest hears the number keys.

## Props

- `title`: `string`. The question, like "Who should review this?". It names the group and the list.
- `hint`: `string`. A quiet line under the question, like "Ticket 482 by Ava Patel".
- `suggestions`: `SmartPickOption[]`. Required. The best guesses, best first. Each is { id, label, reason?, leading?, keywords? }. The first three answer to 1, 2, and 3.
- `options`: `SmartPickOption[]`. The full list. With it, a search field finds anyone, matching label and keywords.
- `onPick`: `(option: SmartPickOption) => void`. Runs with the option picked by a press, Enter, or 1 to 3.
- `onDismiss`: `() => void`. Runs on Esc, with no search typed, and on the close button. Leaves the question unanswered.
- `placeholder`: `string`. Default `"Someone else…"`. The search field's placeholder. Its name drops the ellipsis.
- `SmartPickOption.leading`: `ReactNode`. Before the name, like an avatar or initials.
- `DeltaBump`: `{ value: string; tone?: "neutral" | "brand" | "danger" | "success" | "warning" }`. Part. "+5" rising from the value it changed, then gone. Put it in a relative box around the value.
- `tone`: `"neutral" | "brand" | "danger" | "success" | "warning"`. Default `"neutral"`. On DeltaBump. The change's color.
- `className`: `string`. Extra classes on the box. Later classes win, so className='p-2' replaces the padding.

## Accessibility

- It is a group named by its title. The guesses are a listbox of options, and each keyed guess carries aria-keyshortcuts.
- It opens with focus left where it was. 1 to 3 and Esc reach it from anywhere on the page, but not from a field that types text, or a menu or dialog opened above it, or while it is inert.
- With a search field, the field is a combobox: ↑ and ↓ move the highlight, Enter picks, and aria-activedescendant points at the highlight. Without one, the list is the Tab stop and does the same, with Home and End too.
- The pointer and the keys share one highlight. A finger does not highlight.
- Esc clears a typed search first, then leaves the question unanswered. It stops there, so a Sheet around it stays open.
- "No one matches" is said through a status message.
- DeltaBump is hidden from screen readers. Say the change where the value is.
- Option text is accent-foreground on the accent fill when highlighted, a pair the theme tests hold to 4.5:1 in light and dark.

## Examples

### Pick, then show the answer in place (needs @arbokui/panel-switch)

```tsx
"use client";

import { useState } from "react";

import { PanelSwitch } from "@/components/ui/panel-switch";

function Reviewer({ people }: { people: SmartPickOption[] }) {
  const [reviewer, setReviewer] = useState<SmartPickOption | null>(null);
  const [open, setOpen] = useState(true);
  return (
    <PanelSwitch effect="fade" value={open ? "pick" : "done"}>
      {open ? (
        <SmartPick
          title="Who should review this?"
          hint="Ticket 482 by Ava Patel"
          suggestions={people.slice(0, 3)}
          options={people}
          onPick={(option) => {
            setReviewer(option);
            setOpen(false);
          }}
          onDismiss={() => setOpen(false)}
        />
      ) : (
        <p>Reviewer: {reviewer?.label ?? "Not set"}</p>
      )}
    </PanelSwitch>
  );
}
```

### Guesses only, with no search

```tsx
<SmartPick
  title="Who owns this incident?"
  suggestions={[
    { id: "tom", label: "Tom Hale", reason: "On call this week" },
    { id: "jo", label: "Jo Okafor" },
  ]}
  onPick={(option) => save(option.id)}
/>
```

### A value that bumps by +2

```tsx
<span className="relative">
  19
  <DeltaBump key={bumps} value="+2" tone="success" />
</span>
```
