detailPanel.tsx 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import {useEffect, useRef, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import {IconClose} from 'sentry/icons/iconClose';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import useKeyPress from 'sentry/utils/useKeyPress';
  8. import useOnClickOutside from 'sentry/utils/useOnClickOutside';
  9. import SlideOverPanel from 'sentry/views/starfish/components/slideOverPanel';
  10. type DetailProps = {
  11. children: React.ReactNode;
  12. detailKey?: string;
  13. onClose?: () => void;
  14. onOpen?: () => void;
  15. };
  16. type DetailState = {
  17. collapsed: boolean;
  18. };
  19. export default function Detail({children, detailKey, onClose, onOpen}: DetailProps) {
  20. const [state, setState] = useState<DetailState>({collapsed: true});
  21. const escapeKeyPressed = useKeyPress('Escape');
  22. // Any time the key prop changes (due to user interaction), we want to open the panel
  23. useEffect(() => {
  24. if (detailKey) {
  25. setState({collapsed: false});
  26. } else {
  27. setState({collapsed: true});
  28. }
  29. }, [detailKey]);
  30. const panelRef = useRef<HTMLDivElement>(null);
  31. useOnClickOutside(panelRef, () => {
  32. if (!state.collapsed) {
  33. onClose?.();
  34. setState({collapsed: true});
  35. }
  36. });
  37. useEffect(() => {
  38. if (escapeKeyPressed) {
  39. if (!state.collapsed) {
  40. onClose?.();
  41. setState({collapsed: true});
  42. }
  43. }
  44. // eslint-disable-next-line react-hooks/exhaustive-deps
  45. }, [escapeKeyPressed]);
  46. return (
  47. <SlideOverPanel collapsed={state.collapsed} ref={panelRef} onOpen={onOpen}>
  48. <CloseButtonWrapper>
  49. <CloseButton
  50. priority="link"
  51. size="zero"
  52. borderless
  53. aria-label={t('Close Details')}
  54. icon={<IconClose size="sm" />}
  55. onClick={() => {
  56. setState({collapsed: true});
  57. onClose?.();
  58. }}
  59. />
  60. </CloseButtonWrapper>
  61. <DetailWrapper>{children}</DetailWrapper>
  62. </SlideOverPanel>
  63. );
  64. }
  65. const CloseButton = styled(Button)`
  66. color: ${p => p.theme.gray300};
  67. &:hover {
  68. color: ${p => p.theme.gray400};
  69. }
  70. `;
  71. const CloseButtonWrapper = styled('div')`
  72. justify-content: flex-end;
  73. display: flex;
  74. padding: ${space(2)};
  75. `;
  76. const DetailWrapper = styled('div')`
  77. padding: 0 ${space(4)};
  78. `;