PageBanner.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { css } from '@emotion/css';
  2. import React, { useEffect, useState, useCallback, useMemo } from 'react';
  3. import { GrafanaTheme2 } from '@grafana/data';
  4. import { Icon, IconButton, IconName, useStyles2, useTheme2 } from '@grafana/ui';
  5. import { appEvents } from 'app/core/core';
  6. import { PageBannerDisplayEvent, PageBannerEventPayload, PageBannerSeverity } from './types';
  7. export function PageBanner(): React.ReactElement | null {
  8. const [banner, setBanner] = useState<PageBannerEventPayload | undefined>();
  9. const severityStyling = useStylingBySeverity(banner?.severity);
  10. const styles = useStyles2((theme) => getStyles(theme, severityStyling));
  11. useEffect(() => {
  12. const sub = appEvents.subscribe(PageBannerDisplayEvent, (event) => {
  13. setBanner(event.payload);
  14. });
  15. return sub.unsubscribe;
  16. }, [setBanner]);
  17. const onClose = useCallback(() => {
  18. setBanner(undefined);
  19. if (banner?.onClose) {
  20. banner.onClose();
  21. }
  22. }, [banner]);
  23. if (!banner) {
  24. return null;
  25. }
  26. const BannerBody = banner.body;
  27. return (
  28. <div className={styles.banner}>
  29. <div className={styles.icon}>
  30. <Icon size="xl" name={severityStyling.icon} />
  31. </div>
  32. <div className={styles.content}>
  33. <BannerBody severity={banner.severity ?? PageBannerSeverity.info} />
  34. </div>
  35. {banner?.onClose && (
  36. <div className={styles.icon}>
  37. <IconButton size="xl" name="times" onClick={onClose} className={styles.close} />
  38. </div>
  39. )}
  40. </div>
  41. );
  42. }
  43. type SeverityStyling = {
  44. text: string;
  45. background: string;
  46. icon: IconName;
  47. };
  48. function useStylingBySeverity(severity: PageBannerSeverity | undefined): SeverityStyling {
  49. const theme = useTheme2();
  50. return useMemo(() => {
  51. switch (severity) {
  52. case PageBannerSeverity.error:
  53. return {
  54. icon: 'exclamation-triangle',
  55. background: theme.colors.error.main,
  56. text: theme.colors.error.contrastText,
  57. };
  58. case PageBannerSeverity.warning:
  59. return {
  60. icon: 'exclamation-triangle',
  61. background: theme.colors.warning.main,
  62. text: theme.colors.warning.contrastText,
  63. };
  64. case PageBannerSeverity.info:
  65. default:
  66. return {
  67. icon: 'info-circle',
  68. background: theme.colors.info.main,
  69. text: theme.colors.info.contrastText,
  70. };
  71. }
  72. }, [theme, severity]);
  73. }
  74. function getStyles(theme: GrafanaTheme2, severityStyling: SeverityStyling) {
  75. return {
  76. banner: css`
  77. flex-grow: 0;
  78. flex-shrink: 0;
  79. display: flex;
  80. align-items: center;
  81. background-color: ${severityStyling.background};
  82. border-radius: 2px;
  83. height: 52px;
  84. `,
  85. icon: css`
  86. padding: ${theme.spacing(0, 2)};
  87. color: ${severityStyling.text};
  88. display: flex;
  89. `,
  90. content: css`
  91. flex-grow: 1;
  92. display: flex;
  93. align-items: center;
  94. color: ${severityStyling.text};
  95. `,
  96. close: css`
  97. color: ${severityStyling.text};
  98. `,
  99. };
  100. }
  101. // Uncomment to test this banner
  102. // setTimeout(() => {
  103. // appEvents.publish(
  104. // new PageBannerDisplayEvent({
  105. // onClose: () => {},
  106. // body: function test() {
  107. // return (
  108. // <div>
  109. // This is a warning that you will be suspended{' '}
  110. // <Button fill="outline" variant="secondary">
  111. // Upgrade to Pro
  112. // </Button>
  113. // </div>
  114. // );
  115. // },
  116. // })
  117. // );
  118. // }, 3000);