index.tsx 5.5 KB

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