# rn-avatar-select URL: /docs/native/docyrus/avatar-select Avatar picker with Icon + Color (Font Awesome / Huge Icons), Emoji and Image tabs, multi-column field mapping and a ready-to-save payload. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-avatar-select ``` **Dependencies:** - [@shopify/flash-list](https://www.npmjs.com/package/@shopify/flash-list) - [expo-image-picker (optional)](https://docs.expo.dev/versions/latest/sdk/imagepicker/) - [expo-image-manipulator (optional)](https://docs.expo.dev/versions/latest/sdk/imagemanipulator/) The Image tab needs the optional peer `expo-image-picker` (`npx expo install expo-image-picker`); without it the tab shows an install hint. `imageCrop="editor"` additionally uses the native [rn-image-editor](/docs/native/docyrus/image-editor), which needs `expo-image-manipulator` to apply the crop. ## Usage ```tsx import { AvatarSelect, type AvatarFieldValue } from '@/components/docyrus-native/avatar-select'; const [avatar, setAvatar] = useState({ icon: null, color: null, image: null }); setAvatar(value)} /> ``` ### Field mapping + payload Records often store the avatar in three columns. The mapping renames the keys of the `payload` passed to `onChange` / `onCommit` — the same contract as web. ```tsx import { extractAvatarValue } from '@/lib/docyrus/avatar-utils'; updateRecord(record.id, payload)} // payload → { avatar_icon: 'fal rocket', avatar_color: 'indigo-500', avatar_image: null } /> ``` ### Uploading images `uploadImage` receives the picked (and cropped) file and returns the stored value. Without it the local file URI is stored as `image.signed_url`. ```tsx { const stored = await uploadFile(asset.uri, asset.fileName, asset.mimeType); return { id: stored.id, file_name: stored.name, signed_url: stored.url, file_type: asset.mimeType }; }} onChange={setAvatar} /> ``` - `imageCrop="system"` (default): the OS picker's own square crop (`allowsEditing: true, aspect: [1, 1]`). - `imageCrop="editor"`: picks without cropping, then shows the native `ImageEditor` with a 1:1 stencil. ### Inline editor ```tsx ``` ## Behaviour | Tab | Emits | |-----|-------| | Icon + Color — pick an icon | `{ icon, color: current color ?? 'indigo-500', image: null }` | | Icon + Color — pick a colour | `{ color, icon: current icon ?? first featured icon, image: null }` | | Emoji | `{ icon: emoji, color: null, image: null }` | | Image | `{ icon: null, color: null, image: uploaded value }` | | Clear (native only) | `{ icon: null, color: null, image: null }` | - The colour palette is `TAILWIND_AVATAR_COLORS` — all 22 Tailwind families at levels 200 and 500 (`'indigo-500'`, `'rose-200'`, …), identical to web, so values round-trip between platforms. - The icon library switch covers Font Awesome (`fal *`) and Huge Icons (`huge *`). An empty search shows the featured set; a search matches up to 120 icons, rendered in a 6-column FlashList grid. - The full icon catalog (`lib/icon-libraries`, ~11k names) is loaded lazily the first time the Icon tab is shown, so importing AvatarSelect does not parse it at start-up. - The emoji list is native-only and searchable by keyword; it starts with web's emoji set. - The initial tab is derived from the value: image → Image, emoji → Emoji, otherwise Icon + Color. ## API Reference | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `Partial \| null` | — | Current avatar value (normalised with `normalizeAvatarValue`). | | `size` | `number` | `40` | Thumbnail size in points (web uses Tailwind spacing units). | | `editorDisplay` | `'inline' \| 'popover'` | `'popover'` | `inline` renders the editor in place; `popover` opens it in an ActionSheet from the thumbnail. | | `iconField` | `string \| null` | `'icon'` | Payload key for the icon. | | `colorField` | `string \| null` | `'color'` | Payload key for the colour. | | `imageField` | `string \| null` | `'image'` | Payload key for the image. | | `uploadImage` | `(asset: AvatarUploadAsset) => Promise` | — | Uploads the picked / cropped image and returns the stored value. Without it the local URI is used. | | `imageCrop` | `'system' \| 'editor'` | `'system'` | Crop with the OS picker UI or the native `ImageEditor`. | | `disabled` | `boolean` | `false` | Disables the trigger and every control. | | `onChange` | `(value: AvatarFieldValue, payload: Record) => void` | — | Fired on every change with the value and the field-mapped payload. | | `onCommit` | `(value: AvatarFieldValue, payload: Record) => void` | — | Fired alongside `onChange` — persist here. | | `className` | `string` | — | Additional classes for the trigger (popover) or the root (inline). | ### AvatarFieldValue | Property | Type | Description | |----------|------|-------------| | `icon` | `string \| null` | Icon string (`'fal rocket'`, `'huge star'`) or an emoji. | | `color` | `string \| null` | Tailwind colour name (`'indigo-500'`), resolved with `resolveColorHex`. | | `image` | `AvatarImageValue \| null` | Uploaded image. | ### AvatarImageValue | Property | Type | Description | |----------|------|-------------| | `id` | `string` | File identifier. | | `file_name` | `string` | File name. | | `signed_url` | `string \| null` | URL used to display the image. | | `file_type` | `string` | MIME type. | | `file_size` | `number` | Size in bytes. | | `source` | `string` | Storage source. | | `[key: string]` | `unknown` | Any extra storage fields. | ### AvatarUploadAsset | Property | Type | Description | |----------|------|-------------| | `uri` | `string` | Local file URI. | | `fileName` | `string` | File name (`-cropped.png` after an editor crop). | | `mimeType` | `string` | MIME type. | | `width` | `number` | Width in pixels. | | `height` | `number` | Height in pixels. | | `fileSize` | `number \| undefined` | Size in bytes, when known. | ## Avatar utilities `lib/avatar-utils` (installed with the component) is synced with web: | Export | Description | |--------|-------------| | `resolveAvatarFieldMapping(mapping?)` | Fills missing keys with `DEFAULT_AVATAR_FIELDS`. | | `normalizeAvatarValue(value?)` | Trims strings to `null`, keeps only image-like objects. | | `extractAvatarValue(record, mapping?)` | Reads a value out of a record using the mapping. | | `buildAvatarPayload(value, mapping?)` | `{ [iconField], [colorField], [imageField] }`. | | `DEFAULT_AVATAR_FIELDS` / `EMPTY_AVATAR_VALUE` | Defaults. | | `TAILWIND_AVATAR_COLORS` / `TAILWIND_AVATAR_COLOR_LEVELS` | Palette (22 families × 200 / 500). | | `TAILWIND_COLOR_FAMILIES` / `TAILWIND_HEX` | Full Tailwind v4 palette. | | `getTailwindColorLevel(color)` | `'indigo-500'` → `500`. | | `resolveColorHex(color)` / `resolveColorCssValue(color)` | Tailwind name → hex (unknown values pass through). | | `isEmojiIcon(value)` / `getReadableTextColor(hex)` | Helpers. | ## Differences from web - The **AI** tab is not ported (it is disabled "coming soon" on web too). - `size` is in points, not Tailwind spacing units. - `editorDisplay` defaults to `'popover'` (an ActionSheet — native's behaviour before the port); web defaults to `'inline'`. - `uploadImage` receives an `AvatarUploadAsset` instead of a `File`. - Native adds a Clear action and a searchable emoji list. ## i18n `ui.common.changeAvatar`, `ui.common.searchIconPlaceholder`, `ui.common.noIconsFound`, `ui.common.color` (web keys) plus `ui.avatarSelect.title`, `ui.avatarSelect.iconTab`, `ui.avatarSelect.emojiTab`, `ui.avatarSelect.imageTab`, `ui.avatarSelect.fontAwesome`, `ui.avatarSelect.hugeIcons`, `ui.avatarSelect.pickColor`, `ui.avatarSelect.searchEmojiPlaceholder`, `ui.avatarSelect.noEmojiFound`, `ui.avatarSelect.uploadAndCrop`, `ui.avatarSelect.uploadHint`, `ui.avatarSelect.pickerMissing`, `ui.avatarSelect.uploadFailed`, `ui.avatarSelect.squareAvatar`, `ui.avatarSelect.clear`, `ui.avatarSelect.done`. ## Type Exports | Type | Description | |------|-------------| | `AvatarSelectProps` | Props for the AvatarSelect component. | | `AvatarFieldValue` | Avatar value (icon, color, image). | | `AvatarImageValue` | Image file value. | | `AvatarFieldMapping` | `{ iconField?, colorField?, imageField? }`. | | `AvatarUploadAsset` | Asset passed to `uploadImage` (alias of `ImageEditorAsset`). | | `AvatarSelectTab` | `'icon' \| 'emoji' \| 'image'`. | | `AvatarSelectEditorDisplay` | `'inline' \| 'popover'`. | | `AvatarSelectImageCrop` | `'system' \| 'editor'`. |