Appearance
InlineEditableField
Complete form field implementation for inline editing with label, error display, and customization options.
Import
tsx
import { InlineEditableField } from '@/shared/ui/components/editable/InlineEditableField'Props
InlineEditableFieldProps
| Prop | Type | Default | Description |
|---|---|---|---|
| Core | |||
id | string | Required | Unique field identifier |
value | string | Required | Current value |
onSave | (value: string) => Promise<void> | void | Required | Save function (sync or async) |
| Display | |||
label | ReactNode | - | Field label |
placeholder | string | - | Input placeholder text |
type | string | 'text' | HTML input type |
| State | |||
disabled | boolean | false | Whether field is disabled |
readOnly | boolean | false | Whether field is read-only |
isSaving | boolean | - | External saving state (controlled) |
| Callbacks | |||
onCancel | () => void | - | Called when editing is cancelled |
onBlur | () => void | - | Custom blur handler |
| Customization | |||
renderDisplay | (startEdit: () => void) => ReactNode | - | Custom display render function |
renderInput | (props: InputRenderProps) => ReactNode | - | Custom input render function |
| Styling | |||
className | string | - | Container CSS classes |
displayClassName | string | - | Display mode CSS classes |
inputClassName | string | - | Input CSS classes |
| Features | |||
edited | boolean | false | Mark field as edited |
showEditedBadge | boolean | true | Show "edited" badge when edited is true |
editedBadgeContent | ReactNode | 'Edited' | Content for edited badge |
saveOnBlur | boolean | false | Automatically save when input loses focus |
hideActions | boolean | false | Hide save/cancel buttons |
error | string | - | Error message to display |
InputRenderProps
Props passed to renderInput function:
ts
interface InputRenderProps {
value: string
onChange: (value: string) => void
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
onBlur: () => void
autoFocus: boolean
isSaving: boolean
}Examples
Basic Usage
tsx
<InlineEditableField
id="user-name"
label="Name"
value={user.name}
onSave={async (value) => await updateUser({ name: value })}
placeholder="Enter name..."
/>With Edited Badge
tsx
<InlineEditableField
id="user-email"
label="Email"
value={user.email}
onSave={handleSave}
edited={user.email !== originalEmail}
showEditedBadge
editedBadgeContent="Modified"
/>Save on Blur (No Buttons)
tsx
<InlineEditableField
id="quick-edit"
value={value}
onSave={handleSave}
saveOnBlur
hideActions
placeholder="Click to edit..."
/>With Error Handling
tsx
<InlineEditableField
id="validated-field"
label="Username"
value={username}
onSave={async (value) => {
if (value.length < 3) {
throw new Error('Username must be at least 3 characters')
}
await api.updateUsername(value)
}}
error={validationError}
/>Custom Display
tsx
<InlineEditableField
id="custom-display"
value={user.name}
onSave={handleSave}
renderDisplay={(startEdit) => (
<div className="flex items-center gap-2" onClick={startEdit}>
<Avatar src={user.avatar} size="sm" />
<div>
<p className="font-medium">{user.name}</p>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
)}
/>Custom Input (Textarea)
tsx
<InlineEditableField
id="description"
label="Description"
value={description}
onSave={handleSave}
renderInput={({ value, onChange, onKeyDown, autoFocus, isSaving }) => (
<Textarea
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={(e) => {
// Prevent Enter from saving for multi-line
if (e.key === 'Enter' && !e.shiftKey) {
e.stopPropagation()
}
onKeyDown(e)
}}
autoFocus={autoFocus}
disabled={isSaving}
rows={4}
/>
)}
/>Custom Input (Select)
tsx
<InlineEditableField
id="status"
label="Status"
value={status}
onSave={handleSave}
renderInput={({ value, onChange, autoFocus }) => (
<Select value={value} onValueChange={onChange}>
<SelectTrigger autoFocus={autoFocus}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
</SelectContent>
</Select>
)}
/>With Custom Styling
tsx
<InlineEditableField
id="styled-field"
value={value}
onSave={handleSave}
className="border-b border-border pb-2"
displayClassName="text-lg font-semibold"
inputClassName="text-lg font-semibold"
/>Disabled State
tsx
<InlineEditableField
id="readonly-field"
label="ID"
value={user.id}
onSave={() => {}}
disabled
/>Read-Only State
tsx
<InlineEditableField
id="readonly-field"
label="Created At"
value={formatDate(user.createdAt)}
onSave={() => {}}
readOnly
/>Features
Automatic Keyboard Shortcuts
- Enter: Save changes
- Escape: Cancel editing
Loading State
Shows "Saving..." text during async save operations:
tsx
<InlineEditableField
value={value}
onSave={async (value) => {
// "Saving..." automatically shown
await api.update(value)
// Loading state automatically cleared
}}
/>Error Display
Displays error messages below the input:
tsx
<InlineEditableField
value={value}
onSave={async (value) => {
const result = await api.update(value)
if (!result.ok) throw new Error('Update failed')
}}
error={errorMessage}
/>Edited Badge
Shows a badge when the field has been modified:
tsx
<InlineEditableField
value={currentValue}
onSave={handleSave}
edited={currentValue !== originalValue}
showEditedBadge
editedBadgeContent={<span className="text-orange-500">●</span>}
/>Common Patterns
Profile Field
tsx
function ProfileField({ label, value, onSave }) {
return (
<div className="flex items-center gap-4 py-2">
<span className="w-32 text-sm text-muted-foreground">{label}</span>
<InlineEditableField
id={`profile-${label}`}
value={value}
onSave={onSave}
className="flex-1"
/>
</div>
)
}Quick Edit
tsx
<InlineEditableField
id="quick-edit"
value={value}
onSave={handleSave}
saveOnBlur
hideActions
placeholder="Click to edit..."
/>Validated Field
tsx
function ValidatedField({ value, onSave, validate }) {
const [error, setError] = useState('')
const handleSave = async (newValue: string) => {
const validationError = validate(newValue)
if (validationError) {
setError(validationError)
throw new Error(validationError)
}
setError('')
await onSave(newValue)
}
return (
<InlineEditableField
id="validated"
value={value}
onSave={handleSave}
error={error}
/>
)
}Accessibility
- Labels: Proper label association with
id - Keyboard navigation: Full keyboard support
- Focus management: Auto-focus on edit mode
- Error announcements: Errors are properly announced
- Disabled state: Communicated to screen readers