numberDragControl.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import {Component} from 'react';
  2. import styled from '@emotion/styled';
  3. import {IconArrow} from 'sentry/icons';
  4. import space from 'sentry/styles/space';
  5. import {trackAnalyticsEvent} from 'sentry/utils/analytics';
  6. type NumberDragControlProps = {
  7. onChange: (delta: number, event: React.MouseEvent<HTMLDivElement>) => void;
  8. axis?: 'x' | 'y';
  9. /**
  10. * The value to increment by if the shift key is held. Defaults to 1
  11. */
  12. shiftStep?: number;
  13. /**
  14. * The value to increment by as the mouse is dragged. Defaults to 1
  15. */
  16. step?: number;
  17. };
  18. type Props = Omit<React.HTMLAttributes<HTMLDivElement>, keyof NumberDragControlProps> &
  19. NumberDragControlProps;
  20. type State = {
  21. isClicked: boolean;
  22. };
  23. class NumberDragControl extends Component<Props, State> {
  24. state: State = {
  25. isClicked: false,
  26. };
  27. render() {
  28. const {onChange, axis, step, shiftStep, ...props} = this.props;
  29. const isX = (axis ?? 'x') === 'x';
  30. return (
  31. <Wrapper
  32. {...props}
  33. onMouseDown={(event: React.MouseEvent<HTMLDivElement>) => {
  34. if (event.button !== 0) {
  35. return;
  36. }
  37. // XXX(epurkhiser): We can remove this later, just curious if people
  38. // are actually using the drag control
  39. trackAnalyticsEvent({
  40. eventName: 'Number Drag Control: Clicked',
  41. eventKey: 'number_drag_control.clicked',
  42. organization_id: null,
  43. });
  44. event.currentTarget.requestPointerLock();
  45. this.setState({isClicked: true});
  46. }}
  47. onMouseUp={() => {
  48. document.exitPointerLock();
  49. this.setState({isClicked: false});
  50. }}
  51. onMouseMove={(event: React.MouseEvent<HTMLDivElement>) => {
  52. if (!this.state.isClicked) {
  53. return;
  54. }
  55. const delta = isX ? event.movementX : event.movementY * -1;
  56. const deltaOne = delta > 0 ? Math.ceil(delta / 100) : Math.floor(delta / 100);
  57. const deltaStep = deltaOne * ((event.shiftKey ? shiftStep : step) ?? 1);
  58. onChange(deltaStep, event);
  59. }}
  60. isActive={this.state.isClicked}
  61. isX={isX}
  62. >
  63. <IconArrow direction={isX ? 'left' : 'up'} size="8px" />
  64. <IconArrow direction={isX ? 'right' : 'down'} size="8px" />
  65. </Wrapper>
  66. );
  67. }
  68. }
  69. const Wrapper = styled('div')<{isActive: boolean; isX: boolean}>`
  70. display: grid;
  71. padding: ${space(0.5)};
  72. ${p =>
  73. p.isX
  74. ? 'grid-template-columns: max-content max-content'
  75. : 'grid-template-rows: max-content max-content'};
  76. cursor: ${p => (p.isX ? 'ew-resize' : 'ns-resize')};
  77. color: ${p => (p.isActive ? p.theme.gray500 : p.theme.gray300)};
  78. background: ${p => p.isActive && p.theme.backgroundSecondary};
  79. border-radius: 2px;
  80. `;
  81. export default NumberDragControl;