+`;
diff --git a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap
new file mode 100644
index 00000000000..bf0cc76fdcc
--- /dev/null
+++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiInlineEditTitle props renders as title 1`] = `
+
+
+
+`;
diff --git a/src/components/inline_edit/index.ts b/src/components/inline_edit/index.ts
new file mode 100644
index 00000000000..fb491c0aa6b
--- /dev/null
+++ b/src/components/inline_edit/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { EuiInlineEditText } from './inline_edit_text';
+
+export { EuiInlineEditTitle } from './inline_edit_title';
+
+export type { EuiInlineEditTextSizes } from './inline_edit_text';
diff --git a/src/components/inline_edit/inline_edit.styles.ts b/src/components/inline_edit/inline_edit.styles.ts
new file mode 100644
index 00000000000..61cc6824225
--- /dev/null
+++ b/src/components/inline_edit/inline_edit.styles.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+import { UseEuiTheme } from '../../services';
+
+export const euiInlineEditStyles = ({ euiTheme }: UseEuiTheme) => {
+ return {
+ euiInlineEdit: css`
+ // Always start the object with the first key being the name of the component
+ color: ${euiTheme.colors.primaryText};
+ `,
+ };
+};
diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx
new file mode 100644
index 00000000000..7afcaa57c5f
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_form.tsx
@@ -0,0 +1,207 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { ReactNode, FunctionComponent, useState } from 'react';
+import classNames from 'classnames';
+
+import { CommonProps } from '../common';
+import { EuiFormRow, EuiFieldText, EuiForm, EuiFieldTextProps } from '../form';
+import { EuiButtonIcon, EuiButtonEmpty, EuiButtonEmptyProps } from '../button';
+import { EuiButtonEmptyPropsForButton } from '../button/button_empty/button_empty';
+import { EuiFlexGroup, EuiFlexItem } from '../flex';
+import { useEuiI18n } from '../i18n';
+import { useGeneratedHtmlId } from '../../services/accessibility';
+
+// Props shared between the internal form component as well as consumer-facing components
+export type EuiInlineEditCommonProps = CommonProps & {
+ defaultValue: string;
+ /**
+ * Allow users to pass in a function that is called when the confirm button is clicked
+ * The function should return a boolean flag that will determine if the value will be saved.
+ * When the flag is true, the value will be saved. When the flag is false, the user will be
+ * returned to editMode.
+ */
+ onConfirm?: () => boolean;
+ /**
+ * Form label that appears above the form control
+ * This is required for accessibility because there is no visual label on the input
+ */
+ inputAriaLabel: string;
+ /**
+ * Aria-label for save button in editMode
+ */
+ saveButtonAriaLabel?: string;
+ /**
+ * Aria-label for cancel button in editMode
+ */
+ cancelButtonAriaLabel?: string;
+ /**
+ * Start in editMode
+ */
+ startWithEditOpen?: boolean;
+ /**
+ * Props that will be applied directly to the EuiEmptyButton displayed in readMode
+ */
+ readModeProps?: Omit;
+ /**
+ * Props that will be applied directly to the EuiFieldText displayed in editMode
+ */
+ editModeProps?: EuiFieldTextProps;
+};
+
+// Internal-only props, passed by the consumer-facing components
+export type EuiInlineEditFormProps = EuiInlineEditCommonProps & {
+ /**
+ * Form sizes
+ */
+ sizes: {
+ compressed: boolean;
+ buttonSize: EuiButtonEmptyProps['size'];
+ iconSize: EuiButtonEmptyProps['iconSize'];
+ };
+ /**
+ * Render prop that returns the read mode value as an arg
+ */
+ children: (readModeValue: ReactNode) => ReactNode;
+};
+
+export const SMALL_SIZE_FORM = {
+ iconSize: 's',
+ compressed: true,
+ buttonSize: 's',
+} as const;
+
+export const MEDIUM_SIZE_FORM = {
+ iconSize: 'm',
+ compressed: false,
+ buttonSize: 'm',
+} as const;
+
+export const EuiInlineEditForm: FunctionComponent = ({
+ className,
+ children,
+ sizes,
+ defaultValue,
+ onConfirm,
+ inputAriaLabel,
+ saveButtonAriaLabel,
+ cancelButtonAriaLabel,
+ startWithEditOpen,
+ readModeProps,
+ editModeProps,
+}) => {
+ const classes = classNames('euiInlineEdit', className);
+
+ // Styles to come later! (Styling editMode text to match the size of its readMode counterpart)
+ /*const theme = useEuiTheme();
+ const styles = euiInlineEditStyles(theme);
+ const cssStyles = [styles.euiInlineEdit];*/
+
+ const defaultSaveButtonAriaLabel = useEuiI18n(
+ 'euiInlineEditForm.saveButtonAriaLabel',
+ 'Save edit'
+ );
+ const defaultCancelButtonAriaLabel = useEuiI18n(
+ 'euiInlineEditForm.cancelButtonAriaLabel',
+ 'Cancel edit'
+ );
+
+ const [isEditing, setIsEditing] = useState(false || startWithEditOpen);
+ const inlineEditInputId = useGeneratedHtmlId({ prefix: '__inlineEditInput' });
+
+ const [editModeValue, setEditModeValue] = useState(defaultValue);
+ const [readModeValue, setReadModeValue] = useState(defaultValue);
+
+ const cancelInlineEdit = () => {
+ setEditModeValue(readModeValue);
+ setIsEditing(!isEditing);
+ };
+
+ const saveInlineEditValue = () => {
+ if (editModeValue && onConfirm && !onConfirm()) {
+ // If there is text, an onConfirm method is present, and it has returned false, cancel the action
+ return;
+ } else if (editModeValue) {
+ setReadModeValue(editModeValue);
+ setIsEditing(!isEditing);
+ } else {
+ // If there's no text, cancel the action, reset the input text, and return to readMode
+ cancelInlineEdit();
+ }
+ };
+
+ const editModeForm = (
+
+
+
+ {
+ setEditModeValue(e.target.value);
+ }}
+ aria-label={inputAriaLabel}
+ autoFocus
+ compressed={sizes.compressed}
+ {...editModeProps}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const readModeElement = (
+ {
+ setIsEditing(!isEditing);
+ }}
+ {...readModeProps}
+ >
+ {children(readModeValue)}
+
+ );
+
+ return (
+
{isEditing ? editModeForm : readModeElement}
+ );
+};
diff --git a/src/components/inline_edit/inline_edit_text.test.tsx b/src/components/inline_edit/inline_edit_text.test.tsx
new file mode 100644
index 00000000000..eb47d25e5b8
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_text.test.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { render } from '../../test/rtl';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiInlineEditText } from './inline_edit_text';
+
+describe('EuiInlineEditText', () => {
+ describe('props', () => {
+ test('renders as text', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/components/inline_edit/inline_edit_text.tsx b/src/components/inline_edit/inline_edit_text.tsx
new file mode 100644
index 00000000000..c761481e578
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_text.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FunctionComponent } from 'react';
+import classNames from 'classnames';
+import { EuiText, EuiTextProps } from '../text';
+import {
+ EuiInlineEditCommonProps,
+ EuiInlineEditForm,
+ SMALL_SIZE_FORM,
+ MEDIUM_SIZE_FORM,
+} from './inline_edit_form';
+
+export type EuiInlineEditTextSizes = Exclude;
+
+export type EuiInlineEditTextProps = EuiInlineEditCommonProps & {
+ /**
+ * Text size level
+ */
+ size?: EuiInlineEditTextSizes;
+};
+
+export const EuiInlineEditText: FunctionComponent = ({
+ children,
+ className,
+ size = 'm',
+ defaultValue,
+ onConfirm,
+ inputAriaLabel,
+ saveButtonAriaLabel,
+ cancelButtonAriaLabel,
+ startWithEditOpen,
+ readModeProps,
+ editModeProps,
+ ...rest
+}) => {
+ const classes = classNames('euiInlineEditText', className);
+
+ const isSmallSize = ['xs', 's'].includes(size);
+ const sizes = isSmallSize ? SMALL_SIZE_FORM : MEDIUM_SIZE_FORM;
+
+ const formProps = {
+ sizes,
+ defaultValue,
+ onConfirm,
+ inputAriaLabel,
+ saveButtonAriaLabel,
+ cancelButtonAriaLabel,
+ startWithEditOpen,
+ readModeProps,
+ editModeProps,
+ };
+
+ return (
+
+ {(textReadModeValue) => (
+ {textReadModeValue}
+ )}
+
+ );
+};
diff --git a/src/components/inline_edit/inline_edit_title.test.tsx b/src/components/inline_edit/inline_edit_title.test.tsx
new file mode 100644
index 00000000000..9104347af45
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_title.test.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { render } from '../../test/rtl';
+import { requiredProps } from '../../test/required_props';
+
+import { EuiInlineEditTitle } from './inline_edit_title';
+
+describe('EuiInlineEditTitle', () => {
+ describe('props', () => {
+ test('renders as title', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/components/inline_edit/inline_edit_title.tsx b/src/components/inline_edit/inline_edit_title.tsx
new file mode 100644
index 00000000000..c612199d2b0
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_title.tsx
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FunctionComponent } from 'react';
+import classNames from 'classnames';
+import { EuiTitle, EuiTitleSize } from '../title';
+import {
+ EuiInlineEditCommonProps,
+ EuiInlineEditForm,
+ SMALL_SIZE_FORM,
+ MEDIUM_SIZE_FORM,
+} from './inline_edit_form';
+
+export const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;
+type Heading = typeof HEADINGS[number];
+
+export type EuiInlineEditTitleProps = EuiInlineEditCommonProps & {
+ /**
+ * Title size level
+ */
+ size?: EuiTitleSize;
+ /**
+ * Level of heading to be used for the title
+ */
+ heading: Heading;
+};
+
+export const EuiInlineEditTitle: FunctionComponent = ({
+ children,
+ className,
+ size = 'm',
+ heading,
+ defaultValue,
+ onConfirm,
+ inputAriaLabel,
+ saveButtonAriaLabel,
+ cancelButtonAriaLabel,
+ startWithEditOpen = false,
+ readModeProps,
+ editModeProps,
+ ...rest
+}) => {
+ const classes = classNames('euiInlineEditTitle', className);
+
+ const H: Heading = heading;
+
+ const isSmallSize = ['xxxs', 'xxs', 'xs', 's'].includes(size);
+ const sizes = isSmallSize ? SMALL_SIZE_FORM : MEDIUM_SIZE_FORM;
+
+ const formProps = {
+ sizes,
+ defaultValue,
+ onConfirm,
+ inputAriaLabel,
+ saveButtonAriaLabel,
+ cancelButtonAriaLabel,
+ startWithEditOpen,
+ readModeProps,
+ editModeProps,
+ };
+
+ return (
+
+ {(titleReadModeValue) => (
+
+ {titleReadModeValue}
+
+ )}
+
+ );
+};