# Photo Field

> A person's own photo, changed in place: their photo at header size, then Change photo and Remove. A new photo opens in a circle to place, and saves as a 256px square.

- Name: `photo-field`
- Status: beta
- Categories: dates, forms
- File: the install writes `photo-field.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/motion`, `@arbokui/avatar`, `@arbokui/button`, `@arbokui/field-frame`, `@arbokui/tool-button`. The install adds them.
- Gallery: https://arbokui.com/components/photo-field
- Registry item: https://arbokui.com/r/photo-field.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/photo-field
   ```

## Use it for

- Every name has a photo. Put this in account settings, in joining, and in a guardian's sheet for their child.
- Say who sees the photo in hint, like "Your workspace and the teams you're on see it."
- A chosen file, or one dropped on the photo, opens in a circle to place. Drag it or use the arrow keys, and zoom with the buttons or + and -.
- Use photo calls onValueChange with a 256px square JPEG data URL. Remove calls it with null. Upload it from there.
- Saving and removing go on the page's ⌘Z stack for 8 seconds. ⌘Z calls onValueChange again with the old photo.
- The photo changes where it is, so nothing else needs to say so. No toast.

## Avoid it for

- Other files, like documents or lists. Use a file drop.
- Team badges or logos that must keep their shape. The photo is cut to a circle.
- Choosing among photos already uploaded. Show them as choices instead.

## Props

- `name`: `string`. Whose photo it is. Their initials show until there is a photo.
- `value`: `string | null`. The photo now: a URL, or null for initials. Use it with onValueChange.
- `defaultValue`: `string | null`. Default `null`. The photo at first render, when you do not control it.
- `onValueChange`: `(photo: string | null) => void`. The new photo as a 256px square JPEG data URL, or null when removed. ⌘Z calls it again with the old one.
- `label`: `ReactNode`. Names the field, above it.
- `hint`: `ReactNode`. Who sees the photo. It describes the field.
- `error`: `ReactNode`. Replaces the hint, in red, and is announced once. A file that is not a photo shows its own error here.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. The photo at 48, 56, or 64px, the circle to place it in, and the buttons.
- `disabled`: `boolean`. Default `false`. Blocks the buttons and drops.
- `className`: `string`. Classes for the frame. id and aria-\* land on the group.

## Accessibility

- The label names the group, and the hint or error describes it.
- Change photo opens the file picker. A file can also be dropped on the photo.
- Placing, focus moves to the circle. The arrow keys move the photo, Shift moves it further, + and - zoom, Enter uses it, and Esc puts it back without closing a sheet around it.
- Zoom in and Zoom out are named buttons too, so placing never needs a drag.
- Once saved or removed, focus returns to Change photo or Add a photo, and a polite status says what changed.
- A file held over the photo shows an outline, which forced-colors mode draws too.

## Examples

### A profile photo, with who sees it

```tsx
<PhotoField
  label="Profile photo"
  name="Riley Shaw"
  hint="Your workspace and the teams you're on see it."
  onValueChange={(photo) => console.log(photo)}
/>
```
