index.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import QuestionTooltip from 'sentry/components/questionTooltip';
  2. import ControlState from './controlState';
  3. import FieldControl from './fieldControl';
  4. import FieldDescription from './fieldDescription';
  5. import FieldErrorReason from './fieldErrorReason';
  6. import FieldHelp from './fieldHelp';
  7. import FieldLabel from './fieldLabel';
  8. import FieldQuestion from './fieldQuestion';
  9. import FieldRequiredBadge from './fieldRequiredBadge';
  10. import FieldWrapper from './fieldWrapper';
  11. import {FieldGroupProps} from './types';
  12. /**
  13. * A component to render a Field (i.e. label + help + form "control"),
  14. * generally inside of a Panel.
  15. *
  16. * This is unconnected to any Form state
  17. */
  18. function Field({
  19. className,
  20. disabled = false,
  21. inline = true,
  22. visible = true,
  23. ...rest
  24. }: FieldGroupProps) {
  25. const props = {
  26. inline,
  27. disabled,
  28. visible,
  29. ...rest,
  30. };
  31. const {
  32. alignRight,
  33. children,
  34. controlClassName,
  35. disabledReason,
  36. error,
  37. flexibleControlStateSize,
  38. help,
  39. hideLabel,
  40. highlighted,
  41. id,
  42. isSaved,
  43. isSaving,
  44. label,
  45. labelText,
  46. required,
  47. showHelpInTooltip,
  48. stacked,
  49. style,
  50. } = props;
  51. const isVisible = typeof visible === 'function' ? visible(props) : visible;
  52. const isDisabled = typeof disabled === 'function' ? disabled(props) : disabled;
  53. if (!isVisible) {
  54. return null;
  55. }
  56. const helpElement = typeof help === 'function' ? help(props) : help;
  57. const shouldRenderLabel = !hideLabel && !!label;
  58. const controlProps = {
  59. inline,
  60. alignRight,
  61. disabledReason,
  62. flexibleControlStateSize,
  63. controlState: <ControlState error={!!error} isSaving={isSaving} isSaved={isSaved} />,
  64. errorState: error ? <FieldErrorReason>{error}</FieldErrorReason> : null,
  65. className: controlClassName,
  66. disabled: isDisabled,
  67. help: helpElement,
  68. };
  69. // See comments in prop types
  70. const control =
  71. typeof children === 'function' ? (
  72. children({...props, ...controlProps})
  73. ) : (
  74. <FieldControl {...controlProps}>{children}</FieldControl>
  75. );
  76. // Provide an `aria-label` to the FieldDescription label if our label is a
  77. // string value. This helps with testing and accessability. Without this the
  78. // aria label contains the entire description.
  79. const ariaLabel = labelText ?? (typeof label === 'string' ? label : undefined);
  80. // The help ID is used for the input element to have an `aria-describedby`
  81. const helpId = `${id}_help`;
  82. return (
  83. <FieldWrapper
  84. className={className}
  85. inline={inline}
  86. stacked={stacked}
  87. highlighted={highlighted}
  88. hasControlState={!flexibleControlStateSize}
  89. style={style}
  90. >
  91. {(shouldRenderLabel || helpElement) && (
  92. <FieldDescription inline={inline} htmlFor={id} aria-label={ariaLabel}>
  93. {shouldRenderLabel && (
  94. <FieldLabel disabled={isDisabled}>
  95. <span>
  96. {label}
  97. {required && <FieldRequiredBadge />}
  98. </span>
  99. {helpElement && showHelpInTooltip && (
  100. <FieldQuestion>
  101. <QuestionTooltip position="top" size="sm" title={helpElement} />
  102. </FieldQuestion>
  103. )}
  104. </FieldLabel>
  105. )}
  106. {helpElement && !showHelpInTooltip && (
  107. <FieldHelp id={helpId} stacked={stacked} inline={inline}>
  108. {helpElement}
  109. </FieldHelp>
  110. )}
  111. </FieldDescription>
  112. )}
  113. {control}
  114. </FieldWrapper>
  115. );
  116. }
  117. export default Field;