selectField.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import {Component} from 'react';
  2. import {OptionsType, ValueType} from 'react-select';
  3. import {openConfirmModal} from 'sentry/components/confirm';
  4. import InputField, {InputFieldProps} from 'sentry/components/forms/inputField';
  5. import SelectControl, {ControlProps} from 'sentry/components/forms/selectControl';
  6. import {t} from 'sentry/locale';
  7. import {Choices, SelectValue} from 'sentry/types';
  8. export interface SelectFieldProps<OptionType>
  9. extends InputFieldProps,
  10. Omit<ControlProps<OptionType>, 'onChange'> {
  11. /**
  12. * Should the select be clearable?
  13. */
  14. allowClear?: boolean;
  15. /**
  16. * Should the select allow empty values?
  17. */
  18. allowEmpty?: boolean;
  19. /**
  20. * Allow specific options to be 'confirmed' with a confirmation message.
  21. *
  22. * The key is the value that should be confirmed, the value is the message
  23. * to display in the confirmation modal.
  24. *
  25. * XXX: This only works when using the new-style options format, and _only_
  26. * if the value object has a `value` attribute in the option. The types do
  27. * not correctly reflect this so be careful!
  28. */
  29. confirm?: Record<string, React.ReactNode>;
  30. /**
  31. * A label that is shown inside the select control.
  32. */
  33. inFieldLabel?: string;
  34. small?: boolean;
  35. }
  36. function getChoices<T>(props: SelectFieldProps<T>): Choices {
  37. const choices = props.choices;
  38. if (typeof choices === 'function') {
  39. return choices(props);
  40. }
  41. if (choices === undefined) {
  42. return [];
  43. }
  44. return choices;
  45. }
  46. /**
  47. * Required to type guard for OptionsType<T> which is a readonly Array
  48. */
  49. function isArray<T>(maybe: T | OptionsType<T>): maybe is OptionsType<T> {
  50. return Array.isArray(maybe);
  51. }
  52. export default class SelectField<OptionType extends SelectValue<any>> extends Component<
  53. SelectFieldProps<OptionType>
  54. > {
  55. static defaultProps = {
  56. allowClear: false,
  57. allowEmpty: false,
  58. placeholder: '--',
  59. escapeMarkup: true,
  60. multiple: false,
  61. small: false,
  62. formatMessageValue: (value, props) =>
  63. (getChoices(props).find(choice => choice[0] === value) || [null, value])[1],
  64. };
  65. handleChange = (
  66. onBlur: InputFieldProps['onBlur'],
  67. onChange: InputFieldProps['onChange'],
  68. optionObj: ValueType<OptionType>
  69. ) => {
  70. let value: any = undefined;
  71. // If optionObj is empty, then it probably means that the field was "cleared"
  72. if (!optionObj) {
  73. value = optionObj;
  74. } else if (this.props.multiple && isArray(optionObj)) {
  75. // List of optionObjs
  76. value = optionObj.map(({value: val}) => val);
  77. } else if (!isArray(optionObj)) {
  78. value = optionObj.value;
  79. }
  80. onChange?.(value, {});
  81. onBlur?.(value, {});
  82. };
  83. render() {
  84. const {allowClear, confirm, multiple, small, ...otherProps} = this.props;
  85. return (
  86. <InputField
  87. {...otherProps}
  88. alignRight={small}
  89. field={({onChange, onBlur, required: _required, ...props}) => (
  90. <SelectControl
  91. {...props}
  92. clearable={allowClear}
  93. multiple={multiple}
  94. onChange={val => {
  95. if (!confirm) {
  96. this.handleChange(onBlur, onChange, val);
  97. return;
  98. }
  99. // Support 'confirming' selections. This only works with
  100. // `val` objects that use the new-style options format
  101. const previousValue = props.value?.toString();
  102. // `val` may be null if clearing the select for an optional field
  103. const newValue = val?.value?.toString();
  104. // Value not marked for confirmation, or hasn't changed
  105. if (!confirm[newValue] || previousValue === newValue) {
  106. this.handleChange(onBlur, onChange, val);
  107. return;
  108. }
  109. openConfirmModal({
  110. onConfirm: () => this.handleChange(onBlur, onChange, val),
  111. message: confirm[val?.value] ?? t('Continue with these changes?'),
  112. });
  113. }}
  114. />
  115. )}
  116. />
  117. );
  118. }
  119. }