# EditableContent

*Category:* Components
*Section:* Interaction
*Source:* https://uikit.developers.rio.cloud/#/components/editableContent
*Captured:* 2026-04-27T14:57:21.492Z

The EditableContent component allows for inline editing text or other values. To make sure the user can recognize such an element, it's best to mark the text or content with a dotted line depending on the situation and where it is placed. Alternatively, you can place the "pencil" icon next to the content. Especially on mobile where no hover is available, the editable content must be marked permanently.

## EditableContent

Please also check out the usage guidelines below.

### Example: Example 1

Simple editable content Click Me!

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { useState } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';

export default () => {
    const [text, setText] = useState('Click Me!');

    const handleSaveText = (value: string) => setText(value);

    return (
        <>
            <div>
                <label>Simple editable content</label>
            </div>
            <EditableContent onSave={handleSaveText}>
                <span className='border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted'>
                    {text || <i>&nbsp;&nbsp;&nbsp;&nbsp;</i>}
                </span>
            </EditableContent>
        </>
    );
};
```

#### HTML (html)

```html
<div>
    <label>Simple editable content</label>
</div>
<span class="">
    <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Click Me!</span>
</span>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

### Example: Headline that can change

Editable headline - like dialog or panel header
Headline that can change

Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra dui eget massa efficitur aliquam condimentum in felis. Integer eget metus vel nunc efficitur iaculis. Vivamus elementum ante et est sagittis semper. Ut consectetur enim nec tortor scelerisque tincidunt. Fusce ac ligula bibendum, rutrum libero quis, viverra sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis ut hendrerit massa, in molestie elit. Sed vitae orci ac purus volutpat posuere ac ut neque. Mauris ante orci, semper finibus lorem in, fringilla pretium velit.

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { useState } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';
import { dummyText } from '../../../utils/data';

export default () => {
    const [text, setText] = useState('Headline that can change');

    const handleSaveText = (value: string) => setText(value);

    return (
        <>
            <div>
                <label>Editable headline - like dialog or panel header</label>
            </div>
            <div className='panel panel-default'>
                <div className='panel-heading'>
                    <EditableContent
                        onSave={handleSaveText}
                        editorOffsetTop={-3}
                        inputClassName='min-width-300'
                        size='lg'
                    >
                        <h4 className='display-flex align-items-center gap-5 text-color-darker hover-text-color-darkest text-thin cursor-pointer margin-0'>
                            <span>{text}</span>
                            <span className='rioglyph rioglyph-pencil text-size-20' />
                        </h4>
                    </EditableContent>
                </div>
                <div className='panel-body'>{dummyText}</div>
            </div>
        </>
    );
};
```

#### HTML (html)

```html
<div>
    <label>Editable headline - like dialog or panel header</label>
</div>
<div class="panel panel-default">
    <div class="panel-heading">
        <span class="">
            <h4 class="display-flex align-items-center gap-5 text-color-darker hover-text-color-darkest text-thin cursor-pointer margin-0">
                <span>Headline that can change</span>
                <span class="rioglyph rioglyph-pencil text-size-20">
                </span>
            </h4>
        </span>
    </div>
    <div class="panel-body">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra dui eget massa efficitur aliquam condimentum in felis. Integer eget metus vel nunc efficitur iaculis. Vivamus elementum ante et est sagittis semper. Ut consectetur enim nec tortor scelerisque tincidunt. Fusce ac ligula bibendum, rutrum libero quis, viverra sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis ut hendrerit massa, in molestie elit. Sed vitae orci ac purus volutpat posuere ac ut neque. Mauris ante orci, semper finibus lorem in, fringilla pretium velit.</div>
</div>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

### Example: Example 3

Editable content using a resizable textarea
This text can be edited on multiple lines

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { useState } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';

export default () => {
    const [text, setText] = useState('This text can be edited on multiple lines');

    const handleSaveText = (value: string) => setText(value);

    return (
        <>
            <div>
                <label>Editable content using a resizable textarea</label>
            </div>
            <div className='bg-lightest rounded padding-10'>
                <EditableContent
                    onSave={handleSaveText}
                    inputClassName='min-width-300'
                    inputRows={2}
                    editorOffsetTop={1}
                    allowResize
                >
                    <div>
                        <span className='border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted'>
                            {text}
                        </span>
                    </div>
                </EditableContent>
            </div>
        </>
    );
};
```

#### HTML (html)

```html
<div>
    <label>Editable content using a resizable textarea</label>
