You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchSelect.tsx 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import classNames from 'classnames';
  21. import { omit } from 'lodash';
  22. import React, { RefObject } from 'react';
  23. import { GroupBase, InputProps, components } from 'react-select';
  24. import AsyncSelect, { AsyncProps } from 'react-select/async';
  25. import Select from 'react-select/dist/declarations/src/Select';
  26. import { INPUT_SIZES } from '../../helpers';
  27. import { Key } from '../../helpers/keyboard';
  28. import { LabelValueSelectOption, SelectProps, selectStyle } from './InputSelect';
  29. import { SearchSelectControlledInput } from './SearchSelectControlledInput';
  30. type SearchSelectProps<
  31. V,
  32. Option extends LabelValueSelectOption<V>,
  33. IsMulti extends boolean = false,
  34. Group extends GroupBase<Option> = GroupBase<Option>,
  35. > = SelectProps<V, Option, IsMulti, Group> & AsyncProps<Option, IsMulti, Group>;
  36. export function SearchSelect<
  37. V,
  38. Option extends LabelValueSelectOption<V>,
  39. IsMulti extends boolean = false,
  40. Group extends GroupBase<Option> = GroupBase<Option>,
  41. >({
  42. size = 'full',
  43. selectRef,
  44. ...props
  45. }: SearchSelectProps<V, Option, IsMulti, Group> & {
  46. selectRef?: RefObject<Select<Option, IsMulti, Group>>;
  47. }) {
  48. const styles = selectStyle<V, Option, IsMulti, Group>({ size });
  49. return (
  50. <AsyncSelect<Option, IsMulti, Group>
  51. {...omit(props, 'className', 'large')}
  52. className={classNames('react-select', props.className)}
  53. classNamePrefix="react-select"
  54. classNames={{
  55. control: ({ isDisabled }) =>
  56. classNames(
  57. 'sw-border-0 sw-rounded-2 sw-outline-none sw-shadow-none',
  58. isDisabled && 'sw-pointer-events-none sw-cursor-not-allowed',
  59. ),
  60. indicatorsContainer: () => 'sw-hidden',
  61. input: () => `sw-flex sw-w-full sw-p-0 sw-m-0`,
  62. valueContainer: () => `sw-px-3 sw-pb-1 sw-mb-1 sw-pt-4`,
  63. placeholder: () => 'sw-hidden',
  64. ...props.classNames,
  65. }}
  66. components={{
  67. Input: SearchSelectInput,
  68. ...props.components,
  69. }}
  70. ref={selectRef}
  71. styles={{
  72. ...styles,
  73. menu: (base, props) => ({
  74. ...styles.menu?.(base, props),
  75. width: `calc(${INPUT_SIZES[size]} - 2px)`,
  76. }),
  77. }}
  78. />
  79. );
  80. }
  81. export function SearchSelectInput<
  82. V,
  83. Option extends LabelValueSelectOption<V>,
  84. IsMulti extends boolean = false,
  85. Group extends GroupBase<Option> = GroupBase<Option>,
  86. >(props: InputProps<Option, IsMulti, Group>) {
  87. const {
  88. selectProps: { placeholder, isLoading, inputValue, minLength },
  89. } = props;
  90. const onChange = (v: string, prevValue = '') => {
  91. props.selectProps.onInputChange(v, { action: 'input-change', prevInputValue: prevValue });
  92. };
  93. const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
  94. const target = event.target as HTMLInputElement;
  95. if (event.key === Key.Escape && target.value !== '') {
  96. event.stopPropagation();
  97. onChange('');
  98. }
  99. };
  100. return (
  101. <SearchSelectControlledInput
  102. loading={isLoading && inputValue.length >= (minLength ?? 0)}
  103. minLength={minLength}
  104. onChange={onChange}
  105. size="full"
  106. value={inputValue}
  107. >
  108. <components.Input
  109. {...props}
  110. onKeyDown={handleKeyDown}
  111. placeholder={placeholder as string}
  112. style={{}}
  113. />
  114. </SearchSelectControlledInput>
  115. );
  116. }