featureList.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import {useEffect, useState} from 'react';
  2. import type {Theme} from '@emotion/react';
  3. import {withTheme} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import {AnimatePresence, motion} from 'framer-motion';
  6. import ProgressRing from 'sentry/components/progressRing';
  7. import {IconBusiness} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import testableTransition from 'sentry/utils/testableTransition';
  11. import MoreFeaturesLink from 'getsentry/views/amCheckout/moreFeaturesLink';
  12. import type {Feature} from './types';
  13. type Props = {
  14. features: Feature[];
  15. onClick: (feat: Feature) => void;
  16. shouldShowPerformanceFeatures: boolean;
  17. shouldShowTeamFeatures: boolean;
  18. selected?: Feature;
  19. withCountdown?: number;
  20. };
  21. function FeatureList({
  22. features,
  23. selected,
  24. onClick,
  25. withCountdown,
  26. shouldShowTeamFeatures,
  27. shouldShowPerformanceFeatures,
  28. }: Props) {
  29. return (
  30. <div>
  31. <Heading>
  32. {shouldShowPerformanceFeatures
  33. ? t('Features Include')
  34. : shouldShowTeamFeatures
  35. ? t('Features Include')
  36. : t('Business Features Include')}
  37. <AnimatePresence>
  38. {selected && withCountdown && (
  39. <CountdownRing id={selected.id} totalTime={withCountdown} />
  40. )}
  41. </AnimatePresence>
  42. </Heading>
  43. {features.map(feat => (
  44. <FeatureLink
  45. key={feat.id}
  46. onClick={() => onClick(feat)}
  47. aria-selected={feat === selected ? true : undefined}
  48. data-test-id={feat.id}
  49. whileTap={{x: -7}}
  50. transition={testableTransition()}
  51. >
  52. <IconBusiness gradient={feat === selected} withShine={feat === selected} />
  53. {feat.name}
  54. </FeatureLink>
  55. ))}
  56. <MoreFeaturesLink />
  57. </div>
  58. );
  59. }
  60. type CountdownRingProps = {
  61. id: string;
  62. theme: Theme;
  63. totalTime: number;
  64. };
  65. /**
  66. * Countdown ring is used to show a countdown ring to the right of the header
  67. * when in 'auto rotate' carousel mode
  68. */
  69. const CountdownRing = withTheme(({theme, id, totalTime}: CountdownRingProps) => {
  70. const [timeLeft, setTimeLeft] = useState(totalTime);
  71. const tick = 200;
  72. useEffect(() => {
  73. if (timeLeft <= 0) {
  74. return () => void 0;
  75. }
  76. const intervalId = setInterval(() => setTimeLeft(timeLeft - tick), tick);
  77. return () => clearInterval(intervalId);
  78. }, [timeLeft]);
  79. // Reset time left when id changes
  80. useEffect(() => void setTimeLeft(totalTime), [id, totalTime]);
  81. return (
  82. <RingContainer animate={{opacity: 1}} initial={{opacity: 0}} exit={{opacity: 0}}>
  83. <ProgressRing
  84. maxValue={totalTime}
  85. value={timeLeft}
  86. barWidth={2}
  87. size={14}
  88. backgroundColor={theme.gray100}
  89. progressColor={theme.gray200}
  90. />
  91. </RingContainer>
  92. );
  93. });
  94. const RingContainer = styled(motion.div)`
  95. display: flex;
  96. align-items: center;
  97. `;
  98. const Heading = styled('div')`
  99. font-weight: bold;
  100. margin-bottom: ${space(1)};
  101. display: grid;
  102. grid-template-columns: 1fr max-content;
  103. gap: ${space(0.5)};
  104. `;
  105. const FeatureLink = styled(motion.div)`
  106. cursor: pointer;
  107. transition: color 300ms;
  108. color: ${p => p.theme.gray300};
  109. position: relative;
  110. display: grid;
  111. grid-template-columns: max-content auto;
  112. gap: ${space(1)};
  113. align-items: center;
  114. align-content: center;
  115. margin-bottom: ${space(0.5)};
  116. &:hover {
  117. color: ${p => p.theme.textColor};
  118. }
  119. &[aria-selected] {
  120. color: ${p => p.theme.textColor};
  121. font-weight: bold;
  122. }
  123. `;
  124. export default FeatureList;