</div>
<div class="bg-lightest rounded padding-10">
    <span class="">
        <div>
            <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">This text can be edited on multiple lines</span>
        </div>
    </span>
</div>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

### Example: Example 4

Focusable editable content with input validation This content must not contain numbers.

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { useRef, useState } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';
import useKey from '@rio-cloud/rio-uikit/useKey';

export default () => {
    const [isEditMode, setIsEditMode] = useState(false);
    const [text, setText] = useState('This content must not contain numbers.');

    const ref = useRef<HTMLDivElement>(null);

    useKey(event => {
        if (event.key === 'Enter' && document.activeElement === ref.current) {
            setIsEditMode(true);
        }
    });

    const handleSaveText = (value: string) => {
        setText(value);
        setIsEditMode(false);
    };

    const handleCancelText = () => setIsEditMode(false);
    const handleEnterEditMode = () => setIsEditMode(true);

    const handleValidation = (value: string) => !/\d/.test(value);

    return (
        <div>
            <label>Focusable editable content with input validation</label>
            <EditableContent
                show={isEditMode}
                onSave={handleSaveText}
                onCancel={handleCancelText}
                onEditMode={handleEnterEditMode}
                onSaveValidation={handleValidation}
                editorOffsetTop={1}
                errorMessage={<span>Numbers are not allowed</span>}
                inputClassName='min-width-200'
                editorClassName='max-width-350'
            >
                <div
                    ref={ref}
                    className='border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted'
                    tabIndex={0}
                >
                    <span>{text}</span>
                </div>
            </EditableContent>
        </div>
    );
};
```

#### HTML (html)

```html
<div>
    <label>Focusable editable content with input validation</label>
    <span class="">
        <div class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted" tabindex="0">
            <span>This content must not contain numbers.</span>
        </div>
    </span>
</div>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

### Example: Example 5

Editable text inside of another text Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra dui eget massa efficitur aliquam condimentum in felis. Integer eget metus vel nunc efficitur iaculis. Vivamus elementum ante et est sagittis semper. This text can be edited Ut consectetur enim nec tortor scelerisque tincidunt. Fusce ac ligula bibendum, rutrum libero quis, viverra sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis ut hendrerit massa, in molestie elit. Sed vitae orci ac purus volutpat posuere ac ut neque. Mauris ante orci, semper finibus lorem in, fringilla pretium velit.`;

#### React (tsx)

```tsx
import { useState } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';

