PluginDetailsHeader.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { css, cx } from '@emotion/css';
  2. import React from 'react';
  3. import { GrafanaTheme2 } from '@grafana/data';
  4. import { useStyles2, Icon, HorizontalGroup } from '@grafana/ui';
  5. import { getLatestCompatibleVersion } from '../helpers';
  6. import { CatalogPlugin } from '../types';
  7. import { PluginDisabledBadge } from './Badges';
  8. import { GetStartedWithPlugin } from './GetStartedWithPlugin';
  9. import { InstallControls } from './InstallControls';
  10. import { PluginDetailsHeaderDependencies } from './PluginDetailsHeaderDependencies';
  11. import { PluginDetailsHeaderSignature } from './PluginDetailsHeaderSignature';
  12. import { PluginLogo } from './PluginLogo';
  13. type Props = {
  14. currentUrl: string;
  15. parentUrl: string;
  16. plugin: CatalogPlugin;
  17. };
  18. export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): React.ReactElement {
  19. const styles = useStyles2(getStyles);
  20. const latestCompatibleVersion = getLatestCompatibleVersion(plugin.details?.versions);
  21. const version = plugin.installedVersion || latestCompatibleVersion?.version;
  22. return (
  23. <div>
  24. <div className="page-container">
  25. <div className={styles.headerContainer}>
  26. <PluginLogo
  27. alt={`${plugin.name} logo`}
  28. src={plugin.info.logos.small}
  29. className={css`
  30. object-fit: contain;
  31. width: 100%;
  32. height: 68px;
  33. max-width: 68px;
  34. `}
  35. />
  36. <div className={styles.headerWrapper}>
  37. {/* Title & navigation */}
  38. <nav className={styles.breadcrumb} aria-label="Breadcrumb">
  39. <ol>
  40. <li>
  41. <a className={styles.textUnderline} href={parentUrl}>
  42. Plugins
  43. </a>
  44. </li>
  45. <li>
  46. <a href={currentUrl} aria-current="page">
  47. {plugin.name}
  48. </a>
  49. </li>
  50. </ol>
  51. </nav>
  52. <div className={styles.headerInformationRow}>
  53. {/* Org name */}
  54. <span>{plugin.orgName}</span>
  55. {/* Links */}
  56. {plugin.details?.links.map((link: any) => (
  57. <a key={link.name} href={link.url}>
  58. {link.name}
  59. </a>
  60. ))}
  61. {/* Downloads */}
  62. {plugin.downloads > 0 && (
  63. <span>
  64. <Icon name="cloud-download" />
  65. {` ${new Intl.NumberFormat().format(plugin.downloads)}`}{' '}
  66. </span>
  67. )}
  68. {/* Version */}
  69. {Boolean(version) && <span>{version}</span>}
  70. {/* Signature information */}
  71. <PluginDetailsHeaderSignature plugin={plugin} />
  72. {plugin.isDisabled && <PluginDisabledBadge error={plugin.error!} />}
  73. </div>
  74. <PluginDetailsHeaderDependencies
  75. plugin={plugin}
  76. latestCompatibleVersion={latestCompatibleVersion}
  77. className={cx(styles.headerInformationRow, styles.headerInformationRowSecondary)}
  78. />
  79. <p>{plugin.description}</p>
  80. <HorizontalGroup height="auto">
  81. <InstallControls plugin={plugin} latestCompatibleVersion={latestCompatibleVersion} />
  82. <GetStartedWithPlugin plugin={plugin} />
  83. </HorizontalGroup>
  84. </div>
  85. </div>
  86. </div>
  87. </div>
  88. );
  89. }
  90. export const getStyles = (theme: GrafanaTheme2) => {
  91. return {
  92. headerContainer: css`
  93. display: flex;
  94. margin-bottom: ${theme.spacing(3)};
  95. margin-top: ${theme.spacing(3)};
  96. min-height: 120px;
  97. `,
  98. headerWrapper: css`
  99. margin-left: ${theme.spacing(3)};
  100. `,
  101. breadcrumb: css`
  102. font-size: ${theme.typography.h2.fontSize};
  103. li {
  104. display: inline;
  105. list-style: none;
  106. &::after {
  107. content: '/';
  108. padding: 0 0.25ch;
  109. }
  110. &:last-child::after {
  111. content: '';
  112. }
  113. }
  114. `,
  115. headerInformationRow: css`
  116. display: flex;
  117. align-items: center;
  118. margin-top: ${theme.spacing()};
  119. margin-bottom: ${theme.spacing()};
  120. flex-flow: wrap;
  121. & > * {
  122. &::after {
  123. content: '|';
  124. padding: 0 ${theme.spacing()};
  125. }
  126. &:last-child::after {
  127. content: '';
  128. padding-right: 0;
  129. }
  130. }
  131. font-size: ${theme.typography.h4.fontSize};
  132. a {
  133. &:hover {
  134. text-decoration: underline;
  135. }
  136. }
  137. `,
  138. headerInformationRowSecondary: css`
  139. font-size: ${theme.typography.body.fontSize};
  140. `,
  141. headerOrgName: css`
  142. font-size: ${theme.typography.h4.fontSize};
  143. `,
  144. signature: css`
  145. margin: ${theme.spacing(3)};
  146. margin-bottom: 0;
  147. `,
  148. textUnderline: css`
  149. text-decoration: underline;
  150. `,
  151. };
  152. };