numberDragControl.tsx 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import {IconArrow} from 'app/icons';
  4. import space from 'app/styles/space';
  5. import {trackAnalyticsEvent} from 'app/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 as the mouse is dragged. Defaults to 1
  11. */
  12. step?: number;
  13. /**
  14. * The value to increment by if the shift key is held. Defaults to 1
  15. */
  16. shiftStep?: number;
  17. };
  18. type Props = Omit<React.HTMLAttributes<HTMLDivElement>, keyof NumberDragControlProps> &
  19. NumberDragControlProps;
  20. type State = {
  21. isClicked: boolean;
  22. };
  23. class NumberDragControl extends React.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. });
  43. event.currentTarget.requestPointerLock();
  44. this.setState({isClicked: true});
  45. }}
  46. onMouseUp={() => {
  47. document.exitPointerLock();
  48. this.setState({isClicked: false});
  49. }}
  50. onMouseMove={(event: React.MouseEvent<HTMLDivElement>) => {
  51. if (!this.state.isClicked) {
  52. return;
  53. }
  54. const delta = isX ? event.movementX : event.movementY * -1;
  55. const deltaOne = delta > 0 ? Math.ceil(delta / 100) : Math.floor(delta / 100);
  56. const deltaStep = deltaOne * ((event.shiftKey ? shiftStep : step) ?? 1);
  57. onChange(deltaStep, event);
  58. }}
  59. isActive={this.state.isClicked}
  60. isX={isX}
  61. >
  62. <IconArrow direction={isX ? 'left' : 'up'} size="8px" />
  63. <IconArrow direction={isX ? 'right' : 'down'} size="8px" />
  64. </Wrapper>
  65. );
  66. }
  67. }
  68. const Wrapper = styled('div')<{isActive: boolean; isX: boolean}>`
  69. display: grid;
  70. padding: ${space(0.5)};
  71. ${p =>
  72. p.isX
  73. ? 'grid-template-columns: max-content max-content'
  74. : 'grid-template-rows: max-content max-content'};
  75. cursor: ${p => (p.isX ? 'ew-resize' : 'ns-resize')};
  76. color: ${p => (p.isActive ? p.theme.gray500 : p.theme.gray300)};
  77. background: ${p => p.isActive && p.theme.backgroundSecondary};
  78. border-radius: 2px;
  79. `;
  80. export default NumberDragControl;