👩‍💻 Dev/💫 React

React-draggable official demo convert using hooks

진하링 2021. 5. 4. 14:39

React-draggable의 공식 Demo code는 Class Component로 작성되었습니다.

저는 Hooks Component로 code를 작성하기에, 이 code를 Hooks로 변경해보았습니다.

Demo Preview

Demo : react-grid-layout.github.io/react-draggable/example/

SourceCode : github.com/react-grid-layout/react-draggable/blob/master/example/example.js

Index : github.com/react-grid-layout/react-draggable/blob/master/example/index.html

SourceCode 링크의 code를 hooks로 변경한 코드입니다.

Index page는 링크의 code를 그대로 사용하시면 됩니다.

RemWrapper는 제가 사용하지 않을 기능이고, 무슨 기능인지 잘 이해가 가지 않아서... 정상적으로 변환되지 않았을 수 있습니다.

import React, { useState } from 'react';
import Draggable from 'react-draggable';
import './style.css';

const App = () => {
  const [state, setState] = useState({
    activeDrags: 0,
    deltaPosition: {
      x: 0, y: 0
    },
    controlledPosition: {
      x: -400, y: 200
    }
  });

  const handleDrag = (e, ui) => {
    const { deltaPosition } = state;
    const { x, y } = deltaPosition;
    const { deltaX, deltaY } = ui;
    setState({
      ...state,
      deltaPosition: {
        x: x + deltaX,
        y: y + deltaY
      }
    });
  };
  const onStart = () => {
    const { activeDrags } = state;
    setState({ ...state, activeDrags: activeDrags + 1 });
  };
  const onStop = () => {
    const { activeDrags } = state;
    setState({ ...state, activeDrags: activeDrags - 1 });
  };
  const onControlledDrag = (e, position) => {
    const { x, y } = position;
    setState({ ...state, controlledPosition: { x, y } });
  };
  const onControlledDragStop = (e, position) => {
    onControlledDrag(e, position);
    onStop();
  };
  const adjustXPos = e => {
    e.preventDefault();
    e.stopPropagation();
    const { controlledPosition } = state;
    const { x, y } = controlledPosition;
    setState({ ...state, controlledPosition: { x: x - 10, y } });
  };
  const adjustYPos = e => {
    e.preventDefault();
    e.stopPropagation();
    const { controlledPosition } = state;
    const { x, y } = controlledPosition;
    setState({ ...state, controlledPosition: { x, y: y - 10 } });
  };

  const onDrop = e => {
    setState({ activeDrags: state.activeDrags - 1 });
    if (e.target.classList.contains('drop-target')) {
      alert('Dropped!');
      e.target.classList.remove('hovered');
    }
  };
  const onDropAreaMouseEnter = e => {
    if (state.activeDrags) {
      e.target.classList.add('hovered');
    }
  };
  const onDropAreaMouseLeave = e => {
    e.target.classList.remove('hovered');
  };

  const dragHandlers = { onStart, onStop };
  const { deltaPosition, controlledPosition } = state;
  return (
    <div>
      <h1>React Draggable</h1>
      <p>Active DragHandlers: {state.activeDrags}</p>
      <p>
        <a href='https://github.com/STRML/react-draggable/blob/master/example/example.js'>Demo Source</a>
      </p>
      <Draggable {...dragHandlers}>
        <div className='box'>I can be dragged anywhere</div>
      </Draggable>
      <Draggable axis='x' {...dragHandlers}>
        <div className='box cursor-x'>I can only be dragged horizonally (x axis)</div>
      </Draggable>
      <Draggable axis='y' {...dragHandlers}>
        <div className='box cursor-y'>I can only be dragged vertically (y axis)</div>
      </Draggable>
      <Draggable onStart={() => false}>
        <div className='box'>{'I don\'t want to be dragged'}</div>
      </Draggable>
      <Draggable onDrag={handleDrag} {...dragHandlers}>
        <div className='box'>
          <div>I track my deltas</div>
          <div>x: {deltaPosition.x.toFixed(0)}, y: {deltaPosition.y.toFixed(0)}</div>
        </div>
      </Draggable>
      <Draggable handle='strong' {...dragHandlers}>
        <div className='box no-cursor'>
          <strong className='cursor'><div>Drag here</div></strong>
          <div>You must click my handle to drag me</div>
        </div>
      </Draggable>
      <Draggable handle='strong'>
        <div className='box no-cursor' style={{ display: 'flex', flexDirection: 'column' }}>
          <strong className='cursor'><div>Drag here</div></strong>
          <div style={{ overflow: 'scroll' }}>
            <div style={{ background: 'yellow', whiteSpace: 'pre-wrap' }}>
              I have long scrollable content with a handle
              {'\n' + Array(40).fill('x').join('\n')}
            </div>
          </div>
        </div>
      </Draggable>
      <Draggable cancel='strong' {...dragHandlers}>
        <div className='box'>
          <strong className='no-cursor'>{'Can\'t drag here'}</strong>
          <div>Dragging here works</div>
        </div>
      </Draggable>
      <Draggable grid={[25, 25]} {...dragHandlers}>
        <div className='box'>I snap to a 25 x 25 grid</div>
      </Draggable>
      <Draggable grid={[50, 50]} {...dragHandlers}>
        <div className='box'>I snap to a 50 x 50 grid</div>
      </Draggable>
      <Draggable bounds={{ top: -100, left: -100, right: 100, bottom: 100 }} {...dragHandlers}>
        <div className='box'>I can only be moved 100px in any direction.</div>
      </Draggable>
      <Draggable {...dragHandlers}>
        <div className='box drop-target' onMouseEnter={onDropAreaMouseEnter} onMouseLeave={onDropAreaMouseLeave}>I can detect drops from the next box.</div>
      </Draggable>
      <Draggable {...dragHandlers} onStop={onDrop}>
        <div className={`box ${state.activeDrags ? 'no-pointer-events' : ''}`}>I can be dropped onto another box.</div>
      </Draggable>
      <div className='box' style={{ height: '500px', width: '500px', position: 'relative', overflow: 'auto', padding: '0' }}>
        <div style={{ height: '1000px', width: '1000px', padding: '10px' }}>
          <Draggable bounds='parent' {...dragHandlers}>
            <div className='box'>
              I can only be moved within my offsetParent.<br /><br />
              Both parent padding and child margin work properly.
            </div>
          </Draggable>
          <Draggable bounds='parent' {...dragHandlers}>
            <div className='box'>
              I also can only be moved within my offsetParent.<br /><br />
              Both parent padding and child margin work properly.
            </div>
          </Draggable>
        </div>
      </div>
      <Draggable bounds='body' {...dragHandlers}>
        <div className='box'>
          I can only be moved within the confines of the body element.
        </div>
      </Draggable>
      <Draggable {...dragHandlers}>
        <div className='box' style={{ position: 'absolute', bottom: '100px', right: '100px' }}>
          I already have an absolute position.
        </div>
      </Draggable>
      <Draggable {...dragHandlers}>
        <RemWrapper>
          <div className='box rem-position-fix' style={{ position: 'absolute', bottom: '6.25rem', right: '18rem' }}>
            I use <span style={{ fontWeight: 700 }}>rem</span>
            instead of <span style={{ fontWeight: 700 }}>px</span> for my transforms.
            I also have absolute positioning.
            <br /><br />
            I depend on a CSS hack to avoid double absolute positioning.
          </div>
        </RemWrapper>
      </Draggable>
      <Draggable defaultPosition={{ x: 25, y: 25 }} {...dragHandlers}>
        <div className='box'>
          {'I have a default position of {x: 25, y: 25}, so I\'m slightly offset.'}
        </div>
      </Draggable>
      <Draggable positionOffset={{ x: '-10%', y: '-10%' }} {...dragHandlers}>
        <div className='box'>
          {'I have a default position based on percents {x: \'-10%\', y: \'-10%\'}, so I\'m slightly offset.'}
        </div>
      </Draggable>
      <Draggable position={controlledPosition} {...dragHandlers} onDrag={onControlledDrag}>
        <div className='box'>
          My position can be changed programmatically. <br />
          I have a drag handler to sync state.
          <div>
            <button type='button' onClick={adjustXPos}>Adjust x ({ controlledPosition.x })</button>
          </div>
          <div>
            <button type='button' onClick={adjustYPos}>Adjust y ({ controlledPosition.y })</button>
          </div>
        </div>
      </Draggable>
      <Draggable position={controlledPosition} {...dragHandlers} onStop={onControlledDragStop}>
        <div className='box'>
          My position can be changed programmatically. <br />
          I have a dragStop handler to sync state.
          <div>
            <button type='button' onClick={adjustXPos}>Adjust x ({ controlledPosition.x })</button>
          </div>
          <div>
            <button type='button' onClick={adjustYPos}>Adjust y ({ controlledPosition.y })</button>
          </div>
        </div>
      </Draggable>

    </div>
  );
};

const RemWrapper = props => {
  const { children, style } = props;
  const remBaseline = 16;
  const translateTransformToRem = transform => {
    const convertedValues = transform.replace('translate(', '').replace(')', '')
      .split(',')
      .map(px => px.replace('px', ''))
      .map(px => parseInt(px, 10) / remBaseline)
      .map(x => `${x}rem`);
    const [x, y] = convertedValues;

    return `translate(${x}, ${y})`;
  };

  const child = React.Children.only(children);

  const editedStyle = {
    ...child.props.style,
    ...style,
    transform: translateTransformToRem(style.transform, remBaseline),
  };
  return (
    <div style={editedStyle}>
      {children}
    </div>
  );
};

export default App;