selectableContainer.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {CompactSelect} from 'sentry/components/compactSelect';
  4. import Panel from 'sentry/components/panels/panel';
  5. import PanelHeader from 'sentry/components/panels/panelHeader';
  6. import {space} from 'sentry/styles/space';
  7. type SelectableContainerPanelProps = {
  8. children: React.ReactNode;
  9. extraActions?: React.ReactNode;
  10. };
  11. export type SelectableContainerPanel = React.ComponentType<SelectableContainerPanelProps>;
  12. type ContentRenderProps = {
  13. /**
  14. * May be used to wrap sections in panels
  15. */
  16. Panel: SelectableContainerPanel;
  17. /**
  18. * May be used to render the section selector
  19. */
  20. selector: React.ReactNode;
  21. };
  22. type Section = {
  23. /**
  24. * Renders the content of the section. See the ContentRenderProps to
  25. * understand what each injected Prop is used for.
  26. */
  27. content: (props: ContentRenderProps) => React.ReactElement;
  28. /**
  29. * Section identifier
  30. */
  31. key: string;
  32. /**
  33. * The name of the section. Rendered in the dropdown selector
  34. */
  35. name: string;
  36. };
  37. type Props = {
  38. /**
  39. * Each available section
  40. */
  41. sections: Section[];
  42. /**
  43. * The default section to show first
  44. */
  45. defaultSectionKey?: string;
  46. /**
  47. * Text rendered infront of the dropdown button
  48. */
  49. dropdownPrefix?: string;
  50. /**
  51. * When rendering the injected Panel component in content, this will be used
  52. * as the panel name
  53. */
  54. panelTitle?: string;
  55. };
  56. function SelectableContainer({
  57. dropdownPrefix,
  58. sections,
  59. panelTitle,
  60. defaultSectionKey,
  61. }: Props) {
  62. const [sectionKey, setSection] = useState(defaultSectionKey ?? sections[0]?.key ?? '');
  63. const section = sections.find(s => s.key === sectionKey);
  64. if (section === undefined) {
  65. return null;
  66. }
  67. const selector = (
  68. <CompactSelect
  69. triggerProps={{size: 'xs', prefix: dropdownPrefix}}
  70. value={sectionKey}
  71. options={sections.map(s => ({value: s.key, label: s.name}))}
  72. onChange={opt => setSection(opt.value)}
  73. />
  74. );
  75. // Setup a custom Panel component that renders with the selcetor at the top
  76. // right corner
  77. function InjectedPanel({children, extraActions}: SelectableContainerPanelProps) {
  78. return (
  79. <Panel>
  80. <PanelHeader hasButtons>
  81. <div>{panelTitle}</div>
  82. <Actions>
  83. {extraActions}
  84. {selector}
  85. </Actions>
  86. </PanelHeader>
  87. {children}
  88. </Panel>
  89. );
  90. }
  91. return section.content({selector, Panel: InjectedPanel});
  92. }
  93. const Actions = styled('div')`
  94. text-transform: initial;
  95. font-weight: normal;
  96. display: flex;
  97. align-items: center;
  98. gap: ${space(1)};
  99. `;
  100. export default SelectableContainer;