Mode

There are two modes, read for displaying the value, and edit for enabling control component.

Read

const ReadModeDefault = () => {
    const value = 'basic read example';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    mode="read"
                    value={submittedValue}
                    onSave={onSave}
                    control={getControl}
                />
            </BodyText>
        </Card>
    );
};
render( ReadModeDefault )

Edit

In edit mode, control can be any component, recommended ones are shown in common examples.

const EditMode = () => {
    const value = 'Edit Example';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    mode="edit"
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                    autoFocusControl={false}
                />
            </BodyText>
        </Card>
    );
};
render( EditMode )

Action

Action are default buttons shown in edit mode for save and cancel. It is displayed by default but it can also be hidden. The save and cancel function can be triggered using other components, checkout this example.

Layout

Inline(Default)

Used when there is adjacent component that should be pushed when actions appear.

const LayoutInlineExample = () => {
    const value = 'Inline';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                Hello this is
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                    actionLayout="inline"
                />
                {` example.`}
            </BodyText>
        </Card>
    );
};
render( LayoutInlineExample )

It can also be used with width="fluid" to fill space.

const LayoutInlineTableExample = () => {
    const onSave = props => {};
    const getControl = fieldProps => <Input fluid {...fieldProps} />;
    const MyCustomCell = (props) => {
        return (
            <td style={{display: 'flex'}}>
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    decoration={{color: false, underline: false}}
                    value={props.dataItem.name}
                    actionDirection="r"
                    actionLayout="inline"
                    width="fluid"
                />
            </td>
        )
    }

    const items = [
    { id: 1, quantity: 23,name: 'Chai', price: 18 },
    { id: 2, quantity: 343,name: 'Chang', price: 19 },
    { id: 3, quantity: 134,name: 'Aniseed Syrup', price: 10 },
    { id: 4, quantity: 73,name: 'Chef Anton\'s Cajun Seasoning', price: 22 },
    { id: 5, quantity: 1021,name: 'Chef Anton\'s Gumbo Mix', price: 21.35 }
    ];

    return (
        <Table data={items}>
            <TableColumn field="id" title="Id" width="60px" />
            <TableColumn field="name" width="400px" title="Name" cell={MyCustomCell} />
            <TableColumn field="quantity" title="Quantity" />
            <TableColumn field="price" title="Price" width="180px" format="{0:c}" />
        </Table>
    );
};
render (LayoutInlineTableExample);

Floating

Applies popover to actions. Actions will be absolute positions.

const LayoutPopoverExample = () => {
    const value = 'Floating';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                Hello this is
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                    actionLayout="floating"
                />
                {` example.`}
            </BodyText>
        </Card>
    );
};
render( LayoutPopoverExample )

Here is some practical, if you need the actions to be outside of the cell boundary.

const LayoutPopoverTableExample = () => {
    const [submittedValue, setSubmittedValue] = React.useState();
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input fluid {...fieldProps} />;
    const MyCustomCell = (props) => {
        return (
            <td style={{display: 'flex'}}>
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    decoration={{color: false, underline: false}}
                    value={props.dataItem.name}
                    actionDirection="r"
                    actionLayout="floating"
                    width="fluid"
                />
            </td>
        )
    }

    const items = [
    { id: 1, quantity: 23,name: 'Chai', price: 18 },
    { id: 2, quantity: 343,name: 'Chang', price: 19 },
    { id: 3, quantity: 134,name: 'Aniseed Syrup', price: 10 },
    { id: 4, quantity: 73,name: 'Chef Anton\'s Cajun Seasoning', price: 22 },
    { id: 5, quantity: 1021,name: 'Chef Anton\'s Gumbo Mix', price: 21.35 }
    ];

    return (
        <Table data={items}>
            <TableColumn field="id" title="Id" width="60px" />
            <TableColumn field="name" width="400px" title="Name" cell={MyCustomCell} />
            <TableColumn field="quantity" title="Quantity" />
            <TableColumn field="price" title="Price" width="180px" format="{0:c}" />
        </Table>
    );
};
render (LayoutPopoverTableExample);

None

Example below shows actionLayout='none' with custom button inside control for saving.

For more specific use case checkout using custom read and edit component.

const LayoutPopoverExample = () => {
    const value = 'None';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = (fieldProps, actionProps) => (
        <Stack direction='column'>
            <Input {...fieldProps} />
            <Button onClick={actionProps.save}>Save</Button>
        </Stack>
    )

    return (
        <Card>
            <BodyText el="span">
                Hello this is
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                    actionLayout="none"
                />
                {` example.`}
            </BodyText>
        </Card>
    );
};
render( LayoutPopoverExample )

Direction

The placement of the save and cancel buttons can be controlled in the same way Popover directions are handled. The most common examples are right bottom and bottom left.

const DirectionExample = () => {
    const value = 'Example';
    const directions = ['rb', 'bl'];

    const getControl = fieldProps => <TextArea style={{width: 150}} {...fieldProps} />;

    return (
        <Card>
            <CardSection>
                <Stack alignItems="space-between" justifyContent="center">
                {directions.map((item, index) => (
                    <StackItem key={index} style={{margin: 24, width: 'calc(33% - 48px)'}}>
                        <InlineEdit
                            mode="edit"
                            control={getControl}
                            value={item}
                            autoFocusControl={false}
                            actionDirection={item}
                        />
                    </StackItem>
                ))}
                </Stack>
            </CardSection>
        </Card>
    );
};
render( DirectionExample )

