4 분 소요

Project

원티드 프리온보딩 과제

원티드 프리온보딩 과제

Toggle

javascript를 이용하지 않고 css로만 구현을 하고 싶었다.

처음에는 아무것도 모르고 그냥 Wrap(Switch rail)안에 input(실제 input), label(흰색 동그란 버튼 모양), backgroundDiv(움직이는 배경색)을 따로따로 요소를 만들었다.

하지만 이게 잘 못된 것이었다.

왜냐하면, Toggle Switch ON/OFF를 나타내주는 하단 결과 div가 Wrap의 밖 즉, input의 밖에 있기 때문에 css로 만하면 input의 checked를 이용해서 스타일을 적용할 수 없기 때문에 불가능해진다.

따라서 하단 결과 요소를 Wrap안으로 가져와야 했다.

그러기 위해서는 Wrap이 Switch Rail 자체가 되어서는 안됐다. Wrap은 스위치 모양과 버튼, 그리고 하단 결과 요소를 담고 있는 Container이어야 했다.

그래서 Switch Rail을 div로 두고 그 안에 label을 넣는다. label은 흰색 동그라미 버튼이 된다.

Switch Rail Div에는 before로 background가 색이 있는 가상 요소를 하나 더 생성한다. 이것이 checked 되면 오른쪽으로 움직이며 차오르는 것으로 구현이 될 것이다.

label은 흰색 동그라미 버튼으로 클릭이 되면 오른쪽으로 이동 (left에 값을 줌 absolute이기 때문)한다.

그리고 하단 결과 요소를 p로 가져와서 after에는 ON, before에는 OFF를 두어 구현하면 된다.

const Toggle = () => (
  <Container>
    <Header title="Toggle" />
    <Wrap>
      {/* checkbox input (보이지 않음) */}
      <input type="checkbox" id="button" />
      {/* Switch rail 모양 */}
      <SwitchRail>
        {/* 실제로 input check를 대신하는 보여지는 label 흰색 버튼 모양 나타냄 */}
        <Switch htmlFor="button" />
      </SwitchRail>
      {/* Toggle Switch ON/OFF 나타냄 */}
      <p></p>
    </Wrap>
  </Container>
);

const Container = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  position: relative;
  border: 2px solid gray;
  border-radius: 5px;
  margin: 2px 0;
  padding: 10px 20px;
`;

// switch container (버튼 rail 모양)
const SwitchRail = styled.div`
  width: 100px;
  height: 50px;
  border-radius: 25px;
  padding: 5px;
  position: relative;
  transform: translateX(0);
  background-color: gray;
  overflow: hidden;
  /* 배경 */
  &::before {
    content: "";
    position: absolute;
    top: 0px;
    left: -100%;
    width: 100%;
    height: 100%;
    background-color: purple;
    transition: all 0.5s linear;
  }
`;

// 흰색 동그라미 버튼
const Switch = styled.label`
  width: 40px;
  height: 40px;
  position: absolute;
  top: 5px;
  left: 5px;
  background-color: white;
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.5s linear;
  z-index: 1;
  display: block;
`;

// 버튼과 글씨
const Wrap = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  /*  실제 input은 보이지 않게 */
  input[type="checkbox"] {
    display: none;
  }
  /* Switch Rail 배경 transition */
  input[type="checkbox"]:checked ~ div {
    /* 배경도 오른쪽으로 옮김 */
    &::before {
      left: 0;
    }
  }
  /* click 되었을 때(checkbox가 check 되었을 때) Switch */
  input[type="checkbox"]:checked ~ div > label {
    /* switch에서 동그란 버튼 위치 오른쪽으로 옮김 */
    left: 55px;
  }
  /* Toggle Switch 글 default OFF */
  p::after {
    content: "Toggle Switch OFF";
  }
  /* check 되었을 때는 ON */
  input[type="checkbox"]:checked ~ p {
    &::after {
      content: "Toggle Switch ON";
    }
  }
`;

Tag

