aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/design-system/src/components
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2023-12-06 11:06:12 +0100
committersonartech <sonartech@sonarsource.com>2023-12-06 20:02:46 +0000
commitbde5b0da90c05ef4128287a6062490046217cd2e (patch)
tree6e429bb18991955161c8d95c25e09f98644606a2 /server/sonar-web/design-system/src/components
parent567ff7d183d5fb025abcad808fcd3cb5e8d5bb2e (diff)
downloadsonarqube-bde5b0da90c05ef4128287a6062490046217cd2e.tar.gz
sonarqube-bde5b0da90c05ef4128287a6062490046217cd2e.zip
SONAR-21029 Fix SearchSelectDropdown search input
Diffstat (limited to 'server/sonar-web/design-system/src/components')
-rw-r--r--server/sonar-web/design-system/src/components/input/InputSearch.tsx41
-rw-r--r--server/sonar-web/design-system/src/components/input/SearchSelect.tsx24
-rw-r--r--server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx100
-rw-r--r--server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx3
-rw-r--r--server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx57
-rw-r--r--server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx4
6 files changed, 37 insertions, 192 deletions
diff --git a/server/sonar-web/design-system/src/components/input/InputSearch.tsx b/server/sonar-web/design-system/src/components/input/InputSearch.tsx
index 879b12165cb..c219332300e 100644
--- a/server/sonar-web/design-system/src/components/input/InputSearch.tsx
+++ b/server/sonar-web/design-system/src/components/input/InputSearch.tsx
@@ -39,6 +39,7 @@ interface Props {
className?: string;
id?: string;
innerRef?: React.RefCallback<HTMLInputElement>;
+ inputId?: string;
loading?: boolean;
maxLength?: number;
minLength?: number;
@@ -55,24 +56,27 @@ interface Props {
const DEFAULT_MAX_LENGTH = 100;
-export function InputSearch({
- autoFocus,
- id,
- className,
- innerRef,
- onBlur,
- onChange,
- onFocus,
- onKeyDown,
- onMouseDown,
- placeholder,
- loading,
- minLength,
- maxLength = DEFAULT_MAX_LENGTH,
- size = 'medium',
- value: parentValue,
- searchInputAriaLabel,
-}: PropsWithChildren<Props>) {
+export function InputSearch(props: PropsWithChildren<Props>) {
+ const {
+ autoFocus,
+ id,
+ className,
+ innerRef,
+ inputId,
+ onBlur,
+ onChange,
+ onFocus,
+ onKeyDown,
+ onMouseDown,
+ placeholder,
+ loading,
+ minLength,
+ maxLength = DEFAULT_MAX_LENGTH,
+ size = 'medium',
+ value: parentValue,
+ searchInputAriaLabel,
+ } = props;
+
const intl = useIntl();
const input = useRef<null | HTMLElement>(null);
const [value, setValue] = useState(parentValue ?? '');
@@ -151,6 +155,7 @@ export function InputSearch({
aria-label={searchInputAriaLabel ?? placeholder}
autoComplete="off"
className={inputClassName}
+ id={inputId}
maxLength={maxLength}
onBlur={onBlur}
onChange={handleInputChange}
diff --git a/server/sonar-web/design-system/src/components/input/SearchSelect.tsx b/server/sonar-web/design-system/src/components/input/SearchSelect.tsx
index 8f3ccd78d7a..951b607d53d 100644
--- a/server/sonar-web/design-system/src/components/input/SearchSelect.tsx
+++ b/server/sonar-web/design-system/src/components/input/SearchSelect.tsx
@@ -20,13 +20,13 @@
import classNames from 'classnames';
import { omit } from 'lodash';
import React, { RefObject } from 'react';
-import { GroupBase, InputProps, components } from 'react-select';
+import { GroupBase, InputProps } from 'react-select';
import AsyncSelect, { AsyncProps } from 'react-select/async';
import Select from 'react-select/dist/declarations/src/Select';
import { INPUT_SIZES } from '../../helpers';
import { Key } from '../../helpers/keyboard';
+import { InputSearch } from './InputSearch';
import { LabelValueSelectOption, SelectProps, selectStyle } from './InputSelect';
-import { SearchSelectControlledInput } from './SearchSelectControlledInput';
type SearchSelectProps<
V,
@@ -98,26 +98,24 @@ export function SearchSelectInput<
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const target = event.target as HTMLInputElement;
- if (event.key === Key.Escape && target.value !== '') {
+ if (event.key === Key.Escape.toString() && target.value !== '') {
event.stopPropagation();
onChange('');
}
};
return (
- <SearchSelectControlledInput
+ <InputSearch
+ {...omit(props, 'value', 'aria-label', 'id')}
+ autoFocus
+ inputId={props.id}
loading={isLoading && inputValue.length >= (minLength ?? 0)}
minLength={minLength}
onChange={onChange}
+ onKeyDown={handleKeyDown}
+ placeholder={placeholder as string}
+ searchInputAriaLabel={props['aria-label']}
size="full"
- value={inputValue}
- >
- <components.Input
- {...props}
- onKeyDown={handleKeyDown}
- placeholder={placeholder as string}
- style={{}}
- />
- </SearchSelectControlledInput>
+ />
);
}
diff --git a/server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx b/server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx
deleted file mode 100644
index f737ce04fe7..00000000000
--- a/server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-import React, { PropsWithChildren } from 'react';
-import { useIntl } from 'react-intl';
-import { INPUT_SIZES } from '../../helpers/constants';
-import { isDefined } from '../../helpers/types';
-import { InputSizeKeys } from '../../types/theme';
-import { Spinner } from '../Spinner';
-import { CloseIcon } from '../icons/CloseIcon';
-import {
- InputSearchWrapper,
- StyledInputWrapper,
- StyledInteractiveIcon,
- StyledNote,
- StyledSearchIcon,
-} from './InputSearch';
-
-interface Props {
- className?: string;
- id?: string;
- loading?: boolean;
- minLength?: number;
- onChange: (value: string) => void;
- onMouseDown?: React.MouseEventHandler<HTMLInputElement>;
- size?: InputSizeKeys;
- value: string;
-}
-
-export function SearchSelectControlledInput({
- id,
- className,
- onChange,
- onMouseDown,
- loading,
- minLength,
- size = 'medium',
- value,
- children,
-}: PropsWithChildren<Props>) {
- const intl = useIntl();
- const tooShort = isDefined(minLength) && value.length > 0 && value.length < minLength;
-
- return (
- <InputSearchWrapper
- className={className}
- id={id}
- onMouseDown={onMouseDown}
- style={{ '--inputSize': INPUT_SIZES[size] }}
- title={
- tooShort && isDefined(minLength)
- ? intl.formatMessage({ id: 'select2.tooShort' }, { 0: minLength })
- : ''
- }
- >
- <StyledInputWrapper className="sw-flex sw-items-center">
- {children}
- <Spinner className="sw-z-normal" loading={loading ?? false}>
- <StyledSearchIcon />
- </Spinner>
- {value !== '' && (
- <StyledInteractiveIcon
- Icon={CloseIcon}
- aria-label={intl.formatMessage({ id: 'clear' })}
- className="it__search-box-clear"
- onClick={() => {
- onChange('');
- }}
- size="small"
- />
- )}
-
- {tooShort && isDefined(minLength) && (
- <StyledNote className="sw-ml-1" role="note">
- {intl.formatMessage({ id: 'select2.tooShort' }, { 0: minLength })}
- </StyledNote>
- )}
- </StyledInputWrapper>
- </InputSearchWrapper>
- );
-}
-
-SearchSelectControlledInput.displayName = 'SearchSelectControlledInput'; // so that tests don't see the obfuscated production name
diff --git a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx
index 355b232f9a3..44f9fb6bae8 100644
--- a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx
+++ b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx
@@ -169,7 +169,6 @@ export function SearchSelectDropdown<
Option: IconOption,
...rest.components,
}}
- inputValue={inputValue}
loadOptions={debouncedLoadOptions.current}
menuIsOpen
minLength={minLength}
@@ -177,7 +176,7 @@ export function SearchSelectDropdown<
onInputChange={handleInputChange}
placeholder={placeholder}
selectRef={ref}
- value={value}
+ size="large"
/>
</StyledSearchSelectWrapper>
</SearchHighlighterContext.Provider>
diff --git a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx b/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx
deleted file mode 100644
index 289dd96b1e3..00000000000
--- a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { screen } from '@testing-library/react';
-import { renderWithContext } from '../../../helpers/testUtils';
-import { FCProps } from '../../../types/misc';
-import { InputSearch } from '../InputSearch';
-import { SearchSelectControlledInput } from '../SearchSelectControlledInput';
-
-it('should work properly when input is passed as a children', async () => {
- const onChange = jest.fn();
- const { user } = setupWithProps({
- onChange,
- value: 'foo',
- children: <input onChange={onChange} />,
- });
- await user.click(screen.getByLabelText('clear'));
- expect(onChange).toHaveBeenCalledWith('');
-});
-
-it('should warn when input is too short', () => {
- setupWithProps({
- value: 'f',
- children: <input />,
- });
- expect(screen.getByRole('note')).toBeInTheDocument();
-});
-
-function setupWithProps(props: Partial<FCProps<typeof InputSearch>> = {}) {
- return renderWithContext(
- <SearchSelectControlledInput
- maxLength={150}
- minLength={2}
- onChange={jest.fn()}
- placeholder="placeholder"
- searchInputAriaLabel=""
- value="foo"
- {...props}
- />,
- );
-}
diff --git a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx b/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx
index e0ae78bf93e..b2e845e831a 100644
--- a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx
+++ b/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx
@@ -44,7 +44,7 @@ it('should render select input and be able to search and select an option', asyn
await user.click(screen.getByRole('combobox'));
expect(screen.getByText('label1')).toBeInTheDocument();
expect(screen.getByText('different')).toBeInTheDocument();
- await user.type(screen.getByRole('combobox', { name: 'label' }), 'label');
+ await user.type(screen.getByRole('searchbox', { name: 'label' }), 'label');
expect(await screen.findByText('label')).toBeInTheDocument();
expect(screen.queryByText('different')).not.toBeInTheDocument();
await user.click(screen.getByText('label'));
@@ -60,7 +60,7 @@ it('should handle key navigation', async () => {
renderSearchSelectDropdown();
await user.tab();
await user.keyboard('{Enter}');
- await user.type(screen.getByRole('combobox', { name: 'label' }), 'label');
+ await user.type(screen.getByRole('searchbox', { name: 'label' }), 'label');
expect(await screen.findByText('label')).toBeInTheDocument();
expect(screen.queryByText('different')).not.toBeInTheDocument();
await user.keyboard('{Escape}');