multipleCheckboxField.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import classNames from 'classnames';
  2. import FormField from 'sentry/components/deprecatedforms/formField';
  3. import Tooltip from 'sentry/components/tooltip';
  4. import {IconQuestion} from 'sentry/icons';
  5. import {Choices} from 'sentry/types';
  6. import {defined} from 'sentry/utils';
  7. type Value = string | number | boolean;
  8. type Props = {
  9. choices: Choices;
  10. hideLabelDivider?: boolean;
  11. } & FormField['props'];
  12. type State = FormField['state'] & {
  13. values: Value[];
  14. };
  15. export default class MultipleCheckboxField extends FormField<Props, State> {
  16. onChange = (e: React.ChangeEvent<HTMLInputElement>, _value?: Value) => {
  17. const value = _value as Value; // Casting here to allow _value to be optional, which it has to be since it's overloaded.
  18. let allValues = this.state.values;
  19. if (e.target.checked) {
  20. if (allValues) {
  21. allValues = [...allValues, value];
  22. } else {
  23. allValues = [value];
  24. }
  25. } else {
  26. allValues = allValues.filter(v => v !== value);
  27. }
  28. this.setValues(allValues);
  29. };
  30. setValues(values: Value[]) {
  31. const form = (this.context || {}).form;
  32. this.setState(
  33. {
  34. values,
  35. },
  36. () => {
  37. const finalValue = this.coerceValue(this.state.values);
  38. this.props.onChange && this.props.onChange(finalValue);
  39. form && form.onFieldChange(this.props.name, finalValue);
  40. }
  41. );
  42. }
  43. render() {
  44. const {
  45. required,
  46. className,
  47. disabled,
  48. disabledReason,
  49. label,
  50. help,
  51. choices,
  52. hideLabelDivider,
  53. style,
  54. } = this.props;
  55. const {error} = this.state;
  56. const cx = classNames(className, 'control-group', {
  57. 'has-error': error,
  58. });
  59. // Hacky, but this isn't really a form label vs the checkbox labels, but
  60. // we want to treat it as one (i.e. for "required" indicator)
  61. const labelCx = classNames({
  62. required,
  63. });
  64. const shouldShowDisabledReason = disabled && disabledReason;
  65. return (
  66. <div style={style} className={cx}>
  67. <div className={labelCx}>
  68. <div className="controls">
  69. <label
  70. className="control-label"
  71. style={{
  72. display: 'block',
  73. marginBottom: !hideLabelDivider ? 10 : undefined,
  74. borderBottom: !hideLabelDivider ? '1px solid #f1eff3' : undefined,
  75. }}
  76. >
  77. {label}
  78. {shouldShowDisabledReason && (
  79. <Tooltip title={disabledReason}>
  80. <span className="disabled-indicator">
  81. <IconQuestion size="xs" />
  82. </span>
  83. </Tooltip>
  84. )}
  85. </label>
  86. {help && <p className="help-block">{help}</p>}
  87. {error && <p className="error">{error}</p>}
  88. </div>
  89. </div>
  90. <div className="control-list">
  91. {choices.map(([value, choiceLabel]) => (
  92. <label className="checkbox" key={value}>
  93. <input
  94. type="checkbox"
  95. value={value}
  96. onChange={e => this.onChange(e, value)}
  97. disabled={disabled}
  98. checked={
  99. defined(this.state.values) && this.state.values.indexOf(value) !== -1
  100. }
  101. />
  102. {choiceLabel}
  103. </label>
  104. ))}
  105. </div>
  106. </div>
  107. );
  108. }
  109. }