Decoration

const DecorationExamples = () => {
    return (
        <Card>
            <Stack>
                <StackItem fill style={{flexBasis: '100%'}}>
                <BodyText el="span">
                    <InlineEdit
                        mode="read"
                        value="with decorations"
                        control={() => {}}
                        showContentOnEdit
                        actionLayout="none"
                    />
                </BodyText>
                </StackItem>
                <StackItem fill style={{flexBasis: '100%'}}>
                    <BodyText el="span">
                    <InlineEdit
                        mode="read"
                        value="no icon decoration"
                        decoration={{icon: false}}
                        control={() => {}}
                        showContentOnEdit
                        actionLayout="none"
                    />
                </BodyText>
                </StackItem>
                <StackItem fill style={{flexBasis: '100%'}}>
                <BodyText el="span">
                    <InlineEdit
                        mode="read"
                        value="no icon, underline"
                        decoration={{icon: false, underline: false}}
                        control={() => {}}
                        showContentOnEdit
                        actionLayout="none"
                    />
                </BodyText>
                </StackItem>
                <StackItem fill style={{flexBasis: '100%'}}>
                <BodyText el="span">
                    <InlineEdit
                        mode="read"
                        value="no icon, underline, color"
                        decoration={{icon: false, underline: false, color: false}}
                        control={() => {}}
                        showContentOnEdit
                        actionLayout="none"
                    />
                </BodyText>
                </StackItem>
            </Stack>
        </Card>
    );
};
render( DecorationExamples )

Proximity, and Density

Decorations are used to draw attention to users for click action and when there are too many items fighting for attention, it becomes a distraction - when page has multiple Inline Edit components, be conservative of the decorations.

When the InlineEdit component is lower than tertiary action, do not use decorations.


Examples

Common

Using in Headline

const CommonHeadline = () => {
    const value = 'Headline Example';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;

    return (
        <Card>
            <Headline el="span">
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    decoration={{color: false, underline: false}}
                    value={submittedValue}
                />
            </Headline>
        </Card>
    );
};
render (CommonHeadline);

Input as control

const CommonInput = () => {
    const value = 'Input Example';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                />
            </BodyText>
        </Card>
    );
};
render (CommonInput);

TextArea as control

const CommonTextArea = () => {
    const value = 'TextArea Example';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <TextArea {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                />
            </BodyText>
        </Card>
    );
};
render (CommonTextArea);

Select as control

const CommonSelect = () => {
    const dummyOptions = [
        { text: 'Jane Doe', value: 1 },
        { text: 'Bob Ross', value: 2 },
        { text: 'Jackie Robinson', value: 3 },
        { text: 'Alexandria Garcia', value: 4 },
        { text: 'Zack Bower', value: 5 },
        { text: 'Erin Smith', value: 6 },
        { text: 'Jarrod Saltalamacchia', value: 7 },
        { text: 'Natalia McPhearson', value: 8 }
    ];
    const value = dummyOptions[0];
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <AnvilSelect options={dummyOptions} {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                />
            </BodyText>
        </Card>
    );
};
render (CommonSelect);

Select multiple as control

const CommonSelectMultiple = () => {
    const dummyOptions = [
        { text: 'Jane Doe', value: 1 },
        { text: 'Bob Ross', value: 2 },
        { text: 'Jackie Robinson', value: 3 },
        { text: 'Alexandria Garcia', value: 4 },
        { text: 'Zack Bower', value: 5 },
        { text: 'Erin Smith', value: 6 },
        { text: 'Jarrod Saltalamacchia', value: 7 },
        { text: 'Natalia McPhearson', value: 8 }
    ];
    const value = [dummyOptions[0], dummyOptions[3], dummyOptions[4], dummyOptions[6]];
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <AnvilSelect multiple options={dummyOptions} {...fieldProps} />;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                    decoration={{underline: false}}
                />
            </BodyText>
        </Card>
    );
};
render (CommonSelectMultiple);

Custom

You can add custom content and complex control for the InlineEdit.

Using custom read component

const OverridingContent = () => {
    const value = 'Custom Read mode';
    const [submittedValue, setSubmittedValue] = React.useState(value);
    const onSave = props => setSubmittedValue(props.value);
    const getControl = fieldProps => <Input {...fieldProps} />;
    const getContent = () => <TagGroup><Tag>{submittedValue}</Tag></TagGroup>;

    return (
        <Card>
            <BodyText el="span">
                <InlineEdit
                    control={getControl}
                    onSave={onSave}
                    value={submittedValue}
                    content={getContent}
                />
            </BodyText>
        </Card>
    );
};
render (OverridingContent)

Using custom read and edit component

