detailsPage.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Tag} from 'sentry/components/core/badge/tag';
  4. import ErrorBoundary from 'sentry/components/errorBoundary';
  5. import Panel from 'sentry/components/panels/panel';
  6. import PanelHeader from 'sentry/components/panels/panelHeader';
  7. import {Tooltip} from 'sentry/components/tooltip';
  8. import {space} from 'sentry/styles/space';
  9. import type {openAdminConfirmModal} from 'admin/components/adminConfirmationModal';
  10. import DropdownActions from 'admin/components/dropdownActions';
  11. import PageHeader from 'admin/components/pageHeader';
  12. export type ActionItem = {
  13. key: string;
  14. /**
  15. * Name of the action
  16. */
  17. name: string;
  18. /**
  19. * Triggered when the action is confirmed
  20. */
  21. onAction: (params: Record<string, any>) => void;
  22. /**
  23. * Aditional props for the openAdminConfirmModal call.
  24. *
  25. * Note that some defaults are provided to the openAdminConfirmModal and may
  26. * be overriden in this object.
  27. */
  28. confirmModalOpts?: Omit<Parameters<typeof openAdminConfirmModal>[0], 'onConfirm'>;
  29. /**
  30. * Is the action disabled?
  31. */
  32. disabled?: boolean;
  33. /**
  34. * The reason that the action is disabled
  35. */
  36. disabledReason?: string;
  37. /**
  38. * Help text under the action
  39. */
  40. help?: string;
  41. /**
  42. * Skips calling openAdminConfirmModal if set to true, onAction will be
  43. * immediately triggered with no parameters.
  44. */
  45. skipConfirmModal?: boolean;
  46. /**
  47. * If set to false will hide the item from the menu
  48. */
  49. visible?: boolean;
  50. };
  51. export type BadgeItem = {
  52. name: string;
  53. /**
  54. * A tooltip rendered on the badge
  55. */
  56. help?: React.ReactNode;
  57. /**
  58. * Tag type
  59. */
  60. level?: React.ComponentProps<typeof Tag>['type'];
  61. /**
  62. * If set to false will hide the badge
  63. */
  64. visible?: boolean;
  65. };
  66. type SectionItem = {
  67. content: React.ReactElement;
  68. /**
  69. * Adds a heading to the panel. Does nothing when noPanel is set.
  70. */
  71. name?: string;
  72. /**
  73. * Disables padding within the panel
  74. */
  75. noPadding?: boolean;
  76. /**
  77. * Does not contain the section within a panel.
  78. *
  79. * Note that the name and padding will not be respected, as the content will
  80. * just be rendered
  81. */
  82. noPanel?: boolean;
  83. /**
  84. * If set to false will hide the section
  85. */
  86. visible?: boolean;
  87. };
  88. type Props = {
  89. /**
  90. * The name of the specific item we're looking at details of.
  91. */
  92. name: React.ReactNode;
  93. /**
  94. * The "parent" name of the details view. If you were looking at a specific
  95. * customer this would probably be "Customers"
  96. */
  97. rootName: React.ReactNode;
  98. /**
  99. * List of actions available on this view.
  100. */
  101. actions?: ActionItem[];
  102. /**
  103. * List of badges to display next to the title
  104. */
  105. badges?: BadgeItem[];
  106. /**
  107. * Breadcrumbs between the root and name in the details page title
  108. */
  109. crumbs?: React.ReactNode[];
  110. /**
  111. * List of sections to show. This is the primary content of the page
  112. */
  113. sections?: SectionItem[];
  114. };
  115. function DetailsPage({
  116. rootName,
  117. name,
  118. crumbs = [],
  119. actions = [],
  120. badges = [],
  121. sections = [],
  122. }: Props) {
  123. return (
  124. <Fragment>
  125. <PageHeader
  126. title={rootName}
  127. breadcrumbs={[
  128. ...crumbs,
  129. <NameWithBadges key="page">
  130. {name}
  131. {badges
  132. .filter(badge => badge.visible !== false)
  133. .map(badge => (
  134. <Tooltip key={badge.name} disabled={!badge.help} title={badge.help}>
  135. <Tag type={badge.level}>{badge.name}</Tag>
  136. </Tooltip>
  137. ))}
  138. </NameWithBadges>,
  139. ]}
  140. >
  141. {actions.filter(a => a.visible !== false).length > 0 && (
  142. <DropdownActions actions={actions} label={`${rootName} Actions`} />
  143. )}
  144. </PageHeader>
  145. {sections
  146. .filter(section => section.visible !== false)
  147. .map((section, i) =>
  148. section.noPanel ? (
  149. <Fragment key={section.name ?? i}>{section.content}</Fragment>
  150. ) : (
  151. <Panel key={section.name ?? i}>
  152. {section.name && <PanelHeader>{section.name}</PanelHeader>}
  153. <ErrorBoundary>
  154. <SectionBody withPadding={!section.noPadding}>
  155. {section.content}
  156. </SectionBody>
  157. </ErrorBoundary>
  158. </Panel>
  159. )
  160. )}
  161. </Fragment>
  162. );
  163. }
  164. const NameWithBadges = styled('div')`
  165. display: flex;
  166. gap: ${space(1)};
  167. `;
  168. const SectionBody = styled('div')<{withPadding?: boolean}>`
  169. ${p => p.withPadding && `padding: ${space(2)}`};
  170. `;
  171. export default DetailsPage;