autofixActionSelector.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import styled from '@emotion/styled';
  2. import {AnimatePresence, motion} from 'framer-motion';
  3. import {Button} from 'sentry/components/button';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import {IconArrow} from 'sentry/icons';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import testableTransition from 'sentry/utils/testableTransition';
  9. interface Option<T extends string> {
  10. key: T;
  11. label: string;
  12. active?: boolean;
  13. }
  14. interface Props<T extends string> {
  15. children: (selectedOption: Option<T>) => React.ReactNode;
  16. onBack: () => void;
  17. onSelect: (value: T) => void;
  18. options: Array<Option<T>>;
  19. selected: T | null;
  20. }
  21. function AutofixActionSelector<T extends string>({
  22. options,
  23. selected,
  24. onSelect,
  25. onBack,
  26. children,
  27. }: Props<T>) {
  28. const selectedOption = options.find(opt => opt.key === selected);
  29. return (
  30. <Container>
  31. <AnimatePresence mode="wait">
  32. {!selected ? (
  33. <motion.div
  34. key="options"
  35. initial="initial"
  36. animate="visible"
  37. variants={{
  38. initial: {opacity: 0, scale: 1.05},
  39. visible: {opacity: 1, scale: 1},
  40. }}
  41. transition={testableTransition({duration: 0.1})}
  42. >
  43. <ButtonBar gap={1}>
  44. {options.map(option => (
  45. <Button
  46. key={option.key}
  47. priority={option.active ? 'primary' : 'default'}
  48. onClick={() => onSelect(option.key)}
  49. >
  50. {option.label}
  51. </Button>
  52. ))}
  53. </ButtonBar>
  54. </motion.div>
  55. ) : (
  56. <motion.div
  57. key="content"
  58. initial={{opacity: 0, scale: 0.95}}
  59. animate={{opacity: 1, scale: 1}}
  60. transition={testableTransition({duration: 0.1})}
  61. >
  62. <ContentWrapper>
  63. <BackButton
  64. size="xs"
  65. icon={<IconArrow direction="left" size="xs" />}
  66. onClick={onBack}
  67. title={t('Back to options')}
  68. aria-label={t('Back to options')}
  69. />
  70. <ContentArea>{selectedOption && children(selectedOption)}</ContentArea>
  71. </ContentWrapper>
  72. </motion.div>
  73. )}
  74. </AnimatePresence>
  75. </Container>
  76. );
  77. }
  78. const Container = styled('div')`
  79. min-height: 40px;
  80. `;
  81. const ContentWrapper = styled('div')`
  82. display: flex;
  83. align-items: center;
  84. gap: ${space(1)};
  85. `;
  86. const BackButton = styled(Button)`
  87. flex-shrink: 0;
  88. height: 40px;
  89. `;
  90. const ContentArea = styled('div')`
  91. flex-grow: 1;
  92. `;
  93. export default AutofixActionSelector;