export default () => {
    const [text, setText] = useState('This text can be edited');

    const handleSaveText = (value: string) => setText(value);

    return (
        <>
            <label>Editable text inside of another text</label>
            <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra dui eget massa efficitur aliquam
                condimentum in felis. Integer eget metus vel nunc efficitur iaculis. Vivamus elementum ante et est
                sagittis semper.{' '}
                <EditableContent onSave={handleSaveText}>
                    <span className='border border-bottom-only border-color-dark border-style-dotted hover-bg-lighter'>
                        {text}
                    </span>
                </EditableContent>{' '}
                Ut consectetur enim nec tortor scelerisque tincidunt. Fusce ac ligula bibendum, rutrum libero quis,
                viverra sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis ut hendrerit massa, in
                molestie elit. Sed vitae orci ac purus volutpat posuere ac ut neque. Mauris ante orci, semper finibus
                lorem in, fringilla pretium velit.`;
            </p>
        </>
    );
};
```

#### HTML (html)

```html
<label>Editable text inside of another text</label>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pharetra dui eget massa efficitur aliquam condimentum in felis. Integer eget metus vel nunc efficitur iaculis. Vivamus elementum ante et est sagittis semper. <span class="">
        <span class="border border-bottom-only border-color-dark border-style-dotted hover-bg-lighter">This text can be edited</span>
    </span> Ut consectetur enim nec tortor scelerisque tincidunt. Fusce ac ligula bibendum, rutrum libero quis, viverra sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis ut hendrerit massa, in molestie elit. Sed vitae orci ac purus volutpat posuere ac ut neque. Mauris ante orci, semper finibus lorem in, fringilla pretium velit.`;</p>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

### Example: Example 6

Render NumberControl as custom editor component
234

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { forwardRef, useEffect, useRef, useState, type ForwardedRef } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';
import NumberControl from '@rio-cloud/rio-uikit/NumberControl';

type CustomContentProps = {
    value: number;
    onChange: (newValue?: number | undefined) => void;
    onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
};

const CustomContent = forwardRef((props: CustomContentProps, ref: ForwardedRef<HTMLInputElement>) => {
    const { value, onChange, onKeyDown } = props;

    return (
        <NumberControl
            ref={ref}
            value={value}
            onChange={onChange}
            onKeyDown={onKeyDown}
            className='margin-0 min-width-100'
        />
    );
});

export default () => {
    const [data, setData] = useState(234);
    const [transientData, setTransientData] = useState(0);

    const [isEditMode, setIsEditMode] = useState(false);

    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        if (isEditMode) {
            inputRef.current?.focus();
        }
    }, [isEditMode]);

    const handleChange = (value: number | undefined) => setTransientData(value || 0);

    const handleCancel = () => setIsEditMode(false);

    const handleSaveText = () => {
        setData(transientData);
        setIsEditMode(false);
    };

    const handleEnterEditMode = () => {
        setTransientData(data);
        setIsEditMode(true);
    };

    // Since we use a custom input instead of the built-in textarea, we need to handle keyboard
    // events ourself for instance in order to save on enter.
    const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
        if (event.key === 'Enter') {
            handleSaveText();
        }
    };

    return (
        <div>
            <div>
                <label className='margin-right-20'>Render NumberControl as custom editor component</label>
            </div>
            <div className='display-flex gap-25'>
                <EditableContent
                    show={isEditMode}
                    onSave={handleSaveText}
                    onCancel={handleCancel}
                    onEditMode={handleEnterEditMode}
                    customEditor={
                        <CustomContent
                            ref={inputRef}
                            value={transientData}
                            onChange={handleChange}
                            onKeyDown={handleKeyDown}
                        />
                    }
                    editorOffsetTop={1}
                >
                    <span className='border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted'>
                        {data}
                    </span>
                </EditableContent>
            </div>
        </div>
    );
};
```

#### HTML (html)

```html
<div>
    <div>
        <label class="margin-right-20">Render NumberControl as custom editor component</label>
    </div>
    <div class="display-flex gap-25">
        <span class="">
            <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">234</span>
        </span>
    </div>
</div>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

### Example: Example 7

Render NumberInput as custom editor component with input validation
234

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { useEffect, useRef, useState } from 'react';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';
import NumberInput from '@rio-cloud/rio-uikit/NumberInput';

export default () => {
    const [data, setData] = useState(234);
    const [transientData, setTransientData] = useState(0);

    const [isEditMode, setIsEditMode] = useState(false);
    const [isValid, setIsValid] = useState(true);

    const inputRef = useRef<HTMLInputElement>(null);

    // Focus on the custom input element when entering the edit mode
    useEffect(() => {
        isEditMode && inputRef.current?.focus();
    }, [isEditMode]);

    // Handle change of unsaved entered input values as we control the input ourself
    const handleChange = (value: number | undefined) => setTransientData(value || 0);

    const handleCancel = () => setIsEditMode(false);

    const handleSaveText = () => {
        setData(transientData);
        setIsEditMode(false);
    };

    const handleEnterEditMode = () => {
        setTransientData(data);
        setIsEditMode(true);
    };

    // Since we use a custom input instead of the built-in textarea, we need to handle keyboard
    // events ourself for instance in order to save on enter.
    const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
        if (event.key === 'Enter') {
            validateInput() ? handleSaveText() : setIsValid(false);
        }
    };

    // We have to take care of the input validation here as we use a custom input
    // and render the error message in case.
    const handleValidateOnSave = () => {
        const result = validateInput();
        setIsValid(result);
        return result;
    };

    // Simple input validation for demo purpose. Replace it with a more meaningful validation.
    const validateInput = (): boolean => transientData <= 999;

    return (
        <div>
            <div>
                <label className='margin-right-20'>
                    Render NumberInput as custom editor component with input validation
                </label>
            </div>
            <div className='display-flex gap-25'>
                <EditableContent
                    show={isEditMode}
                    onSave={handleSaveText}
                    onCancel={handleCancel}
                    onEditMode={handleEnterEditMode}
                    onSaveValidation={handleValidateOnSave}
                    isValid={isValid}
                    errorMessage='Please insert maximum 3 digit number'
                    customEditor={
                        <div className='margin-0 min-width-150'>
                            <NumberInput
                                ref={inputRef}
                                value={transientData}
                                onChange={handleChange}
                                onKeyDown={handleKeyDown}
                                digitPrecision={0}
                                unit='km'
                            />
                        </div>
                    }
                    editorOffsetTop={1}
                >
                    <span className='border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted'>
                        {data}
                    </span>
                </EditableContent>
            </div>
        </div>
    );
};
```

#### HTML (html)

```html
<div>
    <div>
        <label class="margin-right-20">Render NumberInput as custom editor component with input validation</label>
    </div>
    <div class="display-flex gap-25">
        <span class="">
            <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">234</span>
        </span>
    </div>
