diff --git a/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiTable_EuiTableHeader_EuiTableHeaderCell_Tooltip.png b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiTable_EuiTableHeader_EuiTableHeaderCell_Tooltip.png new file mode 100644 index 00000000000..4c567086527 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiTable_EuiTableHeader_EuiTableHeaderCell_Tooltip.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiTable_EuiTableHeader_EuiTableHeaderCell_Tooltip.png b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiTable_EuiTableHeader_EuiTableHeaderCell_Tooltip.png new file mode 100644 index 00000000000..c0790fc000a Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiTable_EuiTableHeader_EuiTableHeaderCell_Tooltip.png differ diff --git a/packages/eui/changelogs/upcoming/8273.md b/packages/eui/changelogs/upcoming/8273.md new file mode 100644 index 00000000000..2ada4de6e52 --- /dev/null +++ b/packages/eui/changelogs/upcoming/8273.md @@ -0,0 +1,3 @@ +- Updated table components to support adding tooltips to header cells + - Added `columns.nameTooltip` on `EuiBasicTable` + - Added `tooltipProps` prop on `EuiTableHeaderCell` diff --git a/packages/eui/i18ntokens.json b/packages/eui/i18ntokens.json index 3781ade598b..5568e088cee 100644 --- a/packages/eui/i18ntokens.json +++ b/packages/eui/i18ntokens.json @@ -7181,4 +7181,4 @@ }, "filepath": "src/components/tree_view/tree_view.tsx" } -] \ No newline at end of file +] diff --git a/packages/eui/src-docs/src/views/tables/in_memory/in_memory.tsx b/packages/eui/src-docs/src/views/tables/in_memory/in_memory.tsx index c1b7a6ae175..9d7b60fca93 100644 --- a/packages/eui/src-docs/src/views/tables/in_memory/in_memory.tsx +++ b/packages/eui/src-docs/src/views/tables/in_memory/in_memory.tsx @@ -85,6 +85,9 @@ const columns: Array> = [ { field: 'location', name: 'Location', + nameTooltip: { + content: 'The city and country in which this person resides', + }, truncateText: true, textOnly: true, render: (location: User['location']) => { diff --git a/packages/eui/src-docs/src/views/tables/sorting/sorting.tsx b/packages/eui/src-docs/src/views/tables/sorting/sorting.tsx index ac67d9a9ecf..30239490a11 100644 --- a/packages/eui/src-docs/src/views/tables/sorting/sorting.tsx +++ b/packages/eui/src-docs/src/views/tables/sorting/sorting.tsx @@ -8,9 +8,7 @@ import { EuiTableSortingType, Criteria, EuiHealth, - EuiIcon, EuiLink, - EuiToolTip, EuiFlexGroup, EuiFlexItem, EuiSwitch, @@ -76,19 +74,10 @@ const columns: Array> = [ }, { field: 'github', - name: ( - - <> - Github{' '} - - - - ), + name: 'GibHub', + nameTooltip: { + content: 'Their mascot is the Octokitty', + }, render: (username: User['github']) => ( {username} @@ -97,37 +86,19 @@ const columns: Array> = [ }, { field: 'dateOfBirth', - name: ( - - <> - Date of Birth{' '} - - - - ), + name: 'Date of Birth', + nameTooltip: { + content: "Colloquially known as a 'birthday'", + }, render: (dateOfBirth: User['dateOfBirth']) => formatDate(dateOfBirth, 'dobLong'), }, { field: 'location', - name: ( - - <> - Nationality{' '} - - - - ), + name: 'Nationality', + nameTooltip: { + content: 'The city and country in which this person resides', + }, render: (location: User['location']) => { return `${location.city}, ${location.country}`; }, @@ -136,19 +107,10 @@ const columns: Array> = [ }, { field: 'online', - name: ( - - <> - Online{' '} - - - - ), + name: 'Online', + nameTooltip: { + content: 'Free to talk or busy with business', + }, render: (online: User['online']) => { const color = online ? 'success' : 'danger'; const label = online ? 'Online' : 'Offline'; diff --git a/packages/eui/src/components/basic_table/basic_table.stories.tsx b/packages/eui/src/components/basic_table/basic_table.stories.tsx index b7f1929f78d..fd614305d7e 100644 --- a/packages/eui/src/components/basic_table/basic_table.stories.tsx +++ b/packages/eui/src/components/basic_table/basic_table.stories.tsx @@ -114,6 +114,9 @@ const columns: Array> = [ { field: 'location', name: 'Location', + nameTooltip: { + content: 'The city and country in which this person resides', + }, truncateText: true, textOnly: true, render: (location: User['location']) => { diff --git a/packages/eui/src/components/basic_table/basic_table.tsx b/packages/eui/src/components/basic_table/basic_table.tsx index 3a3edf816b2..ee91e22059f 100644 --- a/packages/eui/src/components/basic_table/basic_table.tsx +++ b/packages/eui/src/components/basic_table/basic_table.tsx @@ -753,6 +753,7 @@ export class EuiBasicTable extends Component< field, width, name, + nameTooltip, align, dataType, sortable, @@ -765,6 +766,7 @@ export class EuiBasicTable extends Component< const sharedProps = { width, + tooltipProps: nameTooltip, description, mobileOptions, align: columnAlign, @@ -1280,6 +1282,7 @@ export class EuiBasicTable extends Component< sortable, footer, mobileOptions, + nameTooltip, ...rest } = column as EuiTableFieldDataColumnType; const columnAlign = align || this.getAlignForDataType(dataType); diff --git a/packages/eui/src/components/basic_table/in_memory_table.stories.tsx b/packages/eui/src/components/basic_table/in_memory_table.stories.tsx index 53cd1889839..b4794b9bb5d 100644 --- a/packages/eui/src/components/basic_table/in_memory_table.stories.tsx +++ b/packages/eui/src/components/basic_table/in_memory_table.stories.tsx @@ -102,6 +102,9 @@ const columns: Array> = [ { field: 'location', name: 'Location', + nameTooltip: { + content: 'The city and country in which this person resides', + }, truncateText: true, textOnly: true, render: (location: User['location']) => { diff --git a/packages/eui/src/components/basic_table/table_types.ts b/packages/eui/src/components/basic_table/table_types.ts index 3641f271c94..bd6418f9e80 100644 --- a/packages/eui/src/components/basic_table/table_types.ts +++ b/packages/eui/src/components/basic_table/table_types.ts @@ -12,10 +12,12 @@ import { Pagination } from './pagination_bar'; import { Action } from './action_types'; import { Primitive } from '../../services/sort/comparators'; import { CommonProps } from '../common'; +import { IconType } from '../icon'; import { EuiTableRowCellProps, EuiTableRowCellMobileOptionsShape, } from '../table/table_row_cell'; +import { EuiToolTipProps, EuiIconTipProps } from '../tool_tip'; export type ItemId = string | number | ((item: T) => string); export type ItemIdResolved = string | number; @@ -31,6 +33,24 @@ export interface EuiTableFooterProps { items: T[]; pagination?: Pagination; } + +export type EuiTableColumnNameTooltipProps = { + /** The main content of the tooltip */ + content: ReactNode; + /** + * The icon type to display + * @default 'questionInCircle' + */ + icon?: IconType; + /** Additional props for EuiIcon */ + iconProps?: EuiIconTipProps['iconProps']; + /** Additional props for the EuiToolip */ + tooltipProps?: Omit & { + delay?: EuiToolTipProps['delay']; + position?: EuiToolTipProps['position']; + }; +}; + export interface EuiTableFieldDataColumnType extends CommonProps, Omit, 'width' | 'align'> { @@ -44,6 +64,10 @@ export interface EuiTableFieldDataColumnType * The display name of the column */ name: ReactNode; + /** + * Allows adding an icon with a tooltip displayed next to the name + */ + nameTooltip?: EuiTableColumnNameTooltipProps; /** * A description of the column (will be presented as a title over the column header) */ @@ -118,6 +142,10 @@ export type EuiTableComputedColumnType = CommonProps & * The display name of the column */ name?: ReactNode; + /** + * Allows configuring an icon with a tooltip, to be displayed next to the name + */ + nameTooltip?: EuiTableColumnNameTooltipProps; /** * If provided, allows this column to be sorted on. Must return the value to sort against. */ @@ -141,6 +169,10 @@ export type EuiTableActionsColumnType = { * The display name of the column */ name?: ReactNode; + /** + * Allows configuring an icon with a tooltip, to be displayed next to the name + */ + nameTooltip?: EuiTableColumnNameTooltipProps; } & Pick, 'description' | 'width'>; export interface EuiTableSortingType { diff --git a/packages/eui/src/components/table/table_header_cell.stories.tsx b/packages/eui/src/components/table/table_header_cell.stories.tsx index bf240ef7564..7994c0fd2b9 100644 --- a/packages/eui/src/components/table/table_header_cell.stories.tsx +++ b/packages/eui/src/components/table/table_header_cell.stories.tsx @@ -66,3 +66,19 @@ export const Playground: Story = { onSort: false, }, }; + +export const Tooltip: Story = { + parameters: { + controls: { + include: ['tooltipProps'], + }, + }, + args: { + children: 'Header cell content', + // @ts-ignore - overwrite meta default to align with base behavior + onSort: false, + tooltipProps: { + content: 'tooltip content', + }, + }, +}; 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 2d2ffbe78aa..ff8b3ee5dec 100644 --- a/packages/eui/src/components/table/table_header_cell.test.tsx +++ b/packages/eui/src/components/table/table_header_cell.test.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { requiredProps } from '../../test/required_props'; -import { render } from '../../test/rtl'; +import { render, waitForEuiToolTipVisible } from '../../test/rtl'; +import { fireEvent } from '@testing-library/react'; import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '../../services'; import { WARNING_MESSAGE } from './utils'; @@ -185,4 +186,57 @@ describe('EuiTableHeaderCell', () => { expect(container.firstChild).toMatchSnapshot(); }); }); + + describe('tooltip', () => { + it('renders an icon with tooltip', async () => { + const { getByTestSubject } = renderInTableHeader( + + Test + + ); + + expect(getByTestSubject('icon')).toHaveAttribute( + 'data-euiicon-type', + 'iInCircle' + ); + + fireEvent.focus(getByTestSubject('icon')); + await waitForEuiToolTipVisible(); + + expect(getByTestSubject('tooltip')).toHaveTextContent( + 'This is the content of the tooltip' + ); + }); + + it('renders the icon next to the text', () => { + const { getByTestSubject, queryByText } = renderInTableHeader( + {}} + > + Test + + ); + + expect(queryByText('Test')?.nextSibling).toEqual( + getByTestSubject('icon').parentElement + ); + }); + }); }); diff --git a/packages/eui/src/components/table/table_header_cell.tsx b/packages/eui/src/components/table/table_header_cell.tsx index 5c2e2a520d6..836b5c8efd0 100644 --- a/packages/eui/src/components/table/table_header_cell.tsx +++ b/packages/eui/src/components/table/table_header_cell.tsx @@ -24,8 +24,10 @@ import { EuiScreenReaderOnly } from '../accessibility'; import { CommonProps, NoArgCallback } from '../common'; import { EuiIcon } from '../icon'; import { EuiInnerText } from '../inner_text'; +import { EuiIconTip } from '../tool_tip'; import type { EuiTableRowCellMobileOptionsShape } from './table_row_cell'; +import type { EuiTableColumnNameTooltipProps } from '../basic_table/table_types'; import { resolveWidthAsStyle } from './utils'; import { useEuiTableIsResponsive } from './mobile/responsive_context'; import { EuiTableCellContent } from './_table_cell_content'; @@ -43,6 +45,8 @@ export type EuiTableHeaderCellProps = CommonProps & onSort?: NoArgCallback; scope?: TableHeaderCellScope; width?: string | number; + /** Allows adding an icon with a tooltip displayed next to the name */ + tooltipProps?: EuiTableColumnNameTooltipProps; description?: string; /** * Shows the sort indicator but removes the button @@ -59,6 +63,7 @@ export type EuiTableHeaderCellProps = CommonProps & const CellContents = ({ className, align, + tooltipProps, description, children, canSort, @@ -67,6 +72,7 @@ const CellContents = ({ }: { className?: string; align: HorizontalAlignment; + tooltipProps?: EuiTableColumnNameTooltipProps; description: EuiTableHeaderCellProps['description']; children: EuiTableHeaderCellProps['children']; canSort?: boolean; @@ -104,6 +110,17 @@ const CellContents = ({ {description} )} + {tooltipProps && ( + + )} {isSorted ? ( = ({ width, style, readOnly, + tooltipProps, description, append, ...rest @@ -162,6 +180,7 @@ export const EuiTableHeaderCell: FunctionComponent = ({ const cellContentsProps = { css: styles.euiTableHeaderCell__content, align, + tooltipProps, description, canSort, isSorted, diff --git a/packages/eui/src/components/tool_tip/icon_tip.stories.tsx b/packages/eui/src/components/tool_tip/icon_tip.stories.tsx index 2aa0621f0c5..0432370749c 100644 --- a/packages/eui/src/components/tool_tip/icon_tip.stories.tsx +++ b/packages/eui/src/components/tool_tip/icon_tip.stories.tsx @@ -15,7 +15,7 @@ import { } from '../../../.storybook/utils'; import { LOKI_SELECTORS } from '../../../.storybook/loki'; import { EuiFlexGroup } from '../flex'; -import { EuiIconTip, Props as EuiIconTipProps } from './icon_tip'; +import { EuiIconTip, EuiIconTipProps } from './icon_tip'; const meta: Meta = { title: 'Display/EuiIconTip', diff --git a/packages/eui/src/components/tool_tip/icon_tip.tsx b/packages/eui/src/components/tool_tip/icon_tip.tsx index a9e52fbff23..7d00fc10841 100644 --- a/packages/eui/src/components/tool_tip/icon_tip.tsx +++ b/packages/eui/src/components/tool_tip/icon_tip.tsx @@ -13,7 +13,10 @@ import { useEuiI18n } from '../i18n'; import { EuiIcon, IconSize, IconType } from '../icon'; import { EuiToolTip, EuiToolTipProps } from './tool_tip'; -export interface EuiIconTipProps { +export type EuiIconTipProps = Omit< + EuiToolTipProps, + 'children' | 'delay' | 'position' +> & { /** * Children are not allowed as they are built using the icon props */ @@ -34,7 +37,6 @@ export interface EuiIconTipProps { * Explain what this icon means for screen readers. */ 'aria-label'?: string; - /** * Pass certain props down to `EuiIcon` */ @@ -42,17 +44,13 @@ export interface EuiIconTipProps { // iconProps; however, due to TS's bivariant function arguments `type` could be // passed without any error/feedback so we explicitly set it to `never` type iconProps?: Omit, 'type'> & { type?: never }; -} - -export type Props = Omit & - EuiIconTipProps & { - // This are copied from EuiToolTipProps, but made optional. Defaults - // are applied below. - delay?: EuiToolTipProps['delay']; - position?: EuiToolTipProps['position']; - }; + // This are copied from EuiToolTipProps, but made optional. Defaults + // are applied below. + delay?: EuiToolTipProps['delay']; + position?: EuiToolTipProps['position']; +}; -export const EuiIconTip: FunctionComponent = ({ +export const EuiIconTip: FunctionComponent = ({ type = 'questionInCircle', 'aria-label': ariaLabel, color, diff --git a/packages/website/docs/components/tabular_content/in_memory_tables.mdx b/packages/website/docs/components/tabular_content/in_memory_tables.mdx index 7cff9b8c7dd..51cd4bc89c1 100644 --- a/packages/website/docs/components/tabular_content/in_memory_tables.mdx +++ b/packages/website/docs/components/tabular_content/in_memory_tables.mdx @@ -85,6 +85,9 @@ const columns: Array> = [ { field: 'github', name: 'Github', + nameTooltip: { + content: 'Their mascot is the Octokitty', + }, render: (username: User['github']) => ( {username} @@ -94,6 +97,9 @@ const columns: Array> = [ { field: 'dateOfBirth', name: 'Date of Birth', + nameTooltip: { + content: "Colloquially known as a 'birthday'", + }, dataType: 'date', render: (dateOfBirth: User['dateOfBirth']) => formatDate(dateOfBirth, 'dobLong'), @@ -102,6 +108,9 @@ const columns: Array> = [ { field: 'location', name: 'Location', + nameTooltip: { + content: 'The city and country in which this person resides', + }, truncateText: true, textOnly: true, render: (location: User['location']) => { @@ -111,6 +120,9 @@ const columns: Array> = [ { field: 'online', name: 'Online', + nameTooltip: { + content: 'Free to talk or busy with business', + }, dataType: 'boolean', render: (online: User['online']) => { const color = online ? 'success' : 'danger'; @@ -135,6 +147,7 @@ export default () => { return ( { { return ( { { return ( { return ( { direction: sortDirection, }, }; - + return ( <> @@ -262,6 +262,7 @@ export default () => { tableCaption="Demo for an EuiBasicTable with ${ isControlled ? 'controlled' : 'uncontrolled' } selection" + responsiveBreakpoint={false} items={pageOfItems} itemId="id" rowHeader="firstName" @@ -434,12 +435,13 @@ export default () => { initialPageSize: 5, pageSizeOptions: [3, 5, 8], }; - + return ( { return ( { > = [ }, { field: 'github', - name: ( - - <> - Github{' '} - - - - ), + name: 'Github', + nameTooltip: { + content: 'Their mascot is the Octokitty', + }, render: (username: User['github']) => ( {username} @@ -472,37 +465,19 @@ const columns: Array> = [ }, { field: 'dateOfBirth', - name: ( - - <> - Date of Birth{' '} - - - - ), + name: 'Date of Birth', + nameTooltip: { + content: "Colloquially known as a 'birthday'", + }, render: (dateOfBirth: User['dateOfBirth']) => formatDate(dateOfBirth, 'dobLong'), }, { field: 'location', - name: ( - - <> - Nationality{' '} - - - - ), + name: 'Nationality', + nameTooltip: { + content: 'The city and country in which this person resides', + }, render: (location: User['location']) => { return `${location.city}, ${location.country}`; }, @@ -511,19 +486,10 @@ const columns: Array> = [ }, { field: 'online', - name: ( - - <> - Online{' '} - - - - ), + name: 'Online', + nameTooltip: { + content: 'Free to talk or busy with business', + }, render: (online: User['online']) => { const color = online ? 'success' : 'danger'; const label = online ? 'Online' : 'Offline'; @@ -637,6 +603,7 @@ export default () => { { return ( { return ( { {