123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import {Fragment} from 'react';
- import styled from '@emotion/styled';
- import {Tag} from 'sentry/components/core/badge/tag';
- import ErrorBoundary from 'sentry/components/errorBoundary';
- import Panel from 'sentry/components/panels/panel';
- import PanelHeader from 'sentry/components/panels/panelHeader';
- import {Tooltip} from 'sentry/components/tooltip';
- import {space} from 'sentry/styles/space';
- import type {openAdminConfirmModal} from 'admin/components/adminConfirmationModal';
- import DropdownActions from 'admin/components/dropdownActions';
- import PageHeader from 'admin/components/pageHeader';
- export type ActionItem = {
- key: string;
- /**
- * Name of the action
- */
- name: string;
- /**
- * Triggered when the action is confirmed
- */
- onAction: (params: Record<string, any>) => void;
- /**
- * Aditional props for the openAdminConfirmModal call.
- *
- * Note that some defaults are provided to the openAdminConfirmModal and may
- * be overriden in this object.
- */
- confirmModalOpts?: Omit<Parameters<typeof openAdminConfirmModal>[0], 'onConfirm'>;
- /**
- * Is the action disabled?
- */
- disabled?: boolean;
- /**
- * The reason that the action is disabled
- */
- disabledReason?: string;
- /**
- * Help text under the action
- */
- help?: string;
- /**
- * Skips calling openAdminConfirmModal if set to true, onAction will be
- * immediately triggered with no parameters.
- */
- skipConfirmModal?: boolean;
- /**
- * If set to false will hide the item from the menu
- */
- visible?: boolean;
- };
- export type BadgeItem = {
- name: string;
- /**
- * A tooltip rendered on the badge
- */
- help?: React.ReactNode;
- /**
- * Tag type
- */
- level?: React.ComponentProps<typeof Tag>['type'];
- /**
- * If set to false will hide the badge
- */
- visible?: boolean;
- };
- type SectionItem = {
- content: React.ReactElement;
- /**
- * Adds a heading to the panel. Does nothing when noPanel is set.
- */
- name?: string;
- /**
- * Disables padding within the panel
- */
- noPadding?: boolean;
- /**
- * Does not contain the section within a panel.
- *
- * Note that the name and padding will not be respected, as the content will
- * just be rendered
- */
- noPanel?: boolean;
- /**
- * If set to false will hide the section
- */
- visible?: boolean;
- };
- type Props = {
- /**
- * The name of the specific item we're looking at details of.
- */
- name: React.ReactNode;
- /**
- * The "parent" name of the details view. If you were looking at a specific
- * customer this would probably be "Customers"
- */
- rootName: React.ReactNode;
- /**
- * List of actions available on this view.
- */
- actions?: ActionItem[];
- /**
- * List of badges to display next to the title
- */
- badges?: BadgeItem[];
- /**
- * Breadcrumbs between the root and name in the details page title
- */
- crumbs?: React.ReactNode[];
- /**
- * List of sections to show. This is the primary content of the page
- */
- sections?: SectionItem[];
- };
- function DetailsPage({
- rootName,
- name,
- crumbs = [],
- actions = [],
- badges = [],
- sections = [],
- }: Props) {
- return (
- <Fragment>
- <PageHeader
- title={rootName}
- breadcrumbs={[
- ...crumbs,
- <NameWithBadges key="page">
- {name}
- {badges
- .filter(badge => badge.visible !== false)
- .map(badge => (
- <Tooltip key={badge.name} disabled={!badge.help} title={badge.help}>
- <Tag type={badge.level}>{badge.name}</Tag>
- </Tooltip>
- ))}
- </NameWithBadges>,
- ]}
- >
- {actions.filter(a => a.visible !== false).length > 0 && (
- <DropdownActions actions={actions} label={`${rootName} Actions`} />
- )}
- </PageHeader>
- {sections
- .filter(section => section.visible !== false)
- .map((section, i) =>
- section.noPanel ? (
- <Fragment key={section.name ?? i}>{section.content}</Fragment>
- ) : (
- <Panel key={section.name ?? i}>
- {section.name && <PanelHeader>{section.name}</PanelHeader>}
- <ErrorBoundary>
- <SectionBody withPadding={!section.noPadding}>
- {section.content}
- </SectionBody>
- </ErrorBoundary>
- </Panel>
- )
- )}
- </Fragment>
- );
- }
- const NameWithBadges = styled('div')`
- display: flex;
- gap: ${space(1)};
- `;
- const SectionBody = styled('div')<{withPadding?: boolean}>`
- ${p => p.withPadding && `padding: ${space(2)}`};
- `;
- export default DetailsPage;
|