const Tag = () => {
  const [tags, setTags] = useState(null);
  const [text, setText] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    setTags((prev) =>
      tags
        ? [{ value: e.target[0].value, id: nanoid() }, ...prev] //
        : [{ value: e.target[0].value, id: nanoid() }]
    );
    setText("");
  };

  const deleteTag = (id) =>
    setTags((prev) => prev.filter((el) => el.id !== id));

  return (
    <>
      <Header title="Tag" />
      <Container>
        <InputContainer onSubmit={handleSubmit}>
          <input
            type="text"
            placeholder="Press enter to add tags"
            value={text}
            onChange={(e) => setText(e.target.value)}
          />
          {tags?.map((tag) => (
            <TagContent key={tag.id}>
              {tag.value}
              <button type="button" onClick={() => deleteTag(tag.id)}>
                x
              </button>
            </TagContent>
          ))}
        </InputContainer>
      </Container>
    </>
  );
};

tag를 나열할 때 배열을 map 돌리는데, tag의 내용은 같아질 수 있는 가능성이 많다. 따라서, uuid/nanoid를 사용하기로 했다.

UUID

UUID는 범용 고유 식별자로 표준 식별자이다. 랜덤으로 생성된 이 UUID는 id가 중복될 일은 없다.

NanoID

UUID보다 4.5배 작은 크기를 가졌고, 60% 정도 더 빠르다고 한다.

따라서 이번 구현에는 nanoId를 사용해보기로 했다.

npm i nanoid

import {nanoid} from ‘nanoid’;

사용할 때에는 변수 = nanoid()로 하면 간단히 끝난다.

ClickToEdit

외부 클릭했을 시 동작하도록 하는 코드 (특정 영역 이외의 영역 클릭 시 이벤트)

const handleClickOutside = useCallback(
  (e) => {
    if (inputRef.current && !inputRef.current.contains(e.target)) {
      // handleSetItems
      setIsEditing({ name: false, age: false });
      setFormData((prev) => ({
        ...prev,
        name: inputData.name,
        age: inputData.age,
      }));
    }
  },
  [inputData]
);

useEffect(() => {
  document.addEventListener("mousedown", handleClickOutside);
  return () => {
    document.removeEventListener("mousedown", handleClickOutside);
  };
}, [inputData.name, inputData.age, handleClickOutside]);

먼저, useEffect로 모든 곳이 클릭될 때 event를 등록한다.

그 event 함수에서 그것이 바깥쪽 영역인지 판단할 것이다.

일단 ref와 연결된 것과 e.target이 다르다면 로직을 실행해준다.

꽤 많이 사용할 것 같고, 꽤 많이 구현이 된 로직인 듯하다. 알아두어야겠다.

배포

netlify로 github을 연동하여 자동 배포되도록 했다.

처음에 배포할 때 문제가 발생했다.

deploy setting에 관한 문제였는데, 초기에는 원래 하던대로 setting 했었다.

Base directory : none
Build command : CI= npm run build
Publish directory : build

그런데 계속 아래와 같은 Error가 발생했다.

이미지

헤메고 헤메이다 어느 글에서 디렉토리에 package.json 파일이 있냐는 물음이 있었다.

그것을 생각하고 이 에러를 다시 보니

no such file or directory, open ‘/opt/build/repo/package.json’

깨달았다.

이번 과제에서는 하던대로가 아니라 wanted_pre_onboarding repo 안에 custom-component 폴더 안에 CRA가 있었다.

그렇다 base directory가 그냥 repo 자체가 아니고 한 단계 더 들어간 custom-component이어야 했던 것이다.

따라서 다음과 같이 설정 후에 정상적으로 배포할 수 있었다.

Base directory : custom-component
Build command : CI= npm run build
Publish directory : custom-component/build

제출

일단 제출은 다 했다.

끝까지 확인해보고 수정 사항들을 모두 수정했다.

이렇게 해서 떨어지면 조금은 속상할 것 같다. 거의 이틀이라는 시간을 소비한 것인데…

일단은 계속 보면서 수정할 게 또 생기면 수정해야겠다.

댓글남기기