회사에서 디자인 시스템을 만들어 보라고 해서 프론트엔드 동료와 함께 둘이서 스펙도 정하고, NPM에 배포도 하고, 피그마에 연동도하고, 열심히 영차영차 만드는 도중 DatePicker을 맡아서 만들게 됌.
일단 내가 만들어야하는 DatePicker는 두 가지 였다.
날짜의 범위를 정하는 DateRangePicker
와 날짜를 단일로 선택하는 DateSinglePicker
를 만들어야 했다.
일단 내가 DatePicker 라이브러리를 결정한 후 커스텀을 해야하기 때문에 몇 가지 찾아보다 airbnb에서 만든 react-dates
가 눈에 들어왔고, 요것으로 결정했다.
react-dates
로 결정한 이유로는 3가지가 있는데,
첫번째 이유로는 airbnb에서 만들어서 믿음이 갔다.
두번째 이유로는 typescript
를 지원하는 패키지도 있었다!
마지막으로 제일 큰 이유가 있다.
주간 다운로드 횟수가 나의 마음을 움직였다 ㅎ..
시작하기 전 react-dates
의 문서를 읽어보고 잘 만들어져 있는 storybook을 살펴봤다.
역시나 잘 정리되어 있다고 생각했고 바로 패키지를 설치해서 작업을 시작했다.
디렉토리 구조는 평소에 생각만 해봤었던 구조로 구성해봤다.
각 컴포넌트 폴더를 만들고, 그 내부에 컴포넌트 파일을 만든 후 options.ts
style.ts
types.ts
파일을 만들어서 해당 컴포넌트에 대한 타입 및 기능, 스타일을 적용 시켰다.
options.ts
: 오브젝트로 만든 후 여러 데이터 및 메소드들을 선언style.ts
: emotion을 사용해서 스타일을 선언types.ts
: DatePicker에 사용될 타입들을 선언위의 구조로 만드니 컴포넌트에 해당되는 기능들과 스타일 및 타입들이 모아져 있어 생각보다 관리하기 편리했다.
하지만, 위의 DatePicker 폴더에서는 DatePickerInput
과 DateRangePicker
밖에 안 만들어져 있어서 큰 문제가 없지만 해당 컴포넌트와 관련있는 추가적인 컴포넌트를 더 생성 해야된다면 조금 불편할 것 같다는 생각이 들었다.
작업 전에 너무 조사없이 airbnb에 대한 믿음만으로 시작해서 그런가..
문제점 들이 발견되기 시작했다.. 두둥..!
일단 당장은 느껴지지 않는 문제지만 묵~직한 레거시 프로젝트인 moment
를 기반으로 만들어졌다.
또 있다.
디자이너분이 만들어주신 DatePicker
디자인에서는 날짜가 해당하는 달의 범위를 벗어나도 그레이 스케일이 적용된 날짜가 나오는 형태로 디자인 되어있어서 항상 6개의 행을 유지해야 했는데, 다른 DatePicker
들은 해당 옵션이 있었는데 요거슨 없다..
또또 있다.
이건 도대체 왜 안되는지 모르겠는데, locale('ko')
를 사용해서 한국어를 적용해 줘야하는데 이것이 절대 안된다.. 되는 분들도 있는 것 같은데 절대 안되더라 .. 그래서 이 부분은 직접 week
데이터들을 반환하는 렌더 옵션을 찾아서 한국어로 내가 직접 필터링 해주게 만들었다.
그리고 이건 내가 캐치하지 못한 부분인데, DatePicker
의 view 옵션을 2개로 설정했을 때는 각각 움직여야 하지만 서로 동기화는 되어 있어야 하는 디자인 이였다.. 그 외에도 생각하지 못한 부분들이 꽤 있었다.
하지만, 가오가 있지; 바꾸자고 말하지 않았다.
결국 간신히 99프로 까지 완성했다.
여러 상황들을 해결해 나가며 디자인 시스템을 만들던 중 위에서 오더가 내려왔다.
다른 팀에서 제작중인 프로젝트에 일단 만든거라도 디자인 시스템을 적용시켜 달라는 오더였다.
나는 알겠다고 대답했고 만들던거만 마무리 짓고 그쪽 팀의 프로젝트에 npm으로 배포 해둔 디자인 시스템을 내려받아 테스트를 해봤다.
에러가 발생했다. react-dates
는 react-16
까지만 지원을 해서 나는 에러였다.. 그래서 해결하려고 자료를 좀 찾아봤다.
솔직히 해결을 한다면 할 수 있는 자료도 있긴 했다(좋은 방법은 아닌 것 같았음). 근데 react 버전에 대한 문제가 지속적으로 발생하고 유일하게 업데이트 하고 계시는 외국분의 댓글이 좀 사나워 보였다..
react-dates
의 props들과 각 props들의 타입들을 일일히 전부 뜯어가면서 제작했지만.. 꽤 현타가 왔당..
결국 react-dates
로 만든 컴포넌트를 그냥 버리기로 결정했고, 다른 패키지를 찾아서 작업했다.
근데 오히려 버리기로 딱 ~ 결정하니까 오히려 속이 편했다.
이번에 고른 패키지는 react-datepicker
요거를 사용했고, 이 패키지는 뭔가 기본에 충실한채 지속적으로 업데이트가 되고 있었으며 패키지 설치 횟수도 굉장히 많았다!
자바스크립트에 내장 되어있는 날짜 인스턴스와 메소드를 사용하는 것도 좋았으며, 각 props도 사용하기 적당했다!
물론 해당 패키지도 불편한 부분이나 이슈가 있었지만 react-dates
에서 단련이 되었는지 할만했다.
디렉토리구조는 위와 같이 잡아주고,
export type DateRangeType = Record<'startDate' | 'endDate', Date | null>
interface DateRangePickerProps extends React.HTMLAttributes<HTMLDivElement>, DateRangeType {
viewCount?: 1 | 2
onToggleChange?: (isOpen: boolean) => void
onDateChange: ({ startDate, endDate }: DateRangeType) => void
onSelectFinish?: ({ startDate, endDate }: DateRangeType) => void
}
const DateRangePicker = ({
startDate,
endDate,
viewCount = 1,
onDateChange,
onSelectFinish,
onToggleChange,
...props
}: DateRangePickerProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false)
useEffect(() => {
onToggleChange?.(isOpen)
}, [isOpen])
const handleDateChange = (dates: [Date | null, Date | null]) => {
const [start, end] = dates
onDateChange({ startDate: start, endDate: end })
selectFinishCheck(start, end, onSelectFinish)
}
const selectFinishCheck = (
startDate: Date | null,
endDate: Date | null,
callbackFn?: ({ startDate, endDate }: DateRangeType) => void,
) => {
if (startDate && endDate && callbackFn) callbackFn({ startDate, endDate })
}
const outSideDaysRegister = () => {
const outSideDays = document.querySelectorAll('.react-datepicker__day--outside-month')
outSideDays.forEach((day) => {
if (!day.textContent) {
let dayText = day.classList[1].split('--0')[1]
if (!+dayText[0]) dayText = dayText[1]
day.textContent = dayText
}
})
}
return (
<DateRangePickerContainer {...props}>
<DatePicker
renderCustomHeader={({ ...headerProps }) => <DatePickerHeader viewCount={viewCount} {...headerProps} />}
selected={startDate}
startDate={startDate}
endDate={endDate}
onChange={handleDateChange}
customInput={<DatePickerInput isOpen={isOpen} />}
onCalendarOpen={() => setIsOpen(true)}
onCalendarClose={() => setIsOpen(false)}
open={isOpen}
renderDayContents={(dayOfMonth) => {
outSideDaysRegister()
return dayOfMonth
}}
dateFormatCalendar="yyyy.MM"
dateFormat="yyyy-MM-dd"
monthsShown={viewCount}
locale={ko}
disabledKeyboardNavigation
selectsRange
fixedHeight
/>
</DateRangePickerContainer>
)
}
코드는 대강 요런 느낌으로다가 1차 마무리 했다.
outSideDaysRegister
함수의 이유는 혹시 궁금해 할 것 같아서 적어두겠다!
각 달 안의 날짜 데이터들을 6개의 행으로 보여줘야 하고, 해당하는 달의 날짜 데이터가 아닌 경우는 그레이 스케일이 적용된채로 보여주고 있는 상황인데 1개의 달만 표시되면 문제가 없지만 두 개의 달력이 표시되었을 때 문제가 생긴다.
아직 내가 만든 건 오픈하면 안되서 예시를 들자면,
위의 달력 이미지처럼 좌측 달에는 이전 달의 outSideDate
값만 나오고 다음 달의 outSideDate
값은 나오지않고 우측 달에는 반대로 안 나온다. 왜 저렇게 동작하는지는 알 것 같긴하다.
하지만 내부 로직 자체를 수정할 수 없으니.. 다른 방법을 찾기 전까진 일단 outSideDaysRegister
함수를 만들어서 처리했다.
이거 말고도 많은 이슈들이 있었지만 일일히 다 적으면 내용이 너무 길어지기 때문에 여기까지만 작성하려고 한다!
이상 DatePicker 만들어버리기*2 였습니다!
많은 것을 배웠습니다, 감사합니다.