multipleCheckboxField.tsx 3.4 KB

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