From d82c5f791be1226b939425899a553d21a2d8a68e Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 18 Feb 2020 18:07:36 -0500 Subject: [PATCH 1/7] [EuiSelectable] Better export and display of props - Also added `id` to the `Option` type - Setting `searchable` to required but adding a default of false --- src-docs/src/views/selectable/props.tsx | 13 +++++ .../views/selectable/selectable_example.js | 48 ++++------------ src/components/selectable/index.ts | 12 +++- src/components/selectable/selectable.tsx | 17 +++--- .../selectable/selectable_list/index.ts | 5 +- .../selectable_list/selectable_list.tsx | 56 +++++++++---------- src/components/selectable/types.tsx | 1 + 7 files changed, 76 insertions(+), 76 deletions(-) create mode 100644 src-docs/src/views/selectable/props.tsx diff --git a/src-docs/src/views/selectable/props.tsx b/src-docs/src/views/selectable/props.tsx new file mode 100644 index 00000000000..c65e09c79c1 --- /dev/null +++ b/src-docs/src/views/selectable/props.tsx @@ -0,0 +1,13 @@ +import React, { FunctionComponent } from 'react'; +import { + EuiSelectableOptionProp, + EuiSelectableOptionsListProps, +} from '../../../../src/components/selectable'; + +export const EuiSelectableOption: FunctionComponent< + EuiSelectableOptionProp +> = () =>
; + +export const EuiSelectableOptionsList: FunctionComponent< + EuiSelectableOptionsListProps +> = () =>
; diff --git a/src-docs/src/views/selectable/selectable_example.js b/src-docs/src/views/selectable/selectable_example.js index 8ea2c1f8ee6..9be861ccd7e 100644 --- a/src-docs/src/views/selectable/selectable_example.js +++ b/src-docs/src/views/selectable/selectable_example.js @@ -8,12 +8,13 @@ import { GuideSectionTypes } from '../../components'; import { EuiCode, EuiSelectable, - EuiSelectableList, EuiSelectableMessage, EuiText, EuiSpacer, } from '../../../../src/components'; +import { EuiSelectableOption, EuiSelectableOptionsList } from './props'; + import Selectable from './selectable'; const selectableSource = require('!!raw-loader!./selectable'); const selectableHtml = renderToHtml(Selectable); @@ -83,46 +84,17 @@ export const SelectableExample = { At its simplest, EuiSelectable requires an array of{' '} options and an onChange{' '} handler which passes back the altered{' '} - selectedOptions array. + selectedOptions array. The{' '} + children is a function that return the{' '} + list and search nodes.

-

- The Option props -

-
    -
  • - label: string required Must be - unique across items if key is not passed -
  • -
  • - key?: string Must be unique across items -
  • -
  • - checked?: 'on' | 'off'{' '} - Leave off to indicate not selected, 'on' to indicate - inclusion and 'off' to indicate exclusion -
  • -
  • - disabled?: boolean -
  • -
  • - isGroupLabel?: boolean Set to true to indicate - object is just a grouping label, not a selectable item -
  • -
  • - prepend?: React.ReactNode Node to add between - the selection icon and the label -
  • -
  • - append?: React.ReactNode Node to add to the far - right of the item -
  • -
  • - ref?: () => void -
  • -
), - props: { EuiSelectable, EuiSelectableList }, + props: { + EuiSelectable, + EuiSelectableOption, + EuiSelectableOptionsList, + }, demo: , snippet: `; +export type EuiSelectableOptionProp = Option; + export type EuiSelectableProps = Omit< HTMLAttributes, 'children' | 'onChange' @@ -67,9 +69,9 @@ export type EuiSelectableProps = Omit< search: ReactElement | undefined ) => ReactNode; /** - * Array or Option objects, see docs for props + * Array of Option objects. See #EuiSelectableOption */ - options: Option[]; + options: EuiSelectableOptionProp[]; /** * Passes back the altered `options` array with selected options as */ @@ -82,7 +84,7 @@ export type EuiSelectableProps = Omit< */ singleSelection?: EuiSelectableSingleOptionProps; /** - * Allows marking options as checked = 'off' as well as 'on' + * Allows marking options as `checked='off'` as well as `'on'` */ allowExclusions?: boolean; /** @@ -96,12 +98,12 @@ export type EuiSelectableProps = Omit< */ height?: number | 'full'; /** - * See `EuiSelectableList` + * See #EuiSelectableOptionsList */ listProps?: EuiSelectableOptionsListPropsWithDefaults; /** * Custom render function for each option. - * Returns (option, searchValue) + * Returns `(option, searchValue)` */ renderOption?: (option: Option, searchValue: string) => {}; }; @@ -119,6 +121,7 @@ export class EuiSelectable extends Component< static defaultProps = { options: [], singleSelection: false, + searchable: false, }; private optionsListRef = createRef(); diff --git a/src/components/selectable/selectable_list/index.ts b/src/components/selectable/selectable_list/index.ts index bc9f38b88b7..2259f87a3e7 100644 --- a/src/components/selectable/selectable_list/index.ts +++ b/src/components/selectable/selectable_list/index.ts @@ -1,2 +1,5 @@ -export { EuiSelectableList } from './selectable_list'; +export { + EuiSelectableList, + EuiSelectableOptionsListProps, +} from './selectable_list'; export { EuiSelectableListItem } from './selectable_list_item'; diff --git a/src/components/selectable/selectable_list/selectable_list.tsx b/src/components/selectable/selectable_list/selectable_list.tsx index 00a81431a82..e53e6fa45a4 100644 --- a/src/components/selectable/selectable_list/selectable_list.tsx +++ b/src/components/selectable/selectable_list/selectable_list.tsx @@ -5,40 +5,40 @@ import { CommonProps } from '../../common'; import { List, AutoSizer, ListProps } from 'react-virtualized'; import { htmlIdGenerator } from '../../../services'; import { EuiSelectableListItem } from './selectable_list_item'; -// @ts-ignore import { EuiHighlight } from '../../highlight'; import { Option } from '../types'; export type EuiSelectableSingleOptionProps = 'always' | boolean; // Consumer Configurable Props via `EuiSelectable.listProps` -export type EuiSelectableOptionsListProps = HTMLAttributes & - CommonProps & { - /** - * The index of the option to be highlighted as pseudo-focused; - * Good for use when only one selection is allowed and needing to open - * directly to that option - */ - activeOptionIndex?: number; - /** - * The height of each option in pixels. Defaults to `32` - */ - rowHeight: number; - /** - * Show the check/cross selection indicator icons - */ - showIcons?: boolean; - singleSelection?: EuiSelectableSingleOptionProps; - /** - * Any props to send specifically to the react-virtualized `List` - */ - virtualizedProps?: ListProps; - /** - * Adds a border around the list to indicate the bounds; - * Useful when the list scrolls, otherwise use your own container - */ - bordered?: boolean; - }; +export interface EuiSelectableOptionsListProps + extends CommonProps, + HTMLAttributes { + /** + * The index of the option to be highlighted as pseudo-focused; + * Good for use when only one selection is allowed and needing to open + * directly to that option + */ + activeOptionIndex?: number; + /** + * The height of each option in pixels. Defaults to `32` + */ + rowHeight: number; + /** + * Show the check/cross selection indicator icons + */ + showIcons?: boolean; + singleSelection?: EuiSelectableSingleOptionProps; + /** + * Any props to send specifically to the react-virtualized `List` + */ + virtualizedProps?: ListProps; + /** + * Adds a border around the list to indicate the bounds; + * Useful when the list scrolls, otherwise use your own container + */ + bordered?: boolean; +} export type EuiSelectableListProps = EuiSelectableOptionsListProps & { /** diff --git a/src/components/selectable/types.tsx b/src/components/selectable/types.tsx index c74da637b95..850b5fa51d9 100644 --- a/src/components/selectable/types.tsx +++ b/src/components/selectable/types.tsx @@ -12,6 +12,7 @@ export interface Option extends CommonProps { * Must be unique across items */ key?: string; + id?: string; /** * Leave off to indicate not selected, * 'on' to indicate inclusion and From c854dfbfa9288d75ce09d32a53ceefb6a0743506 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 18 Feb 2020 18:38:23 -0500 Subject: [PATCH 2/7] [EuiListGroup] Exporting props at top level --- src/components/list_group/index.ts | 4 ++-- src/components/list_group/list_group.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/list_group/index.ts b/src/components/list_group/index.ts index 69dca4816b4..a82a234ef34 100644 --- a/src/components/list_group/index.ts +++ b/src/components/list_group/index.ts @@ -1,2 +1,2 @@ -export { EuiListGroup } from './list_group'; -export { EuiListGroupItem } from './list_group_item'; +export { EuiListGroup, EuiListGroupProps } from './list_group'; +export { EuiListGroupItem, EuiListGroupItemProps } from './list_group_item'; diff --git a/src/components/list_group/list_group.tsx b/src/components/list_group/list_group.tsx index 9f4e3c7de44..1d6354f8867 100644 --- a/src/components/list_group/list_group.tsx +++ b/src/components/list_group/list_group.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { EuiListGroupItem, EuiListGroupItemProps } from './list_group_item'; import { CommonProps } from '../common'; -type EuiListGroupProps = CommonProps & +export type EuiListGroupProps = CommonProps & HTMLAttributes & { /** * Add a border to the list container @@ -17,7 +17,7 @@ type EuiListGroupProps = CommonProps & flush?: boolean; /** - * Items to display in this group + * Items to display in this group. See #EuiListGroupItem */ listItems?: EuiListGroupItemProps[]; From c7961b3ac1a42ef44722550e16cdc19f3c9b9dc3 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 18 Feb 2020 18:48:22 -0500 Subject: [PATCH 3/7] Reverting to `type` instead of `interface` --- .../selectable_list/selectable_list.tsx | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/components/selectable/selectable_list/selectable_list.tsx b/src/components/selectable/selectable_list/selectable_list.tsx index e53e6fa45a4..ce0d7b1be2c 100644 --- a/src/components/selectable/selectable_list/selectable_list.tsx +++ b/src/components/selectable/selectable_list/selectable_list.tsx @@ -11,34 +11,33 @@ import { Option } from '../types'; export type EuiSelectableSingleOptionProps = 'always' | boolean; // Consumer Configurable Props via `EuiSelectable.listProps` -export interface EuiSelectableOptionsListProps - extends CommonProps, - HTMLAttributes { - /** - * The index of the option to be highlighted as pseudo-focused; - * Good for use when only one selection is allowed and needing to open - * directly to that option - */ - activeOptionIndex?: number; - /** - * The height of each option in pixels. Defaults to `32` - */ - rowHeight: number; - /** - * Show the check/cross selection indicator icons - */ - showIcons?: boolean; - singleSelection?: EuiSelectableSingleOptionProps; - /** - * Any props to send specifically to the react-virtualized `List` - */ - virtualizedProps?: ListProps; - /** - * Adds a border around the list to indicate the bounds; - * Useful when the list scrolls, otherwise use your own container - */ - bordered?: boolean; -} +export type EuiSelectableOptionsListProps = CommonProps & + HTMLAttributes & { + /** + * The index of the option to be highlighted as pseudo-focused; + * Good for use when only one selection is allowed and needing to open + * directly to that option + */ + activeOptionIndex?: number; + /** + * The height of each option in pixels. Defaults to `32` + */ + rowHeight: number; + /** + * Show the check/cross selection indicator icons + */ + showIcons?: boolean; + singleSelection?: EuiSelectableSingleOptionProps; + /** + * Any props to send specifically to the react-virtualized `List` + */ + virtualizedProps?: ListProps; + /** + * Adds a border around the list to indicate the bounds; + * Useful when the list scrolls, otherwise use your own container + */ + bordered?: boolean; + }; export type EuiSelectableListProps = EuiSelectableOptionsListProps & { /** From 5abe13b3c7ce539f0b306b3fb09b482c0693af74 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 18 Feb 2020 19:05:28 -0500 Subject: [PATCH 4/7] cl --- CHANGELOG.md | 2 ++ src/components/accordion/accordion.tsx | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd58c908825..32bf6317433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Exported `EuiSelectOptionProps` type ([#2830](https://github.com/elastic/eui/pull/2830)) - Added `paperClip` glyph to `EuiIcon` ([#2845](https://github.com/elastic/eui/pull/2845)) - Added `banner` prop to `EuiFlyoutBody` and updated `euiOverflowShadow` mixin ([#2837](https://github.com/elastic/eui/pull/2837)) +- Exporting TS props from top level `EuiListGroupProps`, `EuiListGroupItemProps`, `EuiSelectableProps`, `EuiSelectableOptionProp`, `EuiSelectableOptionsListProps` ([#2869](https://github.com/elastic/eui/pull/2869)) +- Added `id` prop to `EuiSelectable[options]` option ([#2869](https://github.com/elastic/eui/pull/2869)) **Bug fixes** diff --git a/src/components/accordion/accordion.tsx b/src/components/accordion/accordion.tsx index 25745fdc4ea..6d6df43a694 100644 --- a/src/components/accordion/accordion.tsx +++ b/src/components/accordion/accordion.tsx @@ -10,7 +10,7 @@ import { getDurationAndPerformOnFrame } from '../../services'; const MUTATION_ATTRIBUTE_FILTER = ['style']; const paddingSizeToClassNameMap = { - none: null, + none: '', xs: 'euiAccordion__padding--xs', s: 'euiAccordion__padding--s', m: 'euiAccordion__padding--m', @@ -51,7 +51,7 @@ export type EuiAccordionProps = HTMLAttributes & /** * The padding around the exposed accordion content. */ - paddingSize: EuiAccordionSize; + paddingSize?: EuiAccordionSize; }; export class EuiAccordion extends Component< @@ -139,7 +139,9 @@ export class EuiAccordion extends Component< className ); - const paddingClass = classNames(paddingSizeToClassNameMap[paddingSize]); + const paddingClass = paddingSize + ? classNames(paddingSizeToClassNameMap[paddingSize]) + : undefined; const buttonClasses = classNames('euiAccordion__button', buttonClassName); From c88e8f7bf3e66261e73d7ae3e91e308ff8414f17 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 18 Feb 2020 19:05:52 -0500 Subject: [PATCH 5/7] cl --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32bf6317433..aebb4472823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Exported `EuiSelectOptionProps` type ([#2830](https://github.com/elastic/eui/pull/2830)) - Added `paperClip` glyph to `EuiIcon` ([#2845](https://github.com/elastic/eui/pull/2845)) - Added `banner` prop to `EuiFlyoutBody` and updated `euiOverflowShadow` mixin ([#2837](https://github.com/elastic/eui/pull/2837)) -- Exporting TS props from top level `EuiListGroupProps`, `EuiListGroupItemProps`, `EuiSelectableProps`, `EuiSelectableOptionProp`, `EuiSelectableOptionsListProps` ([#2869](https://github.com/elastic/eui/pull/2869)) +- Exported TS props from top level `EuiListGroupProps`, `EuiListGroupItemProps`, `EuiSelectableProps`, `EuiSelectableOptionProp`, `EuiSelectableOptionsListProps` ([#2869](https://github.com/elastic/eui/pull/2869)) - Added `id` prop to `EuiSelectable[options]` option ([#2869](https://github.com/elastic/eui/pull/2869)) **Bug fixes** From 16f29fc704d00529c2903de83cba98eeadcdca01 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Thu, 20 Feb 2020 13:40:17 -0700 Subject: [PATCH 6/7] Updated selectable's Option type to reflect div vs button realities --- .../selectable_list/selectable_list.tsx | 9 +++++--- src/components/selectable/types.tsx | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/selectable/selectable_list/selectable_list.tsx b/src/components/selectable/selectable_list/selectable_list.tsx index ce0d7b1be2c..808dc6a15e1 100644 --- a/src/components/selectable/selectable_list/selectable_list.tsx +++ b/src/components/selectable/selectable_list/selectable_list.tsx @@ -4,7 +4,10 @@ import { CommonProps } from '../../common'; // eslint-disable-next-line import/named import { List, AutoSizer, ListProps } from 'react-virtualized'; import { htmlIdGenerator } from '../../../services'; -import { EuiSelectableListItem } from './selectable_list_item'; +import { + EuiSelectableListItem, + EuiSelectableListItemProps, +} from './selectable_list_item'; import { EuiHighlight } from '../../highlight'; import { Option } from '../types'; @@ -172,7 +175,7 @@ export class EuiSelectableList extends Component { className="euiSelectableList__groupLabel" key={rowKey} style={style} - {...optionRest}> + {...optionRest as HTMLAttributes}> {prepend} {label} {append} @@ -193,7 +196,7 @@ export class EuiSelectableList extends Component { disabled={disabled} prepend={prepend} append={append} - {...optionRest}> + {...optionRest as EuiSelectableListItemProps}> {renderOption ? ( renderOption(option, searchValue) ) : ( diff --git a/src/components/selectable/types.tsx b/src/components/selectable/types.tsx index 850b5fa51d9..8e64e3a4c34 100644 --- a/src/components/selectable/types.tsx +++ b/src/components/selectable/types.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { CommonProps } from '../common'; +import React, { ButtonHTMLAttributes, HTMLAttributes } from 'react'; +import { CommonProps, ExclusiveUnion } from '../common'; export type OptionCheckedType = 'on' | 'off' | undefined; -export interface Option extends CommonProps { +export interface BaseOption extends CommonProps { /** * Visible label of option. Must be unique across items if `key` is not supplied */ @@ -21,9 +21,9 @@ export interface Option extends CommonProps { checked?: OptionCheckedType; disabled?: boolean; /** - * Set to true to indicate object is just a grouping label, not a selectable item + * Optional boolean. Set to true to indicate object is just a grouping label, not a selectable item */ - isGroupLabel?: boolean; + isGroupLabel?: false; /** * Node to add between the selection icon and the label */ @@ -34,3 +34,15 @@ export interface Option extends CommonProps { append?: React.ReactNode; ref?: (optionIndex: number) => void; } + +export interface GroupLabelOption + extends Omit, + HTMLAttributes { + isGroupLabel: true; +} + +export interface SelectableOption + extends BaseOption, + ButtonHTMLAttributes {} + +export type Option = ExclusiveUnion; From 5e1723b2106098466f32cff372fb35b4a9afeb48 Mon Sep 17 00:00:00 2001 From: cchaos Date: Thu, 20 Feb 2020 16:39:12 -0500 Subject: [PATCH 7/7] Renaming mroe option ts types and updating some doc example files --- src-docs/src/views/selectable/data.ts | 5 +- src-docs/src/views/selectable/props.tsx | 6 +- src-docs/src/views/selectable/selectable.tsx | 41 +++------- .../views/selectable/selectable_example.js | 4 +- .../views/selectable/selectable_exclusion.tsx | 38 +++------- .../views/selectable/selectable_messages.js | 63 ---------------- .../views/selectable/selectable_messages.tsx | 38 ++++++++++ .../views/selectable/selectable_search.tsx | 74 ++++++------------- .../views/selectable/selectable_single.tsx | 51 +++++-------- src/components/selectable/index.ts | 7 +- src/components/selectable/matching_options.ts | 16 ++-- src/components/selectable/selectable.test.tsx | 6 +- src/components/selectable/selectable.tsx | 21 +++--- .../selectable_list/selectable_list.test.tsx | 6 +- .../selectable_list/selectable_list.tsx | 21 +++--- .../selectable_list/selectable_list_item.tsx | 6 +- .../selectable/selectable_option.tsx | 53 +++++++++++++ .../selectable_search/selectable_search.tsx | 11 ++- src/components/selectable/types.tsx | 48 ------------ 19 files changed, 213 insertions(+), 302 deletions(-) delete mode 100644 src-docs/src/views/selectable/selectable_messages.js create mode 100644 src-docs/src/views/selectable/selectable_messages.tsx create mode 100644 src/components/selectable/selectable_option.tsx delete mode 100644 src/components/selectable/types.tsx diff --git a/src-docs/src/views/selectable/data.ts b/src-docs/src/views/selectable/data.ts index b07a24156e0..85b0bda4ad9 100644 --- a/src-docs/src/views/selectable/data.ts +++ b/src-docs/src/views/selectable/data.ts @@ -1,4 +1,6 @@ -export const Options = [ +import { EuiSelectableOption } from '../../../../src/components/selectable/selectable_option'; + +export const Options: EuiSelectableOption[] = [ { label: 'Titan', 'data-test-subj': 'titanOption', @@ -13,6 +15,7 @@ export const Options = [ }, { label: 'Dione', + id: 'id_dione', }, { label: 'Iapetus', diff --git a/src-docs/src/views/selectable/props.tsx b/src-docs/src/views/selectable/props.tsx index c65e09c79c1..294a3cc1fe5 100644 --- a/src-docs/src/views/selectable/props.tsx +++ b/src-docs/src/views/selectable/props.tsx @@ -1,11 +1,11 @@ import React, { FunctionComponent } from 'react'; import { - EuiSelectableOptionProp, + EuiSelectableOption, EuiSelectableOptionsListProps, } from '../../../../src/components/selectable'; -export const EuiSelectableOption: FunctionComponent< - EuiSelectableOptionProp +export const EuiSelectableOptionProps: FunctionComponent< + EuiSelectableOption > = () =>
; export const EuiSelectableOptionsList: FunctionComponent< diff --git a/src-docs/src/views/selectable/selectable.tsx b/src-docs/src/views/selectable/selectable.tsx index 6171a206e71..29993c2bb17 100644 --- a/src-docs/src/views/selectable/selectable.tsx +++ b/src-docs/src/views/selectable/selectable.tsx @@ -1,34 +1,17 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import { EuiSelectable } from '../../../../src/components/selectable'; -import { Option } from '../../../../src/components/selectable/types'; import { Options } from './data'; -export default class extends Component<{}, { options: Option[] }> { - constructor(props: any) { - super(props); +export default () => { + const [options, setOptions] = useState(Options); - this.state = { - options: Options as Option[], - }; - } - - onChange = (options: Option[]) => { - this.setState({ - options, - }); - }; - - render() { - const { options } = this.state; - - return ( - - {list => list} - - ); - } -} + return ( + setOptions(newOptions)}> + {list => list} + + ); +}; diff --git a/src-docs/src/views/selectable/selectable_example.js b/src-docs/src/views/selectable/selectable_example.js index 9be861ccd7e..8fded936382 100644 --- a/src-docs/src/views/selectable/selectable_example.js +++ b/src-docs/src/views/selectable/selectable_example.js @@ -13,7 +13,7 @@ import { EuiSpacer, } from '../../../../src/components'; -import { EuiSelectableOption, EuiSelectableOptionsList } from './props'; +import { EuiSelectableOptionProps, EuiSelectableOptionsList } from './props'; import Selectable from './selectable'; const selectableSource = require('!!raw-loader!./selectable'); @@ -92,7 +92,7 @@ export const SelectableExample = { ), props: { EuiSelectable, - EuiSelectableOption, + EuiSelectableOptionProps, EuiSelectableOptionsList, }, demo: , diff --git a/src-docs/src/views/selectable/selectable_exclusion.tsx b/src-docs/src/views/selectable/selectable_exclusion.tsx index 39d6f796740..1a4c87d27b1 100644 --- a/src-docs/src/views/selectable/selectable_exclusion.tsx +++ b/src-docs/src/views/selectable/selectable_exclusion.tsx @@ -1,31 +1,17 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import { EuiSelectable } from '../../../../src/components/selectable'; -import { Option } from '../../../../src/components/selectable/types'; import { Options } from './data'; -export default class extends Component<{}, { options: Option[] }> { - constructor(props: any) { - super(props); +export default () => { + const [options, setOptions] = useState(Options); - this.state = { - options: Options as Option[], - }; - } - - onChange = (options: Option[]) => { - this.setState({ - options, - }); - }; - - render() { - const { options } = this.state; - - return ( - - {list => list} - - ); - } -} + return ( + setOptions(newOptions)}> + {list => list} + + ); +}; diff --git a/src-docs/src/views/selectable/selectable_messages.js b/src-docs/src/views/selectable/selectable_messages.js deleted file mode 100644 index cbf1de14f1b..00000000000 --- a/src-docs/src/views/selectable/selectable_messages.js +++ /dev/null @@ -1,63 +0,0 @@ -import React, { Component, Fragment } from 'react'; - -import { - EuiSelectable, - EuiSwitch, - EuiSelectableMessage, - EuiSpacer, -} from '../../../../src/components'; - -export default class extends Component { - constructor(props) { - super(props); - - this.state = { - useCustomMessage: false, - isLoading: false, - }; - } - - onSwitch = e => { - this.setState({ - useCustomMessage: e.target.checked, - }); - }; - - onLoading = e => { - this.setState({ - isLoading: e.target.checked, - }); - }; - - render() { - const { useCustomMessage, isLoading } = this.state; - - const customMessage = ( - You have no spice - ); - - return ( - - -   - - - - {list => (useCustomMessage && !isLoading ? customMessage : list)} - - - ); - } -} diff --git a/src-docs/src/views/selectable/selectable_messages.tsx b/src-docs/src/views/selectable/selectable_messages.tsx new file mode 100644 index 00000000000..95ec006c00a --- /dev/null +++ b/src-docs/src/views/selectable/selectable_messages.tsx @@ -0,0 +1,38 @@ +import React, { useState, Fragment } from 'react'; + +import { + EuiSelectable, + EuiSelectableMessage, +} from '../../../../src/components/selectable'; +import { EuiSwitch } from '../../../../src/components/form/switch'; +import { EuiSpacer } from '../../../../src/components/spacer'; + +export default () => { + const [useCustomMessage, setUseCustomMessage] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const customMessage = ( + You have no spice + ); + + return ( + + setUseCustomMessage(e.target.checked)} + checked={!isLoading && useCustomMessage} + disabled={isLoading} + /> +   + setIsLoading(e.target.checked)} + checked={isLoading} + /> + + + {list => (useCustomMessage && !isLoading ? customMessage : list)} + + + ); +}; diff --git a/src-docs/src/views/selectable/selectable_search.tsx b/src-docs/src/views/selectable/selectable_search.tsx index e94c755e1c3..4566e56ab71 100644 --- a/src-docs/src/views/selectable/selectable_search.tsx +++ b/src-docs/src/views/selectable/selectable_search.tsx @@ -1,57 +1,27 @@ -import React, { Component, Fragment } from 'react'; - -import { EuiButtonEmpty } from '../../../../src/components/button'; +import React, { useState, Fragment } from 'react'; import { EuiSelectable } from '../../../../src/components/selectable'; -import { Option } from '../../../../src/components/selectable/types'; import { Options } from './data'; -export default class extends Component<{}, { options: Option[] }> { - constructor(props: any) { - super(props); - - this.state = { - options: Options as Option[], - }; - } - - onChange = (options: Option[]) => { - this.setState({ - options, - }); - }; - - render() { - const { options } = this.state; +export default () => { + const [options, setOptions] = useState(Options); - return ( - - { - this.setState({ - options: Options.map(option => ({ - ...option, - checked: undefined, - })), - }); - }}> - Deselect all - - - {(list, search) => ( - - {search} - {list} - - )} - - - ); - } -} + return ( + + setOptions(newOptions)}> + {(list, search) => ( + + {search} + {list} + + )} + + + ); +}; diff --git a/src-docs/src/views/selectable/selectable_single.tsx b/src-docs/src/views/selectable/selectable_single.tsx index bcb4a80a238..fb884cf08ee 100644 --- a/src-docs/src/views/selectable/selectable_single.tsx +++ b/src-docs/src/views/selectable/selectable_single.tsx @@ -1,38 +1,23 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import { EuiSelectable } from '../../../../src/components/selectable'; -import { Option } from '../../../../src/components/selectable/types'; import { Options } from './data'; -export default class extends Component<{}, { options: Option[] }> { - constructor(props: any) { - super(props); +export default () => { + const [options, setOptions] = useState( + Options.map(option => { + const { checked, ...checklessOption } = option; + return { ...checklessOption }; + }) + ); - this.state = { - options: Options.map(option => { - const { checked, ...checklessOption } = option; - return { ...checklessOption }; - }) as Option[], - }; - } - - onChange = (options: Option[]) => { - this.setState({ - options, - }); - }; - - render() { - const { options } = this.state; - - return ( - - {list => list} - - ); - } -} + return ( + setOptions(newOptions)} + singleSelection={true} + listProps={{ bordered: true }}> + {list => list} + + ); +}; diff --git a/src/components/selectable/index.ts b/src/components/selectable/index.ts index bb047845b88..b31a0b48a54 100644 --- a/src/components/selectable/index.ts +++ b/src/components/selectable/index.ts @@ -1,12 +1,9 @@ -export { - EuiSelectable, - EuiSelectableProps, - EuiSelectableOptionProp, -} from './selectable'; +export { EuiSelectable, EuiSelectableProps } from './selectable'; export { EuiSelectableList, EuiSelectableListItem, EuiSelectableOptionsListProps, } from './selectable_list'; export { EuiSelectableMessage } from './selectable_message'; +export { EuiSelectableOption } from './selectable_option'; export { EuiSelectableSearch } from './selectable_search'; diff --git a/src/components/selectable/matching_options.ts b/src/components/selectable/matching_options.ts index b5fb9faca4e..502d1aa0ddc 100644 --- a/src/components/selectable/matching_options.ts +++ b/src/components/selectable/matching_options.ts @@ -1,8 +1,8 @@ -import { Option } from './types'; +import { EuiSelectableOption } from './selectable_option'; const getSelectedOptionForSearchValue = ( searchValue: string, - selectedOptions: Option[] + selectedOptions: EuiSelectableOption[] ) => { const normalizedSearchValue = searchValue.toLowerCase(); return selectedOptions.find( @@ -11,11 +11,11 @@ const getSelectedOptionForSearchValue = ( }; const collectMatchingOption = ( - accumulator: Option[], - option: Option, + accumulator: EuiSelectableOption[], + option: EuiSelectableOption, normalizedSearchValue: string, isPreFiltered?: boolean, - selectedOptions?: Option[] + selectedOptions?: EuiSelectableOption[] ) => { // Don't show options that have already been requested if // the selectedOptions list exists @@ -51,7 +51,7 @@ export const getMatchingOptions = ( /** * All available options to match against */ - options: Option[], + options: EuiSelectableOption[], /** * String to match option.label against */ @@ -64,10 +64,10 @@ export const getMatchingOptions = ( * To exclude selected options from the search list, * pass the array of selected options */ - selectedOptions?: Option[] + selectedOptions?: EuiSelectableOption[] ) => { const normalizedSearchValue = searchValue.trim().toLowerCase(); - const matchingOptions: Option[] = []; + const matchingOptions: EuiSelectableOption[] = []; options.forEach(option => { collectMatchingOption( diff --git a/src/components/selectable/selectable.test.tsx b/src/components/selectable/selectable.test.tsx index 8fbe405bb33..0a886af1047 100644 --- a/src/components/selectable/selectable.test.tsx +++ b/src/components/selectable/selectable.test.tsx @@ -3,9 +3,9 @@ import { render } from 'enzyme'; import { requiredProps } from '../../test/required_props'; import { EuiSelectable } from './selectable'; -import { Option } from './types'; +import { EuiSelectableOption } from './selectable_option'; -const options: Option[] = [ +const options: EuiSelectableOption[] = [ { label: 'Titan', 'data-test-subj': 'titanOption', @@ -77,7 +77,7 @@ describe('EuiSelectable', () => { const component = render( { + renderOption={(option: EuiSelectableOption, searchValue?: string) => { return ( {searchValue} => {option.label} diff --git a/src/components/selectable/selectable.tsx b/src/components/selectable/selectable.tsx index 76782b9852f..00073a95f6c 100644 --- a/src/components/selectable/selectable.tsx +++ b/src/components/selectable/selectable.tsx @@ -16,7 +16,7 @@ import { getMatchingOptions } from './matching_options'; import { comboBoxKeyCodes } from '../../services'; import { TAB } from '../../services/key_codes'; import { EuiI18n } from '../i18n'; -import { Option } from './types'; +import { EuiSelectableOption } from './selectable_option'; import { EuiSelectableOptionsListProps, EuiSelectableSingleOptionProps, @@ -50,8 +50,6 @@ type EuiSelectableSearchableProps = ExclusiveUnion< } >; -export type EuiSelectableOptionProp = Option; - export type EuiSelectableProps = Omit< HTMLAttributes, 'children' | 'onChange' @@ -69,13 +67,13 @@ export type EuiSelectableProps = Omit< search: ReactElement | undefined ) => ReactNode; /** - * Array of Option objects. See #EuiSelectableOption + * Array of EuiSelectableOption objects. See #EuiSelectableOptionProps */ - options: EuiSelectableOptionProp[]; + options: EuiSelectableOption[]; /** * Passes back the altered `options` array with selected options as */ - onChange?: (options: Option[]) => void; + onChange?: (options: EuiSelectableOption[]) => void; /** * Sets the single selection policy of * `false`: allows multiple selection @@ -105,13 +103,13 @@ export type EuiSelectableProps = Omit< * Custom render function for each option. * Returns `(option, searchValue)` */ - renderOption?: (option: Option, searchValue: string) => {}; + renderOption?: (option: EuiSelectableOption, searchValue: string) => {}; }; export interface EuiSelectableState { activeOptionIndex?: number; searchValue: string; - visibleOptions: Option[]; + visibleOptions: EuiSelectableOption[]; } export class EuiSelectable extends Component< @@ -264,7 +262,10 @@ export class EuiSelectable extends Component< }); }; - onSearchChange = (visibleOptions: Option[], searchValue: string) => { + onSearchChange = ( + visibleOptions: EuiSelectableOption[], + searchValue: string + ) => { this.setState({ visibleOptions, searchValue, @@ -275,7 +276,7 @@ export class EuiSelectable extends Component< this.clearActiveOption(); }; - onOptionClick = (options: Option[]) => { + onOptionClick = (options: EuiSelectableOption[]) => { this.setState(state => ({ visibleOptions: getMatchingOptions(options, state.searchValue), })); diff --git a/src/components/selectable/selectable_list/selectable_list.test.tsx b/src/components/selectable/selectable_list/selectable_list.test.tsx index 88a5cc23ab6..e3438063e7a 100644 --- a/src/components/selectable/selectable_list/selectable_list.test.tsx +++ b/src/components/selectable/selectable_list/selectable_list.test.tsx @@ -3,14 +3,14 @@ import { render } from 'enzyme'; import { requiredProps } from '../../../test/required_props'; import { EuiSelectableList } from './selectable_list'; -import { Option } from '../types'; +import { EuiSelectableOption } from '../selectable_option'; // Mock the htmlIdGenerator to generate predictable ids for snapshot tests jest.mock('../../../services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'htmlId', })); -const options: Option[] = [ +const options: EuiSelectableOption[] = [ { label: 'Titan', 'data-test-subj': 'titanOption', @@ -87,7 +87,7 @@ describe('EuiSelectableListItem', () => { const component = render( { + renderOption={(option: EuiSelectableOption, searchValue?: string) => { return ( {searchValue} => {option.label} diff --git a/src/components/selectable/selectable_list/selectable_list.tsx b/src/components/selectable/selectable_list/selectable_list.tsx index 808dc6a15e1..bb5440c295b 100644 --- a/src/components/selectable/selectable_list/selectable_list.tsx +++ b/src/components/selectable/selectable_list/selectable_list.tsx @@ -9,7 +9,7 @@ import { EuiSelectableListItemProps, } from './selectable_list_item'; import { EuiHighlight } from '../../highlight'; -import { Option } from '../types'; +import { EuiSelectableOption } from '../selectable_option'; export type EuiSelectableSingleOptionProps = 'always' | boolean; @@ -46,11 +46,11 @@ export type EuiSelectableListProps = EuiSelectableOptionsListProps & { /** * All possible options */ - options: Option[]; + options: EuiSelectableOption[]; /** * Filtered options list (if applicable) */ - visibleOptions?: Option[]; + visibleOptions?: EuiSelectableOption[]; /** * Search value to highlight on the option render */ @@ -58,12 +58,15 @@ export type EuiSelectableListProps = EuiSelectableOptionsListProps & { /** * Returns the array of options with altered checked state */ - onOptionClick: (options: Option[]) => void; + onOptionClick: (options: EuiSelectableOption[]) => void; /** * Custom render for the label portion of the option; * Takes (option, searchValue), returns ReactNode */ - renderOption?: (option: Option, searchValue: string) => ReactNode; + renderOption?: ( + option: EuiSelectableOption, + searchValue: string + ) => ReactNode; /** * Sets the max height in pixels or pass `full` to allow * the whole group to fill the height of its container and @@ -212,7 +215,7 @@ export class EuiSelectableList extends Component { ); } - onAddOrRemoveOption = (option: Option) => { + onAddOrRemoveOption = (option: EuiSelectableOption) => { if (option.disabled) { return; } @@ -228,7 +231,7 @@ export class EuiSelectableList extends Component { } }; - private onAddOption = (addedOption: Option) => { + private onAddOption = (addedOption: EuiSelectableOption) => { const { onOptionClick, options, singleSelection } = this.props; const updatedOptions = options.map(option => { @@ -249,7 +252,7 @@ export class EuiSelectableList extends Component { onOptionClick(updatedOptions); }; - private onRemoveOption = (removedOption: Option) => { + private onRemoveOption = (removedOption: EuiSelectableOption) => { const { onOptionClick, singleSelection, options } = this.props; const updatedOptions = options.map(option => { @@ -265,7 +268,7 @@ export class EuiSelectableList extends Component { onOptionClick(updatedOptions); }; - private onExcludeOption = (excludedOption: Option) => { + private onExcludeOption = (excludedOption: EuiSelectableOption) => { const { onOptionClick, options } = this.props; excludedOption.checked = 'off'; diff --git a/src/components/selectable/selectable_list/selectable_list_item.tsx b/src/components/selectable/selectable_list/selectable_list_item.tsx index c88be3fd61a..efb76779ef7 100644 --- a/src/components/selectable/selectable_list/selectable_list_item.tsx +++ b/src/components/selectable/selectable_list/selectable_list_item.tsx @@ -2,10 +2,10 @@ import React, { Component, ButtonHTMLAttributes } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; import { EuiIcon, IconType, IconColor } from '../../icon'; -import { OptionCheckedType } from '../types'; +import { EuiSelectableOptionCheckedType } from '../selectable_option'; function resolveIconAndColor( - checked: OptionCheckedType + checked: EuiSelectableOptionCheckedType ): { icon: IconType; color?: IconColor } { if (!checked) { return { icon: 'empty' }; @@ -23,7 +23,7 @@ export type EuiSelectableListItemProps = ButtonHTMLAttributes< /** * Applies an icon and visual styling to activated items */ - checked?: OptionCheckedType; + checked?: EuiSelectableOptionCheckedType; /** * Shows icons based on `checked` type */ diff --git a/src/components/selectable/selectable_option.tsx b/src/components/selectable/selectable_option.tsx new file mode 100644 index 00000000000..e9d0ff3a7cd --- /dev/null +++ b/src/components/selectable/selectable_option.tsx @@ -0,0 +1,53 @@ +import React, { ButtonHTMLAttributes, HTMLAttributes } from 'react'; +import { CommonProps, ExclusiveUnion } from '../common'; + +export type EuiSelectableOptionCheckedType = 'on' | 'off' | undefined; + +export interface EuiSelectableOptionBase extends CommonProps { + /** + * Visible label of option. + * Must be unique across items if `key` is not supplied + */ + label: string; + /** + * Must be unique across items. + * Will be used to match options instead of `label` + */ + key?: string; + /** + * Leave `undefined` to indicate not selected, + * 'on' to indicate inclusion and + * 'off' to indicate exclusion + */ + checked?: EuiSelectableOptionCheckedType; + disabled?: boolean; + /** + * Optional `boolean`. + * Set to `true` to indicate object is just a grouping label, not a selectable item + */ + isGroupLabel?: false; + /** + * Node to add between the selection icon and the label + */ + prepend?: React.ReactNode; + /** + * Node to add to the far right of the item + */ + append?: React.ReactNode; + ref?: (optionIndex: number) => void; +} + +export interface EuiSelectableGroupLabelOption + extends Omit, + HTMLAttributes { + isGroupLabel: true; +} + +export interface EuiSelectableButtonOption + extends EuiSelectableOptionBase, + ButtonHTMLAttributes {} + +export type EuiSelectableOption = ExclusiveUnion< + EuiSelectableGroupLabelOption, + EuiSelectableButtonOption +>; diff --git a/src/components/selectable/selectable_search/selectable_search.tsx b/src/components/selectable/selectable_search/selectable_search.tsx index d6eedc5ee99..3721e1bd570 100644 --- a/src/components/selectable/selectable_search/selectable_search.tsx +++ b/src/components/selectable/selectable_search/selectable_search.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { CommonProps } from '../../common'; import { EuiFieldSearch, EuiFieldSearchProps } from '../../form/field_search'; import { getMatchingOptions } from '../matching_options'; -import { Option } from '../types'; +import { EuiSelectableOption } from '../selectable_option'; export type EuiSelectableSearchProps = Omit< InputHTMLAttributes & EuiFieldSearchProps, @@ -13,8 +13,11 @@ export type EuiSelectableSearchProps = Omit< /** * Passes back (matchingOptions, searchValue) */ - onChange?: (matchingOptions: Option[], searchValue: string) => void; - options: Option[]; + onChange?: ( + matchingOptions: EuiSelectableOption[], + searchValue: string + ) => void; + options: EuiSelectableOption[]; defaultValue: string; }; @@ -52,7 +55,7 @@ export class EuiSelectableSearch extends Component< this.passUpMatches(matchingOptions, value); }; - passUpMatches = (matches: Option[], searchValue: string) => { + passUpMatches = (matches: EuiSelectableOption[], searchValue: string) => { if (this.props.onChange) { this.props.onChange(matches, searchValue); } diff --git a/src/components/selectable/types.tsx b/src/components/selectable/types.tsx deleted file mode 100644 index 8e64e3a4c34..00000000000 --- a/src/components/selectable/types.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { ButtonHTMLAttributes, HTMLAttributes } from 'react'; -import { CommonProps, ExclusiveUnion } from '../common'; - -export type OptionCheckedType = 'on' | 'off' | undefined; - -export interface BaseOption extends CommonProps { - /** - * Visible label of option. Must be unique across items if `key` is not supplied - */ - label: string; - /** - * Must be unique across items - */ - key?: string; - id?: string; - /** - * Leave off to indicate not selected, - * 'on' to indicate inclusion and - * 'off' to indicate exclusion - */ - checked?: OptionCheckedType; - disabled?: boolean; - /** - * Optional boolean. Set to true to indicate object is just a grouping label, not a selectable item - */ - isGroupLabel?: false; - /** - * Node to add between the selection icon and the label - */ - prepend?: React.ReactNode; - /** - * Node to add to the far right of the item - */ - append?: React.ReactNode; - ref?: (optionIndex: number) => void; -} - -export interface GroupLabelOption - extends Omit, - HTMLAttributes { - isGroupLabel: true; -} - -export interface SelectableOption - extends BaseOption, - ButtonHTMLAttributes {} - -export type Option = ExclusiveUnion;