index.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {Component} from 'react';
  2. import ControlState, {
  3. ControlStateProps,
  4. } from 'sentry/components/forms/field/controlState';
  5. import FieldControl, {
  6. FieldControlProps,
  7. } from 'sentry/components/forms/field/fieldControl';
  8. import FieldDescription from 'sentry/components/forms/field/fieldDescription';
  9. import FieldErrorReason from 'sentry/components/forms/field/fieldErrorReason';
  10. import FieldHelp from 'sentry/components/forms/field/fieldHelp';
  11. import FieldLabel from 'sentry/components/forms/field/fieldLabel';
  12. import FieldRequiredBadge from 'sentry/components/forms/field/fieldRequiredBadge';
  13. import FieldWrapper, {
  14. FieldWrapperProps,
  15. } from 'sentry/components/forms/field/fieldWrapper';
  16. import QuestionTooltip from 'sentry/components/questionTooltip';
  17. import FieldQuestion from './fieldQuestion';
  18. interface InheritedFieldWrapperProps
  19. extends Pick<
  20. FieldWrapperProps,
  21. 'inline' | 'stacked' | 'highlighted' | 'hasControlState'
  22. > {}
  23. interface InheritedFieldControlProps
  24. extends Omit<
  25. FieldControlProps,
  26. 'children' | 'disabled' | 'className' | 'help' | 'errorState'
  27. > {}
  28. interface InheritedControlStateProps
  29. extends Omit<ControlStateProps, 'children' | 'error'> {}
  30. export interface FieldProps
  31. extends InheritedFieldControlProps,
  32. InheritedFieldWrapperProps,
  33. InheritedControlStateProps {
  34. // TODO(TS): Do we need this?
  35. /**
  36. * The control to render. May be given a function to render with resolved
  37. * props.
  38. */
  39. children?: React.ReactNode | ((props: ChildRenderProps) => React.ReactNode);
  40. /**
  41. * The classname of the field
  42. */
  43. className?: string;
  44. /**
  45. * The classname of the field control
  46. */
  47. controlClassName?: string;
  48. /**
  49. * Should field be disabled?
  50. */
  51. disabled?: boolean | ((props: FieldProps) => boolean);
  52. /**
  53. * Error message to display for the field
  54. */
  55. error?: string;
  56. /**
  57. * Help or description of the field
  58. */
  59. help?: React.ReactNode | React.ReactElement | ((props: FieldProps) => React.ReactNode);
  60. /**
  61. * Should the label be rendered for the field?
  62. */
  63. hideLabel?: boolean;
  64. /**
  65. * The control's `id` property
  66. */
  67. id?: string;
  68. /**
  69. * User-facing field name
  70. */
  71. label?: React.ReactNode;
  72. /**
  73. * Show "required" indicator
  74. */
  75. required?: boolean;
  76. /**
  77. * Displays the help element in the tooltip
  78. */
  79. showHelpInTooltip?: boolean;
  80. /**
  81. * Additional inline styles for the field
  82. */
  83. style?: React.CSSProperties;
  84. validate?: Function;
  85. /**
  86. * Should field be visible
  87. */
  88. visible?: boolean | ((props: FieldProps) => boolean);
  89. }
  90. interface ChildRenderProps extends Omit<FieldProps, 'className' | 'disabled'> {
  91. controlState: React.ReactNode;
  92. errorState: React.ReactNode | null;
  93. help: React.ReactNode;
  94. disabled?: boolean;
  95. }
  96. /**
  97. * A component to render a Field (i.e. label + help + form "control"),
  98. * generally inside of a Panel.
  99. *
  100. * This is unconnected to any Form state
  101. */
  102. class Field extends Component<FieldProps> {
  103. static defaultProps = {
  104. alignRight: false,
  105. inline: true,
  106. disabled: false,
  107. required: false,
  108. visible: true,
  109. showHelpInTooltip: false,
  110. };
  111. render() {
  112. const {className, ...otherProps} = this.props;
  113. const {
  114. controlClassName,
  115. alignRight,
  116. inline,
  117. highlighted,
  118. required,
  119. visible,
  120. disabled,
  121. disabledReason,
  122. error,
  123. flexibleControlStateSize,
  124. help,
  125. id,
  126. isSaving,
  127. isSaved,
  128. label,
  129. hideLabel,
  130. stacked,
  131. children,
  132. style,
  133. showHelpInTooltip,
  134. } = otherProps;
  135. const isVisible = typeof visible === 'function' ? visible(this.props) : visible;
  136. const isDisabled = typeof disabled === 'function' ? disabled(this.props) : disabled;
  137. let Control: React.ReactNode;
  138. if (!isVisible) {
  139. return null;
  140. }
  141. const helpElement = typeof help === 'function' ? help(this.props) : help;
  142. const controlProps = {
  143. className: controlClassName,
  144. inline,
  145. alignRight,
  146. disabled: isDisabled,
  147. disabledReason,
  148. flexibleControlStateSize,
  149. help: helpElement,
  150. errorState: error ? <FieldErrorReason>{error}</FieldErrorReason> : null,
  151. controlState: <ControlState error={error} isSaving={isSaving} isSaved={isSaved} />,
  152. };
  153. // See comments in prop types
  154. if (children instanceof Function) {
  155. Control = children({...otherProps, ...controlProps});
  156. } else {
  157. Control = <FieldControl {...controlProps}>{children}</FieldControl>;
  158. }
  159. return (
  160. <FieldWrapper
  161. className={className}
  162. inline={inline}
  163. stacked={stacked}
  164. highlighted={highlighted}
  165. hasControlState={!flexibleControlStateSize}
  166. style={style}
  167. >
  168. {((label && !hideLabel) || helpElement) && (
  169. <FieldDescription inline={inline} htmlFor={id}>
  170. {label && !hideLabel && (
  171. <FieldLabel disabled={isDisabled}>
  172. <span>
  173. {label}
  174. {required && <FieldRequiredBadge />}
  175. </span>
  176. {helpElement && showHelpInTooltip && (
  177. <FieldQuestion>
  178. <QuestionTooltip position="top" size="sm" title={helpElement} />
  179. </FieldQuestion>
  180. )}
  181. </FieldLabel>
  182. )}
  183. {helpElement && !showHelpInTooltip && (
  184. <FieldHelp stacked={stacked} inline={inline}>
  185. {helpElement}
  186. </FieldHelp>
  187. )}
  188. </FieldDescription>
  189. )}
  190. {Control}
  191. </FieldWrapper>
  192. );
  193. }
  194. }
  195. export default Field;