# Peek Card

> The standard content of a peek: who or what, status, facts, the people behind a count, fixes, a note, and next actions.

- Name: `peek-card`
- Status: beta
- Categories: overlays
- File: the install writes `peek-card.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/avatar`, `@arbokui/button`, `@arbokui/hover-card`, `@arbokui/kbd`, `@arbokui/person`, `@arbokui/tool-button`, `@arbokui/tooltip`. The install adds them.
- Gallery: https://arbokui.com/components/peek-card
- Registry item: https://arbokui.com/r/peek-card.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/peek-card
   ```

## Use it for

- As a HoverCard's content. Its title names the peek, and in a glance it shows up to 3 facts and 2 actions. People, fixes, and the note always show.
- Facts are \[label, value, tone?\] rows. Say who and when, like \['Back', 'Mon 19 Oct · HR'\], so people know where a fact came from.
- A fact value that is a PersonValue, like { person: 'Steve Rowe' }, shows as a Person with a photo.
- people lists the people behind a count, like the 6 who have not replied, each with a status and an action like Remind.
- fixes lists ways to fix a problem, best first. The first gets the one-press button. detail says what happens.
- Give a press a result, like { label: 'Sent' }. The button slips to it in place. With onUndo, coming back shows Undo for 8 seconds, and ⌘Z undoes it too. Send the change in onCommit.
- Return a promise from onClick or onCommit, and give the result failed words. The button waits while it runs, and turns into Retry if it fails.
- Other controls that leave a result use useActionResult from tool-button, and resultFaces from this file for their faces. undoable, in tool-button, puts a change with no control, like a saved edit, on the page's ⌘Z stack.
- Put the key in shortcut. It shows in the button's tooltip only. Wire the key itself with ActionKeys.

## Avoid it for

- A long form or a menu of choices. Use a Popover, a Menu, or a Dialog.
- A toast to repeat what a pressed button already says. The button carries the result.
- More than one solid action. The main action is the one solid button.
- A go action as the main action. Going somewhere is never the main action.

## Props

- `person`: `string | PersonValue`. Who it is about: a name, or { person, photo }. Shows their avatar.
- `photo`: `string`. The person's photo, beside a name.
- `icon`: `ReactNode`. An icon for what is not a person, in a soft tile.
- `iconTone`: `"neutral" | "brand" | "danger" | "success" | "warning"`. Default `"neutral"`. The icon tile's color role.
- `leading`: `ReactNode`. Your own avatar or icon, in place of person or icon.
- `title`: `string`. Names the peek. Default: the person's name.
- `subtitle`: `string`. A muted line under the title, like "Workshop · Tue 29 Sep · 19:00".
- `status`: `{ label: string; tone?: "neutral" | "brand" | "danger" | "success" | "warning" }`. Colored status text at the top right. Neutral is muted.
- `facts`: `[label: string, value: ReactNode | PersonValue, tone?: PeekTone][]`. Label and value rows. A glance shows the first 3.
- `people`: `{ person; photo?; meta?; group?; done?; status?; action? }[]`. The people behind a count. group puts them under a heading. done is the record from app state, shown in place of the action. action can ask a question with choices, like a team.
- `peopleAction`: `{ label; choices?; ask?; onClick?; result?: ActionResult }`. The action for a row that has none, like Remind. A choice joins the result, like Approved · Support.
- `fixes`: `{ label; detail?; doneDetail?; icon?; cta?; onClick?; result?: ActionResult }[]`. Fixes for a problem, best first. cta labels the button, default "Fix". Once pressed, doneDetail replaces detail.
- `note`: `ReactNode`. A short muted note.
- `actions`: `{ label; icon?; shortcut?; emphasis?; go?; all?; dismiss?; href?; iconOnly?; onClick?; result?: ActionResult }[]`. Next actions on a thin lip at the foot. The first is the main, solid on the right, unless one sets emphasis. outline sits just left of it, and ghost on the left. all marks every person whose action has that label as done. dismiss closes the peek after the click.
- `more`: `{ label?; href?; onClick? }`. The way to the full page. A plain button beside the main action, or a link under the actions when it has href.
- `className`: `string`. Extra classes on the card. Later classes win.
- `ActionResult`: `{ label; doneLabel?; say?; failed?; onUndo?; undone?; onCommit?; seconds?; toast? }`. Type, from tool-button. What a press leaves. label shows once pressed and doneLabel once the window ends. failed is what Retry says. seconds is the window, default 8. toast goes to the toast slot, only for what the button can't show.

## Accessibility

- In a HoverCard, the title names the peek's dialog. On its own, the card has no role.
- A press says its result once, through one polite live region for the page. The button's name follows its state: the label, then the result, with "Press to undo." while a press undoes, then the record.
- A press that fails is named "Retry:" and what failed, so a voice command for Retry finds it.
- Undo arms only after the pointer leaves, or 1.2 seconds after a key or touch press, so a double press never undoes.
- ⌘Z or Ctrl+Z undoes the newest change still in its window. In a text field it stays the field's own undo.
- An icon-only action is named by its label, which shows in its tooltip. A shortcut key shows in the tooltip too.
- An action with choices sets aria-expanded and moves focus to the first choice. Esc puts the choices away and focus goes back to the action.
- Rows with a group sit in a list named by the group heading.

## Examples

### Who is going

```tsx
<PeekCard
  title="21 going"
  subtitle="Workshop · Tue 29 Sep · 19:00"
  people={[
    { person: "Sam Carter", meta: "Host", status: { label: "Going", tone: "success" } },
    { person: "Josh Tait", meta: "Engineer", status: { label: "No reply" } },
  ]}
  peopleAction={{ label: "Remind", result: { label: "Sent" } }}
  more={{ label: "See all 21", href: "/events/29-sep" }}
/>
```

### A person, in a peek

```tsx
<HoverCard
  content={
    <PeekCard
      person="Josh Tait"
      subtitle="Engineer · Engineering"
      status={{ label: "Out sick", tone: "danger" }}
      facts={[["Back", "Mon 19 Oct · HR"], ["Next", "Team sync, Thu 10:00"]]}
      actions={[{ label: "Message" }, { label: "Profile", go: true, href: "/people/josh-tait" }]}
    />
  }
>
  Josh Tait
</HoverCard>
```

### Fixes, best first

```tsx
<PeekCard
  title="Two people in one slot"
  status={{ label: "Clash", tone: "warning" }}
  fixes={[
    { label: "Move Josh to backup", detail: "Kate stays on call.", cta: "Move", result: { label: "Moved" } },
    { label: "Swap with Kate", detail: "Josh is on call, Kate is backup.", cta: "Swap", result: { label: "Swapped" } },
  ]}
/>
```

### A press that can fail

```tsx
<PeekCard
  title="Josh Tait"
  actions={[
    {
      label: "Remind",
      onClick: () => sendReminder("josh"),
      result: { label: "Reminder sent", failed: "Couldn't send the reminder", onUndo: cancelReminder },
    },
  ]}
/>
```
