diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2024-05-02 17:29:48 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-05-06 20:02:40 +0000 |
commit | 422081485bf8e9646921f48d7b1f8d54b9854bd7 (patch) | |
tree | 4f05d3e9ff70bc1b3788bdd5aa48ffca03999c44 | |
parent | 6e3bc57e5e081c223b9d527f4de45c6b90e132f5 (diff) | |
download | sonarqube-422081485bf8e9646921f48d7b1f8d54b9854bd7.tar.gz sonarqube-422081485bf8e9646921f48d7b1f8d54b9854bd7.zip |
SONAR-22168 Align InputSelect
26 files changed, 254 insertions, 184 deletions
diff --git a/server/sonar-web/design-system/src/components/input/DatePickerCustomCalendarNavigation.tsx b/server/sonar-web/design-system/src/components/input/DatePickerCustomCalendarNavigation.tsx index ac651e16c34..467fdab81a4 100644 --- a/server/sonar-web/design-system/src/components/input/DatePickerCustomCalendarNavigation.tsx +++ b/server/sonar-web/design-system/src/components/input/DatePickerCustomCalendarNavigation.tsx @@ -33,9 +33,9 @@ import { useDayPicker, } from 'react-day-picker'; import { useIntl } from 'react-intl'; +import { InputSelect } from '../../sonar-aligned/components/input'; import { InteractiveIcon } from '../InteractiveIcon'; import { ChevronLeftIcon, ChevronRightIcon } from '../icons'; -import { InputSelect } from './InputSelect'; const YEARS_TO_DISPLAY = 10; const MONTHS_IN_A_YEAR = 12; diff --git a/server/sonar-web/design-system/src/components/input/DiscreetSelect.tsx b/server/sonar-web/design-system/src/components/input/DiscreetSelect.tsx index 13b6068c3fa..6981cefffc5 100644 --- a/server/sonar-web/design-system/src/components/input/DiscreetSelect.tsx +++ b/server/sonar-web/design-system/src/components/input/DiscreetSelect.tsx @@ -18,44 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; +import { GroupBase, OnChangeValue } from 'react-select'; import tw from 'twin.macro'; import { themeBorder, themeColor, themeContrast } from '../../helpers/theme'; -import { InputSizeKeys } from '../../types/theme'; -import { InputSelect, LabelValueSelectOption } from './InputSelect'; +import { InputSelect, SelectProps } from '../../sonar-aligned/components/input'; -interface Props<V> { - className?: string; - components?: Parameters<typeof InputSelect>[0]['components']; +type DiscreetProps< + Option, + IsMulti extends boolean = false, + Group extends GroupBase<Option> = GroupBase<Option>, +> = SelectProps<Option, IsMulti, Group> & { customValue?: JSX.Element; - isDisabled?: boolean; - menuIsOpen?: boolean; - onMenuClose?: () => void; - onMenuOpen?: () => void; - options: Array<LabelValueSelectOption<V>>; - setValue: ({ value }: LabelValueSelectOption<V>) => void; - size?: InputSizeKeys; - value: V; -} + setValue: (value: OnChangeValue<Option, IsMulti>) => void; +}; -export function DiscreetSelect<V>({ - className, - customValue, - onMenuOpen, - options, - size = 'small', - setValue, - value, - ...props -}: Props<V>) { +export function DiscreetSelect< + Option, + IsMulti extends boolean = false, + Group extends GroupBase<Option> = GroupBase<Option>, +>({ customValue, size = 'small', setValue, ...props }: DiscreetProps<Option, IsMulti, Group>) { return ( - <StyledSelect - className={className} + <StyledSelect<Option, IsMulti, Group> onChange={setValue} - onMenuOpen={onMenuOpen} - options={options} placeholder={customValue} size={size} - value={options.find((item) => item.value === value)} {...props} /> ); @@ -121,4 +107,4 @@ const StyledSelect = styled(InputSelect)` & .react-select__control--menu-is-open { ${tw`sw-border-none`}; } -`; +` as typeof InputSelect; 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 673e8385ad6..009dbb9d712 100644 --- a/server/sonar-web/design-system/src/components/input/SearchSelect.tsx +++ b/server/sonar-web/design-system/src/components/input/SearchSelect.tsx @@ -19,35 +19,26 @@ */ import classNames from 'classnames'; import { omit } from 'lodash'; -import React, { RefObject } from 'react'; +import React from 'react'; 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 { SelectProps, selectStyle } from '../../sonar-aligned/components/input'; import { InputSearch } from './InputSearch'; -import { LabelValueSelectOption, SelectProps, selectStyle } from './InputSelect'; type SearchSelectProps< - V, - Option extends LabelValueSelectOption<V>, + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, -> = SelectProps<V, Option, IsMulti, Group> & AsyncProps<Option, IsMulti, Group>; +> = SelectProps<Option, IsMulti, Group> & AsyncProps<Option, IsMulti, Group>; export function SearchSelect< - V, - Option extends LabelValueSelectOption<V>, + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, ->({ - size = 'full', - selectRef, - ...props -}: SearchSelectProps<V, Option, IsMulti, Group> & { - selectRef?: RefObject<Select<Option, IsMulti, Group>>; -}) { - const styles = selectStyle<V, Option, IsMulti, Group>({ size }); +>({ size = 'full', selectRef, ...props }: SearchSelectProps<Option, IsMulti, Group>) { + const styles = selectStyle<Option, IsMulti, Group>({ size }); return ( <AsyncSelect<Option, IsMulti, Group> {...omit(props, 'className', 'large')} @@ -82,8 +73,7 @@ export function SearchSelect< } export function SearchSelectInput< - V, - Option extends LabelValueSelectOption<V>, + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, >(props: InputProps<Option, IsMulti, Group>) { 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 358197c0621..e103e3e4b5d 100644 --- a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx +++ b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx @@ -30,10 +30,14 @@ import { AsyncProps } from 'react-select/async'; import Select from 'react-select/dist/declarations/src/Select'; import tw from 'twin.macro'; import { PopupPlacement, PopupZLevel, themeBorder } from '../../helpers'; +import { + IconOption, + LabelValueSelectOption, + SelectProps, +} from '../../sonar-aligned/components/input'; import { InputSizeKeys } from '../../types/theme'; import { DropdownToggler } from '../DropdownToggler'; import { SearchHighlighterContext } from '../SearchHighlighter'; -import { IconOption, LabelValueSelectOption, SelectProps } from './InputSelect'; import { SearchSelect } from './SearchSelect'; import { SearchSelectDropdownControl } from './SearchSelectDropdownControl'; @@ -48,7 +52,7 @@ export interface SearchSelectDropdownProps< Option extends LabelValueSelectOption<V>, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, -> extends SelectProps<V, Option, IsMulti, Group>, +> extends SelectProps<Option, IsMulti, Group>, AsyncProps<Option, IsMulti, Group> { className?: string; controlAriaLabel?: string; diff --git a/server/sonar-web/design-system/src/components/input/__tests__/DiscreetSelect-test.tsx b/server/sonar-web/design-system/src/components/input/__tests__/DiscreetSelect-test.tsx index 36666870f47..4b8c057021d 100644 --- a/server/sonar-web/design-system/src/components/input/__tests__/DiscreetSelect-test.tsx +++ b/server/sonar-web/design-system/src/components/input/__tests__/DiscreetSelect-test.tsx @@ -24,7 +24,7 @@ import { FCProps } from '../../../types/misc'; import { DiscreetSelect } from '../DiscreetSelect'; it('should render discreet select and invoke CB on value click', async () => { - const value = 'foo'; + const value = options[0]; const setValue = jest.fn(); const user = userEvent.setup(); @@ -36,24 +36,21 @@ it('should render discreet select and invoke CB on value click', async () => { expect(setValue).toHaveBeenCalled(); }); +const options = [ + { label: 'foo-bar', value: 'foo', default: 1 }, + { + label: 'bar-foo', + value: 'bar', + Icon: ( + <span role="note" title="Icon"> + Icon + </span> + ), + }, +]; + function setupWithProps(props: Partial<FCProps<typeof DiscreetSelect>>) { return render( - <DiscreetSelect - options={[ - { label: 'foo-bar', value: 'foo' }, - { - label: 'bar-foo', - value: 'bar', - Icon: ( - <span role="note" title="Icon"> - Icon - </span> - ), - }, - ]} - setValue={jest.fn()} - value="foo" - {...props} - />, + <DiscreetSelect options={options} setValue={jest.fn()} value={options[0]} {...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 0e0c83c33bc..893f886f8c4 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 @@ -20,8 +20,8 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithContext } from '../../../helpers/testUtils'; +import { LabelValueSelectOption } from '../../../sonar-aligned'; import { FCProps } from '../../../types/misc'; -import { LabelValueSelectOption } from '../InputSelect'; import { SearchSelectDropdown } from '../SearchSelectDropdown'; const defaultOptions = [ diff --git a/server/sonar-web/design-system/src/components/input/index.ts b/server/sonar-web/design-system/src/components/input/index.ts index 1545de7f250..958e8a66e96 100644 --- a/server/sonar-web/design-system/src/components/input/index.ts +++ b/server/sonar-web/design-system/src/components/input/index.ts @@ -26,7 +26,6 @@ export * from './FormField'; export * from './InputField'; export * from './InputMultiSelect'; export * from './InputSearch'; -export * from './InputSelect'; export * from './MultiSelectMenu'; export * from './RadioButton'; export * from './SearchSelect'; diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/index.ts b/server/sonar-web/design-system/src/sonar-aligned/components/index.ts index edbf22d1b66..9b86ae7d91a 100644 --- a/server/sonar-web/design-system/src/sonar-aligned/components/index.ts +++ b/server/sonar-web/design-system/src/sonar-aligned/components/index.ts @@ -24,4 +24,5 @@ export * from './MetricsRatingBadge'; export * from './Table'; export * from './ToggleButton'; export * from './buttons'; +export * from './input'; export * from './typography'; diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/input/InputSelect.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/input/InputSelect.tsx new file mode 100644 index 00000000000..58d1d499ccf --- /dev/null +++ b/server/sonar-web/design-system/src/sonar-aligned/components/input/InputSelect.tsx @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 classNames from 'classnames'; +import { omit } from 'lodash'; +import { useMemo } from 'react'; +import ReactSelect, { GroupBase } from 'react-select'; +import { + ClearIndicator, + DropdownIndicator, + IconOption, + SelectProps, + SingleValue, + selectStyle, +} from './SelectCommon'; + +export function InputSelect< + Option, + IsMulti extends boolean = false, + Group extends GroupBase<Option> = GroupBase<Option>, +>({ + size = 'medium', + className, + options, + getOptionLabel, + selectRef, + shouldSortOption = false, + ...props +}: SelectProps<Option, IsMulti, Group>) { + const orderedOptions = useMemo(() => { + if (!options || options.length === 0) { + return options; + } + + if (shouldSortOption) { + return (options as Option[]).sort((a, b) => { + const nameA = getOptionLabel?.(a).toUpperCase() ?? ''; + const nameB = getOptionLabel?.(b).toUpperCase() ?? ''; + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + + return 0; + }); + } + + return options; + }, [shouldSortOption, getOptionLabel, options]); + + return ( + <ReactSelect<Option, IsMulti, Group> + {...omit(props, 'className', 'large')} + className={classNames('react-select', className)} + classNamePrefix="react-select" + classNames={{ + container: () => 'sw-relative sw-inline-block sw-align-middle', + placeholder: () => 'sw-truncate sw-leading-4', + menu: () => 'sw-z-dropdown-menu sw-ml-1/2 sw-mt-2', + menuList: () => 'sw-overflow-y-auto sw-py-2 sw-max-h-[12.25rem]', + clearIndicator: () => 'sw-p-0', + dropdownIndicator: () => classNames(props.isClearable && 'sw-p-0'), + control: ({ isDisabled }) => + classNames( + 'sw-box-border sw-rounded-2 sw-overflow-hidden', + isDisabled && 'sw-pointer-events-none sw-cursor-not-allowed', + ), + option: ({ isDisabled }) => + classNames( + 'it__select-option sw-py-2 sw-px-3 sw-cursor-pointer', + isDisabled && 'sw-pointer-events-none sw-cursor-not-allowed', + ), + ...props.classNames, + }} + components={{ + ClearIndicator, + Option: IconOption, + SingleValue, + DropdownIndicator, + IndicatorSeparator: null, + ...props.components, + }} + getOptionLabel={getOptionLabel} + isClearable={props.isClearable ?? false} + isSearchable={props.isSearchable ?? false} + onMenuOpen={props.onMenuOpen} + options={orderedOptions} + ref={selectRef} + styles={selectStyle({ size })} + /> + ); +} diff --git a/server/sonar-web/design-system/src/components/input/InputSelect.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/input/SelectCommon.tsx index 78be3d1c4af..8414e6f3b79 100644 --- a/server/sonar-web/design-system/src/components/input/InputSelect.tsx +++ b/server/sonar-web/design-system/src/sonar-aligned/components/input/SelectCommon.tsx @@ -18,11 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { useTheme as themeInfo } from '@emotion/react'; -import classNames from 'classnames'; -import { omit } from 'lodash'; -import { ReactNode } from 'react'; +import { RefObject } from 'react'; import { useIntl } from 'react-intl'; -import ReactSelect, { +import { ClearIndicatorProps, GroupBase, Props as NamedProps, @@ -30,45 +28,42 @@ import ReactSelect, { StylesConfig, components, } from 'react-select'; -import { INPUT_SIZES } from '../../helpers'; -import { themeBorder, themeColor, themeContrast } from '../../helpers/theme'; -import { InputSizeKeys } from '../../types/theme'; -import { InteractiveIcon } from '../InteractiveIcon'; -import { SearchHighlighter } from '../SearchHighlighter'; +import Select from 'react-select/dist/declarations/src/Select'; +import { InteractiveIcon } from '../../../components/InteractiveIcon'; +import { SearchHighlighter } from '../../../components/SearchHighlighter'; +import { ChevronDownIcon, CloseIcon } from '../../../components/icons'; +import { INPUT_SIZES } from '../../../helpers'; +import { themeBorder, themeColor, themeContrast } from '../../../helpers/theme'; +import { InputSizeKeys } from '../../../types/theme'; -import { ChevronDownIcon, CloseIcon } from '../icons'; - -export interface LabelValueSelectOption<V> { - Icon?: ReactNode; - label: string; - value: V; -} - -interface ExtensionProps { +export interface ExtensionProps< + Option, + IsMulti extends boolean = false, + Group extends GroupBase<Option> = GroupBase<Option>, +> { clearLabel?: string; + selectRef?: RefObject<Select<Option, IsMulti, Group>>; + shouldSortOption?: boolean; size?: InputSizeKeys; } export type SelectProps< - V, - Option extends LabelValueSelectOption<V>, + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, -> = NamedProps<Option, IsMulti, Group> & ExtensionProps; +> = NamedProps<Option, IsMulti, Group> & ExtensionProps<Option, IsMulti, Group>; export function IconOption< - V, - Option extends LabelValueSelectOption<V>, + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, >(props: OptionProps<Option, IsMulti, Group>) { - const { - data: { label, Icon }, - } = props; + const { label, isSelected } = props; + const { Icon } = props.data as { Icon: JSX.Element }; return ( <components.Option {...props}> - <div className="sw-flex sw-items-center sw-gap-1"> + <div aria-selected={isSelected} className="sw-flex sw-items-center sw-gap-1" role="option"> {Icon} <SearchHighlighter>{label}</SearchHighlighter> </div> @@ -76,15 +71,13 @@ export function IconOption< ); } -function SingleValue< - V, - Option extends LabelValueSelectOption<V>, +export function SingleValue< + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, >(props: OptionProps<Option, IsMulti, Group>) { - const { - data: { label, Icon }, - } = props; + const label = props.selectProps.getOptionLabel(props.data); + const { Icon } = props.data as { Icon: JSX.Element }; return ( <components.SingleValue {...props}> @@ -96,14 +89,13 @@ function SingleValue< ); } -function ClearIndicator< - V, - Option extends LabelValueSelectOption<V>, +export function ClearIndicator< + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, >( props: ClearIndicatorProps<Option, IsMulti, Group> & { - selectProps: SelectProps<V, Option, IsMulti, Group>; + selectProps: SelectProps<Option, IsMulti, Group>; }, ) { const intl = useIntl(); @@ -123,9 +115,8 @@ function ClearIndicator< ); } -function DropdownIndicator< - V, - Option extends LabelValueSelectOption<V>, +export function DropdownIndicator< + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, >(props: OptionProps<Option, IsMulti, Group>) { @@ -138,55 +129,8 @@ function DropdownIndicator< ); } -export function InputSelect< - V, - Option extends LabelValueSelectOption<V>, - IsMulti extends boolean = false, - Group extends GroupBase<Option> = GroupBase<Option>, ->({ size = 'medium', className, ...props }: SelectProps<V, Option, IsMulti, Group>) { - return ( - <ReactSelect<Option, IsMulti, Group> - {...omit(props, 'className', 'large')} - className={classNames('react-select', className)} - classNamePrefix="react-select" - classNames={{ - container: () => 'sw-relative sw-inline-block sw-align-middle', - placeholder: () => 'sw-truncate sw-leading-4', - menu: () => 'sw-z-dropdown-menu sw-ml-1/2 sw-mt-2', - menuList: () => 'sw-overflow-y-auto sw-py-2 sw-max-h-[12.25rem]', - clearIndicator: () => 'sw-p-0', - dropdownIndicator: () => classNames(props.isClearable && 'sw-p-0'), - control: ({ isDisabled }) => - classNames( - 'sw-box-border sw-rounded-2 sw-overflow-hidden', - isDisabled && 'sw-pointer-events-none sw-cursor-not-allowed', - ), - option: ({ isDisabled }) => - classNames( - 'it__select-option sw-py-2 sw-px-3 sw-cursor-pointer', - isDisabled && 'sw-pointer-events-none sw-cursor-not-allowed', - ), - ...props.classNames, - }} - components={{ - ClearIndicator, - Option: IconOption, - SingleValue, - DropdownIndicator, - IndicatorSeparator: null, - ...props.components, - }} - isClearable={props.isClearable ?? false} - isSearchable={props.isSearchable ?? false} - onMenuOpen={props.onMenuOpen} - styles={selectStyle({ size })} - /> - ); -} - export function selectStyle< - V, - Option extends LabelValueSelectOption<V>, + Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>, >({ size }: { size: InputSizeKeys }): StylesConfig<Option, IsMulti, Group> { @@ -231,3 +175,9 @@ export function selectStyle< }), }; } + +export interface LabelValueSelectOption<V = string> { + Icon?: React.ReactNode; + label: string; + value: V; +} diff --git a/server/sonar-web/design-system/src/components/input/__tests__/InputSelect-test.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/input/__tests__/InputSelect-test.tsx index 7079cf5e6f8..f94686b1c53 100644 --- a/server/sonar-web/design-system/src/components/input/__tests__/InputSelect-test.tsx +++ b/server/sonar-web/design-system/src/sonar-aligned/components/input/__tests__/InputSelect-test.tsx @@ -19,8 +19,8 @@ */ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { renderWithContext } from '../../../helpers/testUtils'; -import { FCProps } from '../../../types/misc'; +import { renderWithContext } from '../../../../helpers/testUtils'; +import { FCProps } from '../../../../types/misc'; import { InputSelect } from '../InputSelect'; it('should render select input and be able to click and change', async () => { @@ -68,6 +68,18 @@ it('should render select input with disabled prop', () => { expect(screen.getByRole('combobox')).toBeDisabled(); }); +it('should render the select options with sorting when shouldSortOption is true and getOptionLabel passed', async () => { + const { user } = setupWithProps({ + shouldSortOption: true, + getOptionLabel: (o: { label: string }) => o.label, + }); + await user.click(screen.getByRole('combobox')); + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(2); + expect(options[0]).toHaveTextContent('bar-foo'); + expect(options[1]).toHaveTextContent('foo-bar'); +}); + function setupWithProps(props: Partial<FCProps<typeof InputSelect>>) { return renderWithContext( <InputSelect diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/input/index.ts b/server/sonar-web/design-system/src/sonar-aligned/components/input/index.ts new file mode 100644 index 00000000000..b8fd68f2a75 --- /dev/null +++ b/server/sonar-web/design-system/src/sonar-aligned/components/input/index.ts @@ -0,0 +1,22 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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. + */ + +export * from './InputSelect'; +export * from './SelectCommon'; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx index fc29f14295f..22fb8307f43 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx @@ -32,7 +32,7 @@ interface StatusFilterProps { export default function StatusFilter(props: Readonly<StatusFilterProps>) { const { id, value, onChange } = props; - const options: LabelValueSelectOption<string>[] = [ + const options: LabelValueSelectOption[] = [ { value: STATUSES.ALL, label: translate('background_task.status.ALL') }, { value: STATUSES.ALL_EXCEPT_PENDING, @@ -46,7 +46,7 @@ export default function StatusFilter(props: Readonly<StatusFilterProps>) { ]; const handleChange = React.useCallback( - ({ value }: LabelValueSelectOption<string>) => { + ({ value }: LabelValueSelectOption) => { onChange(value); }, [onChange], diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx index d2d21891ae8..9a7a620c706 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx @@ -30,7 +30,7 @@ interface Props { } export default class TypesFilter extends React.PureComponent<Props> { - handleChange = ({ value }: LabelValueSelectOption<string>) => { + handleChange = ({ value }: LabelValueSelectOption) => { this.props.onChange(value); }; @@ -43,7 +43,7 @@ export default class TypesFilter extends React.PureComponent<Props> { }; }); - const allOptions: LabelValueSelectOption<string>[] = [ + const allOptions: LabelValueSelectOption[] = [ { value: ALL_TYPES, label: translate('background_task.type.ALL') }, ...options, ]; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx index fce45c211b2..4f7142ab3a9 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx @@ -224,7 +224,7 @@ export default function CustomRuleFormModal(props: Readonly<Props>) { ); const StatusField = React.useMemo(() => { - const statusesOptions = RULE_STATUSES.map((status) => ({ + const statusesOptions = RULE_STATUSES.map((status: Status) => ({ label: translate('rules.status', status), value: status, })); diff --git a/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx index 7ef68d6dea5..7a0fb275869 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx @@ -304,7 +304,7 @@ export default function AzureProjectCreate({ function transformToOptions( projects: AzureProject[], repositories?: Dict<AzureRepository[]>, -): Array<GroupBase<LabelValueSelectOption<string>>> { +): Array<GroupBase<LabelValueSelectOption>> { return projects.map(({ name: projectName }) => ({ label: projectName, options: @@ -314,6 +314,6 @@ function transformToOptions( })); } -function transformToOption({ name }: AzureRepository): LabelValueSelectOption<string> { +function transformToOption({ name }: AzureRepository): LabelValueSelectOption { return { value: name, label: name }; } diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx index 1c206512972..a9081c898f5 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx @@ -212,9 +212,6 @@ export default function BitbucketCloudProjectCreate(props: Readonly<Props>) { ); } -function transformToOption({ - name, - slug, -}: BitbucketCloudRepository): LabelValueSelectOption<string> { +function transformToOption({ name, slug }: BitbucketCloudRepository): LabelValueSelectOption { return { value: slug, label: name }; } diff --git a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx index 9eab22d95f5..e22904aedf1 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx @@ -256,6 +256,6 @@ export default function GitHubProjectCreate(props: Readonly<Props>) { function transformToOption({ key, name, -}: GithubOrganization | GithubRepository): LabelValueSelectOption<string> { +}: GithubOrganization | GithubRepository): LabelValueSelectOption { return { value: key, label: name }; } diff --git a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx index 6b72f0db234..a5ff527ff4e 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx @@ -200,6 +200,6 @@ export default function GitlabProjectCreate(props: Readonly<Props>) { ); } -function transformToOption({ id, name }: GitlabProject): LabelValueSelectOption<string> { +function transformToOption({ id, name }: GitlabProject): LabelValueSelectOption { return { value: id, label: name }; } diff --git a/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx b/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx index 3acbfa32de7..781e9aa77b6 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/AssigneeSelect.tsx @@ -33,10 +33,10 @@ export const MIN_QUERY_LENGTH = 2; const UNASSIGNED = { value: '', label: translate('unassigned') }; export interface AssigneeSelectProps { - assignee?: SingleValue<LabelValueSelectOption<string>>; + assignee?: SingleValue<LabelValueSelectOption>; className?: string; issues: Issue[]; - onAssigneeSelect: (assignee: SingleValue<LabelValueSelectOption<string>>) => void; + onAssigneeSelect: (assignee: SingleValue<LabelValueSelectOption>) => void; inputId: string; } diff --git a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx index 3721dfe716c..439af636783 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx @@ -54,7 +54,7 @@ interface Props { interface FormFields { addTags?: Array<string>; - assignee?: SingleValue<LabelValueSelectOption<string>>; + assignee?: SingleValue<LabelValueSelectOption>; comment?: string; notifications?: boolean; removeTags?: Array<string>; @@ -126,7 +126,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { return this.props.fetchIssues({ additionalFields: 'actions,transitions', ps: MAX_PAGE_SIZE }); }; - handleAssigneeSelect = (assignee: SingleValue<LabelValueSelectOption<string>>) => { + handleAssigneeSelect = (assignee: SingleValue<LabelValueSelectOption>) => { this.setState({ assignee }); }; diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx index 78b18822c40..2fe4a93fb8b 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/ApplyTemplate.tsx @@ -89,7 +89,7 @@ export default class ApplyTemplate extends React.PureComponent<Props, State> { } }; - handlePermissionTemplateChange = ({ value }: LabelValueSelectOption<string>) => { + handlePermissionTemplateChange = ({ value }: LabelValueSelectOption) => { this.setState({ permissionTemplate: value }); }; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx index 578135e7c6e..7b76d0800d8 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFilters.tsx @@ -45,7 +45,7 @@ export default function ProjectActivityPageFilters(props: ProjectActivityPageFil const eventTypes = isApp ? Object.values(ApplicationAnalysisEventCategory) : Object.values(ProjectAnalysisEventCategory); - const options: LabelValueSelectOption<string>[] = eventTypes.map((category) => ({ + const options: LabelValueSelectOption[] = eventTypes.map((category) => ({ label: translate('event.category', category), value: category, })); @@ -64,7 +64,7 @@ export default function ProjectActivityPageFilters(props: ProjectActivityPageFil aria-label={translate('project_activity.filter_events')} className="sw-mr-8 sw-body-sm sw-w-abs-200" isClearable - onChange={(data: LabelValueSelectOption<string>) => handleCategoryChange(data)} + onChange={(data: LabelValueSelectOption) => handleCategoryChange(data)} options={options} placeholder={translate('project_activity.filter_events')} size="full" diff --git a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx index 49b50a6b71b..2d4a69f12f3 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx @@ -57,7 +57,7 @@ export default class PerspectiveSelect extends React.PureComponent<Props> { <InputSelect aria-labelledby="aria-projects-perspective" className="sw-mr-4 sw-body-sm" - onChange={(data: LabelValueSelectOption<string>) => this.handleChange(data)} + onChange={(data: LabelValueSelectOption) => this.handleChange(data)} options={options} placeholder={translate('project_activity.filter_events')} size="small" diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx index cc84a71c127..347f3ae37de 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx @@ -130,7 +130,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S } }; - handlePermissionTemplateChange = ({ value }: LabelValueSelectOption<string>) => { + handlePermissionTemplateChange = ({ value }: LabelValueSelectOption) => { this.setState({ permissionTemplate: value }); }; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx index 963f7f8302d..363abc3a087 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx @@ -77,7 +77,9 @@ export default function QualityGatePermissionsAddModalRenderer( noOptionsMessage={() => translate('no_results')} onChange={props.onSelection} loadOptions={props.handleSearch} - getOptionValue={({ value }) => (isUser(value) ? value.login : value.name)} + getOptionValue={({ value }: LabelValueSelectOption<UserBase | UserGroup>) => + isUser(value) ? value.login : value.name + } controlLabel={renderedSelection} components={{ Option, |