From bc8cf0cf1ad8959c6c86029c56215ece562d6d72 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 11 May 2026 16:08:00 +0200 Subject: [PATCH 01/19] feat(eui): replace title with EuiToolTip in EuiAvatar --- .../avatar/__snapshots__/avatar.test.tsx.snap | 596 ++++++++++-------- .../eui/src/components/avatar/avatar.test.tsx | 12 + packages/eui/src/components/avatar/avatar.tsx | 55 +- .../card/__snapshots__/card.test.tsx.snap | 24 +- .../__snapshots__/comment.test.tsx.snap | 125 ++-- .../__snapshots__/comment_event.test.tsx.snap | 27 +- .../__snapshots__/comment_list.test.tsx.snap | 100 +-- .../comment_timeline.test.tsx.snap | 100 +-- .../__snapshots__/timeline.test.tsx.snap | 200 +++--- .../__snapshots__/timeline_item.test.tsx.snap | 125 ++-- .../docs/components/display/avatar.mdx | 4 + 11 files changed, 813 insertions(+), 555 deletions(-) diff --git a/packages/eui/src/components/avatar/__snapshots__/avatar.test.tsx.snap b/packages/eui/src/components/avatar/__snapshots__/avatar.test.tsx.snap index 8c74f3a7dd26..f797133340ea 100644 --- a/packages/eui/src/components/avatar/__snapshots__/avatar.test.tsx.snap +++ b/packages/eui/src/components/avatar/__snapshots__/avatar.test.tsx.snap @@ -1,360 +1,456 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiAvatar allows a name composed entirely of whitespace 1`] = ` - + + + `; exports[`EuiAvatar is rendered 1`] = ` - + + + `; exports[`EuiAvatar props casing capitalize is rendered 1`] = ` - + + + `; exports[`EuiAvatar props casing lowercase is rendered 1`] = ` - + + + `; exports[`EuiAvatar props casing none is rendered 1`] = ` - + + + `; exports[`EuiAvatar props casing uppercase is rendered 1`] = ` - + + + `; exports[`EuiAvatar props color as null is rendered 1`] = ` - + + + `; exports[`EuiAvatar props color as plain is rendered 1`] = ` - + + + `; exports[`EuiAvatar props color as string is rendered 1`] = ` - + + + `; exports[`EuiAvatar props color as subdued is rendered 1`] = ` - + + + `; exports[`EuiAvatar props iconType and iconColor as null is rendered 1`] = ` - + + `; exports[`EuiAvatar props iconType and iconColor is rendered 1`] = ` - + + `; exports[`EuiAvatar props iconType and iconSize is rendered 1`] = ` - + + `; exports[`EuiAvatar props iconType is rendered 1`] = ` - + + `; exports[`EuiAvatar props imageUrl is rendered 1`] = ` - + `; exports[`EuiAvatar props isDisabled is rendered 1`] = ` - + + + `; exports[`EuiAvatar props size l is rendered 1`] = ` - + + + `; exports[`EuiAvatar props size m is rendered 1`] = ` - + + + `; exports[`EuiAvatar props size s is rendered 1`] = ` - + + + `; exports[`EuiAvatar props size xl is rendered 1`] = ` - + + + `; exports[`EuiAvatar props type is rendered 1`] = ` - + + + `; diff --git a/packages/eui/src/components/avatar/avatar.test.tsx b/packages/eui/src/components/avatar/avatar.test.tsx index f0d3fc0fd168..0bb494904eea 100644 --- a/packages/eui/src/components/avatar/avatar.test.tsx +++ b/packages/eui/src/components/avatar/avatar.test.tsx @@ -7,6 +7,7 @@ */ import React from 'react'; +import { fireEvent } from '@testing-library/react'; import { shouldRenderCustomStyles } from '../../test/internal'; import { requiredProps } from '../../test/required_props'; import { render } from '../../test/rtl'; @@ -168,6 +169,17 @@ describe('EuiAvatar', () => { }); }); + describe('tooltip', () => { + it('shows a tooltip with the avatar name on hover', () => { + const { getByRole, queryByRole } = render(); + expect(queryByRole('tooltip')).not.toBeInTheDocument(); + + fireEvent.mouseOver(getByRole('img')); + + expect(getByRole('tooltip')).toHaveTextContent('Jane Doe'); + }); + }); + test('should throw error if color is not a hex', () => { const component = () => render(); diff --git a/packages/eui/src/components/avatar/avatar.tsx b/packages/eui/src/components/avatar/avatar.tsx index 746537783964..dad4c548066d 100644 --- a/packages/eui/src/components/avatar/avatar.tsx +++ b/packages/eui/src/components/avatar/avatar.tsx @@ -22,6 +22,7 @@ import { } from '../../services/color'; import { toInitials, useEuiMemoizedStyles, useEuiTheme } from '../../services'; import { IconType, EuiIcon, IconSize, IconColor } from '../icon'; +import { EuiToolTip } from '../tool_tip'; import { euiAvatarStyles } from './avatar.styles'; @@ -80,7 +81,8 @@ export type EuiAvatarProps = Omit, 'color'> & CommonProps & _EuiAvatarContent & { /** - * Full name of avatar for title attribute and calculating initial if not provided + * Full name of the avatar. Used as the accessible label (`aria-label`), + * tooltip content and used to derive initials when `initials` is not provided. */ name: string; @@ -201,29 +203,34 @@ export const EuiAvatar: FunctionComponent = ({ }, [iconColor, avatarStyle?.color, isForcedColors, euiTheme]); return ( -
- {!imageUrl && - (iconType ? ( - - ) : ( - - ))} -
+ + {/* `EuiAvatar` is not interactive so we don't need to add a `tabIndex`. + It already has `aria-label`, the tooltip is only visual. */} + {/* eslint-disable-next-line @elastic/eui/tooltip-focusable-anchor */} +
+ {!imageUrl && + (iconType ? ( + + ) : ( + + ))} +
+
); }; diff --git a/packages/eui/src/components/card/__snapshots__/card.test.tsx.snap b/packages/eui/src/components/card/__snapshots__/card.test.tsx.snap index 3a6adcf33024..5027ad19241c 100644 --- a/packages/eui/src/components/card/__snapshots__/card.test.tsx.snap +++ b/packages/eui/src/components/card/__snapshots__/card.test.tsx.snap @@ -207,18 +207,22 @@ exports[`EuiCard props an avatar icon 1`] = `
- + +
+
- + +
- + +
- + +
- + +
- + +
- + +
diff --git a/packages/eui/src/components/comment_list/__snapshots__/comment_list.test.tsx.snap b/packages/eui/src/components/comment_list/__snapshots__/comment_list.test.tsx.snap index 99a3e0f58537..d0e81048d67d 100644 --- a/packages/eui/src/components/comment_list/__snapshots__/comment_list.test.tsx.snap +++ b/packages/eui/src/components/comment_list/__snapshots__/comment_list.test.tsx.snap @@ -16,17 +16,22 @@ exports[`EuiCommentList is rendered 1`] = `
- + +
- + +
- + +
- + +
+ + `; exports[`EuiCommentTimeline props timelineAvatar is rendered with a ReactNode 1`] = ` - + + `; exports[`EuiCommentTimeline props timelineAvatar is rendered with a string 1`] = ` - + + `; exports[`EuiCommentTimeline props timelineAvatar is rendered with timelineAvatarAriaLabel 1`] = ` - + + `; diff --git a/packages/eui/src/components/timeline/__snapshots__/timeline.test.tsx.snap b/packages/eui/src/components/timeline/__snapshots__/timeline.test.tsx.snap index c3b2b6ca8692..b605ec62ffe5 100644 --- a/packages/eui/src/components/timeline/__snapshots__/timeline.test.tsx.snap +++ b/packages/eui/src/components/timeline/__snapshots__/timeline.test.tsx.snap @@ -14,17 +14,22 @@ exports[`EuiTimeline is rendered with items 1`] = `
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
Date: Fri, 15 May 2026 16:19:19 +0200 Subject: [PATCH 02/19] feat(eui): replace title with EuiToolTip in EuiBasicTable --- .../__snapshots__/basic_table.test.tsx.snap | 194 ++++++++------- .../collapsed_item_actions.test.tsx.snap | 8 +- .../in_memory_table.test.tsx.snap | 86 ++++--- .../pagination_bar.test.tsx.snap | 60 +++-- .../basic_table/basic_table.test.tsx | 17 +- .../components/basic_table/basic_table.tsx | 56 +++-- .../basic_table/collapsed_item_actions.tsx | 15 +- .../basic_table/default_item_action.tsx | 12 +- .../__snapshots__/pagination.test.tsx.snap | 224 ++++++++++-------- .../pagination/pagination_button_arrow.tsx | 12 +- .../table/table_header_cell.test.tsx | 7 + .../table_pagination.test.tsx.snap | 60 +++-- 12 files changed, 431 insertions(+), 320 deletions(-) diff --git a/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap b/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap index d7b91c4c9368..238906a2cd3f 100644 --- a/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -237,31 +237,35 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- + + + - - +
-
+
diff --git a/packages/eui/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap b/packages/eui/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap index 648e39840f35..5a16bf682e17 100644 --- a/packages/eui/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap +++ b/packages/eui/src/components/basic_table/__snapshots__/collapsed_item_actions.test.tsx.snap @@ -11,7 +11,7 @@ exports[`CollapsedItemActions custom actions 1`] = ` > - - + + - diff --git a/packages/eui/src/components/basic_table/basic_table.test.tsx b/packages/eui/src/components/basic_table/basic_table.test.tsx index d64d2d392641..fa9812fb62ba 100644 --- a/packages/eui/src/components/basic_table/basic_table.test.tsx +++ b/packages/eui/src/components/basic_table/basic_table.test.tsx @@ -831,9 +831,18 @@ describe('EuiBasicTable', () => { }; const { getByTestSubject } = render(); - expect(getByTestSubject('deleteAction-1')).toBeDisabled(); - expect(getByTestSubject('deleteAction-2')).toBeDisabled(); - expect(getByTestSubject('deleteAction-3')).toBeDisabled(); + expect(getByTestSubject('deleteAction-1')).toHaveAttribute( + 'aria-disabled', + 'true' + ); + expect(getByTestSubject('deleteAction-2')).toHaveAttribute( + 'aria-disabled', + 'true' + ); + expect(getByTestSubject('deleteAction-3')).toHaveAttribute( + 'aria-disabled', + 'true' + ); }); test('multiple actions', () => { @@ -855,7 +864,7 @@ describe('EuiBasicTable', () => { getAllByTestSubject('euiCollapsedItemActionsButton').forEach( (button) => { - expect(button).toBeDisabled(); + expect(button).toHaveAttribute('aria-disabled', 'true'); } ); }); diff --git a/packages/eui/src/components/basic_table/basic_table.tsx b/packages/eui/src/components/basic_table/basic_table.tsx index a3e59401bc62..0f1437663d8f 100644 --- a/packages/eui/src/components/basic_table/basic_table.tsx +++ b/packages/eui/src/components/basic_table/basic_table.tsx @@ -82,6 +82,7 @@ import { euiBasicTableBodyLoading, safariLoadingWorkaround, } from './basic_table.styles'; +import { EuiToolTip } from '../tool_tip'; type DataTypeProfiles = Record< EuiTableDataType, @@ -740,17 +741,24 @@ export class EuiBasicTable extends Component< defaults={['Select all rows', 'Deselect rows']} > {([selectAllRows, deselectRows]: string[]) => ( - + + + )} ); @@ -992,7 +1000,8 @@ export class EuiBasicTable extends Component< colSpan={colSpan} mobileOptions={{ width: '100%' }} > - {error} + {' '} + {error} ); @@ -1181,15 +1190,20 @@ export class EuiBasicTable extends Component< values={{ index: displayedRowIndex + 1 }} > {(selectThisRow: string) => ( - + + + )} , diff --git a/packages/eui/src/components/basic_table/collapsed_item_actions.tsx b/packages/eui/src/components/basic_table/collapsed_item_actions.tsx index 742f6c251af0..4a50637c42aa 100644 --- a/packages/eui/src/components/basic_table/collapsed_item_actions.tsx +++ b/packages/eui/src/components/basic_table/collapsed_item_actions.tsx @@ -137,17 +137,24 @@ export const CollapsedItemActions = ({ ? allActionsButtonDisabledAriaLabel : allActionsButtonAriaLabel } - title={actionsDisabled ? allActionsButtonDisabledAriaLabel : undefined} iconType="boxesVertical" color="text" isDisabled={actionsDisabled} + hasAriaDisabled={actionsDisabled} onClick={() => setPopoverOpen((isOpen) => !isOpen)} data-test-subj="euiCollapsedItemActionsButton" /> ); - const withTooltip = !actionsDisabled && ( - {popoverButton} + const withTooltip = ( + + {popoverButton} + ); return ( @@ -155,7 +162,7 @@ export const CollapsedItemActions = ({ className={className} id={`${itemId}-actions`} isOpen={popoverOpen} - button={withTooltip || popoverButton} + button={withTooltip} closePopover={closePopover} panelPaddingSize="none" anchorPosition="leftCenter" diff --git a/packages/eui/src/components/basic_table/default_item_action.tsx b/packages/eui/src/components/basic_table/default_item_action.tsx index 16733202b239..6e4765789af3 100644 --- a/packages/eui/src/components/basic_table/default_item_action.tsx +++ b/packages/eui/src/components/basic_table/default_item_action.tsx @@ -81,15 +81,13 @@ export const DefaultItemAction = ({ className={className} aria-labelledby={ariaLabelId} isDisabled={!enabled} + hasAriaDisabled={!enabled} color={color} iconType={icon} onClick={onClick} href={href} target={action.target} data-test-subj={dataTestSubj} - // If action is disabled, the normal tooltip can't show - attempt to - // provide some amount of affordance with a browser title tooltip - title={!enabled ? tooltipContent : undefined} /> ); // actionContent (action.name) is a ReactNode and must be rendered @@ -105,6 +103,7 @@ export const DefaultItemAction = ({ className={className} size="s" isDisabled={!enabled} + hasAriaDisabled={!enabled} color={color as EuiButtonEmptyProps['color']} iconType={icon} onClick={onClick} @@ -118,17 +117,12 @@ export const DefaultItemAction = ({ ); } - return enabled ? ( + return ( <> {button} {/* SR text has to be rendered outside the tooltip, otherwise EuiToolTip's own aria-labelledby won't properly clone */} {ariaLabelledBy} - ) : ( - <> - {button} - {ariaLabelledBy} - ); }; diff --git a/packages/eui/src/components/pagination/__snapshots__/pagination.test.tsx.snap b/packages/eui/src/components/pagination/__snapshots__/pagination.test.tsx.snap index 2ac467f1080a..e7946896df57 100644 --- a/packages/eui/src/components/pagination/__snapshots__/pagination.test.tsx.snap +++ b/packages/eui/src/components/pagination/__snapshots__/pagination.test.tsx.snap @@ -83,34 +83,42 @@ exports[`EuiPagination props activePage can be -1 1`] = ` > Last Page of collection - - + + + + + + + + +
    @@ -340,20 +352,24 @@ exports[`EuiPagination props activePage is rendered 1`] = `
- + + + `; @@ -549,34 +565,42 @@ exports[`EuiPagination props pageCount can be 0 1`] = ` data-euiicon-type="chevronSingleLeft" /> - - + + + + + + `; @@ -738,20 +762,24 @@ exports[`EuiPagination props pageCount is rendered 1`] = ` - + + + `; diff --git a/packages/eui/src/components/pagination/pagination_button_arrow.tsx b/packages/eui/src/components/pagination/pagination_button_arrow.tsx index d845b087e4ac..b5ca044d572d 100644 --- a/packages/eui/src/components/pagination/pagination_button_arrow.tsx +++ b/packages/eui/src/components/pagination/pagination_button_arrow.tsx @@ -16,6 +16,7 @@ import { import { keysOf } from '../common'; import { useEuiI18n } from '../i18n'; import { useEuiTheme } from '../../services'; +import { EuiToolTip } from '../tool_tip'; import { euiPaginationButtonStyles } from './pagination_button.styles'; const typeToIconTypeMap = { @@ -61,13 +62,12 @@ export const EuiPaginationButtonArrow: FunctionComponent = ({ buttonProps['aria-controls'] = ariaControls; } - return ( + const button = ( = ({ {...buttonProps} /> ); + + return disabled ? ( + button + ) : ( + + {button} + + ); }; diff --git a/packages/eui/src/components/table/table_header_cell.test.tsx b/packages/eui/src/components/table/table_header_cell.test.tsx index a208793c5d93..135a154fd083 100644 --- a/packages/eui/src/components/table/table_header_cell.test.tsx +++ b/packages/eui/src/components/table/table_header_cell.test.tsx @@ -337,6 +337,13 @@ describe('EuiTableHeaderCell', () => { getByTestSubject('icon') ); }); + + it('shows a title attribute with cell text on non-sortable column', () => { + const { getByText } = renderInTableHeader( + Label + ); + expect(getByText('Label')).toHaveAttribute('title', 'Label'); + }); }); describe('sticky', () => { diff --git a/packages/eui/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap b/packages/eui/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap index 710970223e0d..bcba8cbac5d2 100644 --- a/packages/eui/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap +++ b/packages/eui/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap @@ -53,20 +53,24 @@ exports[`EuiTablePagination renders 1`] = ` > Page 2 of 5 - + +
    @@ -173,20 +177,24 @@ exports[`EuiTablePagination renders 1`] = `
- + + From 7735270d8f67b28352ade735fec631c05c9ca2a4 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 16:47:39 +0200 Subject: [PATCH 03/19] feat(eui): replace title with EuiToolTip in EuiBreadcrumbs --- .../_breadcrumb_content.test.tsx.snap | 38 +-- .../__snapshots__/breadcrumb.test.tsx.snap | 38 +-- .../__snapshots__/breadcrumbs.test.tsx.snap | 216 ++++++++++-------- .../breadcrumbs/_breadcrumb_content.tsx | 59 +++-- .../eui/src/components/header/header.a11y.tsx | 2 +- 5 files changed, 204 insertions(+), 149 deletions(-) diff --git a/packages/eui/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap b/packages/eui/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap index 7bc4f8e16efa..98da30e0c46f 100644 --- a/packages/eui/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap +++ b/packages/eui/src/components/breadcrumbs/__snapshots__/_breadcrumb_content.test.tsx.snap @@ -7,25 +7,29 @@ exports[`EuiBreadcrumbContent breadcrumbs with popovers renders with \`popoverCo class="euiPopover euiPopover-isOpen emotion-euiPopover-inline-block-euiBreadcrumb__popoverWrapper-page" data-test-subj="popover" > - + + Toggles a popover + + + - Clicking this button will toggle a popover dialog. + + +
- + + - Clicking this button will toggle a popover dialog. + + +
diff --git a/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap b/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap index c00967f7d3fa..1120a0114d96 100644 --- a/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap +++ b/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap @@ -41,27 +41,31 @@ exports[`EuiBreadcrumbs is rendered 1`] = `
- + + - Clicking this button will toggle a popover dialog. + + +
  • - + + - Clicking this button will toggle a popover dialog. + + +
  • - + + - Clicking this button will toggle a popover dialog. + + +
  • - + + - Clicking this button will toggle a popover dialog. + + +
  • - + + - Clicking this button will toggle a popover dialog. + + +
  • - + + - Clicking this button will toggle a popover dialog. + + +
  • { const isApplication = type === 'application'; @@ -87,7 +89,7 @@ export const EuiBreadcrumbContent: FunctionComponent< return ( {(ref, innerText) => { - const title = innerText === '' ? undefined : innerText; + const title = propTitle || (innerText === '' ? undefined : innerText); const baseProps = { ref, title, @@ -101,6 +103,7 @@ export const EuiBreadcrumbContent: FunctionComponent< return ( - {children} - - + title ? ( + + + {children} + + + + ) : ( + + {children} + + + ) } > {typeof popoverContent === 'function' diff --git a/packages/eui/src/components/header/header.a11y.tsx b/packages/eui/src/components/header/header.a11y.tsx index 7dc6162be3ac..6cacdee08298 100644 --- a/packages/eui/src/components/header/header.a11y.tsx +++ b/packages/eui/src/components/header/header.a11y.tsx @@ -404,7 +404,7 @@ describe('EuiHeader', () => { }); it('has zero violations when a hidden breadcrumb is expanded', () => { - cy.get('button[title="See collapsed breadcrumbs"]').realClick(); + cy.get('[aria-label="See collapsed breadcrumbs"]').realClick(); cy.get('a[data-test-subj="cy-breadcrumb-hidden"]').should('exist'); cy.checkAxe(); }); From ade66fa5d717dd40efcfd486f07b56af61e5cb49 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 18:04:40 +0200 Subject: [PATCH 04/19] feat(eui): pass `title` conditionally in EuiComboBox --- .../combo_box_options_list/combo_box_options_list.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/eui/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx b/packages/eui/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx index 7bdf6de325bd..93d6c15fb553 100644 --- a/packages/eui/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx +++ b/packages/eui/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx @@ -241,6 +241,9 @@ export class EuiComboBoxOptionsList extends Component< const hasOnFocusBadge = onFocusBadge && optionIsFocused && !optionIsDisabled; + const usesTruncation = + !hasTruncationProps && !searchValue && rowHeight !== 'auto'; + return ( extends Component< // uses the original `options` array for the index to ensure a stable `id`, otherwise `aria-activedescendant` // loses focus on selecting an option (due to actively removing it from the list) id={rootId(`_option-${options.indexOf(option)}`)} + title={usesTruncation && !toolTipContent ? label : undefined} key={option.key ?? option.label} - title={label} prepend={option.prepend} append={ hasOnFocusBadge ? ( From ac6586e3df940c88eebec4ea0119d57cfeb5cd79 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 18:25:09 +0200 Subject: [PATCH 05/19] feat(eui): replace title with EuiToolTip in EuiMarkdownEditor --- .../markdown_editor.test.tsx.snap | 12 ++++----- .../markdown_editor.stories.tsx | 10 +++++++ .../markdown_editor_help_button.tsx | 27 +++++++++++-------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/eui/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap b/packages/eui/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap index e25640ad62af..9aa3bd3ab335 100644 --- a/packages/eui/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap +++ b/packages/eui/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap @@ -229,7 +229,7 @@ exports[`EuiMarkdownEditor is rendered 1`] = ` /> + + `; @@ -158,20 +162,24 @@ exports[`EuiFieldPassword props dual dualToggleProps is rendered 1`] = `
    - + +
    `; @@ -389,19 +397,23 @@ exports[`EuiFieldPassword props type dual is rendered 1`] = `
    - + +
    `; diff --git a/packages/eui/src/components/form/field_password/field_password.test.tsx b/packages/eui/src/components/form/field_password/field_password.test.tsx index 34031db78dfe..aa0d9d0ca405 100644 --- a/packages/eui/src/components/form/field_password/field_password.test.tsx +++ b/packages/eui/src/components/form/field_password/field_password.test.tsx @@ -122,7 +122,6 @@ describe('EuiFieldPassword', () => { aria-label="Show password as plain text. Note: this will visually expose your password on the screen." class="euiButtonIcon emotion-euiButtonIcon-xs-empty-primary" data-test-subj="toggleButton" - title="Show password as plain text. Note: this will visually expose your password on the screen." type="button" > { aria-label="Mask password" class="euiButtonIcon emotion-euiButtonIcon-xs-empty-primary" data-test-subj="toggleButton" - title="Mask password" type="button" > = ( const isVisible = inputType === 'text'; return ( - ) => - handleToggle(e, isVisible) - } - /> + + ) => + handleToggle(e, isVisible) + } + /> + ); } }, [ From 000489ec1db52f7969711bd9c8e1b6b05679b285 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 18:40:53 +0200 Subject: [PATCH 07/19] feat(eui): pass `title` conditionally in EuiSelectable --- .../components/selectable/selectable.spec.tsx | 32 +++++-------------- .../selectable_list/selectable_list.tsx | 6 +++- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/packages/eui/src/components/selectable/selectable.spec.tsx b/packages/eui/src/components/selectable/selectable.spec.tsx index 4fe7b12ea6b0..1099c69f07c5 100644 --- a/packages/eui/src/components/selectable/selectable.spec.tsx +++ b/packages/eui/src/components/selectable/selectable.spec.tsx @@ -85,9 +85,7 @@ describe('EuiSelectable', () => { .realClick() .realType('enc') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Enceladus'); + cy.get('li[role=option]').first().should('contain.text', 'Enceladus'); }); }); @@ -99,9 +97,7 @@ describe('EuiSelectable', () => { .realClick() .realType('enc') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Enceladus'); + cy.get('li[role=option]').first().should('contain.text', 'Enceladus'); }); // Clear search using ENTER @@ -109,9 +105,7 @@ describe('EuiSelectable', () => { .focus() .realPress('{enter}') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Titan'); + cy.get('li[role=option]').first().should('contain.text', 'Titan'); }); // Search/filter again @@ -119,9 +113,7 @@ describe('EuiSelectable', () => { .realClick() .realType('enc') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Enceladus'); + cy.get('li[role=option]').first().should('contain.text', 'Enceladus'); }); // Clear search using SPACE @@ -129,9 +121,7 @@ describe('EuiSelectable', () => { .focus() .realPress('Space') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Titan'); + cy.get('li[role=option]').first().should('contain.text', 'Titan'); }); // Ensure the clear button does not respond to up/down arrow keys @@ -139,23 +129,17 @@ describe('EuiSelectable', () => { .realClick() .realType('titan') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Titan'); + cy.get('li[role=option]').first().should('contain.text', 'Titan'); }); cy.get('[data-test-subj="clearSearchButton"]') .focus() .realPress('ArrowDown') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Titan'); + cy.get('li[role=option]').first().should('contain.text', 'Titan'); }) .realPress('ArrowUp') .then(() => { - cy.get('li[role=option]') - .first() - .should('have.attr', 'title', 'Titan'); + cy.get('li[role=option]').first().should('contain.text', 'Titan'); }); }); diff --git a/packages/eui/src/components/selectable/selectable_list/selectable_list.tsx b/packages/eui/src/components/selectable/selectable_list/selectable_list.tsx index bb221f0d877b..6b8115449221 100644 --- a/packages/eui/src/components/selectable/selectable_list/selectable_list.tsx +++ b/packages/eui/src/components/selectable/selectable_list/selectable_list.tsx @@ -493,7 +493,11 @@ export class EuiSelectableList extends Component< this.onAddOrRemoveOption(option, event); }} isFocused={isFocused} - title={searchableLabel || label} + title={ + !truncationProps && !option.toolTipContent + ? searchableLabel || label + : undefined + } checked={checked} disabled={disabled} prepend={prepend} From 1b200f8e2fa8585cd8cbf618fed2d3995d2b0654 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 18:59:14 +0200 Subject: [PATCH 08/19] feat(eui): replace title with EuiToolTip in EuiTextTruncate --- .../text_truncate/text_truncate.spec.tsx | 5 ++-- .../text_truncate/text_truncate.test.tsx | 23 +++++++++++++++++-- .../text_truncate/text_truncate.tsx | 13 +++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/eui/src/components/text_truncate/text_truncate.spec.tsx b/packages/eui/src/components/text_truncate/text_truncate.spec.tsx index 85ded6160732..9967b7c8c96c 100644 --- a/packages/eui/src/components/text_truncate/text_truncate.spec.tsx +++ b/packages/eui/src/components/text_truncate/text_truncate.spec.tsx @@ -34,9 +34,10 @@ describe('EuiTextTruncate', () => { getTruncatedText().should('not.exist'); }); - it('renders truncated text and a title when truncation is needed', () => { + it('renders truncated text and a tooltip when truncation is needed', () => { cy.mount(); - cy.get('#text').should('have.attr', 'title', props.text); + cy.get('#text').should('have.attr', 'tabindex', '0'); + cy.get('#text').parent().should('have.class', 'euiToolTipAnchor'); cy.get('#text [data-test-subj="fullText"]').should('have.text', props.text); getTruncatedText().should('exist'); }); diff --git a/packages/eui/src/components/text_truncate/text_truncate.test.tsx b/packages/eui/src/components/text_truncate/text_truncate.test.tsx index bb2080638ca1..11703ae94871 100644 --- a/packages/eui/src/components/text_truncate/text_truncate.test.tsx +++ b/packages/eui/src/components/text_truncate/text_truncate.test.tsx @@ -18,7 +18,7 @@ jest.mock('./utils', () => ({ })); import { EuiTextTruncate } from './text_truncate'; -import { act } from '@testing-library/react'; +import { act, fireEvent } from '@testing-library/react'; describe('EuiTextTruncate', () => { beforeEach(() => jest.clearAllMocks()); @@ -101,12 +101,31 @@ describe('EuiTextTruncate', () => { ); expect(onResize).toHaveBeenCalledWith(0); - expect(container.firstChild).toHaveAttribute( + expect(container.querySelector('[data-resize-observer]')).toHaveAttribute( 'data-resize-observer', 'true' ); }); }); + describe('tooltip', () => { + it('renders a tooltip with the full text when truncating (width=0)', () => { + const { container, queryByRole } = render( + + ); + expect(queryByRole('tooltip')).not.toBeInTheDocument(); + fireEvent.mouseOver(container.querySelector('.euiTextTruncate')!); + expect(queryByRole('tooltip')).toHaveTextContent('Hello world'); + }); + + it('does not render a tooltip when not truncating', () => { + const { container, queryByRole } = render( + + ); + fireEvent.mouseOver(container.querySelector('.euiTextTruncate')!); + expect(queryByRole('tooltip')).not.toBeInTheDocument(); + }); + }); + // We can't unit test the actual truncation logic in JSDOM - see Cypress spec tests instead }); diff --git a/packages/eui/src/components/text_truncate/text_truncate.tsx b/packages/eui/src/components/text_truncate/text_truncate.tsx index 6e6c852dcf69..bc9b0e98a252 100644 --- a/packages/eui/src/components/text_truncate/text_truncate.tsx +++ b/packages/eui/src/components/text_truncate/text_truncate.tsx @@ -24,6 +24,7 @@ import { EuiResizeObserverProps, } from '../observer/resize_observer'; import type { CommonProps } from '../common'; +import { EuiToolTip } from '../tool_tip'; import { TruncationUtils } from './utils'; import { euiTextTruncateStyles } from './text_truncate.styles'; @@ -216,12 +217,12 @@ const EuiTextTruncateWithWidth: FunctionComponent< const styles = useEuiMemoizedStyles(euiTextTruncateStyles); - return ( + const content = (
    {isTruncating ? ( @@ -249,6 +250,14 @@ const EuiTextTruncateWithWidth: FunctionComponent< )}
    ); + + return isTruncating ? ( + + {content} + + ) : ( + content + ); }; const EuiTextTruncateWithResizeObserver: FunctionComponent< From a1a9c45c491aa8da845ed789d348909b3f317d83 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 19:03:53 +0200 Subject: [PATCH 09/19] feat(eui): replace title with EuiToolTip in EuiSearchBar --- .../__snapshots__/search_bar.test.tsx.snap | 210 ++++++++++-------- .../field_value_selection_filter.spec.tsx | 70 +++--- .../components/search_bar/search_bar.test.tsx | 4 +- .../src/components/search_bar/search_bar.tsx | 47 ++-- .../src/components/search_bar/search_box.tsx | 4 +- 5 files changed, 180 insertions(+), 155 deletions(-) diff --git a/packages/eui/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap b/packages/eui/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap index 102a52d13053..22094b7ac4f4 100644 --- a/packages/eui/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap +++ b/packages/eui/src/components/search_bar/__snapshots__/search_bar.test.tsx.snap @@ -7,36 +7,41 @@ exports[`SearchBar render - box 1`] = `
    -
    - + class="euiFormControlLayoutCustomIcon emotion-euiFormControlLayoutCustomIcon" + > + +
    +
    -
    -
    +
    `; @@ -48,35 +53,40 @@ exports[`SearchBar render - no config, no query 1`] = `
    -
    - + class="euiFormControlLayoutCustomIcon emotion-euiFormControlLayoutCustomIcon" + > + +
    +
    -
    -
    +
    `; @@ -88,50 +98,55 @@ exports[`SearchBar render - provided query, filters 1`] = `
    -
    - -
    - -
    -
    + +
    - - + +
    -
    +
    -
    - + class="euiFormControlLayoutCustomIcon emotion-euiFormControlLayoutCustomIcon" + > + +
    +
    -
    -
    +
    { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .first() - .should('have.attr', 'title', 'feature'); + .should('contain.text', 'feature'); }); it('allows options as an array', () => { @@ -97,7 +97,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .eq(1) - .should('have.attr', 'title', 'Text'); + .should('contain.text', 'Text'); }); it('allows fields in options', () => { @@ -133,7 +133,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .eq(2) - .should('have.attr', 'title', 'Bug'); + .should('contain.text', 'Bug'); }); it('allows all configurations', () => { @@ -168,21 +168,21 @@ describe('FieldValueSelectionFilter', () => { cy.mount(); cy.get('button').click(); - cy.get('li[role="option"][title="feature"]') + cy.contains('li[role="option"]', 'feature') .should('have.attr', 'aria-checked', 'false') .click(); cy.get('.euiNotificationBadge').should('have.text', '1'); - cy.get('li[role="option"][title="feature"]').should( + cy.contains('li[role="option"]', 'feature').should( 'have.attr', 'aria-checked', 'true' ); // Popover should still be open when multiselect is true/or - cy.get('li[role="option"][title="Bug"]') + cy.contains('li[role="option"]', 'Bug') .should('have.attr', 'aria-checked', 'false') .click(); cy.get('.euiNotificationBadge').should('have.text', '2'); - cy.get('li[role="option"][title="Bug"]').should( + cy.contains('li[role="option"]', 'Bug').should( 'have.attr', 'aria-checked', 'true' @@ -193,11 +193,11 @@ describe('FieldValueSelectionFilter', () => { cy.mount(); cy.get('button').click(); - cy.get('li[role="option"][title="feature"]') + cy.contains('li[role="option"]', 'feature') .should('have.attr', 'aria-selected', 'false') .click(); cy.get('.euiNotificationBadge').should('have.text', '1'); - cy.get('li[role="option"][title="feature"]').should( + cy.contains('li[role="option"]', 'feature').should( 'have.attr', 'aria-selected', 'true' @@ -205,18 +205,18 @@ describe('FieldValueSelectionFilter', () => { // Multiselect false should close the popover, so we need to re-open it cy.get('button').click(); - cy.get('li[role="option"][title="Bug"]') + cy.contains('li[role="option"]', 'Bug') .should('have.attr', 'aria-selected', 'false') .click(); // Filter count should have remained at 1 cy.get('.euiNotificationBadge').should('have.text', '1'); - cy.get('li[role="option"][title="Bug"]').should( + cy.contains('li[role="option"]', 'Bug').should( 'have.attr', 'aria-selected', 'true' ); // 'featured' should now be unchecked - cy.get('li[role="option"][title="feature"]').should( + cy.contains('li[role="option"]', 'feature').should( 'have.attr', 'aria-selected', 'false' @@ -225,14 +225,14 @@ describe('FieldValueSelectionFilter', () => { }); describe('auto-close testing', () => { - const selectFilter = (state: 'checked' | 'selected' = 'checked') => { + const selectFilter = () => { // Open popover cy.get('button').click(); cy.get('.euiPopover__panel').should('exist'); // Select filter option - cy.get('li[role="option"][title="feature"]') - .should('have.attr', `aria-${state}`, 'false') + cy.contains('li[role="option"]', 'feature') + .should('have.attr', 'aria-checked', 'false') .click(); }; @@ -244,7 +244,7 @@ describe('FieldValueSelectionFilter', () => { multiSelect={true} /> ); - selectFilter('checked'); + selectFilter(); cy.get('.euiPopover__panel').should('exist'); }); @@ -255,7 +255,7 @@ describe('FieldValueSelectionFilter', () => { multiSelect={false} /> ); - selectFilter('selected'); + selectFilter(); cy.get('.euiPopover__panel').should('not.exist'); }); }); @@ -268,7 +268,7 @@ describe('FieldValueSelectionFilter', () => { multiSelect={true} /> ); - selectFilter('checked'); + selectFilter(); cy.get('.euiPopover__panel').should('exist'); }); @@ -279,7 +279,7 @@ describe('FieldValueSelectionFilter', () => { multiSelect={false} /> ); - selectFilter('selected'); + selectFilter(); cy.get('.euiPopover__panel').should('exist'); }); }); @@ -292,7 +292,7 @@ describe('FieldValueSelectionFilter', () => { multiSelect={true} /> ); - selectFilter('checked'); + selectFilter(); cy.get('.euiPopover__panel').should('not.exist'); }); @@ -304,7 +304,7 @@ describe('FieldValueSelectionFilter', () => { /> ); - selectFilter('selected'); + selectFilter(); cy.get('.euiPopover__panel').should('not.exist'); }); }); @@ -318,15 +318,13 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); getOptions().should('have.length', 3); - getOptions().last().should('have.attr', 'title', 'Bug').click(); + getOptions().last().should('contain.text', 'Bug').click(); // Should have moved to the top of the list and retained active focus getOptions() .first() - .should('have.attr', 'title', 'Bug') - .should('have.attr', 'aria-checked', 'true'); - cy.get('ul[role="listbox"]') - .should('have.attr', 'aria-activedescendant') - .should('include', 'option-0'); + .should('contain.text', 'Bug') + .should('have.attr', 'aria-checked', 'true') + .should('have.attr', 'aria-selected', 'true'); }); it('does not sort selected options to the top when set to false', () => { @@ -334,14 +332,12 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); getOptions().should('have.length', 3); - getOptions().last().should('have.attr', 'title', 'Bug').click(); + getOptions().last().should('contain.text', 'Bug').click(); getOptions() .last() - .should('have.attr', 'title', 'Bug') - .should('have.attr', 'aria-checked', 'true'); - cy.get('ul[role="listbox"]') - .should('have.attr', 'aria-activedescendant') - .should('include', 'option-2'); + .should('contain.text', 'Bug') + .should('have.attr', 'aria-checked', 'true') + .should('have.attr', 'aria-selected', 'true'); }); }); @@ -363,7 +359,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .first() - .should('have.attr', 'title', 'feature'); + .should('contain.text', 'feature'); }); it('has active filters, field is global', () => { @@ -385,7 +381,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('.euiNotificationBadge').should('not.be.undefined'); cy.get('[data-test-subj="euiSelectableList"] li') .first() - .should('have.attr', 'title', 'Bug'); + .should('contain.text', 'Bug'); }); it('has inactive filters, fields in options', () => { @@ -421,7 +417,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .eq(2) - .should('have.attr', 'title', 'Bug'); + .should('contain.text', 'Bug'); }); it('has active filters, fields in options', () => { @@ -458,7 +454,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('.euiNotificationBadge').should('not.be.undefined'); cy.get('[data-test-subj="euiSelectableList"] li') .eq(0) - .should('have.attr', 'title', 'Bug'); + .should('contain.text', 'Bug'); }); it('caches options if options is a function and config.cache is set', () => { diff --git a/packages/eui/src/components/search_bar/search_bar.test.tsx b/packages/eui/src/components/search_bar/search_bar.test.tsx index 77dc9a151d47..9a0ce7c71b9e 100644 --- a/packages/eui/src/components/search_bar/search_bar.test.tsx +++ b/packages/eui/src/components/search_bar/search_bar.test.tsx @@ -79,10 +79,10 @@ describe('SearchBar', () => { onChange: () => {}, }; - const { container, findByTitle } = render(); + const { container, findByText } = render(); // Wait for FieldValueSelectionFilter to finish updating its state on init - await findByTitle('Tag'); + await findByText('Tag'); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/packages/eui/src/components/search_bar/search_bar.tsx b/packages/eui/src/components/search_bar/search_bar.tsx index 4c9730f417ed..f885f323d0f0 100644 --- a/packages/eui/src/components/search_bar/search_bar.tsx +++ b/packages/eui/src/components/search_bar/search_bar.tsx @@ -11,6 +11,7 @@ import React, { Component, ReactElement } from 'react'; import { RenderWithEuiTheme, htmlIdGenerator } from '../../services'; import { isString } from '../../services/predicate'; import { EuiFlexGroup, EuiFlexItem } from '../flex'; +import { EuiToolTip } from '../tool_tip'; import { EuiSearchBox } from './search_box'; import { EuiSearchBarFilters, SearchFilterConfig } from './search_filters'; import { Query } from './query'; @@ -292,26 +293,32 @@ export class EuiSearchBar extends Component { css={euiSearchBar__searchHolder(euiTheme)} grow={true} > - { - this.setState({ isHintVisible: isVisible }); - }, - id: this.hintId, - ...hint, - } - : undefined - } - /> + + { + this.setState({ isHintVisible: isVisible }); + }, + id: this.hintId, + ...hint, + } + : undefined + } + /> + {filters && ( = ({ placeholder, incremental, hint, + onFocus, ...rest }) => { const inputRef = useRef(null); @@ -66,8 +67,9 @@ export const EuiSearchBox: FunctionComponent = ({ incremental={incremental} aria-label={incremental ? ariaLabelIncremental : ariaLabelEnter} placeholder={placeholder ?? defaultPlaceholder} - onFocus={() => { + onFocus={(e) => { hint?.setIsVisible(true); + onFocus?.(e); }} {...rest} /> From fa7385551b93afb043dbcb1b144d51432adaee25 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 19:27:29 +0200 Subject: [PATCH 10/19] feat(eui): replace title with EuiToolTip in EuiSuperDatePicker --- .../__snapshots__/auto_refresh.test.tsx.snap | 106 +++-- .../date_picker/auto_refresh/auto_refresh.tsx | 33 +- .../super_date_picker.test.tsx.snap | 402 ++++++++++-------- .../time_window_buttons.test.tsx.snap | 6 +- .../date_popover_button.test.tsx.snap | 1 - .../date_popover/absolute_tab.tsx | 22 +- .../date_popover/date_popover_button.tsx | 22 +- .../quick_select_popover.test.tsx.snap | 36 +- .../quick_select_popover.tsx | 26 +- .../super_date_picker/time_window_buttons.tsx | 4 - 10 files changed, 371 insertions(+), 287 deletions(-) diff --git a/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap b/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap index f1550aa49134..7cde6ca7a704 100644 --- a/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap +++ b/packages/eui/src/components/date_picker/auto_refresh/__snapshots__/auto_refresh.test.tsx.snap @@ -140,28 +140,32 @@ exports[`EuiAutoRefreshButton is rendered 1`] = `
    - + +
    `; @@ -169,26 +173,30 @@ exports[`EuiAutoRefreshButton isPaused is false 1`] = `
    - + +
    `; @@ -196,25 +204,29 @@ exports[`EuiAutoRefreshButton refreshInterval is rendered 1`] = `
    - + +
    `; diff --git a/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx b/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx index e215c14c681b..aa3e19454d79 100644 --- a/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx +++ b/packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx @@ -14,6 +14,7 @@ import { CommonEuiButtonEmptyProps, } from '../../button/button_empty/button_empty'; import { EuiInputPopover, EuiPopover } from '../../popover'; +import { EuiToolTip } from '../../tool_tip'; import { useEuiI18n } from '../../i18n'; import { usePrettyInterval } from '../super_date_picker/pretty_interval'; @@ -139,21 +140,23 @@ export const EuiAutoRefreshButton: FunctionComponent< return ( setIsPopoverOpen((isOpen) => !isOpen)} - className={classes} - size={size} - color={color} - iconType="refreshTime" - title={isPaused ? autoRefeshLabelOff : autoRefeshLabelOn} - isDisabled={isDisabled} - {...rest} - > - {usePrettyInterval(Boolean(isPaused), refreshInterval, { - shortHand, - unit: intervalUnits, - })} - + + setIsPopoverOpen((isOpen) => !isOpen)} + className={classes} + size={size} + color={color} + iconType="refreshTime" + isDisabled={isDisabled} + hasAriaDisabled={isDisabled} + {...rest} + > + {usePrettyInterval(Boolean(isPaused), refreshInterval, { + shortHand, + unit: intervalUnits, + })} + + } isOpen={isPopoverOpen} closePopover={() => { diff --git a/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap b/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap index a74344ec1dc7..bc01bad96e62 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap +++ b/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/super_date_picker.test.tsx.snap @@ -14,24 +14,28 @@ exports[`EuiSuperDatePicker props accepts data-test-subj and passes to EuiFormCo
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    -
    - -
    + +
    +
    - + +
    - + +
    diff --git a/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/time_window_buttons.test.tsx.snap b/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/time_window_buttons.test.tsx.snap index 6fde4fd1a43e..0c9a1aecfc5d 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/time_window_buttons.test.tsx.snap +++ b/packages/eui/src/components/date_picker/super_date_picker/__snapshots__/time_window_buttons.test.tsx.snap @@ -12,7 +12,7 @@ exports[`EuiTimeWindowButtons renders 1`] = ` - + + + `; diff --git a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx index 1891680f4e2d..8a791000e6e2 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx @@ -20,6 +20,7 @@ import { useEuiMemoizedStyles } from '../../../../services'; import { useEuiI18n } from '../../../i18n'; import { EuiPopover } from '../../../popover'; import { EuiFormAppendPrependButtonProps, EuiFormPrepend } from '../../../form'; +import { EuiToolTip } from '../../../tool_tip'; import { euiQuickSelectPopoverStyles } from './quick_select_popover.styles'; import { EuiQuickSelectPanel } from './quick_select_panel'; import { EuiQuickSelect } from './quick_select'; @@ -93,7 +94,7 @@ export const EuiQuickSelectPopover: FunctionComponent< [_applyTime, closePopover] ); - const buttonlabel = useEuiI18n( + const buttonLabel = useEuiI18n( 'euiQuickSelectPopover.buttonLabel', 'Date quick select' ); @@ -106,17 +107,18 @@ export const EuiQuickSelectPopover: FunctionComponent< }; const quickSelectButton = ( - + + + ); return ( diff --git a/packages/eui/src/components/date_picker/super_date_picker/time_window_buttons.tsx b/packages/eui/src/components/date_picker/super_date_picker/time_window_buttons.tsx index fa585858c87d..563243825306 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/time_window_buttons.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/time_window_buttons.tsx @@ -157,7 +157,6 @@ export const EuiTimeWindowButtons: React.FC = ({ id={previousId} data-test-subj="timeWindowButtonsPrevious" label={previousLabel} - title="" toolTipContent={!isDisabled && previousTooltipContent} color={buttonColor} size={buttonSize} @@ -173,7 +172,6 @@ export const EuiTimeWindowButtons: React.FC = ({ id={zoomInId} data-test-subj="timeWindowButtonsZoomIn" label={zoomInLabel} - title="" toolTipContent={!isDisabled && zoomInTooltipContent} toolTipProps={{ disableScreenReaderOutput: zoomInLabel === zoomInTooltipContent, @@ -192,7 +190,6 @@ export const EuiTimeWindowButtons: React.FC = ({ id={zoomOutId} data-test-subj="timeWindowButtonsZoomOut" label={zoomOutLabel} - title="" toolTipContent={!isDisabled && zoomOutTooltipContent} toolTipProps={{ disableScreenReaderOutput: zoomOutLabel === zoomOutTooltipContent, @@ -211,7 +208,6 @@ export const EuiTimeWindowButtons: React.FC = ({ id={nextId} data-test-subj="timeWindowButtonsNext" label={nextLabel} - title="" toolTipContent={!isDisabled && nextTooltipContent} color={buttonColor} size={buttonSize} From c5e0a7f7ffc4dc37a2502e6dacab741ff002237a Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Mon, 18 May 2026 21:44:55 +0200 Subject: [PATCH 11/19] feat(eui): replace title with EuiToolTip in EuiDataGrid --- .../__snapshots__/data_grid.test.tsx.snap | 50 ++++++++++--------- .../body/cell/data_grid_cell_actions.test.tsx | 1 - .../body/cell/data_grid_cell_actions.tsx | 30 ++++++----- .../display_selector.test.tsx.snap | 2 +- .../keyboard_shortcuts.test.tsx.snap | 4 +- .../column_sorting_draggable.test.tsx | 20 +++++--- .../controls/data_grid_toolbar.stories.tsx | 2 +- .../datagrid/controls/display_selector.tsx | 2 +- .../datagrid/controls/keyboard_shortcuts.tsx | 2 +- .../components/datagrid/data_grid.test.tsx | 6 ++- .../components/tool_tip/tool_tip.styles.ts | 3 ++ .../eui/src/components/tool_tip/tool_tip.tsx | 2 +- 12 files changed, 71 insertions(+), 53 deletions(-) diff --git a/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index 509b86328f8f..9786fa4e15fb 100644 --- a/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -47,22 +47,26 @@ exports[`EuiDataGrid pagination renders 1`] = ` > Page 2 of 2 - - + + +
      @@ -654,7 +658,7 @@ exports[`EuiDataGrid rendering renders additional toolbar controls 1`] = ` > - + + - +
      -
      -
      - -
      - + aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="magnify" + /> +
      +
      -
      +
      Date: Tue, 26 May 2026 12:08:09 +0200 Subject: [PATCH 19/19] fix(eui): address PR feedback --- packages/eui/src/components/avatar/avatar.tsx | 59 ++--- .../__snapshots__/breadcrumb.test.tsx.snap | 2 +- .../__snapshots__/breadcrumbs.test.tsx.snap | 12 +- .../breadcrumbs/_breadcrumb_content.test.tsx | 33 +++ .../breadcrumbs/_breadcrumb_content.tsx | 63 +++--- .../combo_box_options_list.tsx | 4 +- .../__snapshots__/comment.test.tsx.snap | 125 +++++------ .../__snapshots__/comment_list.test.tsx.snap | 100 ++++----- .../comment_timeline.test.tsx.snap | 50 ++--- .../super_date_picker.test.tsx.snap | 4 +- .../date_popover_button.styles.ts | 4 + .../date_popover/date_popover_button.tsx | 8 +- .../__snapshots__/search_bar.test.tsx.snap | 210 ++++++++++-------- .../field_value_selection_filter.spec.tsx | 36 +-- .../src/components/search_bar/search_bar.tsx | 58 +++-- .../text_truncate/text_truncate.spec.tsx | 13 +- .../text_truncate/text_truncate.tsx | 1 - .../__snapshots__/timeline.test.tsx.snap | 100 ++++----- .../__snapshots__/timeline_item.test.tsx.snap | 75 +++---- 19 files changed, 458 insertions(+), 499 deletions(-) diff --git a/packages/eui/src/components/avatar/avatar.tsx b/packages/eui/src/components/avatar/avatar.tsx index dad4c548066d..c2f1386b55a4 100644 --- a/packages/eui/src/components/avatar/avatar.tsx +++ b/packages/eui/src/components/avatar/avatar.tsx @@ -202,35 +202,40 @@ export const EuiAvatar: FunctionComponent = ({ return avatarStyle?.color; }, [iconColor, avatarStyle?.color, isForcedColors, euiTheme]); - return ( + const avatarNode = ( +
      + {!imageUrl && + (iconType ? ( + + ) : ( + + ))} +
      + ); + + // `EuiAvatar` is not interactive so we don't need to add a `tabIndex`. + // It already has `aria-label`, the tooltip is only visual. + return name ? ( - {/* `EuiAvatar` is not interactive so we don't need to add a `tabIndex`. - It already has `aria-label`, the tooltip is only visual. */} - {/* eslint-disable-next-line @elastic/eui/tooltip-focusable-anchor */} -
      - {!imageUrl && - (iconType ? ( - - ) : ( - - ))} -
      + {avatarNode}
      + ) : ( + avatarNode ); }; diff --git a/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap b/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap index 66e24901ef3b..362f4aea29fc 100644 --- a/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +++ b/packages/eui/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap @@ -21,7 +21,7 @@ exports[`EuiBreadcrumbCollapsed renders a ... breadcrumb with collapsed content >
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +
      `; exports[`EuiCommentTimeline props timelineAvatar is rendered with a ReactNode 1`] = ` @@ -39,22 +34,17 @@ exports[`EuiCommentTimeline props timelineAvatar is rendered with a ReactNode 1` `; exports[`EuiCommentTimeline props timelineAvatar is rendered with a string 1`] = ` - - - +
      { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .eq(2) - .should('contain.text', 'Bug'); + .should('contain.text', 'bug'); }); it('allows all configurations', () => { @@ -178,11 +178,11 @@ describe('FieldValueSelectionFilter', () => { 'true' ); // Popover should still be open when multiselect is true/or - cy.contains('li[role="option"]', 'Bug') + cy.contains('li[role="option"]', 'bug') .should('have.attr', 'aria-checked', 'false') .click(); cy.get('.euiNotificationBadge').should('have.text', '2'); - cy.contains('li[role="option"]', 'Bug').should( + cy.contains('li[role="option"]', 'bug').should( 'have.attr', 'aria-checked', 'true' @@ -205,12 +205,12 @@ describe('FieldValueSelectionFilter', () => { // Multiselect false should close the popover, so we need to re-open it cy.get('button').click(); - cy.contains('li[role="option"]', 'Bug') + cy.contains('li[role="option"]', 'bug') .should('have.attr', 'aria-selected', 'false') .click(); // Filter count should have remained at 1 cy.get('.euiNotificationBadge').should('have.text', '1'); - cy.contains('li[role="option"]', 'Bug').should( + cy.contains('li[role="option"]', 'bug').should( 'have.attr', 'aria-selected', 'true' @@ -316,13 +316,15 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); getOptions().should('have.length', 3); - getOptions().last().should('contain.text', 'Bug').click(); + getOptions().last().should('contain.text', 'bug').click(); // Should have moved to the top of the list and retained active focus getOptions() .first() - .should('contain.text', 'Bug') - .should('have.attr', 'aria-checked', 'true') - .should('have.attr', 'aria-selected', 'true'); + .should('contain.text', 'bug') + .should('have.attr', 'aria-checked', 'true'); + cy.get('ul[role="listbox"]') + .should('have.attr', 'aria-activedescendant') + .should('include', 'option-0'); }); it('does not sort selected options to the top when set to false', () => { @@ -330,12 +332,14 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); getOptions().should('have.length', 3); - getOptions().last().should('contain.text', 'Bug').click(); + getOptions().last().should('contain.text', 'bug').click(); getOptions() .last() - .should('contain.text', 'Bug') - .should('have.attr', 'aria-checked', 'true') - .should('have.attr', 'aria-selected', 'true'); + .should('contain.text', 'bug') + .should('have.attr', 'aria-checked', 'true'); + cy.get('ul[role="listbox"]') + .should('have.attr', 'aria-activedescendant') + .should('include', 'option-2'); }); }); @@ -379,7 +383,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('.euiNotificationBadge').should('not.be.undefined'); cy.get('[data-test-subj="euiSelectableList"] li') .first() - .should('contain.text', 'Bug'); + .should('contain.text', 'bug'); }); it('has inactive filters, fields in options', () => { @@ -415,7 +419,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('button').click(); cy.get('[data-test-subj="euiSelectableList"] li') .eq(2) - .should('contain.text', 'Bug'); + .should('contain.text', 'bug'); }); it('has active filters, fields in options', () => { @@ -452,7 +456,7 @@ describe('FieldValueSelectionFilter', () => { cy.get('.euiNotificationBadge').should('not.be.undefined'); cy.get('[data-test-subj="euiSelectableList"] li') .eq(0) - .should('contain.text', 'Bug'); + .should('contain.text', 'bug'); }); it('caches options if options is a function and config.cache is set', () => { diff --git a/packages/eui/src/components/search_bar/search_bar.tsx b/packages/eui/src/components/search_bar/search_bar.tsx index 83aa83cb3ba8..f5736499a4bc 100644 --- a/packages/eui/src/components/search_bar/search_bar.tsx +++ b/packages/eui/src/components/search_bar/search_bar.tsx @@ -283,6 +283,28 @@ export class EuiSearchBar extends Component { const isHintVisible = hint?.popoverProps?.isOpen ?? isHintVisibleState; + const searchBox = ( + { + this.setState({ isHintVisible: isVisible }); + }, + id: this.hintId, + ...hint, + } + : undefined + } + /> + ); + return ( {(euiTheme) => ( @@ -293,39 +315,9 @@ export class EuiSearchBar extends Component { css={euiSearchBar__searchHolder(euiTheme)} grow={true} > - {(() => { - const searchBox = ( - { - this.setState({ isHintVisible: isVisible }); - }, - id: this.hintId, - ...hint, - } - : undefined - } - /> - ); - - return error ? ( - - {searchBox} - - ) : ( - searchBox - ); - })()} + + {searchBox} + {filters && ( { it('renders truncated text and a tooltip when truncation is needed', () => { cy.mount(); - cy.get('#text').should('have.attr', 'tabindex', '0'); cy.get('#text').parent().should('have.class', 'euiToolTipAnchor'); cy.get('#text [data-test-subj="fullText"]').should('have.text', props.text); getTruncatedText().should('exist'); @@ -297,12 +296,14 @@ describe('EuiTextTruncate', () => { getTruncatedText('#text1').should('have.text', ''); getTruncatedText('#text2').should('have.text', ''); - cy.get('@spyConsoleError') - .should( - 'be.calledWith', + // The error should be logged at least once per component. The wrapping + // `EuiToolTip` can cause extra renders so we don't assert an exact count. + cy.get('@spyConsoleError').should((spy: any) => { + expect(spy).to.have.been.calledWith( 'The truncation ellipsis is larger than the available width. No text can be rendered.' - ) - .should('be.calledTwice'); + ); + expect(spy.callCount).to.be.at.least(2); + }); }); }); diff --git a/packages/eui/src/components/text_truncate/text_truncate.tsx b/packages/eui/src/components/text_truncate/text_truncate.tsx index bc9b0e98a252..3f8a59c5ea49 100644 --- a/packages/eui/src/components/text_truncate/text_truncate.tsx +++ b/packages/eui/src/components/text_truncate/text_truncate.tsx @@ -219,7 +219,6 @@ const EuiTextTruncateWithWidth: FunctionComponent< const content = (
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +
      - - - +