diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ef3bb098b..255ec649d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `37.3.1`. +- Updated `EuiToolTip` to remain showing tooltip while child element is in focus ([#5066](https://github.com/elastic/eui/pull/5066)) +- Removed `children` from TypeScript definition in `EuiIconTip` ([#5066](https://github.com/elastic/eui/pull/5066)) + +**Bug fixes** + +- Fixed location of default value of `EuiToolTip`'s `display` prop ([#5066](https://github.com/elastic/eui/pull/5066)) ## [`37.3.1`](https://github.com/elastic/eui/tree/v37.3.1) diff --git a/src-docs/src/views/tool_tip/playground.js b/src-docs/src/views/tool_tip/playground.js index 4d8d12f3386..17a6fba38d9 100644 --- a/src-docs/src/views/tool_tip/playground.js +++ b/src-docs/src/views/tool_tip/playground.js @@ -1,12 +1,13 @@ import { PropTypes } from 'react-view'; -import { EuiToolTip } from '../../../../src/components/'; +import { EuiToolTip, EuiIconTip } from '../../../../src/components'; import { propUtilityForPlayground, dummyFunction, simulateFunction, + iconValidator, } from '../../services/playground'; -export default () => { +export const toolTipConfig = () => { const docgenInfo = Array.isArray(EuiToolTip.__docgenInfo) ? EuiToolTip.__docgenInfo[0] : EuiToolTip.__docgenInfo; @@ -51,3 +52,44 @@ export default () => { }, }; }; + +export const iconTipConfig = () => { + const docgenInfo = Array.isArray(EuiIconTip.__docgenInfo) + ? EuiIconTip.__docgenInfo[0] + : EuiIconTip.__docgenInfo; + const propsToUse = propUtilityForPlayground(docgenInfo.props); + + propsToUse.type = iconValidator(propsToUse.type); + + propsToUse.title = { + ...propsToUse.title, + type: PropTypes.String, + value: 'Title', + }; + + propsToUse.content = { + ...propsToUse.content, + type: PropTypes.String, + value: 'Content', + }; + + propsToUse.onMouseOut = simulateFunction(propsToUse.onMouseOut); + + return { + config: { + componentName: 'EuiIconTip', + props: propsToUse, + scope: { + EuiIconTip, + }, + imports: { + '@elastic/eui': { + named: ['EuiIconTip'], + }, + }, + customProps: { + onMouseOut: dummyFunction, + }, + }, + }; +}; diff --git a/src-docs/src/views/tool_tip/tool_tip.js b/src-docs/src/views/tool_tip/tool_tip.js index cc8e4017890..87d8655027e 100644 --- a/src-docs/src/views/tool_tip/tool_tip.js +++ b/src-docs/src/views/tool_tip/tool_tip.js @@ -5,9 +5,7 @@ import { EuiToolTip, EuiLink, EuiText, - EuiFieldText, - EuiSpacer, - EuiButton, + EuiCode, } from '../../../../src/components'; export default () => ( @@ -18,6 +16,7 @@ export default () => ( top + .

@@ -37,14 +36,16 @@ export default () => ( right + .

- This tooltip has a long delay because it might be in a repeatable - component{' '} + This tooltip has a long delay because it might be in + a repeatable component{' '} wink + .

@@ -54,41 +55,5 @@ export default () => (

- - - -

- - - I am a block level tooltip, applied to a button with fullWidth - - -

- - - - - - - - - - - Works on any kind of element — buttons, inputs, you name it! -

- } - > - {}}>Hover me -
); diff --git a/src-docs/src/views/tool_tip/tool_tip_components.js b/src-docs/src/views/tool_tip/tool_tip_components.js new file mode 100644 index 00000000000..2b1d495e4ff --- /dev/null +++ b/src-docs/src/views/tool_tip/tool_tip_components.js @@ -0,0 +1,47 @@ +import React from 'react'; + +import { + EuiToolTip, + EuiFieldText, + EuiSpacer, + EuiButton, +} from '../../../../src/components'; + +export default () => ( +
+ + Works on any kind of element — buttons, inputs, you name it! +

+ } + > + {}}>Hover me +
+ + + + + + I am a block level tooltip, applied to a button with fullWidth + + + + + + + + +
+); diff --git a/src-docs/src/views/tool_tip/tool_tip_example.js b/src-docs/src/views/tool_tip/tool_tip_example.js index 2aba2b64ebd..5bf9c28d8e9 100644 --- a/src-docs/src/views/tool_tip/tool_tip_example.js +++ b/src-docs/src/views/tool_tip/tool_tip_example.js @@ -1,8 +1,6 @@ import React, { Fragment } from 'react'; import { Link } from 'react-router-dom'; -import { renderToHtml } from '../../services'; - import { GuideSectionTypes } from '../../components'; import { @@ -10,22 +8,24 @@ import { EuiCode, EuiToolTip, EuiIconTip, - EuiSpacer, - EuiTitle, + EuiText, } from '../../../../src/components'; -import toolTipConfig from './playground'; +import { toolTipConfig, iconTipConfig } from './playground'; import ToolTip from './tool_tip'; const toolTipSource = require('!!raw-loader!./tool_tip'); -const toolTipHtml = renderToHtml(ToolTip); const tooltipSnippet = [ - ` + ` `, ` +`, + ` + + `, ` @@ -33,9 +33,11 @@ const tooltipSnippet = [ `, ]; +import ToolTipComponent from './tool_tip_components'; +const toolTipComponentSource = require('!!raw-loader!./tool_tip_components'); + import IconTip from './icon_tip'; const infoTipSource = require('!!raw-loader!./icon_tip'); -const infoTipHtml = renderToHtml(IconTip); const infoTipSnippet = ` +

- EuiToolTip wraps its children in a span element, so if you pass in a - block-level child (e.g. a div) the resulting DOM will be in violation of - the HTML5 spec. + Generally, tooltips should provide short, non-essential + , contextual information, usually naming or describing with more detail. + If you need interactive content or anything other than text, we + recommend using{' '} + + EuiPopover + {' '} + instead.

- + + + Putting anything other than plain text in a tooltip is lost on + screen readers. + + } + /> +
), sections: [ { @@ -60,29 +78,11 @@ export const ToolTipExample = { <>

Wrap EuiToolTip around any item that you need a - tooltip for. The position prop will take a - suggested position, but will change it if the tooltip gets too close - to the edge of the screen. -

- - -

- Applying tooltips to custom components -

-
- -

- Internally, EuiToolTip applies{' '} - onFocus, onBlur,{' '} - onMouseOver, and onMouseOut{' '} - props to whatever you pass as children. If you - pass in a custom component, then you’ll need to make sure - these props are applied to the root element rendered by your - component. The best way to do that is to follow{' '} - - EUI’s guidelines on pass-through props - - . + tooltip for and provide the content and + optionally the title. The{' '} + position prop will take a suggested position, but + will change it if the tooltip gets too close to the edge of the + screen.

} /> - - - - - Putting anything other than plain text in a tooltip is lost on - screen readers. Consider switching to{' '} - - EuiPopover - {' '} - if you need more content inside a tooltip. - - } - /> ), source: [ @@ -119,28 +102,52 @@ export const ToolTipExample = { type: GuideSectionTypes.JS, code: toolTipSource, }, - { - type: GuideSectionTypes.HTML, - code: toolTipHtml, - }, ], - props: { EuiToolTip }, snippet: tooltipSnippet, demo: , + playground: toolTipConfig, }, { - title: 'IconTip', + title: 'Wrapping components', + text: ( + <> +

+ EuiToolTip wraps its children in a{' '} + {''} element that is{' '} + {'display: inline-block'}. If you + are wrapping a block-level child (e.g. a{' '} + {'

'} + ), you may need to change this by passing{' '} + {'display="block"'} but the + resulting DOM may be in violation of the HTML5 spec. +

+

+ It also applies onFocus and{' '} + onBlur props the the cloned{' '} + children. If you pass in a custom component, then + you’ll need to make sure these props are applied to the root + element rendered by your component. The best way to do that is to + follow{' '} + + EUI’s guidelines on pass-through props + + . +

+ + ), source: [ { type: GuideSectionTypes.JS, - code: infoTipSource, - }, - { - type: GuideSectionTypes.HTML, - code: infoTipHtml, + code: toolTipComponentSource, }, ], + + props: { EuiToolTip }, + demo: , + }, + { + title: 'IconTip', text: (

@@ -157,10 +164,16 @@ export const ToolTipExample = {

), - props: { EuiToolTip, EuiIconTip }, + source: [ + { + type: GuideSectionTypes.JS, + code: infoTipSource, + }, + ], + props: { EuiIconTip }, snippet: infoTipSnippet, demo: , + playground: iconTipConfig, }, ], - playground: toolTipConfig, }; 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 324dcb42d81..99ecdfd1bef 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 @@ -4,6 +4,7 @@ exports[`DefaultItemAction render - button 1`] = ` diff --git a/src/components/copy/__snapshots__/copy.test.tsx.snap b/src/components/copy/__snapshots__/copy.test.tsx.snap index c5a84cdc51e..af7e787ae0a 100644 --- a/src/components/copy/__snapshots__/copy.test.tsx.snap +++ b/src/components/copy/__snapshots__/copy.test.tsx.snap @@ -6,6 +6,7 @@ exports[`EuiCopy is rendered 1`] = ` className="testClass1 testClass2" data-test-subj="test subject string" delay="regular" + display="inlineBlock" onMouseOut={[Function]} position="top" > diff --git a/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap b/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap index c5ba9a0aaaf..65c334623ab 100644 --- a/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap +++ b/src/components/date_picker/super_date_picker/__snapshots__/super_update_button.test.tsx.snap @@ -3,6 +3,7 @@ exports[`EuiSuperUpdateButton is rendered 1`] = ` } delay="regular" + display="inlineBlock" position="bottom" > } delay="regular" + display="inlineBlock" position="bottom" > { state: State = { visible: false, + hasFocus: false, calculatedPosition: this.props.position, toolTipStyles: DEFAULT_TOOLTIP_STYLES, arrowStyles: undefined, @@ -139,6 +140,7 @@ export class EuiToolTip extends Component { static defaultProps: Partial = { position: 'top', delay: 'regular', + display: 'inlineBlock', }; clearAnimationTimeout = () => { @@ -154,7 +156,6 @@ export class EuiToolTip extends Component { componentWillUnmount() { this.clearAnimationTimeout(); this._isMounted = false; - window.removeEventListener('mousemove', this.hasFocusMouseMoveListener); } componentDidUpdate(prevProps: EuiToolTipProps, prevState: State) { @@ -252,15 +253,18 @@ export class EuiToolTip extends Component { }); }; - hasFocusMouseMoveListener = () => { - this.hideToolTip(); - window.removeEventListener('mousemove', this.hasFocusMouseMoveListener); + onFocus = () => { + this.setState({ + hasFocus: true, + }); + this.showToolTip(); }; - onKeyUp = (event: KeyboardEvent) => { - if (event.key === keys.TAB) { - window.addEventListener('mousemove', this.hasFocusMouseMoveListener); - } + onBlur = () => { + this.setState({ + hasFocus: false, + }); + this.hideToolTip(); }; onMouseOut = (event: ReactMouseEvent) => { @@ -271,7 +275,9 @@ export class EuiToolTip extends Component { (this.anchor != null && !this.anchor.contains(event.relatedTarget as Node)) ) { - this.hideToolTip(); + if (!this.state.hasFocus) { + this.hideToolTip(); + } } if (this.props.onMouseOut) { @@ -287,7 +293,7 @@ export class EuiToolTip extends Component { content, title, delay, - display = 'inlineBlock', + display, ...rest } = this.props; @@ -335,9 +341,6 @@ export class EuiToolTip extends Component { className={anchorClasses} onMouseOver={this.showToolTip} onMouseOut={this.onMouseOut} - onKeyUp={(event) => { - this.onKeyUp(event); - }} > {/** * Re: jsx-a11y/mouse-events-have-key-events @@ -348,8 +351,8 @@ export class EuiToolTip extends Component { * element has focus. */} {cloneElement(children, { - onFocus: this.showToolTip, - onBlur: this.hideToolTip, + onFocus: this.onFocus, + onBlur: this.onBlur, ...(visible && { 'aria-describedby': this.state.id }), })}