featureBadge.tsx 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import {Fragment} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {captureException, withScope} from '@sentry/react';
  5. import type {SeverityLevel} from '@sentry/types';
  6. import Badge from 'sentry/components/badge';
  7. import CircleIndicator from 'sentry/components/circleIndicator';
  8. import Tooltip from 'sentry/components/tooltip';
  9. import {t} from 'sentry/locale';
  10. import space, {ValidSize} from 'sentry/styles/space';
  11. type BadgeProps = {
  12. type: 'alpha' | 'beta' | 'new';
  13. expiresAt?: Date;
  14. noTooltip?: boolean;
  15. title?: string;
  16. variant?: 'indicator' | 'badge';
  17. };
  18. type Props = Omit<React.HTMLAttributes<HTMLDivElement>, keyof BadgeProps> & BadgeProps;
  19. const defaultTitles = {
  20. alpha: t('This feature is internal and available for QA purposes'),
  21. beta: t('This feature is available for early adopters and may change'),
  22. new: t('This feature is new! Try it out and let us know what you think'),
  23. };
  24. const labels = {
  25. alpha: t('alpha'),
  26. beta: t('beta'),
  27. new: t('new'),
  28. };
  29. function BaseFeatureBadge({
  30. type,
  31. variant = 'badge',
  32. title,
  33. noTooltip,
  34. expiresAt,
  35. ...props
  36. }: Props) {
  37. const theme = useTheme();
  38. if (expiresAt && expiresAt.valueOf() < Date.now()) {
  39. // Only get 1% of events as we don't need many to know that a badge needs to be cleaned up.
  40. if (Math.random() < 0.01) {
  41. withScope(scope => {
  42. scope.setTag('title', title);
  43. scope.setTag('type', type);
  44. scope.setLevel('warning' as SeverityLevel);
  45. captureException(new Error('Expired Feature Badge'));
  46. });
  47. }
  48. return null;
  49. }
  50. return (
  51. <div {...props}>
  52. <Tooltip title={title ?? defaultTitles[type]} disabled={noTooltip} position="right">
  53. <Fragment>
  54. {variant === 'badge' && <StyledBadge type={type} text={labels[type]} />}
  55. {variant === 'indicator' && (
  56. <CircleIndicator color={theme.badge[type].indicatorColor} size={8} />
  57. )}
  58. </Fragment>
  59. </Tooltip>
  60. </div>
  61. );
  62. }
  63. const StyledBadge = styled(Badge)`
  64. margin: 0;
  65. padding: 0 ${space(0.75)};
  66. line-height: ${space(2)};
  67. height: ${space(2)};
  68. font-weight: normal;
  69. font-size: ${p => p.theme.fontSizeExtraSmall};
  70. vertical-align: middle;
  71. `;
  72. const FeatureBadge = styled(BaseFeatureBadge)<{space?: ValidSize}>`
  73. display: inline-flex;
  74. align-items: center;
  75. margin-left: ${p => space(p.space ?? 0.75)};
  76. `;
  77. export default FeatureBadge;