상세 컨텐츠

본문 제목

MUI의 Datepicker에 사용하는 Input을 커스텀 해보자

TECH

by walkinpcm 2022. 1. 12. 20:46

본문

최근에 MUI(Material-UI)의 Datepicker를 Input만 스타일을 수정해서 사용하는 경우가 있었습니다.

MUI의 Datepicker는 기본적으로 아래와 같은 Input 스타일을 가지고 있습니다.

MUI Datepicker의 기본 Input 스타일

저는 여기에 우리 팀이 커스텀해둔 TextField를 사용하고, 캘린더 아이콘을 사용하고 싶었습니다.

 

Datepicker 컴포넌트는 당연하게도 Input에 사용할 컴포넌트를 바꿀 수 있는 방법을 제공합니다.

Datepicker의 prop중에  renderInput  prop을 이용하면 됩니다.  renderInput  prop에 Input으로 사용할 컴포넌트를 return하는 함수를 넣어줍니다. (함수의 매개변수는 공식문서를 참고하시면 좋습니다.)

저는 TextField를 return 하게 넣어줬습니다. 그리고 캘린더 아이콘을 변경하기 위해서 TextField의 InputProps.endAdornment 속성에 캘린더 아이콘을 버튼에 넣었습니다. (TextField의 endAdornment 관련 문서)

<DatePicker
  // ...
  renderInput={({ inputRef, inputProps }: any) => (
    <TextField
      InputProps={{
        endAdornment: (
          <MuiInputAdornment position="end">
            <Button>
              <CalendarIcon />
            </Button>
          </MuiInputAdornment>
        ),
      }}
    />
  )}
  // ...
/>

 

이렇게 하니깐 Input의 모양은 제가 원하던 모양으로 만들어졌습니다. 그런데, 한가지 이슈가 있었습니다. Datepicker의 기본 캘린더 아이콘을 사용할 때는 아이콘을 눌렀을 때 날짜선택창이 나타나는데, 제가 커스텀으로 넣은 캘린더 아이콘 버튼을 눌렀을 때는 날짜선택창이 나타나지 않았습니다. (지금 생각해보니 MUI 기본 로직에서 제가 넣은 버튼에 알아서 onClick 핸들러를 넣을 수 있는.. 단서가 아무 것도 없네요..ㅎㅎㅎ)

 

Datepicker의 기본 캘린더 아이콘을 사용하지 않을 것이라면, 날짜선택창이 뜰 수 있게 직접 제어해줘야 합니다. 이를 위해서 Datepicker 컴포넌트에는  open onOpen onClose  prop이 있습니다.

  • open: boolean type이며, 날짜선택창의 열림 여부를 결정합니다. true로 넘기면 날짜선택창이 열리고 false를 넘기면 닫힙니다.
  • onOpen: Datepicker에서 날짜선택창을 열려는 시도를 감지하는 리스너입니다.
  • onClose: Datepicker에서 날짜선택창을 닫으려는 시도를 감지하는 리스너입니다.

 

수동으로 날짜선택창을 제어하려면, 날짜선택창의 열림 상태를 저장하는 state를 하나 만들어서 open prop에 할당하고, Datepicker의  onOpen  onClose  prop에 각각 open state를 true, false로 변경하는 핸들러 함수를 할당하면 됩니다.

그리고 커스텀 캘린더 아이콘 버튼의 onClick 이벤트 리스너에도 open state를 true로 변경하는 핸들러 함수를 할당합니다.

// 날짜 선택창을 띄우는 상태 제어
const [isOpen, setIsOpen] = useState<boolean>(false);
const setOpen = useCallback(() => {
  setIsOpen(true);
}, [setIsOpen]);
const setClose = useCallback(() => {
  setIsOpen(false);
}, [setIsOpen]);

// ...
<DatePicker
  open={isOpen}
  onOpen={setOpen}
  onClose={setClose}
  // ...
  renderInput={({ inputRef, inputProps }: any) => (
    <Box>
      <TextField
        InputProps={{
          endAdornment: (
            <MuiInputAdornment position="end">
              <Button onClick={setOpen}>
                <CalendarIcon />
              </Button>
            </MuiInputAdornment>
          ),
        }}
      />
    </Box>
  )}
  // ...
/>

 

여기까지 하면 원하는 Input 스타일을 가진 Datepicker 컴포넌트를 만들 수 있습니다.

그런데, 한가지 아쉬운 점이 있었습니다. 날짜선택창을 열었을 때, 캘린더 아이콘 버튼을 포함한 TextField를 기준으로 아래-가운데 정렬되지 않고 오직 값이 들어가는 input 영역을 기준으로 아래-가운데 정렬되었습니다. 그래서 눈으로 보기에 날짜선택창이 가운데보다 조금 왼쪽으로 이동되어 있어 보였습니다.

 

이 문제는, Datepicker가 날짜선택창을 띄울 때, 위치를 잡는 기준이 input에 있어서 그렇습니다.

그래서 아래처럼 ref를 이용해서 날짜선택창이 위치를 잡는 Element를 직접 지정해주었습니다. 저는 TextField 전체를 감싸는 Box 컴포넌트를 추가하여 그곳을 기준으로 날짜선택창이 뜨도록 바꿔주었습니다.

// Datepicker 컴포넌트의 prop
PopperProps={{
  placement: 'bottom', // 날짜선택창이 뜨는 위치
  anchorEl: popperAnchorRef.current,
}}

// Datepicker 컴포넌트의 renderInput에서 return하는 컴포넌트
<Box ref={popperAnchorRef}>
  <TextField
    // ...
  />
</Box>

 

여기까지해서 원하는 스타일을 가진 Datepicker를 완성할 수 있었습니다.

관련글 더보기

댓글 영역