</div>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |

## Usage guidelines

### Example: Example 8

Don't
Avoid overuse in lists or tables. Avoid many repetitions of edit icons.
Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Do
Use an underline to indicate editable text and use a hover icon on Desktop if possible.
Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Lorem ipsum dolor

Do
Using the editable field in isolation like inside a panel header or a like, you can permanently show a edit icon.
Lorem ipsum dolor
Lorem ipsum dolor

Note
In general, don't make every cell in a table editable. The indication style would be too repetitive and the user does not expect this behavior as it is not implemented in other services like so. Strive for a common look and feel with regards to other services.

#### React (tsx)

```tsx
/* eslint-disable max-len */
import { useRef } from 'react';

import { Note } from '../../../components/Note';

import EditableContent from '@rio-cloud/rio-uikit/EditableContent';
import useHover from '@rio-cloud/rio-uikit/useHover';

const Demo1 = () => (
    <EditableContent editorOffsetTop={1}>
        <div className='display-flex align-items-center gap-5 cursor-pointer'>
            <span>Lorem ipsum dolor</span>
            <span className='rioglyph rioglyph-pencil' />
        </div>
    </EditableContent>
);

const Demo2 = () => {
    const targetRef = useRef<HTMLDivElement>(null);
    const isHover = useHover(targetRef);

    return (
        <EditableContent editorOffsetTop={1}>
            <div ref={targetRef} className='display-flex align-items-center gap-5 cursor-pointer'>
                <span className='border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted'>
                    Lorem ipsum dolor
                </span>
                {isHover && <span className='rioglyph rioglyph-pencil' />}
            </div>
        </EditableContent>
    );
};

export default () => (
    <>
        <div className='display-grid grid-cols-2 grid-cols-3-md grid-auto-rows gap-15 margin-bottom-20'>
            <div>
                <div className='text-size-16 text-color-danger'>Don't</div>
                <p>Avoid overuse in lists or tables. Avoid many repetitions of edit icons.</p>
                <ul className='list-group rounded border'>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                    <li className='list-group-item'>
                        <Demo1 />
                    </li>
                </ul>
            </div>
            <div>
                <div className='text-size-16 text-color-success'>Do</div>
                <p>Use an underline to indicate editable text and use a hover icon on Desktop if possible.</p>
                <ul className='list-group rounded border'>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                    <li className='list-group-item'>
                        <Demo2 />
                    </li>
                </ul>
            </div>
            <div className='grid-colspan-2 grid-colspan-1-md'>
                <div className='text-size-16 text-color-success'>Do</div>
                <p>
                    Using the editable field in isolation like inside a panel header or a like, you can permanently show
                    a edit icon.
                </p>
                <EditableContent editorOffsetTop={1}>
                    <h5 className='display-flex align-items-center gap-5 text-thin cursor-pointer'>
                        <span>Lorem ipsum dolor</span>
                        <span className='rioglyph rioglyph-pencil' />
                    </h5>
                </EditableContent>
                <hr />
                <EditableContent editorOffsetTop={1}>
                    <div className='display-flex align-items-center gap-5 text-size-h4 text-thin cursor-pointer'>
                        <span>Lorem ipsum dolor</span>
                        <span className='rioglyph rioglyph-pencil' />
                    </div>
                </EditableContent>
                <hr />
            </div>
        </div>
        <b>
            <Note>
                In general, don't make every cell in a table editable. The indication style would be too repetitive and
                the user does not expect this behavior as it is not implemented in other services like so. Strive for a
                common look and feel with regards to other services.
            </Note>
        </b>
    </>
);
```

