From b88b07c9cd85b169aed5048b6b13b5df9a772851 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:56:40 -0400 Subject: [PATCH 01/23] [EuiInlineEdit] Create Component Directory and Base Functionality (#6598) * Initial directory setup for EuiInlineEdit * Initial structure for EuiInlineEdit docs * Created EuiInlineEdit base component that toggles between a text input and text or title. * Replaced button icons with full EuiButtons with text. Positioned the buttons on the right side of the input * Added early snapshot for component * Added aria-labels with i18n for the editView input, save button, and cancel buttons. Updated the props to require an input aria label because the option to show the label on the input was removed. This was removed to preserve the layout and prevent the input from appearing to bounch when switching between editView and readView. * Combined with last commit - forgot to hit save * Made defaultValue a required field because if there is no value passed in, the button will be empty (with the exception of the edit icon) * Updated basic snapshot tests, updated docs * Added button groups to documentation to make toggling button sizes easier * Update src/components/text/index.ts Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src-docs/src/views/inline_edit/inline_edit.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src-docs/src/views/inline_edit/inline_edit.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src/components/inline_edit/inline_edit.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src/components/inline_edit/inline_edit.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src/components/inline_edit/inline_edit.test.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src/components/inline_edit/inline_edit.tsx Co-authored-by: Elizabet Oliveira * Separated the two EuiInlineEdit examples into their own files * Created additional logic to resize buttons and fieldtext elements when when the size of EuiInlineEdit is small or lower. * Created a heading prop to allow consumers to choose the level heading they would like the InlineEdit (title) to be wrapped inside of. If one isn't provided, h2 will be the default. * Updated basic test snapshots for EuiInlineEdit * Updated new i18n tokens to be more explicit about their purpose * Split EuiInlineEdit into three components: EuiInlineEditText, EuiInlineEditTitle, and EuiInlineEditButtons. - Created utils and types file to share common props and utility functions between all three components * Oops * EuiInlineEdit Updates - Updated the onConfirm prop to return a boolean flag that will determine if the text value is saved or not in editMode - Updated edit/read prop variable names - Exported EuiButtonEmptyPropsForButton because it's being used as a type for the new readMode prop - Added base testing files for EuiInlineEditTitle and EuiInlineEditButtons * Remove testing props from inline edit example * Separated repeated logic inside of EuiInlineEditTitle and EuiInlineEditText into a new component called EuiInlineEditForm. This new form component is responsible for the toggling of readMode and editMode for EuiInlineEdit. * Move types to shared internal form component * Pass top-level props instead of in a `props` obj * Remove top level read state, use render prop instead - so that EuiInlineEditForm is completely in charge of state * Remove EuiInlineEditButtons, roll into EuiInlineEditForm as inline * Removed utility in favor of component-specific size logic - while still DRYing out the underlying form sizes being used * Fix types and update snapshots * Clean up type def --------- Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> Co-authored-by: Elizabet Oliveira Co-authored-by: Constance Chen --- src-docs/src/routes.js | 3 + .../views/inline_edit/inline_edit_confirm.tsx | 29 +++ .../views/inline_edit/inline_edit_example.js | 86 ++++++++ .../views/inline_edit/inline_edit_text.tsx | 52 +++++ .../views/inline_edit/inline_edit_title.tsx | 71 ++++++ .../button/button_empty/button_empty.tsx | 4 +- src/components/index.ts | 2 + .../inline_edit_text.test.tsx.snap | 31 +++ .../inline_edit_title.test.tsx.snap | 31 +++ src/components/inline_edit/index.ts | 13 ++ .../inline_edit/inline_edit.styles.ts | 19 ++ .../inline_edit/inline_edit_form.tsx | 207 ++++++++++++++++++ .../inline_edit/inline_edit_text.test.tsx | 29 +++ .../inline_edit/inline_edit_text.tsx | 66 ++++++ .../inline_edit/inline_edit_title.test.tsx | 30 +++ .../inline_edit/inline_edit_title.tsx | 76 +++++++ 16 files changed, 748 insertions(+), 1 deletion(-) create mode 100644 src-docs/src/views/inline_edit/inline_edit_confirm.tsx create mode 100644 src-docs/src/views/inline_edit/inline_edit_example.js create mode 100644 src-docs/src/views/inline_edit/inline_edit_text.tsx create mode 100644 src-docs/src/views/inline_edit/inline_edit_title.tsx create mode 100644 src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap create mode 100644 src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap create mode 100644 src/components/inline_edit/index.ts create mode 100644 src/components/inline_edit/inline_edit.styles.ts create mode 100644 src/components/inline_edit/inline_edit_form.tsx create mode 100644 src/components/inline_edit/inline_edit_text.test.tsx create mode 100644 src/components/inline_edit/inline_edit_text.tsx create mode 100644 src/components/inline_edit/inline_edit_title.test.tsx create mode 100644 src/components/inline_edit/inline_edit_title.tsx diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index acf9ce0393c..122ddea5b0a 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -151,6 +151,8 @@ import { ImageExample } from './views/image/image_example'; import { InnerTextExample } from './views/inner_text/inner_text_example'; +import { InlineEditExample } from './views/inline_edit/inline_edit_example'; + import { KeyPadMenuExample } from './views/key_pad_menu/key_pad_menu_example'; import { LinkExample } from './views/link/link_example'; @@ -561,6 +563,7 @@ const navigation = [ HealthExample, IconExample, ImageExample, + InlineEditExample, ListGroupExample, LoadingExample, NotificationEventExample, diff --git a/src-docs/src/views/inline_edit/inline_edit_confirm.tsx b/src-docs/src/views/inline_edit/inline_edit_confirm.tsx new file mode 100644 index 00000000000..2443db20b1e --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_confirm.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { EuiInlineEditText } from '../../../../src'; + +export default () => { + // TO DO: Convert this example to use something like a modal + const confirmInlineEditChanges = () => { + // eslint-disable-next-line no-restricted-globals + const flag = confirm('Are you sure you want to save?') ? true : false; + return flag; + }; + + return ( + <> + + + ); +}; diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js new file mode 100644 index 00000000000..41587e0f19b --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -0,0 +1,86 @@ +import React from 'react'; + +import { GuideSectionTypes } from '../../components'; + +import { + EuiText, + EuiInlineEditText, + EuiInlineEditTitle, +} from '../../../../src'; + +import InlineEditText from './inline_edit_text'; +const inlineEditTextSource = require('!!raw-loader!./inline_edit_text'); + +import InlineEditTitle from './inline_edit_title'; +const inlineEditTitleSource = require('!!raw-loader!./inline_edit_title'); + +import InlineEditConfirm from './inline_edit_confirm'; +const inlineEditConfirmSource = require('!!raw-loader!././inline_edit_confirm'); + +export const InlineEditExample = { + title: 'Inline edit', + intro: ( + <> + This is where the description will go + + ), + sections: [ + { + title: 'InlineEditText', + text: ( + <> +

+ Description needed: how to use the EuiInlineEdit{' '} + component. +

+ + ), + source: [ + { + type: GuideSectionTypes.JS, + code: inlineEditTextSource, + }, + ], + demo: , + props: { EuiInlineEditText }, + }, + { + title: 'InlineEditTitle', + text: ( + <> +

+ Description needed: how to use the EuiInlineEdit{' '} + component. +

+ + ), + source: [ + { + type: GuideSectionTypes.JS, + code: inlineEditTitleSource, + }, + ], + demo: , + props: { EuiInlineEditTitle }, + }, + { + title: 'Confirm inline edit', + text: ( + <> +

+ Description needed: how to use the EuiInlineEdit{' '} + component. +

+ + ), + source: [ + { + type: GuideSectionTypes.JS, + code: inlineEditConfirmSource, + }, + ], + demo: , + props: { EuiInlineEditText }, + }, + ], +}; diff --git a/src-docs/src/views/inline_edit/inline_edit_text.tsx b/src-docs/src/views/inline_edit/inline_edit_text.tsx new file mode 100644 index 00000000000..df4657d17b2 --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_text.tsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; + +import { + EuiInlineEditText, + EuiSpacer, + EuiButtonGroup, + EuiInlineEditTextSizes, +} from '../../../../src'; + +export default () => { + const textSizeButtons = [ + { + id: 'xs', + label: 'Extra Small', + }, + { + id: 's', + label: 'Small', + }, + { + id: 'm', + label: 'Medium', + }, + ]; + + const [toggleTextButtonSize, setToggleTextButtonSize] = useState< + EuiInlineEditTextSizes + >('m'); + + const textSizeOnChange = (optionId: EuiInlineEditTextSizes) => { + setToggleTextButtonSize(optionId); + }; + + return ( + <> + textSizeOnChange(id as EuiInlineEditTextSizes)} + /> + + + + + + ); +}; diff --git a/src-docs/src/views/inline_edit/inline_edit_title.tsx b/src-docs/src/views/inline_edit/inline_edit_title.tsx new file mode 100644 index 00000000000..9484bd0a954 --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_title.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; + +import { + EuiInlineEditTitle, + EuiSpacer, + EuiButtonGroup, + EuiTitleSize, +} from '../../../../src'; + +export default () => { + const titleSizeButtons = [ + { + id: 'xxxs', + label: '3X Small', + }, + { + id: 'xxs', + label: '2X Small', + }, + { + id: 'xs', + label: 'Extra small', + }, + { + id: 's', + label: 'Small', + }, + { + id: 'm', + label: 'Medium', + }, + { + id: 'l', + label: 'Large', + }, + ]; + + const [toggleTitleButtonSize, setToggleTitleButtonSize] = useState< + EuiTitleSize + >('m'); + + const titleSizeOnChange = (optionId: EuiTitleSize) => { + setToggleTitleButtonSize(optionId); + }; + + return ( + <> + titleSizeOnChange(id as EuiTitleSize)} + /> + + + + + + ); +}; diff --git a/src/components/button/button_empty/button_empty.tsx b/src/components/button/button_empty/button_empty.tsx index 98d02064411..f172e61aac4 100644 --- a/src/components/button/button_empty/button_empty.tsx +++ b/src/components/button/button_empty/button_empty.tsx @@ -91,7 +91,9 @@ export interface CommonEuiButtonEmptyProps type EuiButtonEmptyPropsForAnchor = PropsForAnchor; -type EuiButtonEmptyPropsForButton = PropsForButton; +export type EuiButtonEmptyPropsForButton = PropsForButton< + CommonEuiButtonEmptyProps +>; export type EuiButtonEmptyProps = ExclusiveUnion< EuiButtonEmptyPropsForAnchor, diff --git a/src/components/index.ts b/src/components/index.ts index f11d7b79db8..671e45d7a43 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -91,6 +91,8 @@ export * from './image'; export * from './inner_text'; +export * from './inline_edit'; + export * from './i18n'; export * from './loading'; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap new file mode 100644 index 00000000000..e235f18852d --- /dev/null +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiInlineEditText props renders as text 1`] = ` +
+ +
+`; 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} + + )} + + ); +}; From d69c093b8cf744c285923e63f2e738c527707da3 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:02:43 -0400 Subject: [PATCH 02/23] [EuiInlineEdit] Add Dynamic Font Sizing & Truncation Styling (#6660) * Created styling for EuiInlineEdit text and title components that adjusts the font-size used within the editMode form control. Added styling to ensure that very long text is truncated inside of the EuiEmptyButton used in readMode * Updated snapshot tests for EuiInlineEdit * Update src/components/inline_edit/inline_edit_title.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src/components/inline_edit/inline_edit_text.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Updated snapshots for EuiInlineEdit test cases after modification in Github UI --------- Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> --- .../inline_edit_text.test.tsx.snap | 4 +- .../inline_edit_title.test.tsx.snap | 4 +- .../inline_edit/inline_edit_text.styles.ts | 37 +++++++++++++ .../inline_edit/inline_edit_text.tsx | 17 +++++- .../inline_edit/inline_edit_title.styles.ts | 52 +++++++++++++++++++ .../inline_edit/inline_edit_title.tsx | 15 +++++- 6 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 src/components/inline_edit/inline_edit_text.styles.ts create mode 100644 src/components/inline_edit/inline_edit_title.styles.ts diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index e235f18852d..345679c9b9a 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -2,7 +2,7 @@ exports[`EuiInlineEditText props renders as text 1`] = `
+
+ + + +
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+ + +`; + +exports[`EuiInlineEditForm Edit Mode renders 1`] = ` +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+`; + +exports[`EuiInlineEditForm Edit Mode renders EuiSkeletonRectangles in place of editMode buttons when loading 1`] = ` +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`EuiInlineEditForm Edit Mode renders editModeProps.formRowProps 1`] = ` +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+`; + +exports[`EuiInlineEditForm Edit Mode renders editModeProps.inputProps 1`] = ` +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+`; + +exports[`EuiInlineEditForm Edit Mode renders save button and cancel button aria-labels 1`] = ` +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+
+
+
+ Loaded +
+ + +
+
+
+
+
+
+
+`; + +exports[`EuiInlineEditForm Read Mode renders 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditForm Read Mode renders readModeProps onto the button 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditForm Read Mode renders small size 1`] = ` +
+ +
+`; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index 345679c9b9a..42d20b7f64e 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -1,11 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiInlineEditText props renders as text 1`] = ` +exports[`EuiInlineEditText renders 1`] = `
+ + + +
+`; + +exports[`EuiInlineEditText text sizes renders m 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditText text sizes renders s 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditText text sizes renders xs 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditTitle title sizes renders size l 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditTitle title sizes renders size m 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditTitle title sizes renders size s 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditTitle title sizes renders size xs 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditTitle title sizes renders size xxs 1`] = ` +
+ +
+`; + +exports[`EuiInlineEditTitle title sizes renders size xxxs 1`] = ` +
+ diff --git a/src/components/inline_edit/inline_edit_form.test.tsx b/src/components/inline_edit/inline_edit_form.test.tsx new file mode 100644 index 00000000000..1710f805942 --- /dev/null +++ b/src/components/inline_edit/inline_edit_form.test.tsx @@ -0,0 +1,313 @@ +/* + * 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 { fireEvent } from '@testing-library/dom'; + +import { + EuiInlineEditForm, + EuiInlineEditFormProps, + SMALL_SIZE_FORM, + MEDIUM_SIZE_FORM, +} from './inline_edit_form'; + +describe('EuiInlineEditForm', () => { + const commonInlineEditFormProps: EuiInlineEditFormProps = { + ...requiredProps, + defaultValue: 'Hello World!', + inputAriaLabel: 'Edit inline', + sizes: MEDIUM_SIZE_FORM, + children: (readModeValue) => readModeValue, + }; + + describe('Read Mode', () => { + it('renders', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('renders readModeProps onto the button', () => { + const { container, getByTestSubject } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + }); + + it('renders small size', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + }); + + describe('Edit Mode', () => { + it('renders', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('renders editModeProps.inputProps', () => { + const { container, getByTestSubject } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + expect(getByTestSubject('customInput')).toBeTruthy(); + }); + + it('renders editModeProps.formRowProps', () => { + const { container, getByTestSubject } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + expect(getByTestSubject('customErrorText')).toBeTruthy(); + }); + + it('renders save button and cancel button aria-labels', () => { + const { container, getByLabelText } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + expect(getByLabelText("Yes! Let's save.")).toBeTruthy(); + expect(getByLabelText('Uh no. Do not save.')).toBeTruthy(); + }); + + it('renders EuiSkeletonRectangles in place of editMode buttons when loading', () => { + const { container, queryByTestSubject } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + + expect(container.querySelectorAll('.euiSkeletonRectangle')).toHaveLength( + 2 + ); + + expect(queryByTestSubject('euiInlineEditModeSaveButton')).toBeFalsy(); + expect(queryByTestSubject('euiInlineEditModeCancelButton')).toBeFalsy(); + }); + + it('disables the save button when input is invalid ', () => { + const { container, getByTestSubject } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + + expect( + getByTestSubject('euiInlineEditModeInput').hasAttribute('aria-invalid') + ).toBeTruthy(); + + expect(getByTestSubject('euiInlineEditModeSaveButton')).toBeDisabled(); + expect( + getByTestSubject('euiInlineEditModeCancelButton') + ).not.toBeDisabled(); + }); + + it('returns the latest value within EuiFieldText upon saving', () => { + const onSaveFunction = jest.fn(); + + const { getByTestSubject } = render( + + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); + + expect(onSaveFunction).toHaveBeenCalledWith('New message!'); + }); + }); + + describe('Toggling between readMode and editMode', () => { + it('clicking on the readModeButton takes us to editMode', () => { + const { getByTestSubject, queryByTestSubject } = render( + + ); + + fireEvent.click(getByTestSubject('euiInlineReadModeButton')); + expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy(); + expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy(); + }); + + it('saves text and returns to readMode', () => { + const { getByTestSubject, getByText } = render( + + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + expect( + getByTestSubject('euiInlineEditModeInput').getAttribute('value') + ).toEqual('New message!'); + fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); + + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('New message!')).toBeTruthy(); + }); + + it('cancels text and returns to readMode', () => { + const onSave = jest.fn(); + + const { getByTestSubject, getByText } = render( + + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + expect( + getByTestSubject('euiInlineEditModeInput').getAttribute('value') + ).toEqual('New message!'); + fireEvent.click(getByTestSubject('euiInlineEditModeCancelButton')); + + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('Hello World!')).toBeTruthy(); + expect(onSave).not.toHaveBeenCalled(); + }); + + describe('onConfirm behavior on save', () => { + it('returns to readMode with updated text when onConfirm returns true', () => { + const { getByTestSubject, getByText } = render( + true} + /> + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); + + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('New message!')).toBeTruthy(); + }); + + it('stays in editMode when onConfirm returns false', () => { + const onSave = jest.fn(); + + const { getByTestSubject, queryByTestSubject } = render( + false} + /> + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); + + expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy(); + expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it('sends the editMode text to the onConfirm callback', () => { + const { getByText, getByTestSubject } = render( + { + return editModeValue === '' ? false : true; + }} + /> + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: '' }, + }); + fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); + + expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy(); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'hey there' }, + }); + fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); + + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('hey there')).toBeTruthy(); + }); + }); + }); +}); diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index f256b1dfa7e..23b68582707 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import React, { ReactNode, FunctionComponent, useState } from 'react'; +import React, { + ReactNode, + FunctionComponent, + useState, + HTMLAttributes, +} from 'react'; import classNames from 'classnames'; import { CommonProps } from '../common'; @@ -27,53 +32,56 @@ 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 `EuiFormRow` and `EuiFieldText` input displayed in editMode - */ - editModeProps?: { - formRowProps?: Partial; - inputProps?: Partial; +export type EuiInlineEditCommonProps = HTMLAttributes & + CommonProps & { + defaultValue: string; + /** + * Callback that passes the updated value of the edited text when the save button is pressed, + * and the `onConfirm` callback (if passed) returns true + */ + onSave?: (onSaveValue: string) => void; + /** + * Callback that fires when users click the save button, but before the text actually saves. Passes the current edited + * text value as an argument. + */ + onConfirm?: (editModeValue: string) => 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 `EuiFormRow` and `EuiFieldText` input displayed in editMode + */ + editModeProps?: { + formRowProps?: Partial; + inputProps?: Partial; + }; + /** + * Loading state when changes are saved in editMode + */ + isLoading?: boolean; + /** + * Validation for the form control used to edit text in editMode + */ + isInvalid?: boolean; }; - /** - * Loading state when changes are saved in editMode - */ - isLoading?: boolean; - - /** - * Validation for the form control used to edit text in editMode - */ - isInvalid?: boolean; -}; // Internal-only props, passed by the consumer-facing components export type EuiInlineEditFormProps = EuiInlineEditCommonProps & { @@ -115,8 +123,9 @@ export const EuiInlineEditForm: FunctionComponent = ({ startWithEditOpen, readModeProps, editModeProps, - isLoading, + isLoading = false, isInvalid, + onSave, }) => { const classes = classNames('euiInlineEdit', className); @@ -147,15 +156,13 @@ export const EuiInlineEditForm: FunctionComponent = ({ }; const saveInlineEditValue = () => { - if (editModeValue && onConfirm && !onConfirm()) { - // If there is text, an onConfirm method is present, and it has returned false, cancel the action + if (onConfirm && !onConfirm(editModeValue)) { + // If an onConfirm method is present, and it has returned false, cancel the action return; - } else if (editModeValue) { + } else { setReadModeValue(editModeValue); setIsEditing(!isEditing); - } else { - // If there's no text, cancel the action, reset the input text, and return to readMode - cancelInlineEdit(); + onSave?.(editModeValue); } }; @@ -179,6 +186,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ compressed={sizes.compressed} isInvalid={isInvalid} isLoading={isLoading} + data-test-subj="euiInlineEditModeInput" {...editModeProps?.inputProps} /> @@ -201,6 +209,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ size={sizes.buttonSize} iconSize={sizes.iconSize} disabled={isInvalid} + data-test-subj="euiInlineEditModeSaveButton" /> @@ -224,6 +233,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ display="base" size={sizes.buttonSize} iconSize={sizes.iconSize} + data-test-subj="euiInlineEditModeCancelButton" /> @@ -244,6 +254,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ onClick={() => { setIsEditing(!isEditing); }} + data-test-subj="euiInlineReadModeButton" {...readModeProps} > {children(readModeValue)} diff --git a/src/components/inline_edit/inline_edit_text.test.tsx b/src/components/inline_edit/inline_edit_text.test.tsx index eb47d25e5b8..736f2b99e8c 100644 --- a/src/components/inline_edit/inline_edit_text.test.tsx +++ b/src/components/inline_edit/inline_edit_text.test.tsx @@ -10,20 +10,39 @@ import React from 'react'; import { render } from '../../test/rtl'; import { requiredProps } from '../../test/required_props'; -import { EuiInlineEditText } from './inline_edit_text'; +import { EuiInlineEditText, EuiInlineEditTextProps } from './inline_edit_text'; +import { TEXT_SIZES } from '../text/text'; describe('EuiInlineEditText', () => { - describe('props', () => { - test('renders as text', () => { - const { container } = render( - - ); - - expect(container.firstChild).toMatchSnapshot(); + const inlineEditTextProps: EuiInlineEditTextProps = { + ...requiredProps, + inputAriaLabel: 'Edit text inline', + defaultValue: 'Hello World!', + }; + + it('renders', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + describe('text sizes', () => { + // Remove 'relative' from text sizes available for EuiInlineEditText + const availableTextSizes = TEXT_SIZES.filter((size) => size !== 'relative'); + + availableTextSizes.forEach((size: string) => { + test(`renders ${size}`, () => { + 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 index 1719e369fdd..5a9451dba73 100644 --- a/src/components/inline_edit/inline_edit_text.tsx +++ b/src/components/inline_edit/inline_edit_text.tsx @@ -39,7 +39,7 @@ export const EuiInlineEditText: FunctionComponent = ({ startWithEditOpen, readModeProps, editModeProps, - isLoading = false, + isLoading, isInvalid = false, ...rest }) => { diff --git a/src/components/inline_edit/inline_edit_title.test.tsx b/src/components/inline_edit/inline_edit_title.test.tsx index 9104347af45..84363d4fd11 100644 --- a/src/components/inline_edit/inline_edit_title.test.tsx +++ b/src/components/inline_edit/inline_edit_title.test.tsx @@ -10,21 +10,44 @@ import React from 'react'; import { render } from '../../test/rtl'; import { requiredProps } from '../../test/required_props'; -import { EuiInlineEditTitle } from './inline_edit_title'; +import { + EuiInlineEditTitle, + EuiInlineEditTitleProps, +} from './inline_edit_title'; +import { TITLE_SIZES } from '../title/title'; describe('EuiInlineEditTitle', () => { - describe('props', () => { - test('renders as title', () => { - const { container } = render( - - ); - - expect(container.firstChild).toMatchSnapshot(); + const inlineEditTitleProps: EuiInlineEditTitleProps = { + ...requiredProps, + inputAriaLabel: 'Edit title inline', + defaultValue: 'Hello World!', + heading: 'h1', + }; + + it('renders', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('renders the heading prop', () => { + const { container } = render( + + ); + expect(container.querySelector('h3')).toBeTruthy(); + }); + + describe('title sizes', () => { + TITLE_SIZES.forEach((size) => { + it(`renders size ${size}`, () => { + 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 index a35ee2f8ef2..be663fa1c61 100644 --- a/src/components/inline_edit/inline_edit_title.tsx +++ b/src/components/inline_edit/inline_edit_title.tsx @@ -45,7 +45,7 @@ export const EuiInlineEditTitle: FunctionComponent = ({ startWithEditOpen = false, readModeProps, editModeProps, - isLoading = false, + isLoading, isInvalid = false, ...rest }) => { From db8d2d718de255f0e00866cf7adfd04c61b64baf Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Wed, 26 Apr 2023 14:33:06 -0700 Subject: [PATCH 06/23] [EuiInlineEdit] Refactor multiple props to harden for real-world production usage + misc cleanup (#6735) * Save example misc cleanup - remove button that clears localStorage - it's not necessary for the demo and we can do so in devtools instead - remove onConfirm doc copy - going to move that to a new section * Add support for async saving + validation - remove confusing extra `onConfirm` callback for handling everything in a single `onSave` - remove extra `onSave` prop test - just roll that behavior into the last describe block * Update documentation examples - update confirm example to more closely mimic production behavior - remove dedicated isLoading/isInvalid demo - we can demo that in the production example instead * Remove specific `ariaLabel` props in favor of generic props that can be spread to the save & cancel buttons - this is necessary for consumers to do things like reset errors on cancel, or fire telemetry events on click + remove `disabled` state of save button on `isInvalid` - but consumers can add that back in if needed via `saveButtonProps`, depending on their specific UX + minor type cleanup * Support custom `onClick` events for all button props - clicks should call both internal EUI handlers and consumer callbacks * Misc cleanup - Remove unnecessary toggle on isEditing switches which could potentially create race conditions - we know it should always be either true or false depending on the mode being entered/exited - Improve prop docs readability - make `it` vs. `test` syntax/grammar more consistent - Remove unnecessary false fallbacks - undefined is already falsy * Improve docs a bit more - react typegen is mutilating `editModeProps`, so we should more explicit in our manual prop docs - update the last docs example * PR feedback: typo Co-authored-by: Bree Hall <40739624+breehall@users.noreply.github.com> --------- Co-authored-by: Bree Hall <40739624+breehall@users.noreply.github.com> --- .../views/inline_edit/inline_edit_confirm.tsx | 22 -- .../views/inline_edit/inline_edit_example.js | 112 +++---- .../inline_edit/inline_edit_mode_props.tsx | 16 +- .../views/inline_edit/inline_edit_save.tsx | 25 +- .../views/inline_edit/inline_edit_states.tsx | 45 --- .../inline_edit/inline_edit_validation.tsx | 53 ++++ .../inline_edit_form.test.tsx.snap | 274 +++++++++++++----- .../inline_edit/inline_edit_form.test.tsx | 146 ++++++---- .../inline_edit/inline_edit_form.tsx | 101 +++---- .../inline_edit/inline_edit_text.tsx | 8 +- .../inline_edit/inline_edit_title.tsx | 10 +- 11 files changed, 478 insertions(+), 334 deletions(-) delete mode 100644 src-docs/src/views/inline_edit/inline_edit_confirm.tsx delete mode 100644 src-docs/src/views/inline_edit/inline_edit_states.tsx create mode 100644 src-docs/src/views/inline_edit/inline_edit_validation.tsx diff --git a/src-docs/src/views/inline_edit/inline_edit_confirm.tsx b/src-docs/src/views/inline_edit/inline_edit_confirm.tsx deleted file mode 100644 index 432fcd73e42..00000000000 --- a/src-docs/src/views/inline_edit/inline_edit_confirm.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import { EuiInlineEditText } from '../../../../src'; - -export default () => { - const confirmInlineEditChanges = () => { - // eslint-disable-next-line no-restricted-globals - const flag = confirm('Are you sure you want to save?') ? true : false; - return flag; - }; - - return ( - <> - - - ); -}; diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index 7d0e95b0a0d..8e4dde90c6b 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -23,11 +23,8 @@ const inlineEditModePropsSource = require('!!raw-loader!./inline_edit_mode_props import InlineEditSave from './inline_edit_save'; const inlineEditSaveSource = require('!!raw-loader!././inline_edit_save'); -import InlineEditConfirm from './inline_edit_confirm'; -const inlineEditConfirmSource = require('!!raw-loader!././inline_edit_confirm'); - -import InlineEditStates from './inline_edit_states'; -const inlineEditStatesSource = require('!!raw-loader!././inline_edit_states'); +import InlineEditValidation from './inline_edit_validation'; +const inlineEditValidationSource = require('!!raw-loader!././inline_edit_validation'); export const InlineEditExample = { title: 'Inline edit', @@ -87,14 +84,12 @@ export const InlineEditExample = { { title: 'Saving edited text', text: ( - <> -

- Use the onSave property to retrieve the value of - the edited text when the save button is pressed, and the{' '} - onConfirm callback (if passed) returns{' '} - true .{' '} -

- +

+ Use the onSave property to retrieve the value of + the edited text when the save button is pressed.{' '} + onSave does not fire if the user cancels their + edit. +

), source: [ { @@ -105,75 +100,84 @@ export const InlineEditExample = { demo: , }, { - title: 'Loading and invalid states', + title: 'Validating edited text', text: ( <>

- Setting the isLoading prop to true will add a - spinner to the input element in editMode and add - the loading state to the confirm and cancel input buttons. + Validation states (isLoading and{' '} + isInvalid) only display while the user is in edit + mode.

- Setting the isInvalid prop to true will display{' '} - EuiInlineEdit's error state. Optionally, use{' '} - editModeProps.formRowProps.error to pass an error - message that will be displayed on the form control. + To validate text when the user presses the save button but before + the user is returned to read mode, return a boolean (or an async + promise returning a boolean) from your onSave{' '} + callback.

- - ), - source: [ - { - type: GuideSectionTypes.TSX, - code: inlineEditStatesSource, - }, - ], - demo: , - }, - { - title: 'Confirm inline edit', - text: ( - <>

- Use the onConfirm property to pass a function - that will prompt users to confirm their changes. + Returning false from onSave{' '} + will keep the user in edit mode, where you can then display + validation state and messages. Returning true or{' '} + undefined will return the user to read mode.

), source: [ { type: GuideSectionTypes.TSX, - code: inlineEditConfirmSource, + code: inlineEditValidationSource, }, ], - demo: , + demo: , }, { title: 'Customizing read and edit modes', text: ( <>

- Customize the readMode state by passing{' '} - readModeProps. readMode{' '} - accepts{' '} + Customize the read mode by passing readModeProps, + which accepts any{' '} EuiButtonEmpty {' '} - properties with the exception of onClick. + properties.

- Customize the editMode state by passing{' '} - editModeProps. These properties are applied - directly to the{' '} - - EuiFormRow - {' '} - and{' '} - - EuiFieldText - {' '} - components. + Customize the edit mode by passing editModeProps. + This prop contains nested object properties that are applied to + various child components in edit mode:

+
    +
  • + editMode.formRowProps accepts any{' '} + + EuiFormRow + {' '} + properties +
  • +
  • + editMode.inputRowProps accepts any{' '} + + EuiFieldText + {' '} + properties +
  • +
  • + editMode.saveButtonProps accepts any{' '} + + EuiIconButton + {' '} + properties +
  • +
  • + editMode.cancelButtonProps accepts any{' '} + + EuiIconButton + {' '} + properties +
  • +
), source: [ diff --git a/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx b/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx index c855d91707d..e2958b9177c 100644 --- a/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx +++ b/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx @@ -8,10 +8,22 @@ export default () => { inputAriaLabel="Edit text inline for readMode and editMode props" defaultValue="This inline edit component has been customized!" size="m" - readModeProps={{ color: 'primary', iconSide: 'left' }} + readModeProps={{ + color: 'primary', + iconSide: 'left', + }} editModeProps={{ inputProps: { - prepend: 'Prepend Example', + prepend: 'Prepend example', + }, + formRowProps: { + helpText: 'Example help text', + }, + saveButtonProps: { + color: 'primary', + }, + cancelButtonProps: { + display: 'empty', }, }} /> diff --git a/src-docs/src/views/inline_edit/inline_edit_save.tsx b/src-docs/src/views/inline_edit/inline_edit_save.tsx index 1dc8017d567..9b01f695a56 100644 --- a/src-docs/src/views/inline_edit/inline_edit_save.tsx +++ b/src-docs/src/views/inline_edit/inline_edit_save.tsx @@ -1,34 +1,21 @@ import React from 'react'; -import { EuiButton, EuiInlineEditText, EuiSpacer } from '../../../../src'; +import { EuiInlineEditText } from '../../../../src'; export default () => { const saveToLocalStorage = (newInlineEditValue: string) => { localStorage.setItem('inlineEditValue', newInlineEditValue); }; - const removeFromLocalStorage = () => { - localStorage.removeItem('inlineEditValue'); - }; - const defaultInlineEditValue = localStorage.getItem('inlineEditValue') || 'This value will persist when you refresh the page!'; return ( - <> - saveToLocalStorage(onSaveVal)} - /> - - - - - Remove saved value from local storage - - + ); }; diff --git a/src-docs/src/views/inline_edit/inline_edit_states.tsx b/src-docs/src/views/inline_edit/inline_edit_states.tsx deleted file mode 100644 index 76eb115b944..00000000000 --- a/src-docs/src/views/inline_edit/inline_edit_states.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useState } from 'react'; -import { - EuiFlexGroup, - EuiInlineEditText, - EuiSwitch, - EuiSpacer, -} from '../../../../src'; - -export default () => { - const [toggleLoading, setToggleLoading] = useState(true); - - const [toggleValid, setToggleValid] = useState(false); - - const errorMessage = ["Here's an example of an error"]; - - return ( - <> - - setToggleLoading(e.target.checked)} - /> - - setToggleValid(e.target.checked)} - /> - - - - - - - ); -}; diff --git a/src-docs/src/views/inline_edit/inline_edit_validation.tsx b/src-docs/src/views/inline_edit/inline_edit_validation.tsx new file mode 100644 index 00000000000..4c178eb9440 --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_validation.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; + +import { EuiInlineEditText } from '../../../../src'; + +export default () => { + const [isLoading, setIsLoading] = useState(false); + const [errors, setErrors] = useState([]); + const isInvalid = errors.length > 0; + + const mockApiCall = (value: string) => + new Promise((resolve) => { + localStorage.setItem('inlineEditValueValidated', value); + setTimeout(resolve, 3000); + }); + + const defaultInlineEditValue = + localStorage.getItem('inlineEditValueValidated') || + 'This value will persist when you refresh the page!'; + + return ( + <> + setErrors([]) }, + }} + isInvalid={isInvalid} + isLoading={isLoading} + onSave={async (value) => { + // Validate edited text + if (!value) { + setErrors(['Please enter text.']); + return false; + } else if (value.length > 20) { + setErrors([ + 'Your text is too long - please enter less than 20 characters', + ]); + return false; + } + + // Clear errors, set loading state, and "call" an API + setErrors([]); + setIsLoading(true); + await mockApiCall(value); + setIsLoading(false); + return true; + }} + /> + + ); +}; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 9ae73106ac9..02c7cae7815 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiInlineEditForm Edit Mode disables the save button when input is invalid 1`] = ` +exports[`EuiInlineEditForm Edit Mode editModeProps.cancelButtonProps 1`] = `
@@ -27,22 +27,13 @@ exports[`EuiInlineEditForm Edit Mode disables the save button when input is inva class="euiFormControlLayout__childrenWrapper" > -
- -
@@ -81,9 +72,8 @@ exports[`EuiInlineEditForm Edit Mode disables the save button when input is inva
@@ -374,16 +386,39 @@ exports[`EuiInlineEditForm Edit Mode renders EuiSkeletonRectangles in place of e class="euiFormRow__fieldWrapper" >
+ class="emotion-euiScreenReaderOnly" + > +
+ Loaded +
+ +
@@ -393,7 +428,7 @@ exports[`EuiInlineEditForm Edit Mode renders EuiSkeletonRectangles in place of e
`; -exports[`EuiInlineEditForm Edit Mode renders editModeProps.formRowProps 1`] = ` +exports[`EuiInlineEditForm Edit Mode editModeProps.saveButtonProps 1`] = `
@@ -408,7 +443,6 @@ exports[`EuiInlineEditForm Edit Mode renders editModeProps.formRowProps 1`] = ` >
`; -exports[`EuiInlineEditForm Edit Mode renders editModeProps.inputProps 1`] = ` +exports[`EuiInlineEditForm Edit Mode isInvalid 1`] = `
@@ -555,25 +589,28 @@ exports[`EuiInlineEditForm Edit Mode renders editModeProps.inputProps 1`] = ` class="euiFormRow__fieldWrapper" >
-
+
+ +
@@ -680,7 +717,110 @@ exports[`EuiInlineEditForm Edit Mode renders editModeProps.inputProps 1`] = `
`; -exports[`EuiInlineEditForm Edit Mode renders save button and cancel button aria-labels 1`] = ` +exports[`EuiInlineEditForm Edit Mode isLoading 1`] = ` +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`EuiInlineEditForm Edit Mode renders 1`] = `
@@ -751,7 +891,7 @@ exports[`EuiInlineEditForm Edit Mode renders save button and cancel button aria- />
+
+
+
+
+
+
+
{ }); }); }); + + describe('keyboard events', () => { + test('pressing the Enter key saves text and returns to readMode', () => { + const { getByTestSubject, getByText } = render( + + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + fireEvent.keyDown(getByTestSubject('euiInlineEditModeInput'), { + key: 'Enter', + }); + + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('New message!')).toBeTruthy(); + }); + + test('pressing the Escape key cancels text changes and returns to readMode', () => { + const { getByTestSubject, getByText } = render( + + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + fireEvent.keyDown(getByTestSubject('euiInlineEditModeInput'), { + key: 'Escape', + }); + + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('Hello World!')).toBeTruthy(); + }); + + it('calls passed `inputModeProps.onKeyDown` callbacks', () => { + const onKeyDown = jest.fn(); + + const { getByTestSubject, getByText } = render( + + ); + + fireEvent.change(getByTestSubject('euiInlineEditModeInput'), { + target: { value: 'New message!' }, + }); + fireEvent.keyDown(getByTestSubject('euiInlineEditModeInput'), { + key: 'Enter', + }); + + // Both EUI and consumer `onKeyDown` events should have run + expect(onKeyDown).toHaveBeenCalled(); + expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + expect(getByText('New message!')).toBeTruthy(); + }); + }); }); }); diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index 4bc3a88d007..7ac900295c6 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -12,6 +12,7 @@ import React, { useState, HTMLAttributes, MouseEvent, + KeyboardEvent, } from 'react'; import classNames from 'classnames'; @@ -29,8 +30,8 @@ import { EuiButtonIconPropsForButton } from '../button/button_icon'; import { EuiButtonEmptyPropsForButton } from '../button/button_empty/button_empty'; import { EuiFlexGroup, EuiFlexItem } from '../flex'; import { EuiSkeletonRectangle } from '../skeleton'; -import { useEuiTheme } from '../../services'; -import { useEuiI18n } from '../i18n'; +import { useEuiTheme, keys } from '../../services'; +import { EuiI18n, useEuiI18n } from '../i18n'; import { useGeneratedHtmlId } from '../../services/accessibility'; // Props shared between the internal form component as well as consumer-facing components @@ -139,6 +140,8 @@ export const EuiInlineEditForm: FunctionComponent = ({ 'Cancel edit' ); + const editModeDescribedById = useGeneratedHtmlId({ prefix: 'inlineEdit' }); + const [isEditing, setIsEditing] = useState(false || startWithEditOpen); const inlineEditInputId = useGeneratedHtmlId({ prefix: '__inlineEditInput' }); @@ -163,6 +166,17 @@ export const EuiInlineEditForm: FunctionComponent = ({ setIsEditing(false); }; + const editModeInputOnKeyDown = (event: KeyboardEvent) => { + switch (event.key) { + case keys.ENTER: + saveInlineEditValue(); + break; + case keys.ESCAPE: + cancelInlineEdit(); + break; + } + }; + const editModeForm = ( @@ -185,8 +199,22 @@ export const EuiInlineEditForm: FunctionComponent = ({ isLoading={isLoading} data-test-subj="euiInlineEditModeInput" {...editModeProps?.inputProps} + aria-describedby={classNames( + editModeDescribedById, + editModeProps?.inputProps?.['aria-describedby'] + )} + onKeyDown={(e: KeyboardEvent) => { + editModeInputOnKeyDown(e); + editModeProps?.inputProps?.onKeyDown?.(e); + }} /> + From 8845e13136cb1dc26941a8e4ae5803771f54f60f Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Tue, 2 May 2023 16:17:52 -0400 Subject: [PATCH 08/23] [EuiInlineEdit] Add Playground & Code Snippets (#6743) * [EuiInlineEdit] Create a playground for both EuiInlineEdit Text and Title * [EuiInlineEdit] Add code snippets for each InlineEdit example in the docs * Removed the inline edit validation code snippet in favor of the DemoJS because it's more helpful * Small updates to DRY util. * Added the span option to the list of headings available for EuiInlineEdit Title. * Update EuiInlineEdit Title heading prop documentation for clarity * Update src/components/inline_edit/inline_edit_title.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> --------- Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> --- .../views/inline_edit/inline_edit_example.js | 47 ++++++++ src-docs/src/views/inline_edit/playground.js | 103 ++++++++++++++++++ .../inline_edit/inline_edit_title.tsx | 5 +- 3 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src-docs/src/views/inline_edit/playground.js diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index 8e4dde90c6b..4273ad6b4fe 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -11,17 +11,58 @@ import { EuiInlineEditTitle, } from '../../../../src'; +import { inlineEditTextConfig, inlineEditTitleConfig } from './playground'; + import InlineEditText from './inline_edit_text'; const inlineEditTextSource = require('!!raw-loader!./inline_edit_text'); +const inlineEditTextSnippet = ``; import InlineEditTitle from './inline_edit_title'; const inlineEditTitleSource = require('!!raw-loader!./inline_edit_title'); +const inlineEditTitleSnippet = ``; import InlineEditModeProps from './inline_edit_mode_props'; const inlineEditModePropsSource = require('!!raw-loader!./inline_edit_mode_props'); +const inlineEditModePropsSnippet = ``; import InlineEditSave from './inline_edit_save'; const inlineEditSaveSource = require('!!raw-loader!././inline_edit_save'); +const inlineEditModeSaveSnippet = ` { + localStorage.setItem('inlineEditValue', newInlineEditValue); + }} +/>`; import InlineEditValidation from './inline_edit_validation'; const inlineEditValidationSource = require('!!raw-loader!././inline_edit_validation'); @@ -60,6 +101,8 @@ export const InlineEditExample = { ], demo: , props: { EuiInlineEditText }, + snippet: inlineEditTextSnippet, + playground: inlineEditTextConfig, }, { title: 'Display and edit headings and titles', @@ -80,6 +123,8 @@ export const InlineEditExample = { ], demo: , props: { EuiInlineEditTitle }, + snippet: inlineEditTitleSnippet, + playground: inlineEditTitleConfig, }, { title: 'Saving edited text', @@ -98,6 +143,7 @@ export const InlineEditExample = { }, ], demo: , + snippet: inlineEditModeSaveSnippet, }, { title: 'Validating edited text', @@ -187,6 +233,7 @@ export const InlineEditExample = { }, ], demo: , + snippet: inlineEditModePropsSnippet, }, ], }; diff --git a/src-docs/src/views/inline_edit/playground.js b/src-docs/src/views/inline_edit/playground.js new file mode 100644 index 00000000000..7d7290905d4 --- /dev/null +++ b/src-docs/src/views/inline_edit/playground.js @@ -0,0 +1,103 @@ +import { PropTypes } from 'react-view'; +import { + EuiInlineEditText, + EuiInlineEditTitle, +} from '../../../../src/components'; +import { + propUtilityForPlayground, + dummyFunction, + simulateFunction, +} from '../../services/playground'; + +const setCommonPropsToUse = (propsToUse) => { + propsToUse.inputAriaLabel = { + ...propsToUse.inputAriaLabel, + value: 'Edit text inline', + type: PropTypes.String, + }; + + propsToUse.isLoading = { + type: PropTypes.Boolean, + }; + + propsToUse.isInvalid = { + type: PropTypes.Boolean, + }; + + propsToUse.startWithEditOpen = { + type: PropTypes.Boolean, + }; + + propsToUse.onSave = simulateFunction(propsToUse.onSave); +}; + +export const inlineEditTextConfig = () => { + const docgenInfo = Array.isArray(EuiInlineEditText.__docgenInfo) + ? EuiInlineEditText.__docgenInfo[0] + : EuiInlineEditText.__docgenInfo; + const propsToUse = propUtilityForPlayground(docgenInfo.props); + + propsToUse.defaultValue = { + ...propsToUse.defaultValue, + value: 'Hello! You are editing text content!', + type: PropTypes.String, + }; + + setCommonPropsToUse(propsToUse); + + return { + config: { + componentName: 'EuiInlineEditText', + props: propsToUse, + scope: { + EuiInlineEditText, + }, + imports: { + '@elastic/eui': { + named: ['EuiInlineEditText'], + }, + }, + customProps: { + onSave: dummyFunction, + }, + }, + }; +}; + +export const inlineEditTitleConfig = () => { + const docgenInfo = Array.isArray(EuiInlineEditTitle.__docgenInfo) + ? EuiInlineEditTitle.__docgenInfo[0] + : EuiInlineEditTitle.__docgenInfo; + const propsToUse = propUtilityForPlayground(docgenInfo.props); + + propsToUse.defaultValue = { + ...propsToUse.defaultValue, + value: 'Hello! You are editing a title!', + type: PropTypes.String, + }; + + propsToUse.heading = { + ...propsToUse.heading, + value: 'h4', + }; + + setCommonPropsToUse(propsToUse); + + return { + config: { + componentName: 'EuiInlineEditTitle', + props: propsToUse, + scope: { + EuiInlineEditTitle, + }, + imports: { + '@elastic/eui': { + named: ['EuiInlineEditTitle'], + }, + }, + customProps: { + onSave: dummyFunction, + }, + }, + }; +}; diff --git a/src/components/inline_edit/inline_edit_title.tsx b/src/components/inline_edit/inline_edit_title.tsx index 04de2ae2056..6f9d3d0b1d4 100644 --- a/src/components/inline_edit/inline_edit_title.tsx +++ b/src/components/inline_edit/inline_edit_title.tsx @@ -18,7 +18,7 @@ import { import { useEuiTheme } from '../../services'; import { euiInlineEditTitleStyles } from './inline_edit_title.styles'; -export const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const; +export const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'] as const; type Heading = typeof HEADINGS[number]; export type EuiInlineEditTitleProps = EuiInlineEditCommonProps & { @@ -27,7 +27,8 @@ export type EuiInlineEditTitleProps = EuiInlineEditCommonProps & { */ size?: EuiTitleSize; /** - * Level of heading to be used for the title + * Level of heading to be used for the title. + * Use `span` for text that is not semantically a heading, but should still visually appear as a title. */ heading: Heading; }; From cdbd2c7287cb29fc380fdd3a28af1df875bb8c7c Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Thu, 4 May 2023 14:47:46 -0700 Subject: [PATCH 09/23] [EuiInlineEdit] DOM cleanup, a11y improvements, and fix breaking `onChange` bug (#6746) * DOM cleanup - Remove unnecessary `EuiForm` - we're not taking advantage of form submit behavior, so we might as well remove this - Remove unnecessary `EuiFormRow`s around save/cancel buttons - they're not inputs and don't need form rows - Remove incorrect `className={classes}` placement * Use `EuiSkeletonLoading` instead of 2 `EuiSkeletonRectangle`s - this prevents double loading screen reader announcements * Update EuiInlineEdit to only announce when state flips to loading - so that a "loaded" announcement doesn't occur whenever edit mode is opened, which doesn't make a whole lot of sense * Improve SR UX of read mode - add an `aria-describedby` that explains to screen reader users what happens when the read mode button is clicked, since the text itself is not indicative + cleanup: remove unused input ID * Fix `editModeProps.inputProps.onChange` overriding/breaking behavior * Remove `autoFocus` in favor of imperative `.focus()` calls on click - rAF is needed to wait a tick after state has changed and DOM node is available + write tests (with waitFor) asserting that focus state works as expected (mocking rAF primarily for test/function coverage) * [PR feedback] `toggle`->`activate` --- .../inline_edit_form.test.tsx.snap | 1179 ++++++----------- .../inline_edit_text.test.tsx.snap | 28 + .../inline_edit_title.test.tsx.snap | 49 + .../inline_edit/inline_edit_form.test.tsx | 56 +- .../inline_edit/inline_edit_form.tsx | 200 +-- 5 files changed, 657 insertions(+), 855 deletions(-) diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 3f2a5a14210..44d0882bc92 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -5,142 +5,81 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.cancelButtonProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -153,142 +92,81 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.formRowProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -301,147 +179,86 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.inputProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
- -
-
-
-
- -
-
-
-
+ Prepend Example +
-
-
- Loaded -
- - +
+ +
+
-
-
-
-
- Loaded -
- - -
-
+
@@ -454,141 +271,80 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.saveButtonProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -601,150 +357,89 @@ exports[`EuiInlineEditForm Edit Mode isInvalid 1`] = ` class="euiInlineEdit testClass1 testClass2" >
+
- -
- -
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -757,103 +452,103 @@ exports[`EuiInlineEditForm Edit Mode isLoading 1`] = ` class="euiInlineEdit testClass1 testClass2" >
+
- -
- -
-
+ +
+
-
-
-
+ Loading
+ -
-
-
-
+ aria-label="Loading " + class="euiSkeletonRectangle emotion-euiSkeletonGradientAnimation-euiSkeletonRectangle-m" + role="progressbar" + style="inline-size: 40px; block-size: 40px;" + /> +
+
+
@@ -867,141 +562,80 @@ exports[`EuiInlineEditForm Edit Mode renders 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -1014,6 +648,7 @@ exports[`EuiInlineEditForm Read Mode readModeProps 1`] = ` class="euiInlineEdit testClass1 testClass2" > +
`; @@ -1041,6 +682,7 @@ exports[`EuiInlineEditForm Read Mode renders 1`] = ` class="euiInlineEdit testClass1 testClass2" > +
`; @@ -1068,6 +716,7 @@ exports[`EuiInlineEditForm Read Mode sizes 1`] = ` class="euiInlineEdit testClass1 testClass2" > +
`; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index 42d20b7f64e..c84ffa0c68a 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -5,6 +5,7 @@ exports[`EuiInlineEditText renders 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-m-m" > +
`; @@ -36,6 +43,7 @@ exports[`EuiInlineEditText text sizes renders m 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-m-m" > +
`; @@ -67,6 +81,7 @@ exports[`EuiInlineEditText text sizes renders s 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-s-s" > +
`; @@ -98,6 +119,7 @@ exports[`EuiInlineEditText text sizes renders xs 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-xs-xs" > +
`; 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 index 19a670a600d..58527fff8f8 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap @@ -5,6 +5,7 @@ exports[`EuiInlineEditTitle renders 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-m-m" > +
`; @@ -36,6 +43,7 @@ exports[`EuiInlineEditTitle title sizes renders size l 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-l-l" > +
`; @@ -67,6 +81,7 @@ exports[`EuiInlineEditTitle title sizes renders size m 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-m-m" > +
`; @@ -98,6 +119,7 @@ exports[`EuiInlineEditTitle title sizes renders size s 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-s-s" > +
`; @@ -129,6 +157,7 @@ exports[`EuiInlineEditTitle title sizes renders size xs 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-xs-xs" > +
`; @@ -160,6 +195,7 @@ exports[`EuiInlineEditTitle title sizes renders size xxs 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-xxs-xxs" > +
`; @@ -191,6 +233,7 @@ exports[`EuiInlineEditTitle title sizes renders size xxxs 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-xxxs-xxxs" > +
`; diff --git a/src/components/inline_edit/inline_edit_form.test.tsx b/src/components/inline_edit/inline_edit_form.test.tsx index 98a46cb935a..49c9e7de684 100644 --- a/src/components/inline_edit/inline_edit_form.test.tsx +++ b/src/components/inline_edit/inline_edit_form.test.tsx @@ -76,6 +76,8 @@ describe('EuiInlineEditForm', () => { }); test('editModeProps.inputProps', () => { + const onChange = jest.fn(); + const { container, getByTestSubject } = render( { inputProps: { prepend: 'Prepend Example', 'data-test-subj': 'customInput', + onChange, }, }} /> ); - expect(container.firstChild).toMatchSnapshot(); - expect(getByTestSubject('customInput')).toBeTruthy(); + + const mockChangeEvent = { target: { value: 'changed' } }; + fireEvent.change(getByTestSubject('customInput'), mockChangeEvent); + expect(onChange).toHaveBeenCalled(); + + // Consumer `onChange` callbacks should not override EUI's + expect( + (getByTestSubject('customInput') as HTMLInputElement).value + ).toEqual('changed'); }); test('editModeProps.formRowProps', () => { @@ -181,11 +191,18 @@ describe('EuiInlineEditForm', () => { }); describe('Toggling between readMode and editMode', () => { + jest + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((cb: Function) => cb()); + const onClick = jest.fn(); const onSave = jest.fn(); - beforeEach(() => jest.resetAllMocks()); + beforeEach(() => { + onClick.mockReset(); + onSave.mockReset(); + }); - it('toggles to editMode when the readModeButton is clicked', () => { + it('activates editMode when the readModeButton is clicked', () => { const { getByTestSubject, queryByTestSubject } = render( { ); fireEvent.click(getByTestSubject('euiInlineReadModeButton')); - expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy(); + expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineEditModeInput') + ); + }); expect(onClick).toHaveBeenCalledTimes(1); }); @@ -217,7 +239,11 @@ describe('EuiInlineEditForm', () => { ).toEqual('New message!'); fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('New message!')).toBeTruthy(); expect(onSave).toHaveBeenCalledWith('New message!'); expect(onClick).toHaveBeenCalledTimes(1); @@ -241,7 +267,11 @@ describe('EuiInlineEditForm', () => { ).toEqual('New message!'); fireEvent.click(getByTestSubject('euiInlineEditModeCancelButton')); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('Hello World!')).toBeTruthy(); expect(onSave).not.toHaveBeenCalled(); expect(onClick).toHaveBeenCalledTimes(1); @@ -350,7 +380,11 @@ describe('EuiInlineEditForm', () => { key: 'Enter', }); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('New message!')).toBeTruthy(); }); @@ -369,7 +403,11 @@ describe('EuiInlineEditForm', () => { key: 'Escape', }); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('Hello World!')).toBeTruthy(); }); diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index 7ac900295c6..eb655660b9d 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -10,6 +10,7 @@ import React, { ReactNode, FunctionComponent, useState, + useRef, HTMLAttributes, MouseEvent, KeyboardEvent, @@ -21,7 +22,6 @@ import { EuiFormRow, EuiFormRowProps, EuiFieldText, - EuiForm, EuiFieldTextProps, } from '../form'; import { euiFormVariables } from '../form/form.styles'; @@ -29,8 +29,8 @@ import { EuiButtonIcon, EuiButtonEmpty } from '../button'; import { EuiButtonIconPropsForButton } from '../button/button_icon'; import { EuiButtonEmptyPropsForButton } from '../button/button_empty/button_empty'; import { EuiFlexGroup, EuiFlexItem } from '../flex'; -import { EuiSkeletonRectangle } from '../skeleton'; -import { useEuiTheme, keys } from '../../services'; +import { EuiSkeletonLoading, EuiSkeletonRectangle } from '../skeleton'; +import { useEuiTheme, useCombinedRefs, keys } from '../../services'; import { EuiI18n, useEuiI18n } from '../i18n'; import { useGeneratedHtmlId } from '../../services/accessibility'; @@ -140,17 +140,34 @@ export const EuiInlineEditForm: FunctionComponent = ({ 'Cancel edit' ); + const readModeDescribedById = useGeneratedHtmlId({ prefix: 'inlineEdit' }); const editModeDescribedById = useGeneratedHtmlId({ prefix: 'inlineEdit' }); - const [isEditing, setIsEditing] = useState(false || startWithEditOpen); - const inlineEditInputId = useGeneratedHtmlId({ prefix: '__inlineEditInput' }); + const readModeFocusRef = useRef(null); + const editModeFocusRef = useRef(null); + const setReadModeRefs = useCombinedRefs([ + readModeFocusRef, + readModeProps?.buttonRef, + ]); + const setEditModeRefs = useCombinedRefs([ + editModeFocusRef, + editModeProps?.inputProps?.inputRef, + ]); + const [isEditing, setIsEditing] = useState(false || startWithEditOpen); const [editModeValue, setEditModeValue] = useState(defaultValue); const [readModeValue, setReadModeValue] = useState(defaultValue); + const activateEditMode = () => { + setIsEditing(true); + // Waits a tick for state to settle and the focus target to render + requestAnimationFrame(() => editModeFocusRef.current?.focus()); + }; + const cancelInlineEdit = () => { setEditModeValue(readModeValue); setIsEditing(false); + requestAnimationFrame(() => readModeFocusRef.current?.focus()); }; const saveInlineEditValue = async () => { @@ -164,6 +181,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ setReadModeValue(editModeValue); setIsEditing(false); + requestAnimationFrame(() => readModeFocusRef.current?.focus()); }; const editModeInputOnKeyDown = (event: KeyboardEvent) => { @@ -178,53 +196,67 @@ export const EuiInlineEditForm: FunctionComponent = ({ }; const editModeForm = ( - - - - + + + - { - setEditModeValue(e.target.value); - }} - aria-label={inputAriaLabel} - autoFocus - compressed={sizes.compressed} - isInvalid={isInvalid} - isLoading={isLoading} - data-test-subj="euiInlineEditModeInput" - {...editModeProps?.inputProps} - aria-describedby={classNames( - editModeDescribedById, - editModeProps?.inputProps?.['aria-describedby'] - )} - onKeyDown={(e: KeyboardEvent) => { - editModeInputOnKeyDown(e); - editModeProps?.inputProps?.onKeyDown?.(e); - }} - /> - - - + isLoading={isLoading} + data-test-subj="euiInlineEditModeInput" + {...editModeProps?.inputProps} + inputRef={setEditModeRefs} + onChange={(e) => { + setEditModeValue(e.target.value); + editModeProps?.inputProps?.onChange?.(e); + }} + onKeyDown={(e) => { + editModeInputOnKeyDown(e); + editModeProps?.inputProps?.onKeyDown?.(e); + }} + aria-describedby={classNames( + editModeDescribedById, + editModeProps?.inputProps?.['aria-describedby'] + )} + /> + + + - - - + + + + + + } + loadedContent={ + = ({ editModeProps?.saveButtonProps?.onClick?.(e); }} /> - - - - - - - = ({ editModeProps?.cancelButtonProps?.onClick?.(e); }} /> - - - - - + + } + /> + + ); const readModeElement = ( - { - setIsEditing(true); - readModeProps?.onClick?.(e); - }} - > - {children(readModeValue)} - + <> + ) => { + activateEditMode(); + readModeProps?.onClick?.(e); + }} + > + {children(readModeValue)} + + + ); return ( From cfe32fa4bb6e4cb3d6146ddccb0286cc5eed6b62 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Mon, 8 May 2023 14:24:49 -0400 Subject: [PATCH 10/23] Removed unneeded style file and references to it. (#6751) --- .../inline_edit/inline_edit.styles.ts | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 src/components/inline_edit/inline_edit.styles.ts diff --git a/src/components/inline_edit/inline_edit.styles.ts b/src/components/inline_edit/inline_edit.styles.ts deleted file mode 100644 index 61cc6824225..00000000000 --- a/src/components/inline_edit/inline_edit.styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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}; - `, - }; -}; From d3277ecc3a2125dceda84d4e68cc7dfc45cd4d21 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Thu, 18 May 2023 05:22:43 -0700 Subject: [PATCH 11/23] Update src-docs/src/views/inline_edit/inline_edit_example.js Co-authored-by: Trevor Pierce <1Copenut@users.noreply.github.com> --- src-docs/src/views/inline_edit/inline_edit_example.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index 4273ad6b4fe..94debdc9ae1 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -155,10 +155,9 @@ export const InlineEditExample = { mode.

- To validate text when the user presses the save button but before - the user is returned to read mode, return a boolean (or an async - promise returning a boolean) from your onSave{' '} - callback. + Return a boolean from your onSave callback to + validate text after pressing Save. You can also return a boolean from a + promise. Validation happens before the user returns to read mode.

Returning false from onSave{' '} From 469ab654a97e273ec0bf93a121198df95b37e1b9 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Thu, 18 May 2023 05:22:52 -0700 Subject: [PATCH 12/23] Update src/components/inline_edit/inline_edit_form.tsx Co-authored-by: Trevor Pierce <1Copenut@users.noreply.github.com> --- src/components/inline_edit/inline_edit_form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index eb655660b9d..7b8f4845433 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -318,7 +318,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ From e11c50fdda48edd87538a1b6ecb1bb00f68b5746 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Thu, 18 May 2023 05:23:39 -0700 Subject: [PATCH 13/23] Update src-docs/src/views/inline_edit/inline_edit_example.js Co-authored-by: Trevor Pierce <1Copenut@users.noreply.github.com> --- src-docs/src/views/inline_edit/inline_edit_example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index 94debdc9ae1..9bb5983e0bf 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -73,7 +73,7 @@ export const InlineEditExample = { <> The EuiInlineEdit components are useful for updating - single-line text outside of a form. The component has two states:{' '} + single lines of text outside a form. The component has two states:{' '} readMode shows editable text inside of a button and{' '} editMode displays a form control to update the text. From abce4048e78fef6464a7093dcd8a7db1c5f1e926 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Thu, 18 May 2023 10:23:11 -0400 Subject: [PATCH 14/23] [EuiInlineEdit] `CHANGELOG` & Copy Linting (#6788) * Changelog & text linting from changes made in Github UI * Snapshot update as a result of updating the copy of the aria-describedby used by inline edit --- .../src/views/inline_edit/inline_edit_example.js | 7 ++++--- .../__snapshots__/inline_edit_form.test.tsx.snap | 6 +++--- .../__snapshots__/inline_edit_text.test.tsx.snap | 8 ++++---- .../__snapshots__/inline_edit_title.test.tsx.snap | 14 +++++++------- upcoming_changelogs/6757.md | 2 ++ 5 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 upcoming_changelogs/6757.md diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index 9bb5983e0bf..cb8f06645c8 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -155,9 +155,10 @@ export const InlineEditExample = { mode.

- Return a boolean from your onSave callback to - validate text after pressing Save. You can also return a boolean from a - promise. Validation happens before the user returns to read mode. + Return a boolean from your onSave callback to + validate text after pressing Save. You can also return a boolean + from a promise. Validation happens before the user returns to read + mode.

Returning false from onSave{' '} diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 44d0882bc92..789434c3736 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -672,7 +672,7 @@ exports[`EuiInlineEditForm Read Mode readModeProps 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.

`; @@ -706,7 +706,7 @@ exports[`EuiInlineEditForm Read Mode renders 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -740,7 +740,7 @@ exports[`EuiInlineEditForm Read Mode sizes 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index c84ffa0c68a..88c3fcab517 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -33,7 +33,7 @@ exports[`EuiInlineEditText renders 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -71,7 +71,7 @@ exports[`EuiInlineEditText text sizes renders m 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -109,7 +109,7 @@ exports[`EuiInlineEditText text sizes renders s 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -147,7 +147,7 @@ exports[`EuiInlineEditText text sizes renders xs 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; 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 index 58527fff8f8..aabe6e1a00e 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap @@ -33,7 +33,7 @@ exports[`EuiInlineEditTitle renders 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -71,7 +71,7 @@ exports[`EuiInlineEditTitle title sizes renders size l 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -109,7 +109,7 @@ exports[`EuiInlineEditTitle title sizes renders size m 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -147,7 +147,7 @@ exports[`EuiInlineEditTitle title sizes renders size s 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline.
`; @@ -185,7 +185,7 @@ exports[`EuiInlineEditTitle title sizes renders size xs 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline. `; @@ -223,7 +223,7 @@ exports[`EuiInlineEditTitle title sizes renders size xxs 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline. `; @@ -261,7 +261,7 @@ exports[`EuiInlineEditTitle title sizes renders size xxxs 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline. `; diff --git a/upcoming_changelogs/6757.md b/upcoming_changelogs/6757.md new file mode 100644 index 00000000000..f2c3b6287b4 --- /dev/null +++ b/upcoming_changelogs/6757.md @@ -0,0 +1,2 @@ +- Added `EuiInlineEditText` and `EuiInlineEditTitle` components + From a864a9f1a7b06d5327bf84b33ca099eb547ccb44 Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Thu, 18 May 2023 10:58:20 -0400 Subject: [PATCH 15/23] [EuiInlineEdit] Create `isReadOnly` Prop for Read Mode (#6777) * [EuiInlineEdit] Add the isReadOnly prop that locks the component in read mode and does not allow the user to update text * [EuiInlineEdit] Update snapshots to account for new inline_edit_form styles. Added a test case for the new isReadOnly prop * [EuiInlineEdit] Forgot to add snapshot updates * [REVERT] Add documentation example to InlineEdit Text to display isReadOnly prop * Revert "[REVERT] Add documentation example to InlineEdit Text to display isReadOnly prop" This reverts commit 8a7b117f8caf591794b6747963ed049943461d54. Revert change made to demo for testing * [PR Review] - Add an official example for the isReadOnly prop - Name correction in edit mode props example * [PR Feedback] - Updated the name of Emotion style object to indicate we're styling the read mode button, not the entire form - Conditionally hide the text content of aria-describedby when isReadOnly is true - Conditionally add a role (and aria-level where required) to the read mode button when isReadOnly is true * Update and add test cases * [PR Feedback] - Update conditional aria-describedby span to surround the text instead of the entire span. - Clean up logic related to adding aria-live and role attributes when isReadOnly is true * Update src-docs/src/views/inline_edit/inline_edit_read_only.tsx Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * Update src/components/inline_edit/inline_edit_form.styles.ts Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * [PR Feedback] -Add note in inline_edit_form.style.ts that we should reevaluate styles once EuiEmptyButton is converted to Eotion. - Updated render condition for edit mode to ensure if isReadOnly is true, we stay / kick back to read mode - Test case & snapshot updates * [PR Feedback] Move the isReadOnly prop example up in the documentation * Snapshots * Update src-docs/src/views/inline_edit/inline_edit_example.js Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> * [PR Feedback] - Removed conditiona lo logic to force a toggle from read mode to edit mode when isReadOnly is true. Opted for a useEffect that will handle the change instead * Oops! --------- Co-authored-by: Cee Chen <549407+cee-chen@users.noreply.github.com> --- .../views/inline_edit/inline_edit_example.js | 30 ++++++++++- .../inline_edit/inline_edit_read_only.tsx | 39 +++++++++++++++ .../inline_edit_form.test.tsx.snap | 36 +++++++++++-- .../inline_edit_text.test.tsx.snap | 43 ++++++++++++++-- .../inline_edit_title.test.tsx.snap | 50 ++++++++++++++++--- .../inline_edit/inline_edit_form.styles.ts | 26 ++++++++++ .../inline_edit/inline_edit_form.test.tsx | 14 ++++++ .../inline_edit/inline_edit_form.tsx | 36 +++++++++++-- .../inline_edit/inline_edit_text.test.tsx | 13 +++++ .../inline_edit/inline_edit_text.tsx | 15 +++++- .../inline_edit/inline_edit_title.test.tsx | 17 +++++++ .../inline_edit/inline_edit_title.tsx | 22 +++++++- 12 files changed, 317 insertions(+), 24 deletions(-) create mode 100644 src-docs/src/views/inline_edit/inline_edit_read_only.tsx create mode 100644 src/components/inline_edit/inline_edit_form.styles.ts diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index cb8f06645c8..2855a752bac 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -67,6 +67,14 @@ const inlineEditModeSaveSnippet = ``; + export const InlineEditExample = { title: 'Inline edit', intro: ( @@ -176,6 +184,26 @@ export const InlineEditExample = { ], demo: , }, + { + title: 'Read only', + text: ( + <> +

+ Use the isReadOnly prop to lock{' '} + EuiInlineEdit in read mode and display the text + value. This does not affect the input form control in edit mode. +

+ + ), + source: [ + { + type: GuideSectionTypes.TSX, + code: InlineEditReadOnlySource, + }, + ], + demo: , + snippet: inlineEditReadOnlySnippet, + }, { title: 'Customizing read and edit modes', text: ( @@ -203,7 +231,7 @@ export const InlineEditExample = { properties
  • - editMode.inputRowProps accepts any{' '} + editMode.inputProps accepts any{' '} EuiFieldText {' '} diff --git a/src-docs/src/views/inline_edit/inline_edit_read_only.tsx b/src-docs/src/views/inline_edit/inline_edit_read_only.tsx new file mode 100644 index 00000000000..66ae90c8d52 --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_read_only.tsx @@ -0,0 +1,39 @@ +import React, { useState } from 'react'; + +import { + EuiInlineEditText, + EuiInlineEditTitle, + EuiSpacer, + EuiSwitch, +} from '../../../../src'; + +export default () => { + const [isReadOnly, setIsReadOnly] = useState(true); + + return ( + <> + setIsReadOnly(e.target.checked)} + /> + + + + + + + + + + ); +}; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 789434c3736..767fb2a97a4 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -643,13 +643,43 @@ exports[`EuiInlineEditForm Edit Mode renders 1`] = ` `; +exports[`EuiInlineEditForm Read Mode isReadOnly 1`] = ` +
    + + +
    +`; + exports[`EuiInlineEditForm Read Mode readModeProps 1`] = `
    + +
    +`; + exports[`EuiInlineEditText renders 1`] = `
    + +
    +`; + exports[`EuiInlineEditTitle renders 1`] = `
    `; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index dd5d5976f1f..c3e7dcba701 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -30,7 +30,7 @@ exports[`EuiInlineEditText isReadOnly 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline. `; 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 index 1def24c3dfc..393e8d316f1 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap @@ -31,7 +31,7 @@ exports[`EuiInlineEditTitle isReadOnly 1`] = ` hidden="" id="inlineEdit_generated-id" > - Click this button to edit this text inline. + Click to edit this text inline. `; From cee0519078e0c2cb072c5ce51ad2f28161f8a270 Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Wed, 24 May 2023 18:00:58 -0700 Subject: [PATCH 17/23] [EuiInlineEdit] Final screen reader / a11y polish pass (#6805) * Fix conditional readonly aria-describedby - it was wrapped around the wrong SR text * Prevent double heading roles when readonly - leave role as undefined/unset otherwise, as `span` elements should not have a `heading` role * (hopefully) Fix enter screen reader behavior * Fix VO announcing EuiInlineEditText as `clickable` in read only mode * Revert "Fix VO announcing EuiInlineEditText as `clickable` in read only mode" This reverts commit 0f483bd24dc68d5342c7aab1c3acd83cbd93d982. * NVDA browser mode fix --- .../inline_edit_form.test.tsx.snap | 4 +--- .../inline_edit_text.test.tsx.snap | 4 +--- .../inline_edit_title.test.tsx.snap | 5 ++-- .../inline_edit/inline_edit_form.tsx | 23 ++++++++++--------- .../inline_edit/inline_edit_title.tsx | 4 +++- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 1cb43f93a8d..4e8db2e6eb0 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -667,9 +667,7 @@ exports[`EuiInlineEditForm Read Mode isReadOnly 1`] = ` + /> `; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index c3e7dcba701..c5d30da1fe7 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -29,9 +29,7 @@ exports[`EuiInlineEditText isReadOnly 1`] = ` + /> `; 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 index 393e8d316f1..1db0a616d9a 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap @@ -21,6 +21,7 @@ exports[`EuiInlineEditTitle isReadOnly 1`] = ` >

    Hello World!

    @@ -30,9 +31,7 @@ exports[`EuiInlineEditTitle isReadOnly 1`] = ` + /> `; diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index 9806fab5d15..b0df19e44e9 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -201,9 +201,11 @@ export const EuiInlineEditForm: FunctionComponent = ({ const editModeInputOnKeyDown = (event: KeyboardEvent) => { switch (event.key) { case keys.ENTER: + event.preventDefault(); // Enter keypresses will not proceed otherwise on webkit browsers & screen readers saveInlineEditValue(); break; case keys.ESCAPE: + event.preventDefault(); // NVDA will trigger Browse mode otherwise cancelInlineEdit(); break; } @@ -249,14 +251,11 @@ export const EuiInlineEditForm: FunctionComponent = ({ )} /> - @@ -342,10 +341,12 @@ export const EuiInlineEditForm: FunctionComponent = ({ {children(readModeValue)} ); diff --git a/src/components/inline_edit/inline_edit_title.tsx b/src/components/inline_edit/inline_edit_title.tsx index 451cd1a4697..ffa1b7ea077 100644 --- a/src/components/inline_edit/inline_edit_title.tsx +++ b/src/components/inline_edit/inline_edit_title.tsx @@ -96,7 +96,9 @@ export const EuiInlineEditTitle: FunctionComponent = ({ > {(titleReadModeValue) => ( - {titleReadModeValue} + + {titleReadModeValue} + )} From 98a264add84f77d7417b5f3548654d41e2c8f445 Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Thu, 25 May 2023 09:45:45 -0700 Subject: [PATCH 18/23] Revert Escape preventDefault - not actually doing anything :wompwomp: --- src/components/inline_edit/inline_edit_form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index b0df19e44e9..d9f812d9742 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -205,7 +205,6 @@ export const EuiInlineEditForm: FunctionComponent = ({ saveInlineEditValue(); break; case keys.ESCAPE: - event.preventDefault(); // NVDA will trigger Browse mode otherwise cancelInlineEdit(); break; } From 08bfd424cea6ab59134d2c2d71788da041a8376e Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 25 May 2023 10:30:19 -0700 Subject: [PATCH 19/23] [misc UX enhancement] Set `readOnly` on input / prevent further user editing when saving & is in `isLoading` state - docs only improvement as I don't want to tie the two props together (slightly opinionated), but hopefully consumers copy it --- src-docs/src/views/inline_edit/inline_edit_validation.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src-docs/src/views/inline_edit/inline_edit_validation.tsx b/src-docs/src/views/inline_edit/inline_edit_validation.tsx index 4c178eb9440..f3977e7da3c 100644 --- a/src-docs/src/views/inline_edit/inline_edit_validation.tsx +++ b/src-docs/src/views/inline_edit/inline_edit_validation.tsx @@ -25,6 +25,7 @@ export default () => { editModeProps={{ formRowProps: { error: errors }, cancelButtonProps: { onClick: () => setErrors([]) }, + inputProps: { readOnly: isLoading }, }} isInvalid={isInvalid} isLoading={isLoading} From 52ef4696bf3f7bb6ec8b3485934382898f33945f Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 25 May 2023 10:32:53 -0700 Subject: [PATCH 20/23] [mobile] Fix edit mode buttons wrapping to next line in small screens --- .../__snapshots__/inline_edit_form.test.tsx.snap | 14 +++++++------- src/components/inline_edit/inline_edit_form.tsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 4e8db2e6eb0..acc40d34c0e 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -5,7 +5,7 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.cancelButtonProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
    = ({ }, [isReadOnly]); const editModeForm = ( - + Date: Thu, 25 May 2023 11:01:04 -0700 Subject: [PATCH 21/23] [misc docs tweaks] - add `size` props to text/title snippets - move longer mode props snippet further down to match location on page - reorder props in demos by relative importance/requirement (or remove if not important) --- .../views/inline_edit/inline_edit_example.js | 44 ++++++++++--------- .../inline_edit/inline_edit_mode_props.tsx | 1 - .../inline_edit/inline_edit_read_only.tsx | 2 +- .../views/inline_edit/inline_edit_text.tsx | 2 +- .../views/inline_edit/inline_edit_title.tsx | 4 +- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index 2855a752bac..ec8bb11819a 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -18,6 +18,7 @@ const inlineEditTextSource = require('!!raw-loader!./inline_edit_text'); const inlineEditTextSnippet = ``; import InlineEditTitle from './inline_edit_title'; @@ -26,6 +27,28 @@ const inlineEditTitleSnippet = ``; + +import InlineEditSave from './inline_edit_save'; +const inlineEditSaveSource = require('!!raw-loader!././inline_edit_save'); +const inlineEditModeSaveSnippet = ` { + localStorage.setItem('inlineEditValue', newInlineEditValue); + }} +/>`; + +import InlineEditValidation from './inline_edit_validation'; +const inlineEditValidationSource = require('!!raw-loader!././inline_edit_validation'); + +import InlineEditReadOnly from './inline_edit_read_only'; +const InlineEditReadOnlySource = require('!!raw-loader!././inline_edit_read_only'); +const inlineEditReadOnlySnippet = ``; import InlineEditModeProps from './inline_edit_mode_props'; @@ -54,27 +77,6 @@ const inlineEditModePropsSnippet = ``; -import InlineEditSave from './inline_edit_save'; -const inlineEditSaveSource = require('!!raw-loader!././inline_edit_save'); -const inlineEditModeSaveSnippet = ` { - localStorage.setItem('inlineEditValue', newInlineEditValue); - }} -/>`; - -import InlineEditValidation from './inline_edit_validation'; -const inlineEditValidationSource = require('!!raw-loader!././inline_edit_validation'); - -import InlineEditReadOnly from './inline_edit_read_only'; -const InlineEditReadOnlySource = require('!!raw-loader!././inline_edit_read_only'); -const inlineEditReadOnlySnippet = ``; - export const InlineEditExample = { title: 'Inline edit', intro: ( diff --git a/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx b/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx index e2958b9177c..5239fe5361c 100644 --- a/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx +++ b/src-docs/src/views/inline_edit/inline_edit_mode_props.tsx @@ -7,7 +7,6 @@ export default () => { { diff --git a/src-docs/src/views/inline_edit/inline_edit_text.tsx b/src-docs/src/views/inline_edit/inline_edit_text.tsx index 80330c2dd81..04d837e4e42 100644 --- a/src-docs/src/views/inline_edit/inline_edit_text.tsx +++ b/src-docs/src/views/inline_edit/inline_edit_text.tsx @@ -45,9 +45,9 @@ export default () => { ); diff --git a/src-docs/src/views/inline_edit/inline_edit_title.tsx b/src-docs/src/views/inline_edit/inline_edit_title.tsx index b35c24c9fce..edd3794ceed 100644 --- a/src-docs/src/views/inline_edit/inline_edit_title.tsx +++ b/src-docs/src/views/inline_edit/inline_edit_title.tsx @@ -55,10 +55,10 @@ export default () => { ); From cd9511fde2031a9776da4525758359544c982af9 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 25 May 2023 11:05:21 -0700 Subject: [PATCH 22/23] Add demo section for `startWithEditOpen` prop --- .../views/inline_edit/inline_edit_example.js | 37 +++++++++++++++---- .../inline_edit/inline_edit_start_in_edit.tsx | 28 ++++++++++++++ 2 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src-docs/src/views/inline_edit/inline_edit_start_in_edit.tsx diff --git a/src-docs/src/views/inline_edit/inline_edit_example.js b/src-docs/src/views/inline_edit/inline_edit_example.js index ec8bb11819a..f76960b12ec 100644 --- a/src-docs/src/views/inline_edit/inline_edit_example.js +++ b/src-docs/src/views/inline_edit/inline_edit_example.js @@ -43,6 +43,14 @@ const inlineEditModeSaveSnippet = ``; + import InlineEditReadOnly from './inline_edit_read_only'; const InlineEditReadOnlySource = require('!!raw-loader!././inline_edit_read_only'); const inlineEditReadOnlySnippet = `, }, + { + title: 'Start in edit mode', + text: ( +

    + Use the startWithEditOpen prop to default to + opening in edit mode. +

    + ), + source: [ + { + type: GuideSectionTypes.TSX, + code: inlineEditStartInEditSource, + }, + ], + demo: , + snippet: inlineEditStartInEditSnippet, + }, { title: 'Read only', text: ( - <> -

    - Use the isReadOnly prop to lock{' '} - EuiInlineEdit in read mode and display the text - value. This does not affect the input form control in edit mode. -

    - +

    + Use the isReadOnly prop to lock{' '} + EuiInlineEdit in read mode and display the text + value. This does not affect the input form control in edit mode. +

    ), source: [ { diff --git a/src-docs/src/views/inline_edit/inline_edit_start_in_edit.tsx b/src-docs/src/views/inline_edit/inline_edit_start_in_edit.tsx new file mode 100644 index 00000000000..0c2ed4bdf2a --- /dev/null +++ b/src-docs/src/views/inline_edit/inline_edit_start_in_edit.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import { + EuiInlineEditText, + EuiInlineEditTitle, + EuiSpacer, +} from '../../../../src'; + +export default () => { + return ( + <> + + + + + + + ); +}; From 704d3727915fecf76c803fbd229cfa50238c1837 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 25 May 2023 11:17:17 -0700 Subject: [PATCH 23/23] [misc cleanup/nits] - rename exported form styles more accurately to their usage - test name casing, + don't use camelCasing for non props --- .../inline_edit_form.test.tsx.snap | 30 +++++++++---------- .../inline_edit_text.test.tsx.snap | 10 +++---- .../inline_edit_title.test.tsx.snap | 16 +++++----- .../inline_edit/inline_edit_form.styles.ts | 4 +-- .../inline_edit/inline_edit_form.test.tsx | 20 ++++++------- .../inline_edit/inline_edit_form.tsx | 12 ++++---- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index acc40d34c0e..16d2621d462 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiInlineEditForm Edit Mode editModeProps.cancelButtonProps 1`] = ` +exports[`EuiInlineEditForm edit mode editModeProps.cancelButtonProps 1`] = `
    @@ -87,7 +87,7 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.cancelButtonProps 1`] = `
    `; -exports[`EuiInlineEditForm Edit Mode editModeProps.formRowProps 1`] = ` +exports[`EuiInlineEditForm edit mode editModeProps.formRowProps 1`] = `
    @@ -174,7 +174,7 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.formRowProps 1`] = `
    `; -exports[`EuiInlineEditForm Edit Mode editModeProps.inputProps 1`] = ` +exports[`EuiInlineEditForm edit mode editModeProps.inputProps 1`] = `
    @@ -266,7 +266,7 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.inputProps 1`] = `
    `; -exports[`EuiInlineEditForm Edit Mode editModeProps.saveButtonProps 1`] = ` +exports[`EuiInlineEditForm edit mode editModeProps.saveButtonProps 1`] = `
    @@ -352,7 +352,7 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.saveButtonProps 1`] = `
    `; -exports[`EuiInlineEditForm Edit Mode isInvalid 1`] = ` +exports[`EuiInlineEditForm edit mode isInvalid 1`] = `
    @@ -447,7 +447,7 @@ exports[`EuiInlineEditForm Edit Mode isInvalid 1`] = `
    `; -exports[`EuiInlineEditForm Edit Mode isLoading 1`] = ` +exports[`EuiInlineEditForm edit mode isLoading 1`] = `
    @@ -557,7 +557,7 @@ exports[`EuiInlineEditForm Edit Mode isLoading 1`] = `
    `; -exports[`EuiInlineEditForm Edit Mode renders 1`] = ` +exports[`EuiInlineEditForm edit mode renders 1`] = `
    @@ -643,13 +643,13 @@ exports[`EuiInlineEditForm Edit Mode renders 1`] = `
    `; -exports[`EuiInlineEditForm Read Mode isReadOnly 1`] = ` +exports[`EuiInlineEditForm read mode isReadOnly 1`] = `
    `; -exports[`EuiInlineEditForm Read Mode renders 1`] = ` +exports[`EuiInlineEditForm read mode renders 1`] = `
    `; -exports[`EuiInlineEditForm Read Mode sizes 1`] = ` +exports[`EuiInlineEditForm read mode sizes 1`] = `