const CustomModalExample = () => {
    const [savedValue, setSavedValue] = React.useState(['Bob']);
    const [value, setValue] = React.useState(savedValue);
    const [open, setOpen] = React.useState(false);

    const handleSave = () => {
        setSavedValue(value);
        setOpen(false);
    };

    const handleOnChange = (value, checked) => {
        if(checked){
            setValue(prevState => [...prevState, value])
        } else {
            setValue(prevState => [...prevState].filter(item => item !== value))
        }
    };

    const handleOnClick = () => {
        setOpen(true);
    };

    const handleCancel = () => {
        setValue(savedValue);
        setOpen(false);
    };

    const getContent = () => {
        return (
            <TagGroup>
                {savedValue.map((item, i) => {
                    return (
                        <Tag key={i}>{item}</Tag>
                    );
                })}
            </TagGroup>
        );
    };

    const getControl = () => {
        return (
            <Modal
                open={open}
                closable={true}
                onClose={handleCancel}
                title="Test Input in Modal"
                footer={(
                    <ButtonGroup>
                        <Button onClick={handleCancel}>Cancel</Button>
                        <Button onClick={handleSave} primary>Save</Button>
                    </ButtonGroup>
                )}
            >
                <Form>
                    <Form.Checkbox onChange={handleOnChange} label="Bob" value="Bob" checked={value.includes('Bob')}/>
                    <Form.Checkbox onChange={handleOnChange} label="Jane" value="Jane" checked={value.includes('Jane')}/>
                </Form>
            </Modal>
        );
    };

    return (
        <Card>
            <InlineEdit
                mode={open ? 'edit' : 'read'}
                control={getControl}
                onClick={handleOnClick}
                actionLayout="none"
                content={getContent}
                showContentOnEdit
            />
        </Card>
    );
};
render(CustomModalExample)

Bulk change

const BulkChange = () => {
    const values = [
        { id: 'name', value: 'Jane Doe', icon: 'person' },
        { id: 'address', value: '1234 Jane Ave. Los Angeles, CA 90000 USA', icon: 'place' },
        { id: 'phone', value: '123 456 7890', icon: 'call' },
        { id: 'email', value: 'janedoe@gmail.com', icon: 'email' },
    ];
    const newValues = values.map(item => Object.assign({ savedValue: item.value }, item));
    const reducer = (state, action) => {
        switch (action.type) {
            case 'singleSave':
                return state.map(item => item.id === action.payload.id
                    ? { ...item, savedValue: action.payload.value }
                    : item
                );
            case 'onChange':
                return state.map(item => item.id === action.payload.id
                    ? { ...item, value: action.payload.value }
                    : item
                );
            case 'bulkSave':
                return state.map((item) => {
                    const itemValue = item.value;
                    return { ...item, savedValue: itemValue };
                });
            case 'bulkCancel':
                return state.map((item) => {
                    const savedValue = item.savedValue;
                    return { ...item, value: savedValue };
                });
            default: null;
        }
    };

    const [state, dispatch] = React.useReducer(reducer, newValues);
    const [mode, setMode] = React.useState('read');
    const handleEdit = () => setMode('edit');
    const getControl = fieldProps => <Input style={{flexGrow: 1}} {...fieldProps} />;
    const decoration = {icon: false, underline: false, color: false};

    const handleOnChange = (e, props) => {
        dispatch({ type: 'onChange', payload: { id: props.id, value: props.value } });
    };

    const handleOnSave = (props) => {
        dispatch({ type: 'singleSave', payload: { id: props.id, value: props.value } });
    };

    const bulkSave = () => {
        dispatch({ type: 'bulkSave' });
        setMode('read');
    };
    const bulkCancel = () => {
        dispatch({ type: 'bulkCancel' });
        setMode('read');
    };

    return (
        <Card>
            <CardSection>
                <Stack justifyContent="space-between" alignItems="center">
                    <Headline style={{margin: 0}}>Service Location</Headline>
                    {mode === 'read'
                        ? <Button small iconName="edit" onClick={handleEdit} fill='subtle' />
                        : (
                                <ButtonGroup attached>
                                    <Button small iconName="check" onClick={bulkSave} primary/>
                                    <Button small iconName="close" onClick={bulkCancel}/>
                                </ButtonGroup>
                            )
                    }
                </Stack>
            </CardSection>
            <CardSection>
                <BodyText size="small" el="span">
                    <Stack direction="column" spacing={1}>
                    {state.map((item, index) => (
                        <Stack key={index} alignItems="center" spacing={mode === 'edit' ? 2 : 1}>
                            <Icon name={item.icon} color="grey"/>
                            <InlineEdit
                                mode={mode}
                                decoration={decoration}
                                value={item.value}
                                width="fluid"
                                id={item.id}
                                autoFocusControl={index === 0 ? true : false}
                                control={getControl}
                                onSave={handleOnSave}
                                onChange={handleOnChange}
                                actionLayout={mode === 'edit' ? 'none' : 'inline'}
                            />
                        </Stack>
                    ))}
                    </Stack>
                </BodyText>
            </CardSection>
        </Card>
    )
};
render( BulkChange )

Best Practices

  • Use to update a single pre-existing data.
  • When editing content is a primary task, use form instead.
  • Do not use InlineEdit and other form controls in the same form.

Related Components