newIssueExperienceButton.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import {motion} from 'framer-motion';
  4. import {Button} from 'sentry/components/button';
  5. import DropdownButton from 'sentry/components/dropdownButton';
  6. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  7. import {IconLab} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {defined} from 'sentry/utils';
  10. import {trackAnalytics} from 'sentry/utils/analytics';
  11. import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
  12. import useMutateUserOptions from 'sentry/utils/useMutateUserOptions';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {useUser} from 'sentry/utils/useUser';
  15. import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
  16. export function NewIssueExperienceButton() {
  17. const user = useUser();
  18. const organization = useOrganization();
  19. const hasStreamlinedUIFlag = organization.features.includes('issue-details-streamline');
  20. const hasStreamlinedUI = useHasStreamlinedUI();
  21. const openForm = useFeedbackForm();
  22. const {mutate} = useMutateUserOptions();
  23. const handleToggle = useCallback(() => {
  24. mutate({['prefersIssueDetailsStreamlinedUI']: !hasStreamlinedUI});
  25. trackAnalytics('issue_details.streamline_ui_toggle', {
  26. isEnabled: !hasStreamlinedUI,
  27. organization: organization,
  28. });
  29. }, [mutate, organization, hasStreamlinedUI]);
  30. if (!hasStreamlinedUIFlag) {
  31. return null;
  32. }
  33. if (!openForm || !hasStreamlinedUI) {
  34. const label = hasStreamlinedUI
  35. ? t('Switch to the old issue experience')
  36. : t('Switch to the new issue experience');
  37. return (
  38. <ToggleButtonWrapper>
  39. <ToggleButton
  40. enabled={hasStreamlinedUI}
  41. size={hasStreamlinedUI ? 'xs' : 'sm'}
  42. icon={
  43. defined(user?.options?.prefersIssueDetailsStreamlinedUI) ? (
  44. <IconLab isSolid={hasStreamlinedUI} />
  45. ) : (
  46. <motion.div
  47. style={{height: 14}}
  48. animate={{
  49. rotate: [null, 6, -6, 12, -12, 6, -6, 0],
  50. }}
  51. transition={{
  52. duration: 1,
  53. delay: 1,
  54. repeatDelay: 3,
  55. repeat: 3,
  56. }}
  57. >
  58. <IconLab isSolid={hasStreamlinedUI} />
  59. </motion.div>
  60. )
  61. }
  62. title={label}
  63. aria-label={label}
  64. borderless={!hasStreamlinedUI}
  65. onClick={handleToggle}
  66. >
  67. <ToggleBorder />
  68. </ToggleButton>
  69. </ToggleButtonWrapper>
  70. );
  71. }
  72. return (
  73. <DropdownMenu
  74. trigger={triggerProps => (
  75. <StyledDropdownButton
  76. {...triggerProps}
  77. enabled={hasStreamlinedUI}
  78. size={hasStreamlinedUI ? 'xs' : 'sm'}
  79. aria-label={t('Switch issue experience')}
  80. >
  81. {/* Passing icon as child to avoid extra icon margin */}
  82. <IconLab isSolid={hasStreamlinedUI} />
  83. </StyledDropdownButton>
  84. )}
  85. items={[
  86. {
  87. key: 'switch-to-old-ui',
  88. label: t('Switch to the old issue experience'),
  89. onAction: handleToggle,
  90. },
  91. {
  92. key: 'learn-more',
  93. label: t('Learn more about the new UI'),
  94. onAction: () => {
  95. trackAnalytics('issue_details.streamline_ui_learn_more', {
  96. organization,
  97. });
  98. window.open(
  99. 'https://sentry.zendesk.com/hc/en-us/articles/30882241712795',
  100. '_blank'
  101. );
  102. },
  103. },
  104. {
  105. key: 'give-feedback',
  106. label: t('Give feedback on new UI'),
  107. hidden: !openForm,
  108. onAction: () => {
  109. openForm({
  110. messagePlaceholder: t(
  111. 'Excluding bribes, what would make you excited to use the new UI?'
  112. ),
  113. tags: {
  114. ['feedback.source']: 'streamlined_issue_details',
  115. ['feedback.owner']: 'issues',
  116. },
  117. });
  118. },
  119. },
  120. ]}
  121. position="bottom-end"
  122. />
  123. );
  124. }
  125. const StyledDropdownButton = styled(DropdownButton)<{enabled: boolean}>`
  126. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  127. :hover {
  128. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  129. }
  130. `;
  131. const ToggleButtonWrapper = styled('div')`
  132. overflow: hidden;
  133. margin: 0 -1px;
  134. border-radius: 7px;
  135. `;
  136. const ToggleButton = styled(Button)<{enabled: boolean}>`
  137. position: relative;
  138. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  139. :hover {
  140. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  141. }
  142. &:after {
  143. position: absolute;
  144. content: '';
  145. inset: 0;
  146. background: ${p => p.theme.background};
  147. border-radius: ${p => p.theme.borderRadius};
  148. }
  149. span {
  150. z-index: 1;
  151. margin: 0;
  152. }
  153. `;
  154. const ToggleBorder = styled('div')`
  155. @keyframes rotating {
  156. from {
  157. transform: rotate(0deg);
  158. }
  159. to {
  160. transform: rotate(360deg);
  161. }
  162. }
  163. position: absolute;
  164. content: '';
  165. z-index: -1;
  166. width: 46px;
  167. height: 46px;
  168. border-radius: 7px;
  169. background: ${p => p.theme.badge.beta.background};
  170. animation: rotating 10s linear infinite;
  171. `;