formPanel.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import {Component} from 'react';
  2. import styled from '@emotion/styled';
  3. import FieldFromConfig from 'sentry/components/forms/fieldFromConfig';
  4. import {Panel, PanelBody, PanelHeader} from 'sentry/components/panels';
  5. import {IconChevron} from 'sentry/icons';
  6. import {Scope} from 'sentry/types';
  7. import {sanitizeQuerySelector} from 'sentry/utils/sanitizeQuerySelector';
  8. import {FieldObject, JsonFormObject} from './type';
  9. type DefaultProps = {
  10. additionalFieldProps: {[key: string]: any};
  11. };
  12. type Props = DefaultProps & {
  13. /**
  14. * List of fields to render
  15. */
  16. fields: FieldObject[];
  17. access?: Set<Scope>;
  18. /** Can the PanelBody be hidden with a click? */
  19. collapsible?: boolean;
  20. /**
  21. * Disables the entire form
  22. */
  23. disabled?: boolean;
  24. features?: Record<string, any>;
  25. /**
  26. * The name of the field that should be highlighted
  27. */
  28. highlighted?: string;
  29. /**
  30. * Renders inside of PanelBody before PanelBody close
  31. */
  32. renderFooter?: (arg: JsonFormObject) => React.ReactNode;
  33. /**
  34. * Renders inside of PanelBody at the start
  35. */
  36. renderHeader?: (arg: JsonFormObject) => React.ReactNode;
  37. /**
  38. * Panel title
  39. */
  40. title?: React.ReactNode;
  41. };
  42. type State = {
  43. collapsed: boolean;
  44. };
  45. export default class FormPanel extends Component<Props, State> {
  46. static defaultProps: DefaultProps = {
  47. additionalFieldProps: {},
  48. };
  49. state: State = {
  50. collapsed: false,
  51. };
  52. handleToggleEvents = () => {
  53. const {collapsed} = this.state;
  54. this.setState({collapsed: !collapsed});
  55. };
  56. render() {
  57. const {
  58. title,
  59. fields,
  60. access,
  61. disabled,
  62. additionalFieldProps,
  63. renderFooter,
  64. renderHeader,
  65. collapsible,
  66. ...otherProps
  67. } = this.props;
  68. const {collapsed} = this.state;
  69. return (
  70. <Panel id={typeof title === 'string' ? sanitizeQuerySelector(title) : undefined}>
  71. {title && (
  72. <PanelHeader>
  73. {title}
  74. {collapsible && (
  75. <Collapse onClick={this.handleToggleEvents}>
  76. <IconChevron direction={collapsed ? 'down' : 'up'} size="xs" />
  77. </Collapse>
  78. )}
  79. </PanelHeader>
  80. )}
  81. {!collapsed && (
  82. <PanelBody>
  83. {typeof renderHeader === 'function' && renderHeader({title, fields})}
  84. {fields.map(field => {
  85. if (typeof field === 'function') {
  86. return field();
  87. }
  88. const {defaultValue: _, ...fieldWithoutDefaultValue} = field;
  89. // Allow the form panel disabled prop to override the fields
  90. // disabled prop, with fallback to the fields disabled state.
  91. if (disabled === true) {
  92. fieldWithoutDefaultValue.disabled = true;
  93. fieldWithoutDefaultValue.disabledReason = undefined;
  94. }
  95. return (
  96. <FieldFromConfig
  97. access={access}
  98. disabled={disabled}
  99. key={field.name}
  100. {...otherProps}
  101. {...additionalFieldProps}
  102. field={fieldWithoutDefaultValue}
  103. highlighted={this.props.highlighted === `#${field.name}`}
  104. />
  105. );
  106. })}
  107. {typeof renderFooter === 'function' && renderFooter({title, fields})}
  108. </PanelBody>
  109. )}
  110. </Panel>
  111. );
  112. }
  113. }
  114. const Collapse = styled('span')`
  115. cursor: pointer;
  116. `;