Appearance
InlineEditable API
Complete type definitions and prop reference for the InlineEditable component ecosystem.
InlineEditable (Headless)
Headless component for inline editing with full render control.
Import
tsx
import { InlineEditable, type InlineEditableProps, type InlineEditableDisplayProps, type InlineEditableEditorProps } from '@/shared/ui/components/editable/InlineEditable'Props
InlineEditableProps<T>
| Prop | Type | Default | Description |
|---|---|---|---|
value | T | Required | Current value to display/edit |
onSave | (value: T) => Promise<void> | void | Required | Save function (sync or async) |
renderDisplay | (props: InlineEditableDisplayProps<T>) => ReactNode | Required | Render function for display mode |
renderEditor | (props: InlineEditableEditorProps<T>) => ReactNode | Required | Render function for edit mode |
onCancel | () => void | - | Called when editing is cancelled |
onSaveSuccess | () => void | - | Called when save succeeds |
onSaveError | (error: Error) => void | - | Called when save fails |
enableKeyboardShortcuts | boolean | true | Enable Enter/Escape shortcuts |
className | string | - | Additional CSS classes for wrapper |
InlineEditableDisplayProps<T>
Props passed to renderDisplay function:
| Property | Type | Description |
|---|---|---|
value | T | Current value |
onStartEdit | () => void | Function to enter edit mode |
InlineEditableEditorProps<T>
Props passed to renderEditor function:
| Property | Type | Description |
|---|---|---|
value | T | Current draft value |
onChange | (value: T) => void | Update draft value |
onSave | () => void | Save current draft |
onCancel | () => void | Cancel editing |
isSaving | boolean | Whether save is in progress |
error | Error | null | Error from save attempt |
inputProps | { onKeyDown: (e: KeyboardEvent) => void } | Props to spread on input for keyboard handling |
Example
tsx
import { InlineEditable } from '@/shared/ui/components/editable/InlineEditable'
function EditableName({ name, onUpdate }) {
return (
<InlineEditable
value={name}
onSave={async (newName) => {
await api.updateName(newName)
}}
onSaveSuccess={() => toast.success('Name updated')}
onSaveError={(error) => toast.error(error.message)}
renderDisplay={({ value, onStartEdit }) => (
<button
onClick={onStartEdit}
className="hover:underline cursor-pointer"
>
{value}
</button>
)}
renderEditor={({ value, onChange, onSave, onCancel, isSaving, error, inputProps }) => (
<div className="flex gap-2">
<input
value={value}
onChange={(e) => onChange(e.target.value)}
{...inputProps}
autoFocus
disabled={isSaving}
/>
<button onClick={onSave} disabled={isSaving}>
{isSaving ? 'Saving...' : 'Save'}
</button>
<button onClick={onCancel} disabled={isSaving}>
Cancel
</button>
{error && <p className="text-destructive">{error.message}</p>}
</div>
)}
/>
)
}EditableTrigger
Reusable trigger button for entering edit mode.
Import
tsx
import { EditableTrigger, type EditableTriggerProps } from '@/shared/ui/components/editable/EditableTrigger'Props
EditableTriggerProps
| Prop | Type | Default | Description |
|---|---|---|---|
onClick | () => void | Required | Callback when trigger is clicked |
children | ReactNode | Required | Content to display |
showEditIcon | boolean | true | Show edit icon on hover |
className | string | - | Additional CSS classes |
disabled | boolean | false | Whether trigger is disabled |
Features
- Ghost button styling (looks like inline content)
- Edit icon appears on hover
- Keyboard accessible (Enter/Space)
- Focus ring for accessibility
Example
tsx
import { EditableTrigger } from '@/shared/ui/components/editable/EditableTrigger'
<EditableTrigger onClick={() => startEdit()}>
<span className="font-medium">{userName}</span>
</EditableTrigger>
// Without edit icon
<EditableTrigger onClick={() => startEdit()} showEditIcon={false}>
<h1 className="text-2xl">{title}</h1>
</EditableTrigger>
// Disabled state
<EditableTrigger onClick={() => startEdit()} disabled={!canEdit}>
<span>{value}</span>
</EditableTrigger>EditableActions
Reusable save/cancel action buttons.
Import
tsx
import { EditableActions, type EditableActionsProps } from '@/shared/ui/components/editable/EditableActions'Props
EditableActionsProps
| Prop | Type | Default | Description |
|---|---|---|---|
onSave | () => void | Required | Callback when save is clicked |
onCancel | () => void | Required | Callback when cancel is clicked |
isSaving | boolean | false | Whether save operation is in progress |
disabled | boolean | false | Whether actions are disabled |
size | 'default' | 'sm' | 'icon' | 'default' | Button size variant |
className | string | - | Additional CSS classes for container |
Features
- Check (✓) icon for save
- X icon for cancel
- Consistent styling across app
- Loading state support
- Size variants
Example
tsx
import { EditableActions } from '@/shared/ui/components/editable/EditableActions'
// Default size
<EditableActions
onSave={handleSave}
onCancel={handleCancel}
/>
// Small size
<EditableActions
onSave={handleSave}
onCancel={handleCancel}
size="sm"
/>
// Icon size (for compact UIs)
<EditableActions
onSave={handleSave}
onCancel={handleCancel}
size="icon"
/>
// With loading state
<EditableActions
onSave={handleSave}
onCancel={handleCancel}
isSaving={isSaving}
/>InlineEditableField
Complete form field implementation with label, error display, and more.
Import
tsx
import { InlineEditableField, type InlineEditableFieldProps } 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 |
| Display | |||
label | ReactNode | - | Field label |
placeholder | string | - | Input placeholder |
type | string | 'text' | 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 |
renderInput | (props: InputProps) => ReactNode | - | Custom input render |
| 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 |
editedBadgeContent | ReactNode | 'Edited' | Badge content |
saveOnBlur | boolean | false | Save when input loses focus |
hideActions | boolean | false | Hide save/cancel buttons |
error | string | - | Error message to display |
InputProps (for renderInput)
ts
interface InputProps {
value: string
onChange: (value: string) => void
onKeyDown: (e: 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
tsx
<InlineEditableField
id="quick-edit"
value={value}
onSave={handleSave}
saveOnBlur
hideActions
placeholder="Click to edit..."
/>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} />
<span className="font-bold">{user.name}</span>
</div>
)}
/>Custom Input (Textarea)
tsx
<InlineEditableField
id="description"
label="Description"
value={description}
onSave={handleSave}
renderInput={({ value, onChange, onKeyDown, autoFocus }) => (
<Textarea
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={onKeyDown}
autoFocus={autoFocus}
rows={4}
/>
)}
/>With Error Handling
tsx
<InlineEditableField
id="validated-field"
value={value}
onSave={async (value) => {
if (!value.trim()) throw new Error('Value required')
await api.update(value)
}}
error={validationError}
/>Type Reference
Common Types
ts
// Generic value type
type InlineEditableValue = string | number | boolean | any
// Save function
type SaveFunction<T> = (value: T) => Promise<void> | void
// Lifecycle callbacks
type OnCancel = () => void
type OnSaveSuccess = () => void
type OnSaveError = (error: Error) => void
// Keyboard event handler
type KeyboardHandler = (e: React.KeyboardEvent) => voidComposition Patterns
Building Custom Editables
Combine primitives for custom implementations:
tsx
import { InlineEditable } from '@/shared/ui/components/editable/InlineEditable'
import { EditableTrigger } from '@/shared/ui/components/editable/EditableTrigger'
import { EditableActions } from '@/shared/ui/components/editable/EditableActions'
function CustomEditable({ value, onSave }) {
return (
<InlineEditable
value={value}
onSave={onSave}
renderDisplay={({ value, onStartEdit }) => (
<EditableTrigger onClick={onStartEdit}>
<YourCustomDisplay value={value} />
</EditableTrigger>
)}
renderEditor={({ value, onChange, onSave, onCancel, isSaving, inputProps }) => (
<div className="flex items-center gap-2">
<YourCustomInput
value={value}
onChange={onChange}
{...inputProps}
/>
<EditableActions
onSave={onSave}
onCancel={onCancel}
isSaving={isSaving}
/>
</div>
)}
/>
)
}