React 아이콘 드래그 통한 필드 생성

이전 포스팅 가기 

이전 시간에 저는 특정한 버튼을 누르면 버튼 이미지가 변경되는 방식을 넣었습니다.

저는 이 버튼을 통해서 드롭다운 필드가 나타나도록 할 건데요. 

아이콘이 팔레트? 마냥 있는 필드를 나타내고, 이 아이콘을 드래그 해서 특정한 필드를 생성하는 로직을 구현하고 싶습니다.

이를 위해 차근차근 진행하겠습니다.

 

 

팔레트 필드 만들기

				
					const Test = ({ display }) => {
    if (display) {
      return (
        <div className="Basic-Field-Section">
          <div className="Basic-Field-Row">
            
            <div className="Basic-Icon-left">
              <img src={testBasicText} />
            </div>
  
            <div className="Basic-Icon-right">
              <img src={testBasicRadioButton} />
            </div>
          </div>
  
          <div className="Basic-Field-Row">
            <div className="Basic-Icon-left">
              <img src={testBasicLongText} />
            </div>
  
            <div className="Basic-Icon-right">
              <img src={testBasicCheckBox} />
            </div>
          </div>
  
        </div>
      );
    } else {
      return null;
    }
  };
				
			

왼쪽이 바로 우리의 팔레트 필드가 될 겁니다.

지금은 아주 간단하게 구성이 되어 있습니다.

저는 아이콘을 드래그 하여 필드를 생성할 계획이거든요?

그래서 Basic-Icon-left , right 라는 ClassName 을 가진 div 내부에 각각의 아이콘 이미지를 박아 넣었습니다.

이러한 아이콘 이미지는 Basic-Field-Row 라는 하나의 div 내에 2개씩 줄지어 있을 겁니다.

이거 꾸며주는 CSS도 같이 보시죠.

저는 아래처럼 배치했습니다.

				
					.Basic-Field-Row{
    width: 322px;
    height: 120px;
    display: flex;
    justify-content: center;
}


.Basic-Icon-left{
    width: 100px;
    height: 80px;
    margin-right: 20px;
}

.Basic-Icon-left img{
    width: 100%;
    height: auto;
}

.Basic-Icon-right{
    width: 100px;
    height: 80px;
}

.Basic-Icon-right img{
    width: 100%;
    height: auto;
}
				
			

이런 식으로, 아이콘을 추가할 때마다 Field-Row 를 추가하는 방식을 사용해서 필드를 구성합니다.

그럼 이제 버튼을 눌렀을 때, 저 display 가 True 인지 False 인지를 할당을 해야겠지요?

				
					  const TestFieldSide = () => {
    const [currentBasicField, setCurrentBasicField] = useState(BasicFieldChoice);
    const [BasicFieldSection, setBasicFieldSection] = useState(false);
  
    const toggleBasicField = () => {
      setCurrentBasicField(
        currentBasicField === BasicFieldChoice
          ? BasicFieldChoiceHover
          : BasicFieldChoice
      );
  
      setBasicFieldSection(!BasicFieldSection);
    };
  
    return (
      <div className="field-side-bar">
        <br />
        <div className="field-search-container">
          <img src={GlassIcon} alt="Search" className="search-icon" />
  
          <input
            type="text"
            placeholder="Search Field..."
            className="field-search-bar"
          />
        </div>
  
        <br />
  
        <button className="basic-field-choice" onClick={toggleBasicField}>
          <img src={currentBasicField} />
        </button>
  
        <br />
  
        <Test display={BasicFieldSection} />
      </div>
    );
  };
				
			

저는 오른쪽과 같은 방법을 사용했습니다.

먼저 BasicfieldSection 이라는 값에 false 를 할당해 놓습니다.

요거를 아까 위에 만든 팔레트 필드에 있는 display 라는 값에 할당해줌 됩니다.

일전 포스팅에서 사용했던 5번 함수 있죠?

여기에 setBasicFieldSection 을 넣어줍니다.

! 을 앞에 넣어줌으로써 언제나 현재 BasicFieldSection 과는 반대가 되는 값을 할당해주게 됩니다.

이러면 버튼을 누를 때마다, false 또는 True 가 번갈아서 

BasicFieldSection  라는 변수에 할당이 되는 겁니다~

요 변수를 <Test> 컴포넌트에 넣어주면 됩니다.

36번 줄처럼 값을 너어주면 이제 버튼을 누를 때마다 아래쪽으로 팔레트 필드가 나타났다가, 다시 누르면 사라지고 뭐 그럴 겁니다.

어때요? 아이콘으로 설정한 이미지가 2개씩 주르륵 나타나고, 

버튼을 다시 누르면 없어집니다.

다만, 아직 드래그를 통한 생성은 안 되는군요. 이를 위해 더 진행합시다. 

드래그를 위한 로직

우리는 각 아이콘을 통해 특정한 필드를 생성하려는 거죠?
이를 위해서는 가 아이콘이 각자 고유한 어떤 값을 가지고 있어야 할 겁니다.

이 고유한 값을 통해서 아이콘을 드래그 한 곳에 특정한 필드가 생상되는 거죠.

이를 위해서 Chat GPT 에게 간단한 로직 생성을 부탁했습니다.

				
					const DraggableIcon = ({ value }) => {
  const handleDragStart = (e) => {
    e.dataTransfer.setData("iconValue", value);
  };

  return (
    <div className="Basic-Icon" draggable="true" onDragStart={handleDragStart}>
      {/* 아이콘 내용 */}
    </div>
  );
};

