From 792b31bce8cf0ddb0756ad6a04e0c83c15ae5b8c Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 16 Nov 2023 15:25:55 -0800 Subject: [PATCH 01/11] Allow description to be a callback that changes depending on `item` --- changelogs/upcoming/7373.md | 1 + src/components/basic_table/action_types.ts | 6 +++--- src/components/basic_table/default_item_action.tsx | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 changelogs/upcoming/7373.md diff --git a/changelogs/upcoming/7373.md b/changelogs/upcoming/7373.md new file mode 100644 index 00000000000..99c3bf9fae3 --- /dev/null +++ b/changelogs/upcoming/7373.md @@ -0,0 +1 @@ +- Updated the actions column in `EuiBasicTable` and `EuiInMemoryTable`s. Alongside `name`, the `description` property now also accepts an optional callback that the current `item` will be passed to diff --git a/src/components/basic_table/action_types.ts b/src/components/basic_table/action_types.ts index 09fb84d0109..9c622ee3b08 100644 --- a/src/components/basic_table/action_types.ts +++ b/src/components/basic_table/action_types.ts @@ -18,13 +18,13 @@ type EuiButtonIconColorFunction = (item: T) => ButtonColor; export interface DefaultItemActionBase { /** - * The display name of the action (will be the button caption) + * The display name of the action (will render as visible text if rendered within a collapsed menu) */ name: ReactNode | ((item: T) => ReactNode); /** - * Describes the action (will be the button title) + * Describes the action (will render as tooltip content) */ - description: string; + description: string | ((item: T) => string); /** * A handler function to execute the action */ diff --git a/src/components/basic_table/default_item_action.tsx b/src/components/basic_table/default_item_action.tsx index 3cac7ddc1c0..e5d86d12ee5 100644 --- a/src/components/basic_table/default_item_action.tsx +++ b/src/components/basic_table/default_item_action.tsx @@ -57,6 +57,11 @@ export const DefaultItemAction = ({ let button; const actionContent = typeof action.name === 'function' ? action.name(item) : action.name; + const tooltipContent = + typeof action.description === 'function' + ? action.description(item) + : action.description; + const ariaLabelId = useGeneratedHtmlId(); if (action.type === 'icon') { if (!icon) { @@ -101,8 +106,8 @@ export const DefaultItemAction = ({ ); } - return enabled && action.description ? ( - + return enabled && tooltipContent ? ( + {button} ) : ( From bf1aaec2baa088f0c72aa0ee8dbe2dd87481d5ba Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 16 Nov 2023 15:43:58 -0800 Subject: [PATCH 02/11] Let other props that seem useful take callbacks as well, and DRY out typeof check to a util --- changelogs/upcoming/7373.md | 2 +- src/components/basic_table/action_types.ts | 4 +- .../basic_table/default_item_action.test.tsx | 50 +++++++++++++++++++ .../basic_table/default_item_action.tsx | 23 +++++---- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/changelogs/upcoming/7373.md b/changelogs/upcoming/7373.md index 99c3bf9fae3..180ec79d60c 100644 --- a/changelogs/upcoming/7373.md +++ b/changelogs/upcoming/7373.md @@ -1 +1 @@ -- Updated the actions column in `EuiBasicTable` and `EuiInMemoryTable`s. Alongside `name`, the `description` property now also accepts an optional callback that the current `item` will be passed to +- Updated the actions column in `EuiBasicTable` and `EuiInMemoryTable`s. Alongside `name`, the `description`, `href`, and `data-test-subj` properties now also accept an optional callback that the current `item` will be passed to diff --git a/src/components/basic_table/action_types.ts b/src/components/basic_table/action_types.ts index 9c622ee3b08..84907363fef 100644 --- a/src/components/basic_table/action_types.ts +++ b/src/components/basic_table/action_types.ts @@ -29,7 +29,7 @@ export interface DefaultItemActionBase { * A handler function to execute the action */ onClick?: (item: T) => void; - href?: string; + href?: string | ((item: T) => string); target?: string; /** * A callback function that determines whether the action is available @@ -40,7 +40,7 @@ export interface DefaultItemActionBase { */ enabled?: (item: T) => boolean; isPrimary?: boolean; - 'data-test-subj'?: string; + 'data-test-subj'?: string | ((item: T) => string); } export interface DefaultItemEmptyButtonAction diff --git a/src/components/basic_table/default_item_action.test.tsx b/src/components/basic_table/default_item_action.test.tsx index 607c1ab44e6..6c1b7699e9b 100644 --- a/src/components/basic_table/default_item_action.test.tsx +++ b/src/components/basic_table/default_item_action.test.tsx @@ -7,6 +7,12 @@ */ import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import { + render, + waitForEuiToolTipVisible, + waitForEuiToolTipHidden, +} from '../../test/rtl'; import { shallow } from 'enzyme'; import { DefaultItemAction } from './default_item_action'; import { @@ -90,4 +96,48 @@ describe('DefaultItemAction', () => { expect(component).toMatchSnapshot(); }); + + test('props that can be functions', async () => { + const action: EmptyButtonAction = { + name: ({ id }) => + id === 'hello' ? Hello : world, + description: ({ id }) => + id === 'hello' ? 'hello tooltip' : 'goodbye tooltip', + href: ({ id }) => `#/${id}`, + 'data-test-subj': ({ id }) => `action-${id}`, + }; + + const { getByTestSubject, getByText } = render( + <> + + + + ); + + const firstAction = getByTestSubject('action-hello'); + expect(firstAction).toHaveTextContent('Hello'); + expect(firstAction).toHaveAttribute('href', '#/hello'); + + const secondAction = getByTestSubject('action-world'); + expect(secondAction).toHaveTextContent('world'); + expect(secondAction).toHaveAttribute('href', '#/world'); + + fireEvent.mouseOver(firstAction); + await waitForEuiToolTipVisible(); + expect(getByText('hello tooltip')).toBeInTheDocument(); + fireEvent.mouseOut(firstAction); + await waitForEuiToolTipHidden(); + + fireEvent.mouseOver(secondAction); + await waitForEuiToolTipVisible(); + expect(getByText('goodbye tooltip')).toBeInTheDocument(); + }); }); diff --git a/src/components/basic_table/default_item_action.tsx b/src/components/basic_table/default_item_action.tsx index e5d86d12ee5..c8398baff36 100644 --- a/src/components/basic_table/default_item_action.tsx +++ b/src/components/basic_table/default_item_action.tsx @@ -55,12 +55,10 @@ export const DefaultItemAction = ({ } let button; - const actionContent = - typeof action.name === 'function' ? action.name(item) : action.name; - const tooltipContent = - typeof action.description === 'function' - ? action.description(item) - : action.description; + const actionContent = callWithItemIfFunction(item)(action.name); + const tooltipContent = callWithItemIfFunction(item)(action.description); + const href = callWithItemIfFunction(item)(action.href); + const dataTestSubj = callWithItemIfFunction(item)(action['data-test-subj']); const ariaLabelId = useGeneratedHtmlId(); if (action.type === 'icon') { @@ -77,9 +75,9 @@ export const DefaultItemAction = ({ color={color} iconType={icon} onClick={onClick} - href={action.href} + href={href} target={action.target} - data-test-subj={action['data-test-subj']} + data-test-subj={dataTestSubj} /> {/* actionContent (action.name) is a ReactNode and must be rendered to an element and referenced by ID for screen readers */} @@ -96,9 +94,9 @@ export const DefaultItemAction = ({ color={color as EuiButtonEmptyProps['color']} iconType={icon} onClick={onClick} - href={action.href} + href={href} target={action.target} - data-test-subj={action['data-test-subj']} + data-test-subj={dataTestSubj} flush="right" > {actionContent} @@ -114,3 +112,8 @@ export const DefaultItemAction = ({ button ); }; + +const callWithItemIfFunction = + (item: T) => + (prop: U | ((item: T) => U)): U => + typeof prop === 'function' ? (prop as Function)(item) : prop; From b038a8c1eac4587a75b581949ddd0ad1f81a564c Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 16 Nov 2023 15:59:17 -0800 Subject: [PATCH 03/11] [tech debt] misc `DefaultItemAction` cleanup - convert tests to RTL, and remove unnecessary extra snapshot - remove TS comment, and replace extends with `,` workaround --- .../default_item_action.test.tsx.snap | 103 ++++++------------ .../basic_table/default_item_action.test.tsx | 50 +++------ .../basic_table/default_item_action.tsx | 5 +- 3 files changed, 50 insertions(+), 108 deletions(-) diff --git a/src/components/basic_table/__snapshots__/default_item_action.test.tsx.snap b/src/components/basic_table/__snapshots__/default_item_action.test.tsx.snap index 99ecdfd1bef..5afeeba49e4 100644 --- a/src/components/basic_table/__snapshots__/default_item_action.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/default_item_action.test.tsx.snap @@ -1,86 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DefaultItemAction render - button 1`] = ` - - - action1 - - -`; - -exports[`DefaultItemAction render - default button 1`] = ` - - - action1 - - -`; - -exports[`DefaultItemAction render - icon 1`] = ` - - - - + action1 - - + + `; -exports[`DefaultItemAction render - name 1`] = ` - - + - + + `; diff --git a/src/components/basic_table/default_item_action.test.tsx b/src/components/basic_table/default_item_action.test.tsx index 6c1b7699e9b..a95ef449e80 100644 --- a/src/components/basic_table/default_item_action.test.tsx +++ b/src/components/basic_table/default_item_action.test.tsx @@ -13,7 +13,7 @@ import { waitForEuiToolTipVisible, waitForEuiToolTipHidden, } from '../../test/rtl'; -import { shallow } from 'enzyme'; + import { DefaultItemAction } from './default_item_action'; import { DefaultItemEmptyButtonAction as EmptyButtonAction, @@ -25,24 +25,7 @@ interface Item { } describe('DefaultItemAction', () => { - test('render - default button', () => { - const action: EmptyButtonAction = { - name: 'action1', - description: 'action 1', - onClick: () => {}, - }; - const props = { - action, - enabled: true, - item: { id: 'xyz' }, - }; - - const component = shallow(); - - expect(component).toMatchSnapshot(); - }); - - test('render - button', () => { + it('renders an EuiButtonEmpty when `type="button"', () => { const action: EmptyButtonAction = { name: 'action1', description: 'action 1', @@ -55,16 +38,17 @@ describe('DefaultItemAction', () => { item: { id: 'xyz' }, }; - const component = shallow(); + const { container } = render(); - expect(component).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); - test('render - name', () => { - const action: EmptyButtonAction = { - name: (item) => {item.id}, + it('renders an EuiButtonIcon when `type="icon"`', () => { + const action: IconButtonAction = { + name: action1, description: 'action 1', - type: 'button', + type: 'icon', + icon: 'trash', onClick: () => {}, }; const props = { @@ -73,17 +57,15 @@ describe('DefaultItemAction', () => { item: { id: 'xyz' }, }; - const component = shallow(); + const { container } = render(); - expect(component).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); - test('render - icon', () => { - const action: IconButtonAction = { - name: action1, + it('renders an EuiButtonEmpty if no type is specified', () => { + const action: EmptyButtonAction = { + name: 'action1', description: 'action 1', - type: 'icon', - icon: 'trash', onClick: () => {}, }; const props = { @@ -92,9 +74,9 @@ describe('DefaultItemAction', () => { item: { id: 'xyz' }, }; - const component = shallow(); + const { container } = render(); - expect(component).toMatchSnapshot(); + expect(container.querySelector('.euiButtonEmpty')).toBeInTheDocument(); }); test('props that can be functions', async () => { diff --git a/src/components/basic_table/default_item_action.tsx b/src/components/basic_table/default_item_action.tsx index c8398baff36..73a1b9bb983 100644 --- a/src/components/basic_table/default_item_action.tsx +++ b/src/components/basic_table/default_item_action.tsx @@ -26,10 +26,7 @@ export interface DefaultItemActionProps { className?: string; } -// In order to use generics with an arrow function inside a .tsx file, it's necessary to use -// this `extends` hack and declare the types as shown, instead of declaring the const as a -// FunctionComponent -export const DefaultItemAction = ({ +export const DefaultItemAction = ({ action, enabled, item, From 6cf42115c13ce93ba06e752d543f25b84ad69d5e Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 16 Nov 2023 16:17:47 -0800 Subject: [PATCH 04/11] [setup] DRY out utils to `action_types` + `collapsed_item_actions` is repeating a util already in there unnecessarily, DRY it out too --- src/components/basic_table/action_types.ts | 11 ++++++++--- .../basic_table/collapsed_item_actions.tsx | 13 +++++++------ src/components/basic_table/default_item_action.tsx | 12 ++++++------ .../basic_table/expanded_item_actions.tsx | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/components/basic_table/action_types.ts b/src/components/basic_table/action_types.ts index 84907363fef..6121c708f5f 100644 --- a/src/components/basic_table/action_types.ts +++ b/src/components/basic_table/action_types.ts @@ -88,8 +88,13 @@ export interface CustomItemAction { export type Action = DefaultItemAction | CustomItemAction; -export const isCustomItemAction = ( - action: DefaultItemAction | CustomItemAction -): action is CustomItemAction => { +export const isCustomItemAction = ( + action: DefaultItemAction | CustomItemAction +): action is CustomItemAction => { return action.hasOwnProperty('render'); }; + +export const callWithItemIfFunction = + (item: T) => + (prop: U | ((item: T) => U)): U => + typeof prop === 'function' ? (prop as Function)(item) : prop; diff --git a/src/components/basic_table/collapsed_item_actions.tsx b/src/components/basic_table/collapsed_item_actions.tsx index bc93d2ce296..ec112f58023 100644 --- a/src/components/basic_table/collapsed_item_actions.tsx +++ b/src/components/basic_table/collapsed_item_actions.tsx @@ -21,7 +21,12 @@ import { EuiButtonIcon } from '../button'; import { EuiToolTip } from '../tool_tip'; import { EuiI18n } from '../i18n'; -import { Action, CustomItemAction } from './action_types'; +import { + Action, + CustomItemAction, + isCustomItemAction, + callWithItemIfFunction, +} from './action_types'; import { ItemIdResolved } from './table_types'; export interface CollapsedItemActionsProps { @@ -32,10 +37,6 @@ export interface CollapsedItemActionsProps { className?: string; } -const actionIsCustomItemAction = ( - action: Action -): action is CustomItemAction => action.hasOwnProperty('render'); - export const CollapsedItemActions = ({ actions, itemId, @@ -59,7 +60,7 @@ export const CollapsedItemActions = ({ const enabled = actionEnabled(action); if (enabled) setAllDisabled(false); - if (actionIsCustomItemAction(action)) { + if (isCustomItemAction(action)) { const customAction = action as CustomItemAction; const actionControl = customAction.render(item, enabled); controls.push( diff --git a/src/components/basic_table/default_item_action.tsx b/src/components/basic_table/default_item_action.tsx index 73a1b9bb983..398319dedb1 100644 --- a/src/components/basic_table/default_item_action.tsx +++ b/src/components/basic_table/default_item_action.tsx @@ -7,6 +7,7 @@ */ import React, { ReactElement } from 'react'; + import { isString } from '../../services/predicate'; import { EuiButtonEmpty, @@ -15,10 +16,14 @@ import { EuiButtonIconProps, } from '../button'; import { EuiToolTip } from '../tool_tip'; -import { DefaultItemAction as Action } from './action_types'; import { useGeneratedHtmlId } from '../../services/accessibility'; import { EuiScreenReaderOnly } from '../accessibility'; +import { + DefaultItemAction as Action, + callWithItemIfFunction, +} from './action_types'; + export interface DefaultItemActionProps { action: Action; enabled: boolean; @@ -109,8 +114,3 @@ export const DefaultItemAction = ({ button ); }; - -const callWithItemIfFunction = - (item: T) => - (prop: U | ((item: T) => U)): U => - typeof prop === 'function' ? (prop as Function)(item) : prop; diff --git a/src/components/basic_table/expanded_item_actions.tsx b/src/components/basic_table/expanded_item_actions.tsx index 6aa89b2b2e7..677f3db8bac 100644 --- a/src/components/basic_table/expanded_item_actions.tsx +++ b/src/components/basic_table/expanded_item_actions.tsx @@ -51,7 +51,7 @@ export const ExpandedItemActions = ({ expandedItemActions__completelyHide: moreThanThree && index < 2, }); - if (isCustomItemAction(action)) { + if (isCustomItemAction(action)) { // custom action has a render function tools.push( Date: Thu, 16 Nov 2023 16:52:11 -0800 Subject: [PATCH 05/11] Update default actions in collapsed popover --- .../collapsed_item_actions.test.tsx.snap | 5 +++-- .../basic_table/collapsed_item_actions.test.tsx | 14 ++++++++++---- .../basic_table/collapsed_item_actions.tsx | 17 ++++++++--------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap b/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap index 7d351bcfc26..06814083b1e 100644 --- a/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap @@ -189,14 +189,15 @@ exports[`CollapsedItemActions default actions 1`] = ` - default2 + name xyz diff --git a/src/components/basic_table/collapsed_item_actions.test.tsx b/src/components/basic_table/collapsed_item_actions.test.tsx index f369e459f9f..4055595c38a 100644 --- a/src/components/basic_table/collapsed_item_actions.test.tsx +++ b/src/components/basic_table/collapsed_item_actions.test.tsx @@ -16,12 +16,14 @@ import { import { CollapsedItemActions } from './collapsed_item_actions'; +type Item = { id: string }; + describe('CollapsedItemActions', () => { it('renders', () => { const props = { actions: [ { - name: (item: { id: string }) => `default${item.id}`, + name: (item: Item) => `default${item.id}`, description: 'default 1', onClick: () => {}, }, @@ -51,10 +53,11 @@ describe('CollapsedItemActions', () => { 'data-test-subj': 'defaultAction', }, { - name: 'default2', - description: 'default 2', - href: 'https://www.elastic.co/', + name: ({ id }: Item) => `name ${id}`, + description: ({ id }: Item) => `description ${id}`, + href: ({ id }: Item) => `#/${id}`, target: '_blank', + 'data-test-subj': ({ id }: Item) => `${id}-link`, }, ], itemId: 'id', @@ -70,6 +73,9 @@ describe('CollapsedItemActions', () => { expect(baseElement).toMatchSnapshot(); + expect(getByTestSubject('link-xyz')).toHaveAttribute('href', '#/xyz'); + expect(getByTestSubject('link-xyz')).toHaveTextContent('name xyz'); + fireEvent.click(getByTestSubject('defaultAction')); await waitForEuiPopoverClose(); }); diff --git a/src/components/basic_table/collapsed_item_actions.tsx b/src/components/basic_table/collapsed_item_actions.tsx index ec112f58023..639e0c3d497 100644 --- a/src/components/basic_table/collapsed_item_actions.tsx +++ b/src/components/basic_table/collapsed_item_actions.tsx @@ -75,20 +75,19 @@ export const CollapsedItemActions = ({ ); } else { - const { - onClick, - name, - href, - target, - 'data-test-subj': dataTestSubj, - } = action; - const buttonIcon = action.icon; let icon; if (buttonIcon) { icon = isString(buttonIcon) ? buttonIcon : buttonIcon(item); } - const buttonContent = typeof name === 'function' ? name(item) : name; + + const buttonContent = callWithItemIfFunction(item)(action.name); + const href = callWithItemIfFunction(item)(action.href); + const dataTestSubj = callWithItemIfFunction(item)( + action['data-test-subj'] + ); + + const { onClick, target } = action; controls.push( Date: Thu, 16 Nov 2023 17:47:34 -0800 Subject: [PATCH 06/11] Fix collapsed popovers not displaying descriptions as tooltips + silence `act()` errors caused by the fact that we load multiple versions of testing-library/DOM (tests still pass otherwise) --- changelogs/upcoming/7373.md | 4 ++ scripts/jest/setup/throw_on_console_error.js | 11 ++++ .../collapsed_item_actions.test.tsx.snap | 52 +++++++++++-------- .../collapsed_item_actions.test.tsx | 10 ++-- .../basic_table/collapsed_item_actions.tsx | 2 + 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/changelogs/upcoming/7373.md b/changelogs/upcoming/7373.md index 180ec79d60c..152123f9368 100644 --- a/changelogs/upcoming/7373.md +++ b/changelogs/upcoming/7373.md @@ -1 +1,5 @@ - Updated the actions column in `EuiBasicTable` and `EuiInMemoryTable`s. Alongside `name`, the `description`, `href`, and `data-test-subj` properties now also accept an optional callback that the current `item` will be passed to + +**Bug fixes** + +- Fixed `EuiBasicTable` and `EuiInMemoryTable` actions not showing tooltip descriptions when rendered in the all actions popover menu diff --git a/scripts/jest/setup/throw_on_console_error.js b/scripts/jest/setup/throw_on_console_error.js index a0829e622c3..b0c208cd5b8 100644 --- a/scripts/jest/setup/throw_on_console_error.js +++ b/scripts/jest/setup/throw_on_console_error.js @@ -26,6 +26,17 @@ console.error = (message, ...rest) => { return; } + // Silence RTL act() errors, that appear to primarily come from the fact + // that we have multiple versions of `@testing-library/dom` installed + if ( + typeof message === 'string' && + message.startsWith( + 'Warning: The current testing environment is not configured to support act(...)' + ) + ) { + return; + } + // Print React validateDOMNesting warning as a console.warn instead // of throwing an error. // TODO: Remove when edge-case DOM nesting is fixed in all components diff --git a/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap b/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap index 06814083b1e..5b6e87d2620 100644 --- a/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CollapsedItemActions custom actions 1`] = ` - +
- - + default1 + + + + - - name xyz - - + + name xyz + + +
diff --git a/src/components/basic_table/collapsed_item_actions.test.tsx b/src/components/basic_table/collapsed_item_actions.test.tsx index 4055595c38a..8d76caf5871 100644 --- a/src/components/basic_table/collapsed_item_actions.test.tsx +++ b/src/components/basic_table/collapsed_item_actions.test.tsx @@ -12,6 +12,7 @@ import { render, waitForEuiPopoverOpen, waitForEuiPopoverClose, + waitForEuiToolTipVisible, } from '../../test/rtl'; import { CollapsedItemActions } from './collapsed_item_actions'; @@ -65,7 +66,7 @@ describe('CollapsedItemActions', () => { actionEnabled: () => true, }; - const { getByTestSubject, baseElement } = render( + const { getByTestSubject, getByText, baseElement } = render( ); fireEvent.click(getByTestSubject('euiCollapsedItemActionsButton')); @@ -73,8 +74,11 @@ describe('CollapsedItemActions', () => { expect(baseElement).toMatchSnapshot(); - expect(getByTestSubject('link-xyz')).toHaveAttribute('href', '#/xyz'); - expect(getByTestSubject('link-xyz')).toHaveTextContent('name xyz'); + expect(getByTestSubject('xyz-link')).toHaveAttribute('href', '#/xyz'); + expect(getByTestSubject('xyz-link')).toHaveTextContent('name xyz'); + fireEvent.mouseEnter(getByTestSubject('xyz-link')); + await waitForEuiToolTipVisible(); + expect(getByText('description xyz')).toBeInTheDocument(); fireEvent.click(getByTestSubject('defaultAction')); await waitForEuiPopoverClose(); diff --git a/src/components/basic_table/collapsed_item_actions.tsx b/src/components/basic_table/collapsed_item_actions.tsx index 639e0c3d497..8a81efd4d91 100644 --- a/src/components/basic_table/collapsed_item_actions.tsx +++ b/src/components/basic_table/collapsed_item_actions.tsx @@ -82,6 +82,7 @@ export const CollapsedItemActions = ({ } const buttonContent = callWithItemIfFunction(item)(action.name); + const toolTipContent = callWithItemIfFunction(item)(action.description); const href = callWithItemIfFunction(item)(action.href); const dataTestSubj = callWithItemIfFunction(item)( action['data-test-subj'] @@ -101,6 +102,7 @@ export const CollapsedItemActions = ({ onClick={() => onClickItem(onClick ? () => onClick(item) : undefined) } + toolTipContent={toolTipContent} > {buttonContent}
From 57e600348075c7d1655d6e07a4552256c0c8b939 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 16 Nov 2023 17:56:44 -0800 Subject: [PATCH 07/11] Fix collapsed action to have the same tooltip delay as non-collapsed actions - this involves adding `toolTipProps` to `EuiContextMenuItem` - deprecate a few other top-level tooltip props while here, for simpler API usage --- changelogs/upcoming/7373.md | 6 +++ .../basic_table/collapsed_item_actions.tsx | 1 + .../context_menu_item.test.tsx.snap | 49 +++++++++++++++++++ .../context_menu/context_menu_item.test.tsx | 32 +++++++++++- .../context_menu/context_menu_item.tsx | 23 ++++++--- 5 files changed, 104 insertions(+), 7 deletions(-) diff --git a/changelogs/upcoming/7373.md b/changelogs/upcoming/7373.md index 152123f9368..8602a363d39 100644 --- a/changelogs/upcoming/7373.md +++ b/changelogs/upcoming/7373.md @@ -1,5 +1,11 @@ - Updated the actions column in `EuiBasicTable` and `EuiInMemoryTable`s. Alongside `name`, the `description`, `href`, and `data-test-subj` properties now also accept an optional callback that the current `item` will be passed to +- Updated `EuiContextMenuItem` with a new `toolTipProps` prop **Bug fixes** - Fixed `EuiBasicTable` and `EuiInMemoryTable` actions not showing tooltip descriptions when rendered in the all actions popover menu + +**Deprecations** + +- Deprecated `EuiContextMenuItem`'s `toolTipTitle` prop. Use `toolTipProps.title` instead +- Deprecated `EuiContextMenuItem`'s `toolTipPosition` prop. Use `toolTipProps.position` instead diff --git a/src/components/basic_table/collapsed_item_actions.tsx b/src/components/basic_table/collapsed_item_actions.tsx index 8a81efd4d91..580e21d6a65 100644 --- a/src/components/basic_table/collapsed_item_actions.tsx +++ b/src/components/basic_table/collapsed_item_actions.tsx @@ -103,6 +103,7 @@ export const CollapsedItemActions = ({ onClickItem(onClick ? () => onClick(item) : undefined) } toolTipContent={toolTipContent} + toolTipProps={{ delay: 'long' }} > {buttonContent} diff --git a/src/components/context_menu/__snapshots__/context_menu_item.test.tsx.snap b/src/components/context_menu/__snapshots__/context_menu_item.test.tsx.snap index 4901078ca83..3f3195e9a97 100644 --- a/src/components/context_menu/__snapshots__/context_menu_item.test.tsx.snap +++ b/src/components/context_menu/__snapshots__/context_menu_item.test.tsx.snap @@ -60,3 +60,52 @@ exports[`EuiContextMenuItem renders 1`] = ` `; + +exports[`EuiContextMenuItem tooltip behavior 1`] = ` + +
+ + + +
+
+ + +`; diff --git a/src/components/context_menu/context_menu_item.test.tsx b/src/components/context_menu/context_menu_item.test.tsx index 7ec564cc5e2..8c26344cfcc 100644 --- a/src/components/context_menu/context_menu_item.test.tsx +++ b/src/components/context_menu/context_menu_item.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { render } from '../../test/rtl'; +import { render, waitForEuiToolTipVisible } from '../../test/rtl'; import { shouldRenderCustomStyles } from '../../test/internal'; import { requiredProps } from '../../test/required_props'; @@ -17,6 +17,18 @@ import { EuiContextMenuItem, SIZES } from './context_menu_item'; describe('EuiContextMenuItem', () => { shouldRenderCustomStyles(); + shouldRenderCustomStyles( + , + { + childProps: ['toolTipProps', 'toolTipProps.anchorProps'], + skip: { parentTest: true }, + renderCallback: async ({ getByTestSubject }) => { + fireEvent.mouseOver(getByTestSubject('trigger')); + await waitForEuiToolTipVisible(); + }, + } + ); + it('renders', () => { const { container } = render( @@ -121,4 +133,22 @@ describe('EuiContextMenuItem', () => { ).toBeInTheDocument(); }); }); + + test('tooltip behavior', async () => { + const { getByRole, baseElement } = render( + + Hello + + ); + + fireEvent.mouseOver(getByRole('button')); + await waitForEuiToolTipVisible(); + + expect(baseElement).toMatchSnapshot(); + }); }); diff --git a/src/components/context_menu/context_menu_item.tsx b/src/components/context_menu/context_menu_item.tsx index 50c6158e213..a531be2431e 100644 --- a/src/components/context_menu/context_menu_item.tsx +++ b/src/components/context_menu/context_menu_item.tsx @@ -25,7 +25,7 @@ import { import { validateHref } from '../../services/security/href_validator'; import { CommonProps, keysOf } from '../common'; import { EuiIcon } from '../icon'; -import { EuiToolTip, ToolTipPositions } from '../tool_tip'; +import { EuiToolTip, EuiToolTipProps, ToolTipPositions } from '../tool_tip'; import { euiContextMenuItemStyles } from './context_menu_item.styles'; @@ -46,11 +46,16 @@ export interface EuiContextMenuItemProps extends CommonProps { */ toolTipContent?: ReactNode; /** - * Optional title for the tooltip + * Optional configuration to pass to the underlying [EuiToolTip](/#/display/tooltip). + * Accepts any prop that EuiToolTip does, except for `content` and `children`. + */ + toolTipProps?: Partial>; + /** + * @deprecated Use toolTipProps.title instead */ toolTipTitle?: ReactNode; /** - * Dictates the position of the tooltip. + * @deprecated Use tooltipProps.position instead */ toolTipPosition?: ToolTipPositions; href?: string; @@ -94,6 +99,7 @@ export const EuiContextMenuItem: FunctionComponent = ({ toolTipTitle, toolTipContent, toolTipPosition = 'right', + toolTipProps, href, target, rel, @@ -173,7 +179,7 @@ export const EuiContextMenuItem: FunctionComponent = ({ {buttonContent} ); - } else if (href || rest.onClick) { + } else if (href || rest.onClick || toolTipContent) { button = (