Docyrus
EditableRecordDetail
IField-driven record editor — EditableValue rows with change tracking, conditional required / hidden, validation and a sticky save bar.
iOSAndroid
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-editable-record-detailUsage
import {
EditableRecordDetail,
EditableRecordDetailField,
type RecordDetailField
} from '@/components/docyrus-native/editable-record-detail';
const fields: RecordDetailField[] = [
{ field: { id: 'title', slug: 'title', name: 'Title', type: 'field-text' }, required: true },
{ field: { id: 'status', slug: 'status', name: 'Status', type: 'field-status' }, enumOptions: statusOptions },
{
field: { id: 'reason', slug: 'reason', name: 'Lost reason', type: 'field-textarea' },
hidden: values => values.status !== 'lost',
required: values => values.status === 'lost'
}
];
{/* Every field, in order */}
<EditableRecordDetail fields={fields} record={record} onSave={(changes, values) => api.update(values)} />
{/* Composed layout */}
<EditableRecordDetail fields={fields} record={record} onSave={save} fieldLayout="horizontal">
<EditableRecordDetailField slug="title" />
<EditableRecordDetailField slug="status" label="Stage" />
<EditableRecordDetailField slug="reason" />
</EditableRecordDetail>API Reference
EditableRecordDetailProps
| Prop | Type | Default | Description |
|---|---|---|---|
fields | Array<RecordDetailField | LegacyRecordDetailField> | — | Field configs (required). Legacy { key, label, value, type } entries are converted automatically |
record | Record<string, unknown> | — | Current values keyed by slug (a record with different content resets the baseline) |
onSave | (changes: FieldChange[], values: Record<string, unknown>) => void | Promise<void> | — | Save handler; the baseline resets after it resolves |
onCancel | () => void | — | Pending changes discarded |
readOnly | boolean | false | All fields read-only |
disabled | boolean | false | All fields disabled |
trackChanges | boolean | true | Highlight changed rows |
client | RestApiClient | — | Threaded into every field |
children | ReactNode | — | EditableRecordDetailField composition; omitted → every field |
isSaving | boolean | false | External saving flag |
fieldLayout | 'vertical' | 'horizontal' | 'vertical' | Label above / beside the value |
saveLabel / cancelLabel | string | ui.editableRecordDetail.save / ui.common.cancel | Action bar labels |
emptyValueText | string | '—' | Empty text in renderEditor rows |
scrollable | boolean | true without children | Wrap rows in a ScrollView |
className | string | — | Additional classes |
RecordDetailField
| Field | Type | Description |
|---|---|---|
field | IField | Field definition |
enumOptions | EnumOption[] | Options |
readOnly | boolean | Read-only row |
required | boolean | (values) => boolean | Required (conditional) |
hidden | boolean | (values) => boolean | Hidden (conditional, re-evaluated on every change) |
appSlug / dataSourceSlug | string | Dynamic enum context |
renderValue | (value) => ReactNode | Native escape hatch: custom display |
renderEditor | (value, onChange, onDone?) => ReactNode | Native escape hatch: custom inline editor |
EditableRecordDetailFieldProps
EditableValueProps (minus the context-provided props) plus:
| Prop | Type | Default | Description |
|---|---|---|---|
slug | string | — | Field slug (required) |
label | ReactNode | field.name | Label override |
showLabel | boolean | true | Label + value row |
layout | 'vertical' | 'horizontal' | parent fieldLayout | Row layout override |
FieldChange
| Field | Type | Description |
|---|---|---|
fieldSlug | string | Slug |
fieldName | string | Field name |
originalValue | unknown | Baseline value |
newValue | unknown | Current value |
fieldKey / fieldLabel | string | Deprecated legacy aliases |
useEditableRecordDetail()
Returns EditableRecordDetailContextValue: values, changedFields, validationErrors, setFieldValue(slug, value), getChanges(), handleSave(), handleCancel(), plus the flags (readOnly, disabled, isSaving, trackChanges, fieldLayout).
Components
| Component | Description |
|---|---|
EditableRecordDetail | Provider + rows + sticky action bar (pending-changes sheet, Cancel, Save) |
EditableRecordDetailField | EditableValue bound to the context |
Type Exports
| Type | Description |
|---|---|
EditableRecordDetailProps, EditableRecordDetailFieldProps | Props |
RecordDetailField, LegacyRecordDetailField | Field configs |
FieldChange, FieldLayout | Change + layout |
EditableRecordDetailContextValue | Context value |
Breaking changes (web-parity release)
fieldsareRecordDetailField({ field: IField, … }) with values inrecord; the legacy{ key, label, value, type }shape is still accepted.onSave(changes, values)— changes carryfieldSlug/fieldName(legacyfieldKey/fieldLabelkept as deprecated aliases).- Rows edit through
EditableValue(field-aware) instead of plain text inputs.