formPanel.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {useCallback, useState} 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 './types';
  9. type Props = {
  10. /**
  11. * List of fields to render
  12. */
  13. fields: FieldObject[];
  14. access?: Set<Scope>;
  15. additionalFieldProps?: {[key: string]: any};
  16. /**
  17. * Can the PanelBody be hidden with a click?
  18. */
  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. function FormPanel({
  43. additionalFieldProps = {},
  44. title,
  45. fields,
  46. access,
  47. disabled,
  48. renderFooter,
  49. renderHeader,
  50. collapsible,
  51. ...otherProps
  52. }: Props) {
  53. const [collapsed, setCollapse] = useState(false);
  54. const handleCollapseToggle = useCallback(() => setCollapse(current => !current), []);
  55. return (
  56. <Panel id={typeof title === 'string' ? sanitizeQuerySelector(title) : undefined}>
  57. {title && (
  58. <PanelHeader>
  59. {title}
  60. {collapsible && (
  61. <Collapse onClick={handleCollapseToggle}>
  62. <IconChevron direction={collapsed ? 'down' : 'up'} size="xs" />
  63. </Collapse>
  64. )}
  65. </PanelHeader>
  66. )}
  67. {!collapsed && (
  68. <PanelBody>
  69. {typeof renderHeader === 'function' && renderHeader({title, fields})}
  70. {fields.map(field => {
  71. if (typeof field === 'function') {
  72. return field();
  73. }
  74. const {defaultValue: _, ...fieldWithoutDefaultValue} = field;
  75. // Allow the form panel disabled prop to override the fields
  76. // disabled prop, with fallback to the fields disabled state.
  77. if (disabled === true) {
  78. fieldWithoutDefaultValue.disabled = true;
  79. fieldWithoutDefaultValue.disabledReason = undefined;
  80. }
  81. return (
  82. <FieldFromConfig
  83. access={access}
  84. disabled={disabled}
  85. key={field.name}
  86. {...otherProps}
  87. {...additionalFieldProps}
  88. field={fieldWithoutDefaultValue}
  89. highlighted={otherProps.highlighted === `#${field.name}`}
  90. />
  91. );
  92. })}
  93. {typeof renderFooter === 'function' && renderFooter({title, fields})}
  94. </PanelBody>
  95. )}
  96. </Panel>
  97. );
  98. }
  99. export default FormPanel;
  100. const Collapse = styled('span')`
  101. cursor: pointer;
  102. `;