diff --git a/CHANGELOG.md b/CHANGELOG.md index 84616e78661..c52426ad960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Converted `EuiFieldText` to Typescript ([#2688](https://github.com/elastic/eui/pull/2688)) - Added `nested` glyph to `EuiIcon` ([#2707](https://github.com/elastic/eui/pull/2707)) - Added `tableLayout` prop to `EuiTable`, `EuiBasicTable` and `EuiInMemoryTable` to provide the option of auto layout ([#2697](https://github.com/elastic/eui/pull/2697)) +- Converted `EuiSuggest` to Typescript ([#2692](https://github.com/elastic/eui/pull/2692)) - Converted `EuiErrorBoundary` to Typescript ([#2690](https://github.com/elastic/eui/pull/2690)) **Bug fixes** diff --git a/src/components/suggest/__snapshots__/suggest.test.js.snap b/src/components/suggest/__snapshots__/suggest.test.tsx.snap similarity index 100% rename from src/components/suggest/__snapshots__/suggest.test.js.snap rename to src/components/suggest/__snapshots__/suggest.test.tsx.snap diff --git a/src/components/suggest/__snapshots__/suggest_input.test.js.snap b/src/components/suggest/__snapshots__/suggest_input.test.tsx.snap similarity index 100% rename from src/components/suggest/__snapshots__/suggest_input.test.js.snap rename to src/components/suggest/__snapshots__/suggest_input.test.tsx.snap diff --git a/src/components/suggest/__snapshots__/suggest_item.test.js.snap b/src/components/suggest/__snapshots__/suggest_item.test.tsx.snap similarity index 100% rename from src/components/suggest/__snapshots__/suggest_item.test.js.snap rename to src/components/suggest/__snapshots__/suggest_item.test.tsx.snap diff --git a/src/components/suggest/index.js b/src/components/suggest/index.js deleted file mode 100644 index 2204a3cb211..00000000000 --- a/src/components/suggest/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { EuiSuggestInput } from './suggest_input'; - -export { EuiSuggestItem } from './suggest_item'; - -export { EuiSuggest } from './suggest'; diff --git a/src/components/suggest/index.ts b/src/components/suggest/index.ts new file mode 100644 index 00000000000..e3f48cdaccf --- /dev/null +++ b/src/components/suggest/index.ts @@ -0,0 +1,5 @@ +export { EuiSuggestInput, EuiSuggestInputProps } from './suggest_input'; + +export { EuiSuggestItem, EuiSuggestItemProps } from './suggest_item'; + +export { EuiSuggest, EuiSuggestProps } from './suggest'; diff --git a/src/components/suggest/suggest.js b/src/components/suggest/suggest.js deleted file mode 100644 index 0f657f08c6a..00000000000 --- a/src/components/suggest/suggest.js +++ /dev/null @@ -1,81 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { EuiSuggestItem } from './suggest_item'; -import { EuiSuggestInput } from './suggest_input'; - -export class EuiSuggest extends Component { - state = { - value: '', - status: 'unsaved', - }; - - getValue = val => { - this.setState({ - value: val, - }); - }; - - onChange = e => { - this.props.onInputChange(e.target.value); - }; - - render() { - const { - onItemClick, - onInputChange, - status, - append, - tooltipContent, - suggestions, - ...rest - } = this.props; - - const suggestionList = suggestions.map((item, index) => ( - onItemClick(item) : null} - description={item.description} - /> - )); - - const suggestInput = ( - - ); - return
{suggestInput}
; - } -} - -EuiSuggest.propTypes = { - className: PropTypes.string, - /** - * Status of the current query 'notYetSaved', 'saved', 'unchanged' or 'loading'. - */ - status: PropTypes.oneOf(['unsaved', 'saved', 'unchanged', 'loading']), - tooltipContent: PropTypes.string, - /** - * Element to be appended to the input bar (e.g. hashtag popover). - */ - append: PropTypes.node, - /** - * List of suggestions to display using 'suggestItem'. - */ - suggestions: PropTypes.array, - /** - * Handler for click on a suggestItem. - */ - onItemClick: PropTypes.func, - onInputChange: PropTypes.func, -}; - -EuiSuggestInput.defaultProps = { - status: 'unchanged', -}; diff --git a/src/components/suggest/suggest.test.js b/src/components/suggest/suggest.test.tsx similarity index 100% rename from src/components/suggest/suggest.test.js rename to src/components/suggest/suggest.test.tsx diff --git a/src/components/suggest/suggest.tsx b/src/components/suggest/suggest.tsx new file mode 100644 index 00000000000..25a2b31a636 --- /dev/null +++ b/src/components/suggest/suggest.tsx @@ -0,0 +1,63 @@ +import React, { FunctionComponent } from 'react'; +import { CommonProps } from '../common'; +import { EuiSuggestItem, EuiSuggestItemProps } from './suggest_item'; +import { EuiSuggestInput, EuiSuggestInputProps } from './suggest_input'; + +export type EuiSuggestProps = CommonProps & + EuiSuggestInputProps & { + /** + * List of suggestions to display using 'suggestItem'. + */ + suggestions: EuiSuggestItemProps[]; + + /** + * Handler for click on a suggestItem. + */ + onItemClick?: (item: EuiSuggestItemProps) => void; + + onInputChange?: (target: EventTarget) => void; + }; + +export const EuiSuggest: FunctionComponent = ( + props: EuiSuggestProps +) => { + const { + onItemClick, + onInputChange, + status, + append, + tooltipContent, + suggestions, + ...rest + } = props; + + const onChange = (e: React.FormEvent) => { + onInputChange ? onInputChange(e.target) : null; + }; + + const suggestionList = suggestions.map((item: EuiSuggestItemProps, index) => ( + onItemClick(item) : undefined} + description={item.description} + /> + )); + + const suggestInput = ( + + ); + + return
{suggestInput}
; +}; + +EuiSuggestInput.defaultProps = { + status: 'unchanged', +}; diff --git a/src/components/suggest/suggest_input.js b/src/components/suggest/suggest_input.js deleted file mode 100644 index 3fc461baaa3..00000000000 --- a/src/components/suggest/suggest_input.js +++ /dev/null @@ -1,134 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { EuiFilterButton } from '../filter_group'; -import { EuiFieldText } from '../form'; -import { EuiToolTip } from '../tool_tip'; -import { EuiIcon } from '../icon'; -import { EuiPopover, EuiInputPopover } from '../popover'; - -const statusMap = { - unsaved: { - icon: 'dot', - color: 'accent', - tooltip: 'Changes have not been saved.', - }, - saved: { - icon: 'checkInCircleFilled', - color: 'secondary', - tooltip: 'Saved.', - }, - unchanged: { - icon: '', - color: 'secondary', - }, -}; - -export class EuiSuggestInput extends Component { - state = { - value: '', - isPopoverOpen: false, - }; - - onFieldChange = e => { - this.setState({ - value: e.target.value, - isPopoverOpen: e.target.value !== '' ? true : false, - }); - this.props.sendValue(e.target.value); - }; - - closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; - - render() { - const { - className, - status, - append, - tooltipContent, - suggestions, - sendValue, - ...rest - } = this.props; - - let icon; - let color; - - if (statusMap[status]) { - icon = statusMap[status].icon; - color = statusMap[status].color; - } - const classes = classNames('euiSuggestInput', className); - - // EuiFieldText's append accepts an array of elements so start by creating an empty array - const appendArray = []; - - const statusElement = (status === 'saved' || status === 'unsaved') && ( - - - - ); - - // Push the status element to the array if it is not undefined - if (statusElement) appendArray.push(statusElement); - - // Check to see if consumer passed an append item and if so, add it to the array - if (append) appendArray.push(append); - - const customInput = ( - - ); - - return ( -
- -
{suggestions}
-
-
- ); - } -} - -EuiSuggestInput.propTypes = { - className: PropTypes.string, - /** - * Status of the current query 'unsaved', 'saved', 'unchanged' or 'loading'. - */ - status: PropTypes.oneOf(['unsaved', 'saved', 'unchanged', 'loading']), - tooltipContent: PropTypes.string, - /** - * Element to be appended to the input bar. - */ - append: PropTypes.node, - /** - * List of suggestions to display using 'suggestItem'. - */ - suggestions: PropTypes.array, -}; - -EuiSuggestInput.defaultProps = { - status: 'unchanged', -}; diff --git a/src/components/suggest/suggest_input.test.js b/src/components/suggest/suggest_input.test.tsx similarity index 100% rename from src/components/suggest/suggest_input.test.js rename to src/components/suggest/suggest_input.test.tsx diff --git a/src/components/suggest/suggest_input.tsx b/src/components/suggest/suggest_input.tsx new file mode 100644 index 00000000000..2cb5d2fd2a0 --- /dev/null +++ b/src/components/suggest/suggest_input.tsx @@ -0,0 +1,143 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useState, FunctionComponent } from 'react'; +import { CommonProps } from '../common'; +import classNames from 'classnames'; +// @ts-ignore +import { EuiFieldText } from '../form/field_text'; +import { EuiToolTip } from '../tool_tip'; +import { EuiIcon } from '../icon'; +import { EuiInputPopover } from '../popover'; +import { EuiSuggestItemProps } from './suggest_item'; + +export type EuiSuggestInputProps = CommonProps & { + tooltipContent?: string; + + /** + * Status of the current query 'unsaved', 'saved', 'unchanged' or 'loading'. + */ + status?: 'unsaved' | 'saved' | 'unchanged' | 'loading'; + + /** + * Element to be appended to the input bar. + */ + append?: JSX.Element; + + /** + * List of suggestions to display using 'suggestItem'. + */ + suggestions: JSX.Element[] | EuiSuggestItemProps[]; + + sendValue?: Function; +}; + +interface Status { + icon?: string; + color?: string; + tooltip?: string; +} + +interface StatusMap { + unsaved: Status; + saved: Status; + unchanged: Status; + loading: Status; +} + +const statusMap: StatusMap = { + unsaved: { + icon: 'dot', + color: 'accent', + tooltip: 'Changes have not been saved.', + }, + saved: { + icon: 'checkInCircleFilled', + color: 'secondary', + tooltip: 'Saved.', + }, + unchanged: { + icon: '', + color: 'secondary', + }, + loading: {}, +}; + +export const EuiSuggestInput: FunctionComponent< + EuiSuggestInputProps +> = props => { + const [value, setValue] = useState(''); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const { + className, + status = 'unchanged', + append, + tooltipContent, + suggestions, + sendValue, + ...rest + } = props; + + const onFieldChange = (e: any) => { + setValue(e.target.value); + setIsPopoverOpen(e.target.value !== '' ? true : false); + if (sendValue) sendValue(e.target.value); + }; + + const closePopover = () => { + setIsPopoverOpen(false); + }; + + let icon = ''; + let color = ''; + + if (statusMap[status]) { + icon = statusMap[status].icon || ''; + color = statusMap[status].color || ''; + } + const classes = classNames('euiSuggestInput', className); + + // EuiFieldText's append accepts an array of elements so start by creating an empty array + const appendArray = []; + + const statusElement = (status === 'saved' || status === 'unsaved') && ( + + + + ); + + // Push the status element to the array if it is not undefined + if (statusElement) appendArray.push(statusElement); + + // Check to see if consumer passed an append item and if so, add it to the array + if (append) appendArray.push(append); + + const customInput = ( + + ); + + return ( +
+ +
{suggestions}
+
+
+ ); +}; diff --git a/src/components/suggest/suggest_item.js b/src/components/suggest/suggest_item.js deleted file mode 100644 index 7b07559c936..00000000000 --- a/src/components/suggest/suggest_item.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { EuiIcon, IconPropType } from '../icon'; - -const colorToClassNameMap = { - tint0: 'euiSuggestItem__type--tint0', - tint1: 'euiSuggestItem__type--tint1', - tint2: 'euiSuggestItem__type--tint2', - tint3: 'euiSuggestItem__type--tint3', - tint4: 'euiSuggestItem__type--tint4', - tint5: 'euiSuggestItem__type--tint5', - tint6: 'euiSuggestItem__type--tint6', - tint7: 'euiSuggestItem__type--tint7', - tint8: 'euiSuggestItem__type--tint8', - tint9: 'euiSuggestItem__type--tint9', -}; - -export const COLORS = Object.keys(colorToClassNameMap); - -const labelDisplayToClassMap = { - fixed: 'euiSuggestItem__labelDisplay--fixed', - expand: 'euiSuggestItem__labelDisplay--expand', -}; - -export const DISPLAYS = Object.keys(labelDisplayToClassMap); - -export const EuiSuggestItem = ({ - className, - label, - type, - labelDisplay, - description, - onClick, - ...rest -}) => { - const classes = classNames( - 'euiSuggestItem', - { - 'euiSuggestItem-isClickable': onClick, - }, - className - ); - - let colorClass = ''; - - const labelDisplayClass = classNames( - 'euiSuggestItem__label', - labelDisplayToClassMap[labelDisplay], - { - 'euiSuggestItem__labelDisplay--expand': !description, - } - ); - - if (type && type.color) { - if (COLORS.indexOf(type.color) > -1) { - colorClass = colorToClassNameMap[type.color]; - } - } - - let OuterElement = 'div'; - if (onClick) { - OuterElement = 'button'; - } - - return ( - - - - - {label} - {description} - - ); -}; - -EuiSuggestItem.propTypes = { - className: PropTypes.string, - /** - * Takes 'iconType' for EuiIcon and 'color'. 'color' can be tint1 through tint9. - */ - type: PropTypes.shape({ - iconType: IconPropType.isRequired, - color: PropTypes.oneOfType([PropTypes.oneOf(COLORS), PropTypes.string]) - .isRequired, - }).isRequired, - /** - * Label or primary text. - */ - label: PropTypes.string.isRequired, - /** - * Description or secondary text (optional). - */ - description: PropTypes.string, - /** - * Label display is 'fixed' by default. Label will increase its width beyond 50% if needed with 'expand'. - */ - labelDisplay: PropTypes.oneOf(DISPLAYS), - /** - * Handler for click on a suggestItem. - */ - onClick: PropTypes.func, -}; - -EuiSuggestItem.defaultProps = { - labelDisplay: 'fixed', -}; diff --git a/src/components/suggest/suggest_item.test.js b/src/components/suggest/suggest_item.test.tsx similarity index 100% rename from src/components/suggest/suggest_item.test.js rename to src/components/suggest/suggest_item.test.tsx diff --git a/src/components/suggest/suggest_item.tsx b/src/components/suggest/suggest_item.tsx new file mode 100644 index 00000000000..cb4c3209151 --- /dev/null +++ b/src/components/suggest/suggest_item.tsx @@ -0,0 +1,142 @@ +import React, { + FunctionComponent, + HTMLAttributes, + ButtonHTMLAttributes, + MouseEventHandler, +} from 'react'; +import { CommonProps, ExclusiveUnion, keysOf } from '../common'; +import classNames from 'classnames'; +import { EuiIcon, IconType } from '../icon'; + +interface Type { + iconType: IconType; + color: string | keyof typeof colorToClassNameMap; +} + +interface EuiSuggestItemPropsBase { + /** + * Takes 'iconType' for EuiIcon and 'color'. 'color' can be tint1 through tint9. + */ + type: Type; + + /** + * Label or primary text. + */ + label: string; + + /** + * Description or secondary text (optional). + */ + description?: string; + + /** + * Label display is 'fixed' by default. Label will increase its width beyond 50% if needed with 'expand'. + */ + labelDisplay?: keyof typeof labelDisplayToClassMap; +} + +type PropsForDiv = Omit, 'onClick'>; +type PropsForButton = Omit< + ButtonHTMLAttributes, + 'onClick' | 'type' +> & { + onClick: MouseEventHandler | undefined; +}; + +export type EuiSuggestItemProps = CommonProps & + EuiSuggestItemPropsBase & + ExclusiveUnion; + +interface ColorToClassMap { + tint0: string; + tint1: string; + tint2: string; + tint3: string; + tint4: string; + tint5: string; + tint6: string; + tint7: string; + tint8: string; + tint9: string; + [key: string]: string; +} + +const colorToClassNameMap: ColorToClassMap = { + tint0: 'euiSuggestItem__type--tint0', + tint1: 'euiSuggestItem__type--tint1', + tint2: 'euiSuggestItem__type--tint2', + tint3: 'euiSuggestItem__type--tint3', + tint4: 'euiSuggestItem__type--tint4', + tint5: 'euiSuggestItem__type--tint5', + tint6: 'euiSuggestItem__type--tint6', + tint7: 'euiSuggestItem__type--tint7', + tint8: 'euiSuggestItem__type--tint8', + tint9: 'euiSuggestItem__type--tint9', +}; + +export const COLORS = keysOf(colorToClassNameMap); + +const labelDisplayToClassMap = { + fixed: 'euiSuggestItem__labelDisplay--fixed', + expand: 'euiSuggestItem__labelDisplay--expand', +}; + +export const DISPLAYS = keysOf(labelDisplayToClassMap); + +export const EuiSuggestItem: FunctionComponent = ({ + className, + label, + type, + labelDisplay = 'fixed', + description, + onClick, + ...rest +}) => { + const classes = classNames( + 'euiSuggestItem', + { + 'euiSuggestItem-isClickable': onClick, + }, + className + ); + + let colorClass = ''; + + const labelDisplayClass = classNames( + 'euiSuggestItem__label', + labelDisplayToClassMap[labelDisplay], + { + 'euiSuggestItem__labelDisplay--expand': !description, + } + ); + + if (type && type.color) { + if (COLORS.indexOf(type.color as string) > -1) { + colorClass = colorToClassNameMap[type.color]; + } + } + + const innerContent = ( + + + + + {label} + {description} + + ); + + if (onClick) { + return ( + + ); + } else { + return ( +
+ {innerContent} +
+ ); + } +};