numberDragControl.tsx 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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 trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  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. trackAdvancedAnalyticsEvent('number_drag_control.clicked', {
  40. organization: null,
  41. });
  42. event.currentTarget.requestPointerLock();
  43. this.setState({isClicked: true});
  44. }}
  45. onMouseUp={() => {
  46. document.exitPointerLock();
  47. this.setState({isClicked: false});
  48. }}
  49. onMouseMove={(event: React.MouseEvent<HTMLDivElement>) => {
  50. if (!this.state.isClicked) {
  51. return;
  52. }
  53. const delta = isX ? event.movementX : event.movementY * -1;
  54. const deltaOne = delta > 0 ? Math.ceil(delta / 100) : Math.floor(delta / 100);
  55. const deltaStep = deltaOne * ((event.shiftKey ? shiftStep : step) ?? 1);
  56. onChange(deltaStep, event);
  57. }}
  58. isActive={this.state.isClicked}
  59. isX={isX}
  60. >
  61. <IconArrow direction={isX ? 'left' : 'up'} size="8px" />
  62. <IconArrow direction={isX ? 'right' : 'down'} size="8px" />
  63. </Wrapper>
  64. );
  65. }
  66. }
  67. const Wrapper = styled('div')<{isActive: boolean; isX: boolean}>`
  68. display: grid;
  69. padding: ${space(0.5)};
  70. ${p =>
  71. p.isX
  72. ? 'grid-template-columns: max-content max-content'
  73. : 'grid-template-rows: max-content max-content'};
  74. cursor: ${p => (p.isX ? 'ew-resize' : 'ns-resize')};
  75. color: ${p => (p.isActive ? p.theme.gray500 : p.theme.gray300)};
  76. background: ${p => p.isActive && p.theme.backgroundSecondary};
  77. border-radius: 2px;
  78. `;
  79. export default NumberDragControl;