From ee900a89b0ec7e1b9579bd892dcaf37776c93ad8 Mon Sep 17 00:00:00 2001 From: imechoim Date: Thu, 27 Jul 2023 14:17:55 +0200 Subject: [PATCH] feat(Select): loading indicator (#845) Co-authored-by: Yevhenii Chernovol --- src/components/Select/README.md | 65 ++++++++++--------- src/components/Select/Select.tsx | 3 +- .../components/SelectList/SelectList.scss | 7 ++ .../components/SelectList/SelectList.tsx | 19 +++++- .../SelectList/SelectLoadingIndicator.tsx | 12 ++++ src/components/Select/types.ts | 1 + 6 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 src/components/Select/components/SelectList/SelectLoadingIndicator.tsx diff --git a/src/components/Select/README.md b/src/components/Select/README.md index 7885a81ec0..192e307da3 100644 --- a/src/components/Select/README.md +++ b/src/components/Select/README.md @@ -1,35 +1,36 @@ -| Property | Type | Default | Description | -| :------------------------------ | :-------------------------------------- | :-------------- | :--------------------------------------------------------------------------------------------------------- | -| onUpdate | `function` | `-` | Fires when an alteration to the Select value is committed by the user | -| onOpenChange | `function` | `-` | Fires every time after changing popup visibility | -| onFilterChange | `function` | `-` | Fires every time after changing filter | -| filterOption | `function` | `-` | Used to compare option with filter | -| [renderControl](#rendercontrol) | `function` | `-` | Used to render user control | -| [renderFilter](#renderfilter) | `function` | `-` | Used to render user filter section | -| renderOption | `function` | `-` | Used to render user options | -| renderSelectedOption | `function` | `-` | Used to render user selected options | -| renderEmptyOptions | `function` | `-` | Used to render node for an empty options list | -| getOptionHeight | `function` | `-` | Used to set height of customized user options | -| [options](#options) | `(SelectOption \| SelectOptionGroup)[]` | `-` | Options to select | -| view | `string` | `'normal'` | Control [view](https://github.com/gravity-ui/uikit/blob/main/src/components/TextInput/types.ts#L4) | -| size | `string` | `'m'` | Control/options [size](https://github.com/gravity-ui/uikit/blob/main/src/components/TextInput/types.ts#L6) | -| pin | `string` | `'round-round'` | Control [border view](https://github.com/gravity-ui/uikit/blob/main/src/components/TextInput/types.ts#L8) | -| width | `string \| number` | `undefined` | Control width | -| popupWidth | `number` | `-` | Popup width | -| virtualizationThreshold | `number` | `50` | The threshold of the options count after which virtualization is enabled | -| name | `string` | `-` | Name of the control | -| className | `string` | `-` | Control className | -| popupClassName | `string` | `-` | Popup with options list className | -| label | `string` | `-` | Control label | -| placeholder | `string` | `-` | Placeholder text | -| filterPlaceholder | `string` | `-` | Default filter input placeholder text | -| value | `string[]` | `-` | Values that represent selected options | -| defaultValue | `string[]` | `-` | Default values that represent selected options in case of using uncontrolled state | -| qa | `string` | `-` | Test id attribute (`data-qa`) | -| multiple | `boolean` | `false` | Indicates that multiple options can be selected in the list | -| filterable | `boolean` | `false` | Indicates that select popup have filter section | -| disabled | `boolean` | `false` | Indicates that the user cannot interact with the control | -| hasClear | `boolean` | `false` | Enable displaying icon for clear selected options | +| Property | Type | Default | Description | +| :------------------------------ | :-------------------------------------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------- | +| onUpdate | `function` | `-` | Fires when an alteration to the Select value is committed by the user | +| onOpenChange | `function` | `-` | Fires every time after changing popup visibility | +| onFilterChange | `function` | `-` | Fires every time after changing filter | +| filterOption | `function` | `-` | Used to compare option with filter | +| [renderControl](#rendercontrol) | `function` | `-` | Used to render user control | +| [renderFilter](#renderfilter) | `function` | `-` | Used to render user filter section | +| renderOption | `function` | `-` | Used to render user options | +| renderSelectedOption | `function` | `-` | Used to render user selected options | +| renderEmptyOptions | `function` | `-` | Used to render node for an empty options list | +| getOptionHeight | `function` | `-` | Used to set height of customized user options | +| [options](#options) | `(SelectOption \| SelectOptionGroup)[]` | `-` | Options to select | +| view | `string` | `'normal'` | Control [view](https://github.com/gravity-ui/uikit/blob/main/src/components/TextInput/types.ts#L4) | +| size | `string` | `'m'` | Control/options [size](https://github.com/gravity-ui/uikit/blob/main/src/components/TextInput/types.ts#L6) | +| pin | `string` | `'round-round'` | Control [border view](https://github.com/gravity-ui/uikit/blob/main/src/components/TextInput/types.ts#L8) | +| width | `string \| number` | `undefined` | Control width | +| popupWidth | `number` | `-` | Popup width | +| virtualizationThreshold | `number` | `50` | The threshold of the options count after which virtualization is enabled | +| name | `string` | `-` | Name of the control | +| className | `string` | `-` | Control className | +| popupClassName | `string` | `-` | Popup with options list className | +| label | `string` | `-` | Control label | +| placeholder | `string` | `-` | Placeholder text | +| filterPlaceholder | `string` | `-` | Default filter input placeholder text | +| value | `string[]` | `-` | Values that represent selected options | +| defaultValue | `string[]` | `-` | Default values that represent selected options in case of using uncontrolled state | +| qa | `string` | `-` | Test id attribute (`data-qa`) | +| multiple | `boolean` | `false` | Indicates that multiple options can be selected in the list | +| filterable | `boolean` | `false` | Indicates that select popup have filter section | +| disabled | `boolean` | `false` | Indicates that the user cannot interact with the control | +| hasClear | `boolean` | `false` | Enable displaying icon for clear selected options | +| loading | `boolean` | `-` | Add the loading item to the end of the options list. Works like persistant loading indicator while the options list is empty. | --- diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 783aa8a974..ee12407d55 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -254,7 +254,7 @@ export const Select = React.forwardRef(function renderFilter={renderFilter} /> )} - {filteredFlattenOptions.length ? ( + {filteredFlattenOptions.length || props.loading ? ( (function onOptionClick={handleOptionClick} renderOption={renderOption} getOptionHeight={getOptionHeight} + loading={props.loading} /> ) : ( diff --git a/src/components/Select/components/SelectList/SelectList.scss b/src/components/Select/components/SelectList/SelectList.scss index 2bffc4eece..1fb4240c68 100644 --- a/src/components/Select/components/SelectList/SelectList.scss +++ b/src/components/Select/components/SelectList/SelectList.scss @@ -175,4 +175,11 @@ $xl-right-padding: '12px'; visibility: visible; } } + + &__loading-indicator { + display: flex; + width: 100%; + align-items: center; + justify-content: center; + } } diff --git a/src/components/Select/components/SelectList/SelectList.tsx b/src/components/Select/components/SelectList/SelectList.tsx index 19ab95842c..eff3bca65e 100644 --- a/src/components/Select/components/SelectList/SelectList.tsx +++ b/src/components/Select/components/SelectList/SelectList.tsx @@ -3,11 +3,12 @@ import React from 'react'; import {List} from '../../../List'; import {SelectQa, selectListBlock} from '../../constants'; import type {SelectOption, SelectProps} from '../../types'; -import {getOptionsHeight, getPopupItemHeight} from '../../utils'; import type {FlattenOption} from '../../utils'; +import {getOptionsHeight, getPopupItemHeight} from '../../utils'; import {GroupLabel} from './GroupLabel'; import {OptionWrap} from './OptionWrap'; +import {SelectLoadingIndicator} from './SelectLoadingIndicator'; import './SelectList.scss'; @@ -21,8 +22,11 @@ type SelectListProps = { flattenOptions: FlattenOption[]; multiple?: boolean; virtualized?: boolean; + loading?: boolean; }; +const loadingOption = {value: '__SELECT_LIST_ITEM_LOADING__', disabled: true}; + export const SelectList = React.forwardRef, SelectListProps>((props, ref) => { const { onOptionClick, @@ -34,9 +38,15 @@ export const SelectList = React.forwardRef, SelectListProps> multiple, virtualized, mobile, + loading, } = props; + const items = React.useMemo( + () => (loading ? [...flattenOptions, loadingOption] : flattenOptions), + [flattenOptions, loading], + ); + const optionsHeight = getOptionsHeight({ - options: flattenOptions, + options: items, getOptionHeight, size, mobile, @@ -54,6 +64,9 @@ export const SelectList = React.forwardRef, SelectListProps> if ('label' in option) { return ; } + if (option.value === loadingOption.value) { + return ; + } const wrappedRenderOption = renderOption ? (option: SelectOption) => { @@ -81,7 +94,7 @@ export const SelectList = React.forwardRef, SelectListProps> itemClassName={selectListBlock('item')} itemHeight={getItemHeight} itemsHeight={virtualized ? optionsHeight : undefined} - items={flattenOptions} + items={items} filterable={false} virtualized={virtualized} renderItem={renderItem} diff --git a/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx b/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx new file mode 100644 index 0000000000..6db11928e5 --- /dev/null +++ b/src/components/Select/components/SelectList/SelectLoadingIndicator.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import {Loader} from '../../../Loader/Loader'; +import {selectListBlock} from '../../constants'; + +export const SelectLoadingIndicator = () => { + return ( +
+ +
+ ); +}; diff --git a/src/components/Select/types.ts b/src/components/Select/types.ts index 6605de517f..1e8b4dd8ff 100644 --- a/src/components/Select/types.ts +++ b/src/components/Select/types.ts @@ -74,6 +74,7 @@ export type SelectProps = QAProps & filterable?: boolean; disablePortal?: boolean; hasClear?: boolean; + loading?: boolean; children?: | React.ReactElement, typeof Option> | React.ReactElement, typeof Option>[]