From a49824f22821c42078622fc1b76ab936c140922d Mon Sep 17 00:00:00 2001 From: Kirill Kharitonov Date: Mon, 29 Jan 2024 19:56:51 +0300 Subject: [PATCH 1/7] feat: added errorMessage, errorPlacement and validationState props in Select --- src/components/Select/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Select/types.ts b/src/components/Select/types.ts index e40c4d1daf..781d619422 100644 --- a/src/components/Select/types.ts +++ b/src/components/Select/types.ts @@ -83,6 +83,9 @@ export type SelectProps = QAProps & defaultValue?: string[]; options?: (SelectOption | SelectOptionGroup)[]; error?: string | boolean; + errorMessage?: React.ReactNode; + errorPlacement?: 'outside' | 'inside'; + validationState?: 'invalid'; multiple?: boolean; filterable?: boolean; disablePortal?: boolean; From 7bcd95b5e5cded45b88680308137eab61a247ac7 Mon Sep 17 00:00:00 2001 From: Kirill Kharitonov Date: Mon, 29 Jan 2024 19:57:16 +0300 Subject: [PATCH 2/7] feat: deprecate error prop in Select --- src/components/Select/types.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Select/types.ts b/src/components/Select/types.ts index 781d619422..c6739f840b 100644 --- a/src/components/Select/types.ts +++ b/src/components/Select/types.ts @@ -82,9 +82,15 @@ export type SelectProps = QAProps & value?: string[]; defaultValue?: string[]; options?: (SelectOption | SelectOptionGroup)[]; + /** + * @deprecated Prop `error` has a lower priority than `errorMessage`. Use `errorMessage` instead + */ error?: string | boolean; + /** Determines content of the error message */ errorMessage?: React.ReactNode; + /** Determines whether the error message will be placed under the input field as text or in the tooltip */ errorPlacement?: 'outside' | 'inside'; + /** Describes the validation state */ validationState?: 'invalid'; multiple?: boolean; filterable?: boolean; From 41bf9a1ff8103a5503e284b14327d1d96a5d5642 Mon Sep 17 00:00:00 2001 From: Kirill Kharitonov Date: Tue, 30 Jan 2024 19:32:23 +0300 Subject: [PATCH 3/7] feat(Select): migrated from error to errorMessage, errorPlacement and validationError props --- src/components/Select/Select.tsx | 24 ++++++++++++- .../Select/__stories__/SelectShowcase.tsx | 34 +++++++++++++++++++ .../SelectControl/SelectControl.scss | 7 ++-- .../SelectControl/SelectControl.tsx | 32 ++++++++++++----- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 668320c650..16999e98da 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -3,6 +3,8 @@ import React from 'react'; import {KeyCode} from '../../constants'; import {useFocusWithin, useForkRef, useSelect, useUniqId} from '../../hooks'; import type {List} from '../List'; +import {OuterAdditionalContent} from '../controls/common/OuterAdditionalContent/OuterAdditionalContent'; +import {errorPropsMapper} from '../controls/utils'; import {useMobile} from '../mobile'; import type {CnMods} from '../utils/cn'; @@ -122,6 +124,19 @@ export const Select = React.forwardRef(function ); const virtualized = filteredFlattenOptions.length >= virtualizationThreshold; + const {errorMessage, errorPlacement, validationState} = errorPropsMapper({ + error, + errorMessage: props.errorMessage, + errorPlacement: props.errorPlacement, + validationState: props.validationState, + }); + const errorMessageId = useUniqId(); + + const isErrorMsgVisible = + validationState === 'invalid' && Boolean(errorMessage) && errorPlacement === 'outside'; + const isErrorIconVisible = + validationState === 'invalid' && Boolean(errorMessage) && errorPlacement === 'inside'; + const handleOptionClick = React.useCallback( (option?: FlattenOption) => { if (!option || option?.disabled || 'label' in option) { @@ -250,7 +265,8 @@ export const Select = React.forwardRef(function label={label} placeholder={placeholder} selectedOptionsContent={selectedOptionsContent} - error={error} + isErrorVisible={isErrorIconVisible} + errorMessage={errorMessage} open={open} disabled={disabled} onKeyDown={handleControlKeyDown} @@ -260,6 +276,7 @@ export const Select = React.forwardRef(function selectId={`select-${selectId}`} activeIndex={activeIndex} /> + (function )} + + ); }) as unknown as SelectComponent; diff --git a/src/components/Select/__stories__/SelectShowcase.tsx b/src/components/Select/__stories__/SelectShowcase.tsx index 6dee20adf7..7aeffb22de 100644 --- a/src/components/Select/__stories__/SelectShowcase.tsx +++ b/src/components/Select/__stories__/SelectShowcase.tsx @@ -380,6 +380,40 @@ export const SelectShowcase = (props: SelectProps) => { + +
+

Select (with text error)

+ + + + + + + + + + + + + + +
); }; diff --git a/src/components/Select/components/SelectControl/SelectControl.scss b/src/components/Select/components/SelectControl/SelectControl.scss index 1144b25c34..b312d6f5cb 100644 --- a/src/components/Select/components/SelectControl/SelectControl.scss +++ b/src/components/Select/components/SelectControl/SelectControl.scss @@ -239,10 +239,9 @@ $blockButton: '.#{variables.$ns-new}select-control__button'; margin-left: 0; } - &__error { - @include mixins.text-body-1(); - + &__error-icon { + box-sizing: content-box; color: var(--g-color-text-danger); - margin-top: 2px; + padding: var(--_--text-input-error-icon-padding); } } diff --git a/src/components/Select/components/SelectControl/SelectControl.tsx b/src/components/Select/components/SelectControl/SelectControl.tsx index 10a372ed76..33077d2eca 100644 --- a/src/components/Select/components/SelectControl/SelectControl.tsx +++ b/src/components/Select/components/SelectControl/SelectControl.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import {ChevronDown} from '@gravity-ui/icons'; +import {ChevronDown, TriangleExclamation} from '@gravity-ui/icons'; import isEmpty from 'lodash/isEmpty'; +import type {CnMods} from 'src/components/utils/cn'; import {Icon} from '../../../Icon'; -import type {CnMods} from '../../../utils/cn'; +import {Popover} from '../../../Popover'; +import {CONTROL_ERROR_ICON_QA} from '../../../controls/utils'; import {selectControlBlock, selectControlButtonBlock} from '../../constants'; import type { SelectProps, @@ -28,7 +30,8 @@ type ControlProps = { qa?: string; label?: string; placeholder?: SelectProps['placeholder']; - error?: SelectProps['error']; + isErrorVisible?: boolean; + errorMessage?: SelectProps['errorMessage']; disabled?: boolean; value: SelectProps['value']; clearValue: () => void; @@ -50,7 +53,8 @@ export const SelectControl = React.forwardRef(( name, label, placeholder, - error, + isErrorVisible, + errorMessage, open, disabled, value, @@ -70,7 +74,7 @@ export const SelectControl = React.forwardRef(( size, pin, disabled, - error: Boolean(error), + error: Boolean(errorMessage), 'has-clear': hasClear, 'no-active': isDisabledButtonAnimation, 'has-value': hasValue, @@ -82,7 +86,7 @@ export const SelectControl = React.forwardRef(( view, pin, disabled, - error: Boolean(error), + error: Boolean(errorMessage), }; const disableButtonAnimation = React.useCallback(() => { @@ -163,15 +167,25 @@ export const SelectControl = React.forwardRef(( )} {renderClearIcon({})} + + {isErrorVisible && ( + + + + + + )} +