const DropField = () => {
  const [fields, setFields] = useState([]);

  const handleDrop = (e) => {
    e.preventDefault();
    const iconValue = e.dataTransfer.getData("iconValue");
    const newField = { value: iconValue }; // 필드 객체 생성
    setFields([...fields, newField]); // 상태 업데이트
  };

  const handleDragOver = (e) => {
    e.preventDefault();
  };

  return (
    <div onDrop={handleDrop} onDragOver={handleDragOver}>
      {/* 필드를 여기에 렌더링 */}
      {fields.map((field, index) => (
        <div key={index}>{field.value}</div>
      ))}
    </div>
  );
};

				
			

왼쪽처럼 코드를 제안 받았습니다.

그러니까 DraggableIcon 이라는 컴포넌트를 통해서 기존의 아이콘 구성을 변경하고..

이 아이콘을 드래그 하는 동안에는  Value 내에 필요한 값을 저장하게 됩니다.

그리고 드래그를 해제하는 순간에는, dataTaransfer 이라는 드래그를 하는 중에 생성했던 스토리지에서 iconValue 라는 key 가 가진 Value 를 찾아옵니다.

이 Value 를 통해서, 특정한 필드를 나타내게 되는 겁니다.

그렇다면 이 작업을 보다 구체화 하겠습니다. 

				
					// 드래그를 위한 아이콘 컴포넌트
const DraggableIcon = ({ src, type, position }) => {
  const [positionName, setPositionName] = useState("Basic-Icon-left");

  useEffect(() => {
    if (position === "left") {
      setPositionName("Basic-Icon-left");
    } else {
      setPositionName("Basic-Icon-right");
    }
  }, [position]); // position 값이 변경될 때마다 이 코드가 실행됩니다.

  const handleDragStart = (e) => {
    e.dataTransfer.setData("FieldType", type);
  };

  return (
    <div
      className={positionName}
      draggable="true"
      onDragStart={handleDragStart}
    >
      <img src={src} alt="icon" />
    </div>
  );
};

				
			

먼저 저는 왼쪽처럼, DraggableIcon 을 만들었습니다.

이 컴포넌트는 src, type, position 이라는 인자를 건네받아 사용됩니다.

드래그를 하는 동안에는 setData 를 통해서 type 을 저장합니다.

처음에 만들었던 팔레트 내에 있는 아이콘을 아래처럼 바꿔주면 됩니다.

				
					 <DraggableIcon src={testBasicText} type={"text"} position={"left"} />
				
			

왼쪽에 배열하고 싶다면, left 

오른쪽에 배열하고 싶다면, right 를 넣어주도록 설정했습니다.

				
					const FormWindow = () => {
  const [fields, setFields] = useState([]);

  const handleDrop = (e) => {
    e.preventDefault();
    const fieldType = e.dataTransfer.getData("FieldType");
    const newField = { type: fieldType };
    setFields([...fields, newField]);
  };

  const handleDragOver = (e) => {
    e.preventDefault();
  };

  return (
    <div className="form-window">
      <div
        className="field-input-row"
        onDrop={handleDrop}
        onDragOver={handleDragOver}
      >
        {fields.map((field, index) => (
          <FieldComponent key={index} type={field.type} />
        ))}
      </div>
    </div>
  );
};

				
			

다음은 실제로 이 아이콘을 드래그하고 놓을 공간입니다.

이를 위해서 hanldDrop 이라는 함수를 넣습니다.

이 동작은 드래그를 해제하는 순간 발생하며,

fields 라는 배열에 newField 를 추가하게 됩니다.

결과적으로, fields 라는 배열 안에는 드래그로 아이콘을 넣을 때마다, newField 가 들어가겠죠?

그럼 이것들을 아래의 반복문을 통해서 나타내는 겁니다.

이때, <FieldComponet > 라는 새로운 컴포넌트를 하나 만들었고, 여기에 field.type 이라는 값을 줌으로써  무언가가 나타나도록 하는 겁니다.

FieldComponet 에서는 type 을 그대로 출력하도록 설정했습니다. 

				
					const FieldComponent = ({ type }) => {
  return (
    <div className="field-component">
      Type: {type}
      {/* 필드 타입에 따른 추가적인 렌더링이나 로직을 여기에 추가할 수 있습니다 */}
    </div>
  );
};

				
			

왼쪽이 FieldComponet 입니다.

이걸 코드 내에 같이 지정을 해주면 되겠죠?

지금은 type 뿐이지만, 저는 보다 다양한 인자를 icon 내부에 설정함으로써 더 다양한 필드를 만들 수 있도록 할 계획입니다. 

결과는...

다음처럼 드래그를 통해서 특정한 인자를 FieldComponent 에 전달해서 해당 컴포넌트가 특정한 div 를 생성하게 되었습니다.

이 로직을 활용하여 보다 다양한 인자를 전달하고, 그 다양한 인자를 통해서 보다 복잡한 필드를 생성 가능할 것입니다. 

마치며...

드래그 앤 드랍 기능을 사용하여 필드를 생성하는 로직을 이해했습니다.

이해한 로직을 바탕으로 다음 번에는 더 복잡한 기능이 생성 완료된 페이지로 진화가 가능할 것 같습니다.^^