[React] 날짜 선택 컴포넌트 만들기 3탄

Yeonsu Summer·2023년 9월 8일
1

React

목록 보기
13/15
post-thumbnail
post-custom-banner

DateSelection 컴포넌트의 진짜 마지막 기능을 구현하기 위해 3탄으로 왔다.

1탄 포스트 보러가기
2탄 포스트 보러가기


9. 마우스 이벤트

// ./components/DateSelection.jsx
const DateSelection = ({ format = 'YYYY-MM-DD', autoFormatting = true }) => {
  const inputRef = useRef(null);
  const calendarRef = useRef(null);
  
  ...
  
  const handleClickOut = useCallback(
    (e) => {
      if (open && inputRef?.current && calendarRef?.current) {
        const inputArea = inputRef.current;
        const calendarArea = calendarRef.current;
        const { target } = e;
        const outArea =
          !inputArea.contains(target) && !calendarArea.contains(target);

        if (outArea) {
          setOpen(false);
        }
      }
    },
    [open, inputRef, calendarRef]
  );

  useEffect(() => {
    if (open && inputRef?.current && calendarRef?.current) {
      document.addEventListener('click', handleClickOut);
    }
  }, [open, inputRef, calendarRef, handleClickOut]);

  return (
    <section>
      <div ref={inputRef}>
        <input ... />
        <button type='button' onClick={handleClickButton}> ...
      </div>
      {open && (
        <div className='calendar' ref={calendarRef}>
        ...

많은 사용자들은 Modal이나 Popover와 같은 컴포넌트가 뜨면 컴포넌트 바깥을 눌러 닫는 행동을 한다.

대략적인 로직은 마우스가 가리킨 위치가 입력창이나 달력이 아닌 곳일 경우에 달력을 닫아주는 것이다.

useRef를 사용하여 DOM에 접근할 수 있다.
inputbutton을 감싸는 divinputRef를, Datetime을 감싸는 divcalendarRef를 부여한다.

open 상태이면서 inputRefcalendarRef의 요소가 존재한다면 document 전체에 click 이벤트를 넣어준다.
dependency 안에는 open, inputRef, calendarRef를 넣으며, 이 값들 중 하나만 바뀌더라고 코드가 실행된다.

click 이벤트가 발생하면 handleClickout으로 클릭 위치에 따른 조작을 한다.
useCallback을 사용한 이유는 useEffect의 dependency가 변경될 때마다 handleClickout이 호출되므로 불필요한 호출을 방지하기 위함이다.

inputRef 영역과 calendarRef 영역에 둘 다 클릭한 영역인 target이 포함되어 있지 않다면 그 곳이 바깥 영역인 outArea이다.
outArea가 true인 경우 열려 있는 달력을 닫아준다.

10. 키보드 이벤트

// ./components/DateSelection.jsx
  ...

  const handleKeyDownInput = (e) => {
    if (e.code === 'Enter') {
      setOpen(!open);
    }
  };

  const handleKeyDownButton = (e) => {
    if (e.code === 'Tab' && open) {
      setOpen(false);
    }
  };

  ...

  return (
    <section>
      <div ref={inputRef}>
        <input
          ...
          onKeyDown={handleKeyDownInput}
        />
        <button
          ...
          onKeyDown={handleKeyDownButton}
        >
          <BsFillCalendarHeartFill />
        </button>
      ...

input에 focus가 된 상태에서 키보드를 입력할 때 해당 이벤트의 코드가 Enter인 경우에 달력을 열고 닫아준다.

button에 focus가 된 상태에서 키보드를 입력할 때 해당 이벤트의 코드가 Tab이고 달력이 열려 있는 경우에 달력을 닫아준다.

개인적으로 달력에도 키보드 이벤트를 넣고 싶었지만 react-datetime에서 제공하지 않는 것 같다.

11. 예쁘게 만들기 ✨

// ./components/DateSelection.jsx
import 'react-datetime/css/react-datetime.css';
import '../App.css';
...

  return (
    <section className='dateselection'>
      <div className='input-wrapper' ref={inputRef}>
        <input
          className='input'
          ...
        />
        <button
          className='input-button'
          ...
        >
          <BsFillCalendarHeartFill />
        </button>
      </div>
      {open && (
        <div className='calendar' ...>
    	...
// ./DateSelection.css
.dateselection {
  position: relative;
  width: 272px;
}

.input-wrapper {
  position: relative;
  width: 100%;
}

.input {
  width: calc(100% - 34px);
  padding: 8px 16px;
  border-radius: 4px;
  border: 1px solid #e0e2e7;
  outline: none;
}

.input-button {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
}

.calendar {
  width: 100%;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

.calendar td {
  border-radius: 100px;
}

.rdtPicker {
  border: 1px solid #e0e2e7 !important;
  border-radius: 4px !important;
}

td.rdtToday {
  border: 1px solid #428bca !important;
}

td.rdtToday::before {
  display: none !important;
}

보기에 깔끔한 정도로만 CSS를 작성해보았다.

react-datetime에서 제공하는 기본적인 스타일을 넣어주고 추가로 커스텀하였다.
!important를 꼭 넣어야만 기본 스타일을 덮어 씌운다.


사내에서 사용될 UI를 구축하다보니 여러 경우의 수를 생각하고 더욱 완벽한 컴포넌트를 구현하기 위해 힘을 쓰게 되었다.

개인 프로젝트인 인생네컷에도 DateSelection 컴포넌트를 도입해야겠다.

(전체 코드는 깃허브 링크로 올리도록 하겠습니다!)

profile
🍀 an evenful day, life, journey
post-custom-banner

0개의 댓글