#### HTML (html)

```html
<div class="display-grid grid-cols-2 grid-cols-3-md grid-auto-rows gap-15 margin-bottom-20">
    <div>
        <div class="text-size-16 text-color-danger">Don't</div>
        <p>Avoid overuse in lists or tables. Avoid many repetitions of edit icons.</p>
        <ul class="list-group rounded border">
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span>Lorem ipsum dolor</span>
                        <span class="rioglyph rioglyph-pencil">
                        </span>
                    </div>
                </span>
            </li>
        </ul>
    </div>
    <div>
        <div class="text-size-16 text-color-success">Do</div>
        <p>Use an underline to indicate editable text and use a hover icon on Desktop if possible.</p>
        <ul class="list-group rounded border">
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
            <li class="list-group-item">
                <span class="">
                    <div class="display-flex align-items-center gap-5 cursor-pointer">
                        <span class="border border-bottom-only border-color-gray hover-border-color-darkest border-style-dotted">Lorem ipsum dolor</span>
                    </div>
                </span>
            </li>
        </ul>
    </div>
    <div class="grid-colspan-2 grid-colspan-1-md">
        <div class="text-size-16 text-color-success">Do</div>
        <p>Using the editable field in isolation like inside a panel header or a like, you can permanently show a edit icon.</p>
        <span class="">
            <h5 class="display-flex align-items-center gap-5 text-thin cursor-pointer">
                <span>Lorem ipsum dolor</span>
                <span class="rioglyph rioglyph-pencil">
                </span>
            </h5>
        </span>
        <hr>
        <span class="">
            <div class="display-flex align-items-center gap-5 text-size-h4 text-thin cursor-pointer">
                <span>Lorem ipsum dolor</span>
                <span class="rioglyph rioglyph-pencil">
                </span>
            </div>
        </span>
        <hr>
    </div>
</div>
<b>
    <div class="display-flex gap-10 max-width-1000 margin-bottom-10 last-child-margin-bottom-0">
        <div>
            <span class="label label-info label-condensed label-filled">Note</span>
        </div>
        <div class="width-100pct children-first-margin-top-0 children-last-margin-bottom-0 padding-top-1">In general, don't make every cell in a table editable. The indication style would be too repetitive and the user does not expect this behavior as it is not implemented in other services like so. Strive for a common look and feel with regards to other services.</div>
    </div>
</b>
```

#### Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| show | boolean | undefined | Visibility flag used to control edit mode from outside. |
| onSave | (value: string) => void | — | Callback function triggered when the save button is clicked. |
| onCancel | VoidFunction | — | Callback function triggered when the cancel button is clicked. |
| onFocus | VoidFunction | — | Callback function triggered when the input gets focused. |
| onBlur | VoidFunction | — | Callback function triggered when input looses it's focus. |
| onEditMode | VoidFunction | — | Callback function triggered when entering the edit mode. |
| onSaveValidation | (value: string) => boolean | — | Validation function to intercept saving and prevent save on error. |
| isValid | boolean | true | Controls the error message visibility from outside. This is useful when using a custom input where the validation happens outside of this component. |
| errorMessage | React.ReactNode | — | The error message that shall be shown to the user in case the validation fails. |
| customEditor | React.ReactNode | — | Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker. Handling input state changes of a custom editor need to be handled outside of this component. By default the EditableContent uses a built-in textarea to allow resizing for larger text. |
| editorOffsetTop | number | 0 | Offset value to control the vertical position for the editor in case the text element has borders, spacings different text sizes. |
| editorOffsetLeft | number | 0 | Offset value to control the horizontal position for the editor in case the text element has borders, spacings different text sizes. |
| size | 'md' \| 'lg' | 'md' | Defines the input and button size. Use 'lg' for headlines. |
| allowResize | boolean | false | Defines if the internal textarea is allowed to resize vertically. This comes in handy for larger text and when using multiple input rows. |
| inputRows | number | 1 | Defines the number of rows to use by the internal textarea component. When a single line is used, the input is saved on "enter" key. |
| inputClassName | string | — | Additional classes to be set on the editor input itself. |
| editorClassName | string | — | Additional classes to be set on the editor wrapper element. |
| className | string | — | Additional classes to be set on the text wrapper element. |