# rn-enum-option-editor URL: /docs/native/docyrus/enum-option-editor Optimistic editor for a field's selectable options — inline rename, colour and icon pickers, active switch, advanced flags and long-press drag reordering, plus a bottom-sheet launcher and a Docyrus-connected wrapper. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-enum-option-editor ``` **Dependencies:** - [@tanstack/react-query](https://www.npmjs.com/package/@tanstack/react-query) - [@docyrus/api-client](https://www.npmjs.com/package/@docyrus/api-client) - [@docyrus/app-utils](https://www.npmjs.com/package/@docyrus/app-utils) - [react-native-gesture-handler](https://www.npmjs.com/package/react-native-gesture-handler) - [react-native-reanimated](https://www.npmjs.com/package/react-native-reanimated) - [tailwind-variants](https://www.npmjs.com/package/tailwind-variants) The module exports four building blocks, API-aligned with the web `@docyrus/ui` enum option editor: | Export | Kind | Use it for | |--------|------|-----------| | `EnumOptionEditor` | Component | Pure, backend-agnostic inline editor (settings screens, tests) | | `EnumOptionEditorDialog` | Component | The same editor inside a large bottom sheet (web: Dialog) with a trigger | | `DocyrusEnumOptionEditor` | Component | The sheet wired to the Docyrus enum admin API for one field | | `useDocyrusEnumEditorPermission` | Hook | Whether the signed-in user may manage options (ADMIN / ARCHITECT role) | ## Usage ### Pure editor ```tsx import { useState } from 'react'; import { EnumOptionEditor, type EnumEditorOption } from '@/components/docyrus-native/enum-option-editor'; const INITIAL: EnumEditorOption[] = [ { id: 's-1', name: 'Open', color: 'green-500', sort_order: 10, origin: 'inherited' }, { id: 's-2', name: 'Doing', color: 'orange-500', sort_order: 20, origin: 'inherited', isCustomized: true }, { id: 's-3', name: 'On hold', color: '#8b5cf6', icon: 'fal pause', sort_order: 30, origin: 'custom' } ]; export function StatusOptions() { const [options, setOptions] = useState(INITIAL); return ( } /> ``` ### Docyrus-connected (permission-gated) ```tsx import { DocyrusEnumOptionEditor, useDocyrusEnumEditorPermission } from '@/components/docyrus-native/enum-option-editor'; export function ManageStatusOptions({ client, field, dataSource }) { const { canManage } = useDocyrusEnumEditorPermission(client); if (!canManage) return null; return ( refetchSchema()} /> ); } ``` ### Interaction model | Gesture | Result | |---------|--------| | Tap the icon / colour tile | Opens the icon sheet (Font Awesome / Huge Icons, featured + search) or the colour sheet (Tailwind families at tone 500, stored as `blue-500`, plus a free token / hex input) | | Edit the name | Committed on blur or the keyboard's Done key — not per keystroke. Duplicate names show an inline error | | Active switch | Retires / re-activates the option (`active`) | | "⋯" | Action sheet: **More settings** (slug, description, `is_final_option`, `force_description`, `force_followup_date`, active), **Move up / Move down**, **Delete** (custom) or **Revert to default** (inherited, enabled only when customized) | | Long-press the grip + drag | Reorders the list; neighbours slide out of the way and `onReorder(orderedIds)` fires on drop | Every change is applied optimistically; if the matching callback rejects, the row rolls back and shows the error message. ## API Reference ### EnumOptionEditor | Prop | Type | Default | Description | |------|------|---------|-------------| | `options` | `EnumEditorOption[]` | — | **Required.** Initial options, rendered by `sort_order` (nulls last) then name. The editor reseeds when the incoming options change | | `onCreateOption` | `(input: EnumOptionInput) => Promise \| EnumEditorOption` | — | Persist a new option; must resolve to the created option. Omitted → a local `tmp_` row is added | | `onUpdateOption` | `(id: string, patch: Partial) => Promise \| void` | — | Persist an edit (name on blur / submit, pickers, switches, advanced fields) | | `onDeleteOption` | `(id: string) => Promise \| void` | — | Permanently delete a `custom` option | | `onRevertOption` | `(id: string) => Promise \| void` | — | Drop the workspace override of an `inherited` option | | `onReorder` | `(orderedIds: string[]) => Promise \| void` | — | Persist the new order after a drag or Move up / down | | `readOnly` | `boolean` | `false` | Render everything read-only (e.g. options managed via a shared enum set) | | `allowCreate` | `boolean` | `true` | Show the add-option row | | `allowDelete` | `boolean` | `true` | Offer Delete / Revert in the row menu | | `allowReorder` | `boolean` | `true` | Enable the drag grip and the Move up / Move down menu items | | `noticeText` | `string` | — | Info banner (`Alert`) above the list | | `emptyText` | `string` | `'No options yet.'` | Empty-state copy | | `className` | `string` | — | Container className | ### EnumOptionEditorDialog Accepts every `EnumOptionEditor` prop plus: | Prop | Type | Default | Description | |------|------|---------|-------------| | `trigger` | `ReactNode \| ((opts: { onPress: () => void }) => ReactNode) \| null` | "Manage options" button | Custom trigger. A node is wrapped in a `Pressable`; a function receives `onPress`; `null` renders no trigger (fully controlled) | | `open` | `boolean` | — | Controlled open state | | `onOpenChange` | `(open: boolean) => void` | — | Open state callback | | `title` | `string` | `'Manage options'` | Sheet title | | `description` | `string` | `'Add, edit, reorder and retire the selectable options for this field.'` | Sheet message | | `disabled` | `boolean` | — | Disable the default trigger | ### DocyrusEnumOptionEditor | Prop | Type | Default | Description | |------|------|---------|-------------| | `client` | `RestApiClient` | — | **Required.** Authenticated Docyrus REST client | | `appId` | `string` | — | **Required.** App id (`tenant_app_id`) or slug — the `{appId}` path segment | | `dataSourceId` | `string` | — | **Required.** Data-source id | | `fieldId` | `string` | — | **Required.** Field id whose options are managed | | `fieldName` | `string` | — | Used as the sheet title when `title` is omitted | | `trigger` | `ReactNode \| ((opts: { onPress: () => void }) => ReactNode) \| null` | "Manage options" button | Same as `EnumOptionEditorDialog` | | `open` | `boolean` | — | Controlled open state | | `onOpenChange` | `(open: boolean) => void` | — | Open state callback | | `onChanged` | `() => void` | — | Called after every successful write (refresh the owning form's schema) | | `disabled` | `boolean` | — | Disable the default trigger | | `title` | `string` | `fieldName` | Sheet title | Endpoints (all under `/v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId}/enums`): `GET` (options + `enumSetId`), `POST` create, `PATCH` update / reorder (`sortOrder = (i + 1) * 10`), `DELETE` (`enumIds`), `DELETE …/{id}/customization` revert. Ownership (`custom` vs `inherited`) comes from `GET /v1/dev/data-sources/enums?fieldId=…`. A field on a shared enum set opens read-only with a notice. Options are fetched only while the sheet is open (query key `['docyrus-enum-options', dataSourceId, fieldId]`); every write invalidates it and the shared inventory's data sources (`getSharedDocyrusInventory(client).invalidateDataSources()`). Requires the `Architect.ReadWrite.All` scope plus the ADMIN or ARCHITECT role. ### useDocyrusEnumEditorPermission ```ts useDocyrusEnumEditorPermission( client: RestApiClient | undefined, options?: UseDocyrusEnumEditorPermissionOptions ): { canManage: boolean; isLoading: boolean } ``` | Option | Type | Default | Description | |--------|------|---------|-------------| | `enabled` | `boolean` | `true` | Gate the profile fetch | | `adminRoleIds` | `string[]` | `[ADMIN_ROLE_ID, ARCHITECT_ROLE_ID]` | Role ids allowed to manage options | | `meEndpoint` | `string` | `'/v1/users/me'` | Profile endpoint (query key `['docyrus-me', meEndpoint]`, 5-minute stale time) | Constants: `ADMIN_ROLE_ID = 'e37ea658-8cfa-11ed-a098-7703de2a84b1'`, `ARCHITECT_ROLE_ID = '61672102-b5c8-11ee-b08a-23d1fbdaaa6a'`. ## Components | Component | Description | |-----------|-------------| | `EnumOptionEditor` | Inline optimistic option list editor | | `EnumOptionEditorDialog` | Bottom-sheet launcher for the editor | | `DocyrusEnumOptionEditor` | Launcher wired to the Docyrus enum admin API | ## Type Exports | Type | Description | |------|-------------| | `EnumEditorOption` | `EnumOption` + `origin?: EnumOptionOrigin` + `isCustomized?: boolean` | | `EnumOptionOrigin` | `'custom' \| 'inherited'` | | `EnumOptionInput` | Editable attributes: `name`, `slug?`, `color?`, `icon?`, `description?`, `sort_order?`, `active?`, `is_final_option?`, `force_description?`, `force_followup_date?`, `parent?` | | `EnumOptionEditorProps` | Props for `EnumOptionEditor` | | `EnumOptionEditorDialogProps` | Props for `EnumOptionEditorDialog` | | `EnumOptionEditorTrigger` | Native trigger type (`ReactNode \| (({ onPress }) => ReactNode)`) | | `DocyrusEnumOptionEditorProps` | Props for `DocyrusEnumOptionEditor` | | `UseDocyrusEnumEditorPermissionOptions` | Options for `useDocyrusEnumEditorPermission` | ## Differences from web - `EnumOptionEditorDialog` / `DocyrusEnumOptionEditor` render a large bottom sheet instead of a Dialog; `trigger` additionally accepts `({ onPress }) => ReactNode`. - Row actions live in a "⋯" action sheet; Move up / Move down are added as an accessible alternative to drag. - The Active switch is shown on every row (web keeps it in the advanced popover; native has it in both places). ## Translation keys Copy is read through `useUiTranslation()` with the web keys: `ui.enumEditor.*` (`manageOptions`, `title`, `dialogDescription`, `color`, `colorPlaceholder`, `icon`, `iconFontAwesome`, `iconHugeIcons`, `iconSearch`, `noIcons`, `clear`, `slug`, `slugAuto`, `description`, `active`, `activeHint`, `finalOption`, `finalOptionHint`, `forceDescription`, `forceFollowup`, `moreSettings`, `reorder`, `revert`, `delete`, `inherited`, `overridden`, `empty`, `addPlaceholder`, `add`, `duplicateName`, `saveFailed`, `deleteFailed`, `revertFailed`, `createFailed`, `sharedSetNotice`) plus native-only `ui.enumEditor.name`, `ui.enumEditor.reorderHint`, `ui.enumEditor.moveUp`, `ui.enumEditor.moveDown`, and the shared `ui.common.done` / `ui.common.loading`.